feat(s3): align harness logic contracts

This commit is contained in:
zizi 2026-06-04 18:26:06 +08:00
parent e90bf2d692
commit 006f65ec9b
22 changed files with 639 additions and 72 deletions

View File

@ -0,0 +1,157 @@
# 2026-06-04 S3 Task1 合同门禁
## 结论
S3 Task1 `Align S0 Harness GameLogicModule And ValidationReport Contracts` 已完成实现、controller 自测,并通过 fresh review gate
- spec compliance reviewPASS无 Critical / Important。
- quality / feasibility reviewPASS无 Critical / Important。
可以提交 Task1并在提交后进入 S3 Task2`Define GameConfig, GameLogicModule, And RuntimeSdkContract Shared Contracts`
## 变更范围
- `harness/schemas/game-logic-module.schema.json`
- `harness/schemas/validation-report.schema.json`
- `harness/scripts/validate-harness.mjs`
- `harness/fixtures/mvp/valid/simulation-game-logic-module-valid.json`
- `harness/fixtures/mvp/valid/game-logic-validation-report-valid.json`
- `harness/fixtures/mvp/invalid/**` 中 S3 GameLogicModule / ValidationReport 相关 fixtures。
未创建 shared-contracts TS 文件、API modules、business routes/UI、runtime、conversion、feed 或 deploy 实现。
## 子代理与 Review Gate
| Role | Agent ID | Result | Blocking findings |
| --- | --- | --- | --- |
| implementer | `019e9218-daa7-7860-b10a-7e157edf221d` | DONE | 无 |
| spec compliance review | `019e9226-8a98-7e42-ab7e-923aa405d871` | PASS | 无 Critical / Important |
| quality / feasibility review | `019e9226-cc72-7983-85bc-cdd51cf7ca10` | PASS | 无 Critical / Important |
## 实现摘要
- `GameLogicModule` schema 升级为 S3 descriptor 形态:
- `sourceRef` 改为结构化对象:`gameConfigId``templateVersion``buildInputPath`
- 新增必需字段:`exportSignature``runtimeSdkContractVersion``buildGraphId`
- `validationStatus` 加入 `forbiddenProperties`descriptor 自报 gate 状态会被拒绝。
- `ValidationReport` schema 升级为 S3 validator output 形态:
- `validatorVersion` 必填。
- valid schema 只接受 `result="passed"`
- `additionalProperties=false` 继续 fail closed。
- `validate-harness.mjs` 仅补充 Task1 所需 harness 逻辑:
- 新增 GameLogicModule / ValidationReport fixture target 映射。
- 新增 `importGraph` 必须被 `allowedImports` 覆盖的跨字段校验。
- 新增 descriptor 自报 `validationStatus` 的稳定 reason`GAME_LOGIC_VALIDATION_STATUS_SELF_REPORTED`
- 更新 invalid fixture 总数:`invalidFixtures=60``mvpInvalidFixtures=39`
- 新增或升级 invalid fixtures 覆盖:
- missing `exportSignature`
- missing `runtimeSdkContractVersion`
- missing `buildGraphId`
- string `sourceRef` old shape
- descriptor self-reported `validationStatus`
- `ValidationReport result=failed`
- `ValidationReport` checksum mismatch
- `ValidationReport importGraph` using disallowed import
- missing `targetContract`
- missing `validatorVersion`
- missing `createdAt`
- `ValidationReport` extra properties
## Controller 自测
已运行并通过:
```bash
node --check harness/scripts/validate-harness.mjs
node harness/scripts/validate-harness.mjs
node harness/scripts/validate-harness.mjs --check-invalid
node harness/scripts/validate-harness.mjs --contract GameLogicModule --input harness/fixtures/mvp/valid/simulation-game-logic-module-valid.json
node harness/scripts/validate-harness.mjs --contract ValidationReport --input harness/fixtures/mvp/valid/game-logic-validation-report-valid.json
pnpm check:s3-scope
git diff --check
```
关键输出:
```text
PASS harness validation
validFixtures=45
invalidFixtures=60
foundationValidFixtures=21
mvpValidFixtures=24
foundationInvalidFixtures=21
mvpInvalidFixtures=39
invalidCoverage=60/60
S3 scope check passed.
```
## Review Evidence
Spec reviewer 额外运行并确认:
```text
jq .
node --check harness/scripts/validate-harness.mjs
node harness/scripts/validate-harness.mjs --check-invalid
node harness/scripts/validate-harness.mjs --contract GameLogicModule --input harness/fixtures/mvp/valid/simulation-game-logic-module-valid.json
node harness/scripts/validate-harness.mjs --contract ValidationReport --input harness/fixtures/mvp/valid/game-logic-validation-report-valid.json
git diff --check -- harness/...
pnpm check:s3-scope
```
Quality reviewer 额外确认:
```text
full harness PASS
invalidCoverage=60/60
新增 untracked fixtures 已被加载并计入 mvpInvalidFixtures=39
ValidationReport.result passed-only valid shape 未破坏 --check-invalid fail-closed 路径
```
## SHA-256
| Path | SHA-256 |
| --- | --- |
| `harness/schemas/game-logic-module.schema.json` | `d88db45e2aa76c12ecd257240d66b4306e04c34cbeeb28000acb1ebc93400d06` |
| `harness/schemas/validation-report.schema.json` | `92fb4f979cd33efc808e5d84533ad578ac9244f26ab1fe27374bfdeaeb9a39b1` |
| `harness/scripts/validate-harness.mjs` | `16bfcfe662b34d5d13cc9ab1c604ee47cbb9bbc18e7517822a85583c200850e1` |
| `harness/fixtures/mvp/valid/simulation-game-logic-module-valid.json` | `5760d2593f0162af3e6b0b63dfb01d369e6c187e3b7ce42b04a0b1ce355beca2` |
| `harness/fixtures/mvp/valid/game-logic-validation-report-valid.json` | `3016a98ab390fe517f7d39315f9231da49a23b849619a18e047411e176825a1e` |
| `docs/superpowers/plans/2026-05-31-mvp-S3-simulation-slice.md` | `cbc7fb2afaab3cf64b578984b9def7ef265a46aa07029b732ac4ecbc2d68b76a` |
| `docs/superpowers/specs/2026-05-31-mvp-S3-simulation-slice-design.md` | `08abff96b9a1a7b7680e71678f5c479edab24467dd10185c0b0b0e29ea7e613e` |
| `docs/superpowers/plans/2026-06-01-implementation-review-gate-protocol.md` | `c8726acc5457ab87fb8f26c2e439e1d1b98df0255af78e4d6e8d9636dc518478` |
## Git Status Snapshot
写入本文档前 `git status --short --branch --untracked-files=all`
```text
## codex/s1-task9-s2-prep...origin/codex/s1-task9-s2-prep [ahead 2]
M harness/fixtures/mvp/invalid/dom-bom-usage-invalid.json
M harness/fixtures/mvp/invalid/dynamic-execution-invalid.json
M harness/fixtures/mvp/invalid/game-logic-validated-without-report-invalid.json
M harness/fixtures/mvp/invalid/game-logic-validation-report-failed-invalid.json
M harness/fixtures/mvp/invalid/game-logic-validation-report-web-only-sdk-invalid.json
M harness/fixtures/mvp/invalid/node-api-usage-invalid.json
M harness/fixtures/mvp/invalid/validation-report-checksum-mismatch-invalid.json
M harness/fixtures/mvp/valid/game-logic-validation-report-valid.json
M harness/fixtures/mvp/valid/simulation-game-logic-module-valid.json
M harness/schemas/game-logic-module.schema.json
M harness/schemas/validation-report.schema.json
M harness/scripts/validate-harness.mjs
?? harness/fixtures/mvp/invalid/game-logic-missing-build-graph-id-invalid.json
?? harness/fixtures/mvp/invalid/game-logic-missing-export-signature-invalid.json
?? harness/fixtures/mvp/invalid/game-logic-missing-runtime-sdk-contract-version-invalid.json
?? harness/fixtures/mvp/invalid/game-logic-string-source-ref-invalid.json
?? harness/fixtures/mvp/invalid/validation-report-disallowed-import-invalid.json
?? harness/fixtures/mvp/invalid/validation-report-extra-properties-invalid.json
?? harness/fixtures/mvp/invalid/validation-report-missing-created-at-invalid.json
?? harness/fixtures/mvp/invalid/validation-report-missing-target-contract-invalid.json
?? harness/fixtures/mvp/invalid/validation-report-missing-validator-version-invalid.json
```
## Residual Notes
- `ValidationReport.result` 在 schema CLI 里对 failed report 会先触发 `SCHEMA_CONST`;但 full fixture gate 的 custom invalid path 仍覆盖 `VALIDATION_REPORT_FAILED`reviewer 已确认不影响 Task1 fail-closed 目标。
- `harness/scripts/validate-harness.mjs` 的新增逻辑仅服务 schema 无法表达的跨字段约束和 fixture routing没有引入 S3 Task2+ business implementation。
- 下一步必须进入 S3 Task2 shared contracts不能跳过到 API compiler、logic compiler、runtime 或 conversion implementation。

View File

@ -17,6 +17,7 @@
"window"
],
"ruleVersion": "mvp-static-logic-rules-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "failed",
"createdAt": "2026-06-01T10:00:00+08:00"
}

View File

@ -18,6 +18,7 @@
"dynamic import"
],
"ruleVersion": "mvp-static-logic-rules-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "failed",
"createdAt": "2026-06-01T10:00:00+08:00"
}

View File

@ -0,0 +1,37 @@
{
"expectedReasonCode": "SCHEMA_REQUIRED",
"id": "game-logic-module-missing-build-graph-id-001",
"gameConfigId": "simulation-fixture-v1",
"moduleType": "simulation_management",
"sourceKind": "template_codegen",
"sourceRef": {
"gameConfigId": "simulation-fixture-v1",
"templateVersion": "simulation-management@1",
"buildInputPath": "apps/api/src/modules/simulation/fixtures/simulation-fixture-v1.game-config.json"
},
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"exportSignature": {
"init": "(sdk, config) => GameState",
"update": "(state, tick, sdk) => GameState",
"render": "(state, sdk) => RenderCommand[]",
"handleInput": "(state, input, sdk) => GameState",
"onPause": "(state, sdk) => void",
"onResume": "(state, sdk) => void"
},
"runtimeSdkContractVersion": "mvp-s3-runtime-sdk-v1",
"validationReportId": "validation-report-simulation-fixture-v1",
"languageSubset": "s3-safe-esm-game-logic-v1",
"imports": [
"@huijing/runtime-sdk"
],
"forbiddenApis": [],
"entrypoints": [
"init",
"update",
"render",
"handleInput",
"onPause",
"onResume"
]
}

View File

@ -0,0 +1,30 @@
{
"expectedReasonCode": "SCHEMA_REQUIRED",
"id": "game-logic-module-missing-export-signature-001",
"gameConfigId": "simulation-fixture-v1",
"moduleType": "simulation_management",
"sourceKind": "template_codegen",
"sourceRef": {
"gameConfigId": "simulation-fixture-v1",
"templateVersion": "simulation-management@1",
"buildInputPath": "apps/api/src/modules/simulation/fixtures/simulation-fixture-v1.game-config.json"
},
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"runtimeSdkContractVersion": "mvp-s3-runtime-sdk-v1",
"validationReportId": "validation-report-simulation-fixture-v1",
"buildGraphId": "build-graph-simulation-fixture-v1",
"languageSubset": "s3-safe-esm-game-logic-v1",
"imports": [
"@huijing/runtime-sdk"
],
"forbiddenApis": [],
"entrypoints": [
"init",
"update",
"render",
"handleInput",
"onPause",
"onResume"
]
}

View File

@ -0,0 +1,37 @@
{
"expectedReasonCode": "SCHEMA_REQUIRED",
"id": "game-logic-module-missing-runtime-sdk-contract-version-001",
"gameConfigId": "simulation-fixture-v1",
"moduleType": "simulation_management",
"sourceKind": "template_codegen",
"sourceRef": {
"gameConfigId": "simulation-fixture-v1",
"templateVersion": "simulation-management@1",
"buildInputPath": "apps/api/src/modules/simulation/fixtures/simulation-fixture-v1.game-config.json"
},
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"exportSignature": {
"init": "(sdk, config) => GameState",
"update": "(state, tick, sdk) => GameState",
"render": "(state, sdk) => RenderCommand[]",
"handleInput": "(state, input, sdk) => GameState",
"onPause": "(state, sdk) => void",
"onResume": "(state, sdk) => void"
},
"validationReportId": "validation-report-simulation-fixture-v1",
"buildGraphId": "build-graph-simulation-fixture-v1",
"languageSubset": "s3-safe-esm-game-logic-v1",
"imports": [
"@huijing/runtime-sdk"
],
"forbiddenApis": [],
"entrypoints": [
"init",
"update",
"render",
"handleInput",
"onPause",
"onResume"
]
}

View File

@ -0,0 +1,34 @@
{
"expectedReasonCode": "SCHEMA_TYPE",
"id": "game-logic-module-string-source-ref-001",
"gameConfigId": "simulation-fixture-v1",
"moduleType": "simulation_management",
"sourceKind": "template_codegen",
"sourceRef": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"exportSignature": {
"init": "(sdk, config) => GameState",
"update": "(state, tick, sdk) => GameState",
"render": "(state, sdk) => RenderCommand[]",
"handleInput": "(state, input, sdk) => GameState",
"onPause": "(state, sdk) => void",
"onResume": "(state, sdk) => void"
},
"runtimeSdkContractVersion": "mvp-s3-runtime-sdk-v1",
"validationReportId": "validation-report-simulation-fixture-v1",
"buildGraphId": "build-graph-simulation-fixture-v1",
"languageSubset": "s3-safe-esm-game-logic-v1",
"imports": [
"@huijing/runtime-sdk"
],
"forbiddenApis": [],
"entrypoints": [
"init",
"update",
"render",
"handleInput",
"onPause",
"onResume"
]
}

View File

@ -1,20 +1,39 @@
{
"expectedReasonCode": "VALIDATION_REPORT_REQUIRED",
"id": "game-logic-module-no-report-001",
"gameConfigId": "game-config-simulation-001",
"moduleType": "simulation_core_loop",
"sourceKind": "generated_static_module",
"sourceRef": "sha256:source-simulation-core-v1",
"artifactPath": "artifacts/game-logic/simulation-core.mjs",
"checksum": "sha256:logic-module-simulation-core-v1",
"expectedReasonCode": "GAME_LOGIC_VALIDATION_STATUS_SELF_REPORTED",
"id": "game-logic-module-self-reported-validation-001",
"gameConfigId": "simulation-fixture-v1",
"moduleType": "simulation_management",
"sourceKind": "template_codegen",
"sourceRef": {
"gameConfigId": "simulation-fixture-v1",
"templateVersion": "simulation-management@1",
"buildInputPath": "apps/api/src/modules/simulation/fixtures/simulation-fixture-v1.game-config.json"
},
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"exportSignature": {
"init": "(sdk, config) => GameState",
"update": "(state, tick, sdk) => GameState",
"render": "(state, sdk) => RenderCommand[]",
"handleInput": "(state, input, sdk) => GameState",
"onPause": "(state, sdk) => void",
"onResume": "(state, sdk) => void"
},
"runtimeSdkContractVersion": "mvp-s3-runtime-sdk-v1",
"validationReportId": "missing-validation-report-999",
"languageSubset": "mvp-safe-js-expression-subset",
"imports": [],
"buildGraphId": "build-graph-simulation-fixture-v1",
"languageSubset": "s3-safe-esm-game-logic-v1",
"imports": [
"@huijing/runtime-sdk"
],
"forbiddenApis": [],
"entrypoints": [
"createInitialState",
"applyTurnAction",
"deriveOutcome"
"init",
"update",
"render",
"handleInput",
"onPause",
"onResume"
],
"validationStatus": "validated"
}

View File

@ -2,20 +2,27 @@
"expectedReasonCode": "VALIDATION_REPORT_FAILED",
"id": "validation-report-failed-001",
"targetContract": "GameLogicModule",
"artifactPath": "artifacts/game-logic/simulation-core.mjs",
"sourceHash": "sha256:source-simulation-core-v1",
"checksum": "sha256:logic-module-simulation-core-v1",
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"sourceHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"checkedFiles": [
"artifacts/game-logic/simulation-core.mjs"
"dist/game-logic/simulation-fixture-v1/logic.mjs"
],
"importGraph": {
"artifacts/game-logic/simulation-core.mjs": []
"logic.mjs": [
"@huijing/runtime-sdk"
]
},
"allowedImports": [],
"allowedImports": [
"@huijing/runtime-sdk",
"@huijing/rules",
"@huijing/math"
],
"deniedSymbols": [
"unsafe_global_access"
],
"ruleVersion": "mvp-static-logic-rules-v1",
"ruleVersion": "game-logic-allowlist-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "failed",
"createdAt": "2026-06-01T10:00:00+08:00"
"createdAt": "2026-06-04T00:00:00.000Z"
}

View File

@ -18,6 +18,7 @@
"WebAudio"
],
"ruleVersion": "mvp-static-logic-rules-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "passed",
"createdAt": "2026-06-01T10:00:00+08:00"
}

View File

@ -21,6 +21,7 @@
"node:fs"
],
"ruleVersion": "mvp-static-logic-rules-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "failed",
"createdAt": "2026-06-01T10:00:00+08:00"
}

View File

@ -2,19 +2,26 @@
"expectedReasonCode": "VALIDATION_REPORT_CHECKSUM_MISMATCH",
"id": "validation-report-checksum-mismatch-001",
"targetContract": "GameLogicModule",
"artifactPath": "artifacts/game-logic/simulation-core.mjs",
"sourceHash": "sha256:source-simulation-core-v1",
"checksum": "sha256:wrong-logic-module-checksum",
"expectedModuleChecksum": "sha256:logic-module-simulation-core-v1",
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"sourceHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"checksum": "sha256:9999999999999999999999999999999999999999999999999999999999999999",
"expectedModuleChecksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"checkedFiles": [
"artifacts/game-logic/simulation-core.mjs"
"dist/game-logic/simulation-fixture-v1/logic.mjs"
],
"importGraph": {
"artifacts/game-logic/simulation-core.mjs": []
"logic.mjs": [
"@huijing/runtime-sdk"
]
},
"allowedImports": [],
"allowedImports": [
"@huijing/runtime-sdk",
"@huijing/rules",
"@huijing/math"
],
"deniedSymbols": [],
"ruleVersion": "mvp-static-logic-rules-v1",
"ruleVersion": "game-logic-allowlist-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "passed",
"createdAt": "2026-06-01T10:00:00+08:00"
"createdAt": "2026-06-04T00:00:00.000Z"
}

View File

@ -0,0 +1,27 @@
{
"expectedReasonCode": "VALIDATION_REPORT_IMPORT_NOT_ALLOWED",
"id": "validation-report-disallowed-import-001",
"targetContract": "GameLogicModule",
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"sourceHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"checkedFiles": [
"dist/game-logic/simulation-fixture-v1/logic.mjs"
],
"importGraph": {
"logic.mjs": [
"@huijing/runtime-sdk",
"@huijing/unsafe-renderer"
]
},
"allowedImports": [
"@huijing/runtime-sdk",
"@huijing/rules",
"@huijing/math"
],
"deniedSymbols": [],
"ruleVersion": "game-logic-allowlist-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "passed",
"createdAt": "2026-06-04T00:00:00.000Z"
}

View File

@ -0,0 +1,27 @@
{
"expectedReasonCode": "SCHEMA_ADDITIONAL_PROPERTY",
"id": "validation-report-extra-properties-001",
"targetContract": "GameLogicModule",
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"sourceHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"checkedFiles": [
"dist/game-logic/simulation-fixture-v1/logic.mjs"
],
"importGraph": {
"logic.mjs": [
"@huijing/runtime-sdk"
]
},
"allowedImports": [
"@huijing/runtime-sdk",
"@huijing/rules",
"@huijing/math"
],
"deniedSymbols": [],
"ruleVersion": "game-logic-allowlist-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "passed",
"createdAt": "2026-06-04T00:00:00.000Z",
"selfReportedGate": "passed"
}

View File

@ -0,0 +1,25 @@
{
"expectedReasonCode": "VALIDATION_REPORT_REQUIRED_FIELD_MISSING",
"id": "validation-report-missing-created-at-001",
"targetContract": "GameLogicModule",
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"sourceHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"checkedFiles": [
"dist/game-logic/simulation-fixture-v1/logic.mjs"
],
"importGraph": {
"logic.mjs": [
"@huijing/runtime-sdk"
]
},
"allowedImports": [
"@huijing/runtime-sdk",
"@huijing/rules",
"@huijing/math"
],
"deniedSymbols": [],
"ruleVersion": "game-logic-allowlist-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "passed"
}

View File

@ -0,0 +1,25 @@
{
"expectedReasonCode": "VALIDATION_REPORT_REQUIRED_FIELD_MISSING",
"id": "validation-report-missing-target-contract-001",
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"sourceHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"checkedFiles": [
"dist/game-logic/simulation-fixture-v1/logic.mjs"
],
"importGraph": {
"logic.mjs": [
"@huijing/runtime-sdk"
]
},
"allowedImports": [
"@huijing/runtime-sdk",
"@huijing/rules",
"@huijing/math"
],
"deniedSymbols": [],
"ruleVersion": "game-logic-allowlist-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "passed",
"createdAt": "2026-06-04T00:00:00.000Z"
}

View File

@ -0,0 +1,25 @@
{
"expectedReasonCode": "VALIDATION_REPORT_REQUIRED_FIELD_MISSING",
"id": "validation-report-missing-validator-version-001",
"targetContract": "GameLogicModule",
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"sourceHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"checkedFiles": [
"dist/game-logic/simulation-fixture-v1/logic.mjs"
],
"importGraph": {
"logic.mjs": [
"@huijing/runtime-sdk"
]
},
"allowedImports": [
"@huijing/runtime-sdk",
"@huijing/rules",
"@huijing/math"
],
"deniedSymbols": [],
"ruleVersion": "game-logic-allowlist-v1",
"result": "passed",
"createdAt": "2026-06-04T00:00:00.000Z"
}

View File

@ -1,18 +1,25 @@
{
"id": "validation-report-logic-simulation-core-001",
"id": "validation-report-simulation-fixture-v1",
"targetContract": "GameLogicModule",
"artifactPath": "artifacts/game-logic/simulation-core.mjs",
"sourceHash": "sha256:source-simulation-core-v1",
"checksum": "sha256:logic-module-simulation-core-v1",
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"sourceHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"checkedFiles": [
"artifacts/game-logic/simulation-core.mjs"
"dist/game-logic/simulation-fixture-v1/logic.mjs"
],
"importGraph": {
"artifacts/game-logic/simulation-core.mjs": []
"logic.mjs": [
"@huijing/runtime-sdk"
]
},
"allowedImports": [],
"allowedImports": [
"@huijing/runtime-sdk",
"@huijing/rules",
"@huijing/math"
],
"deniedSymbols": [],
"ruleVersion": "mvp-static-logic-rules-v1",
"ruleVersion": "game-logic-allowlist-v1",
"validatorVersion": "mvp-s3-validator-v1",
"result": "passed",
"createdAt": "2026-06-01T10:00:00+08:00"
"createdAt": "2026-06-04T00:00:00.000Z"
}

View File

@ -1,19 +1,37 @@
{
"id": "game-logic-module-simulation-core-001",
"gameConfigId": "game-config-simulation-001",
"moduleType": "simulation_core_loop",
"sourceKind": "generated_static_module",
"sourceRef": "sha256:source-simulation-core-v1",
"artifactPath": "artifacts/game-logic/simulation-core.mjs",
"checksum": "sha256:logic-module-simulation-core-v1",
"validationReportId": "validation-report-logic-simulation-core-001",
"languageSubset": "mvp-safe-js-expression-subset",
"imports": [],
"id": "game-logic-module-simulation-fixture-v1",
"gameConfigId": "simulation-fixture-v1",
"moduleType": "simulation_management",
"sourceKind": "template_codegen",
"sourceRef": {
"gameConfigId": "simulation-fixture-v1",
"templateVersion": "simulation-management@1",
"buildInputPath": "apps/api/src/modules/simulation/fixtures/simulation-fixture-v1.game-config.json"
},
"artifactPath": "dist/game-logic/simulation-fixture-v1/logic.mjs",
"checksum": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"exportSignature": {
"init": "(sdk, config) => GameState",
"update": "(state, tick, sdk) => GameState",
"render": "(state, sdk) => RenderCommand[]",
"handleInput": "(state, input, sdk) => GameState",
"onPause": "(state, sdk) => void",
"onResume": "(state, sdk) => void"
},
"runtimeSdkContractVersion": "mvp-s3-runtime-sdk-v1",
"validationReportId": "validation-report-simulation-fixture-v1",
"buildGraphId": "build-graph-simulation-fixture-v1",
"languageSubset": "s3-safe-esm-game-logic-v1",
"imports": [
"@huijing/runtime-sdk"
],
"forbiddenApis": [],
"entrypoints": [
"createInitialState",
"applyTurnAction",
"deriveOutcome"
],
"validationStatus": "validated"
"init",
"update",
"render",
"handleInput",
"onPause",
"onResume"
]
}

View File

@ -2,21 +2,45 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "GameLogicModule",
"type": "object",
"required": ["id", "gameConfigId", "moduleType", "sourceKind", "sourceRef", "artifactPath", "checksum", "validationReportId", "languageSubset", "imports", "forbiddenApis", "entrypoints", "validationStatus"],
"required": ["id", "gameConfigId", "moduleType", "sourceKind", "sourceRef", "artifactPath", "checksum", "exportSignature", "runtimeSdkContractVersion", "validationReportId", "buildGraphId", "languageSubset", "imports", "forbiddenApis", "entrypoints"],
"forbiddenProperties": ["validationStatus"],
"properties": {
"id": { "type": "string" },
"gameConfigId": { "type": "string" },
"moduleType": { "type": "string" },
"sourceKind": { "type": "string" },
"sourceRef": { "type": "string" },
"sourceRef": {
"type": "object",
"required": ["gameConfigId", "templateVersion", "buildInputPath"],
"properties": {
"gameConfigId": { "type": "string" },
"templateVersion": { "type": "string" },
"buildInputPath": { "type": "string" }
},
"additionalProperties": false
},
"artifactPath": { "type": "string" },
"checksum": { "type": "string" },
"exportSignature": {
"type": "object",
"required": ["init", "update", "render", "handleInput", "onPause", "onResume"],
"properties": {
"init": { "type": "string" },
"update": { "type": "string" },
"render": { "type": "string" },
"handleInput": { "type": "string" },
"onPause": { "type": "string" },
"onResume": { "type": "string" }
},
"additionalProperties": false
},
"runtimeSdkContractVersion": { "type": "string" },
"validationReportId": { "type": "string" },
"buildGraphId": { "type": "string" },
"languageSubset": { "type": "string" },
"imports": { "type": "array" },
"forbiddenApis": { "type": "array" },
"entrypoints": { "type": "array" },
"validationStatus": { "type": "string" }
"entrypoints": { "type": "array" }
},
"additionalProperties": false
}

View File

@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ValidationReport",
"type": "object",
"required": ["id", "targetContract", "artifactPath", "sourceHash", "checksum", "checkedFiles", "importGraph", "allowedImports", "deniedSymbols", "ruleVersion", "result", "createdAt"],
"required": ["id", "targetContract", "artifactPath", "sourceHash", "checksum", "checkedFiles", "importGraph", "allowedImports", "deniedSymbols", "ruleVersion", "validatorVersion", "result", "createdAt"],
"properties": {
"id": { "type": "string" },
"targetContract": { "type": "string" },
@ -14,7 +14,8 @@
"allowedImports": { "type": "array" },
"deniedSymbols": { "type": "array" },
"ruleVersion": { "type": "string" },
"result": { "enum": ["passed", "failed"] },
"validatorVersion": { "type": "string" },
"result": { "const": "passed" },
"createdAt": { "type": "string" }
},
"additionalProperties": false

View File

@ -31,7 +31,16 @@ const mvpSummaryFixtureNames = [
"game-logic-validation-report-web-only-sdk-invalid.json",
"validation-report-checksum-mismatch-invalid.json",
"game-logic-validated-without-report-invalid.json",
"game-logic-validation-report-failed-invalid.json"
"game-logic-missing-export-signature-invalid.json",
"game-logic-missing-runtime-sdk-contract-version-invalid.json",
"game-logic-missing-build-graph-id-invalid.json",
"game-logic-string-source-ref-invalid.json",
"game-logic-validation-report-failed-invalid.json",
"validation-report-disallowed-import-invalid.json",
"validation-report-missing-target-contract-invalid.json",
"validation-report-missing-validator-version-invalid.json",
"validation-report-missing-created-at-invalid.json",
"validation-report-extra-properties-invalid.json"
];
const allowedAgentTaskPairs = new Map([
@ -296,13 +305,37 @@ function schemaTargetsForFixture(file) {
if (fileName === "design-section-patch-valid.json" || fileName === "design-section-patch-checksum-conflict-invalid.json") return ["DesignSectionPatch"];
if (fileName === "asset-manifest-valid.json" || fileName === "resource-over-budget-invalid.json") return ["AssetManifest"];
if (fileName === "simulation-game-ir-valid.json") return ["GameIR"];
if (fileName === "simulation-game-logic-module-valid.json" || fileName === "game-logic-validated-without-report-invalid.json") return ["GameLogicModule"];
if (
fileName === "simulation-game-logic-module-valid.json" ||
[
"game-logic-validated-without-report-invalid.json",
"game-logic-missing-export-signature-invalid.json",
"game-logic-missing-runtime-sdk-contract-version-invalid.json",
"game-logic-missing-build-graph-id-invalid.json",
"game-logic-string-source-ref-invalid.json"
].includes(fileName)
) return ["GameLogicModule"];
if (fileName === "simulation-game-package-four-profiles-valid.json") return ["GamePackage"];
if (fileName === "minigame-code-conversion-valid.json" || ["missing-adapter-invalid.json", "web-package-disguised-as-minigame-invalid.json"].includes(fileName)) return ["MiniGameCodeConversion"];
if (fileName.startsWith("minigame-project-") || ["checksum-mismatch-invalid.json", "empty-minigame-project-shell-invalid.json", "missing-entry-config-invalid.json"].includes(fileName)) return ["MiniGameProject"];
if (fileName.endsWith("conversion-report-valid.json") || ["forged-conversion-result-invalid.json", "external-script-invalid.json", "web-only-sdk-invalid.json"].includes(fileName)) return ["ConversionReport"];
if (fileName === "runtime-smoke-evidence-valid.json" || fileName === "runtime-smoke-failed-as-passed-invalid.json") return ["RuntimeSmokeEvidence"];
if (fileName === "game-logic-validation-report-valid.json" || ["dom-bom-usage-invalid.json", "dynamic-execution-invalid.json", "node-api-usage-invalid.json", "game-logic-validation-report-web-only-sdk-invalid.json", "validation-report-checksum-mismatch-invalid.json", "game-logic-validation-report-failed-invalid.json"].includes(fileName)) return ["ValidationReport"];
if (
fileName === "game-logic-validation-report-valid.json" ||
[
"dom-bom-usage-invalid.json",
"dynamic-execution-invalid.json",
"node-api-usage-invalid.json",
"game-logic-validation-report-web-only-sdk-invalid.json",
"validation-report-checksum-mismatch-invalid.json",
"game-logic-validation-report-failed-invalid.json",
"validation-report-disallowed-import-invalid.json",
"validation-report-missing-target-contract-invalid.json",
"validation-report-missing-validator-version-invalid.json",
"validation-report-missing-created-at-invalid.json",
"validation-report-extra-properties-invalid.json"
].includes(fileName)
) return ["ValidationReport"];
if (fileName === "devtool-import-evidence-valid.json" || ["blocker-as-passed-invalid.json", "failed-import-used-as-passed-invalid.json"].includes(fileName)) return ["DevToolImportEvidence"];
if (fileName === "devtool-import-evidence-record-valid.json" || ["note-as-evidence-invalid.json", "evidence-record-non-passed-import-invalid.json"].includes(fileName)) return ["EvidenceRecord"];
if (fileName === "channel-readiness-note-valid.json" || fileName === "channel-readiness-as-review-invalid.json") return ["ChannelReadinessNote"];
@ -406,6 +439,11 @@ function importGraphSpecifiers(data) {
return Object.values(data.importGraph ?? {}).flatMap((item) => asArray(item));
}
function validationReportHasDisallowedImport(data) {
const allowedImports = new Set(asArray(data.allowedImports));
return importGraphSpecifiers(data).some((specifier) => !allowedImports.has(specifier));
}
function hasAny(values, candidates) {
const valueSet = new Set(values);
return candidates.some((candidate) => valueSet.has(candidate));
@ -448,7 +486,21 @@ function conversionReportMissingRequired(data) {
}
function validationReportMissingRequired(data) {
return ["sourceHash", "checksum", "importGraph", "ruleVersion", "result"].some((field) => !valueExists(data[field]));
return [
"id",
"targetContract",
"artifactPath",
"sourceHash",
"checksum",
"checkedFiles",
"importGraph",
"allowedImports",
"deniedSymbols",
"ruleVersion",
"validatorVersion",
"result",
"createdAt"
].some((field) => !valueExists(data[field]));
}
function customContractSetRejection(contracts, rawData, context, fileName = "") {
@ -481,10 +533,8 @@ function customContractSetRejection(contracts, rawData, context, fileName = "")
if (contracts.has("DesignSectionPatch") && data.conflictStatus === "conflict") return rejectedWhen(true, "CHECKSUM_CONFLICT");
if (contracts.has("GameLogicModule") && data.validationStatus === "validated") {
const report = context.mvp.validationReportsById.get(data.validationReportId);
if (!report || report.result !== "passed") return rejectedWhen(true, "VALIDATION_REPORT_REQUIRED", data.validationReportId);
if (report.checksum !== data.checksum || report.sourceHash !== data.sourceRef) return rejectedWhen(true, "VALIDATION_REPORT_CHECKSUM_MISMATCH", data.validationReportId);
if (contracts.has("GameLogicModule") && Object.prototype.hasOwnProperty.call(data, "validationStatus")) {
return rejectedWhen(true, "GAME_LOGIC_VALIDATION_STATUS_SELF_REPORTED", data.validationStatus);
}
if (contracts.has("GamePackage") && gamePackageMissingProfile(data)) return rejectedWhen(true, "GAME_PACKAGE_PROFILE_MISSING");
@ -531,6 +581,8 @@ function customContractSetRejection(contracts, rawData, context, fileName = "")
return rejectedWhen(true, "DYNAMIC_EXECUTION_FORBIDDEN");
}
if (validationReportMissingRequired(data)) return rejectedWhen(true, "VALIDATION_REPORT_REQUIRED_FIELD_MISSING");
// importGraph 和 allowedImports 是跨字段约束,当前轻量 JSON Schema 校验器无法声明式表达。
if (validationReportHasDisallowedImport(data)) return rejectedWhen(true, "VALIDATION_REPORT_IMPORT_NOT_ALLOWED");
if (data.expectedModuleChecksum && data.checksum !== data.expectedModuleChecksum) return rejectedWhen(true, "VALIDATION_REPORT_CHECKSUM_MISMATCH");
if (data.result !== "passed") return rejectedWhen(true, "VALIDATION_REPORT_FAILED");
}
@ -572,8 +624,12 @@ function customContractRejection(contract, rawData, context) {
}
function customContractRejectionBeforeSchema(contract, rawData) {
if (contract !== "ValidationReport") return rejectedWhen(false, null);
const data = stripFixtureMetadata(rawData);
// descriptor 自报 gate 状态需要在 schema 前拒绝,避免 CLI 返回泛化的额外字段错误。
if (contract === "GameLogicModule" && Object.prototype.hasOwnProperty.call(data ?? {}, "validationStatus")) {
return rejectedWhen(true, "GAME_LOGIC_VALIDATION_STATUS_SELF_REPORTED", data.validationStatus);
}
if (contract !== "ValidationReport") return rejectedWhen(false, null);
// diagnostics 不在当前 schema 中;只把这个已知语义错误前置,避免其它 malformed payload 绕过 schema。
if (data?.targetContract === "GameLogicModule" && diagnosticsHaveCode(data, "WEB_ONLY_SDK_USAGE")) {
return rejectedWhen(true, "WEB_ONLY_SDK_USAGE");
@ -937,11 +993,11 @@ function validateFixtures(mappings, context) {
check(exists("harness/fixtures/manifests/invalid-csp-violation.json"), "CSP violation fixture missing");
for (const name of mvpSummaryFixtureNames) check(loadedFixtureNames.includes(name), `summary fixture ${name} was not loaded`);
check(validFixtures === 45, `expected 45 valid fixtures, got ${validFixtures}`);
check(invalidFixtures === 51, `expected 51 invalid fixtures, got ${invalidFixtures}`);
check(invalidFixtures === 60, `expected 60 invalid fixtures, got ${invalidFixtures}`);
check(foundationValidFixtures === 21, `expected 21 foundation valid fixtures, got ${foundationValidFixtures}`);
check(mvpValidFixtures === 24, `expected 24 MVP valid fixtures, got ${mvpValidFixtures}`);
check(foundationInvalidFixtures === 21, `expected 21 foundation invalid fixtures, got ${foundationInvalidFixtures}`);
check(mvpInvalidFixtures === 30, `expected 30 MVP invalid fixtures, got ${mvpInvalidFixtures}`);
check(mvpInvalidFixtures === 39, `expected 39 MVP invalid fixtures, got ${mvpInvalidFixtures}`);
return { validFixtures, invalidFixtures, foundationValidFixtures, mvpValidFixtures, foundationInvalidFixtures, mvpInvalidFixtures, invalidFixturesRejected, invalidReasonCounts, loadedFixtureNames };
}
@ -967,7 +1023,7 @@ function main() {
const { validFixtures, invalidFixtures, foundationValidFixtures, mvpValidFixtures, foundationInvalidFixtures, mvpInvalidFixtures, invalidFixturesRejected, invalidReasonCounts, loadedFixtureNames } = validateFixtures(tools.toolTransitionMapping, context);
if (checkInvalidOnly) {
check(invalidFixtures === 51, `expected 51 invalid fixtures, got ${invalidFixtures}`);
check(invalidFixtures === 60, `expected 60 invalid fixtures, got ${invalidFixtures}`);
check(invalidFixturesRejected === invalidFixtures, `expected every invalid fixture rejected, got ${invalidFixturesRejected}/${invalidFixtures}`);
}