From 006f65ec9b6d683a4d784078a14ed49b5e21278e Mon Sep 17 00:00:00 2001 From: zizi Date: Thu, 4 Jun 2026 18:26:06 +0800 Subject: [PATCH] feat(s3): align harness logic contracts --- docs/memorys/2026-06-04-S3Task1合同门禁.md | 157 ++++++++++++++++++ .../mvp/invalid/dom-bom-usage-invalid.json | 1 + .../invalid/dynamic-execution-invalid.json | 1 + ...-logic-missing-build-graph-id-invalid.json | 37 +++++ ...ogic-missing-export-signature-invalid.json | 30 ++++ ...-runtime-sdk-contract-version-invalid.json | 37 +++++ .../game-logic-string-source-ref-invalid.json | 34 ++++ ...ogic-validated-without-report-invalid.json | 45 +++-- ...ogic-validation-report-failed-invalid.json | 23 ++- ...alidation-report-web-only-sdk-invalid.json | 1 + .../mvp/invalid/node-api-usage-invalid.json | 1 + ...tion-report-checksum-mismatch-invalid.json | 25 ++- ...tion-report-disallowed-import-invalid.json | 27 +++ ...ation-report-extra-properties-invalid.json | 27 +++ ...ion-report-missing-created-at-invalid.json | 25 +++ ...eport-missing-target-contract-invalid.json | 25 +++ ...ort-missing-validator-version-invalid.json | 25 +++ .../game-logic-validation-report-valid.json | 25 ++- .../simulation-game-logic-module-valid.json | 48 ++++-- harness/schemas/game-logic-module.schema.json | 32 +++- harness/schemas/validation-report.schema.json | 5 +- harness/scripts/validate-harness.mjs | 80 +++++++-- 22 files changed, 639 insertions(+), 72 deletions(-) create mode 100644 docs/memorys/2026-06-04-S3Task1合同门禁.md create mode 100644 harness/fixtures/mvp/invalid/game-logic-missing-build-graph-id-invalid.json create mode 100644 harness/fixtures/mvp/invalid/game-logic-missing-export-signature-invalid.json create mode 100644 harness/fixtures/mvp/invalid/game-logic-missing-runtime-sdk-contract-version-invalid.json create mode 100644 harness/fixtures/mvp/invalid/game-logic-string-source-ref-invalid.json create mode 100644 harness/fixtures/mvp/invalid/validation-report-disallowed-import-invalid.json create mode 100644 harness/fixtures/mvp/invalid/validation-report-extra-properties-invalid.json create mode 100644 harness/fixtures/mvp/invalid/validation-report-missing-created-at-invalid.json create mode 100644 harness/fixtures/mvp/invalid/validation-report-missing-target-contract-invalid.json create mode 100644 harness/fixtures/mvp/invalid/validation-report-missing-validator-version-invalid.json diff --git a/docs/memorys/2026-06-04-S3Task1合同门禁.md b/docs/memorys/2026-06-04-S3Task1合同门禁.md new file mode 100644 index 00000000..eeb31ff4 --- /dev/null +++ b/docs/memorys/2026-06-04-S3Task1合同门禁.md @@ -0,0 +1,157 @@ +# 2026-06-04 S3 Task1 合同门禁 + +## 结论 + +S3 Task1 `Align S0 Harness GameLogicModule And ValidationReport Contracts` 已完成实现、controller 自测,并通过 fresh review gate: + +- spec compliance review:PASS,无 Critical / Important。 +- quality / feasibility review:PASS,无 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。 diff --git a/harness/fixtures/mvp/invalid/dom-bom-usage-invalid.json b/harness/fixtures/mvp/invalid/dom-bom-usage-invalid.json index 80fedb1d..5f358657 100644 --- a/harness/fixtures/mvp/invalid/dom-bom-usage-invalid.json +++ b/harness/fixtures/mvp/invalid/dom-bom-usage-invalid.json @@ -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" } diff --git a/harness/fixtures/mvp/invalid/dynamic-execution-invalid.json b/harness/fixtures/mvp/invalid/dynamic-execution-invalid.json index 1507ed62..b536cd9c 100644 --- a/harness/fixtures/mvp/invalid/dynamic-execution-invalid.json +++ b/harness/fixtures/mvp/invalid/dynamic-execution-invalid.json @@ -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" } diff --git a/harness/fixtures/mvp/invalid/game-logic-missing-build-graph-id-invalid.json b/harness/fixtures/mvp/invalid/game-logic-missing-build-graph-id-invalid.json new file mode 100644 index 00000000..61d9135b --- /dev/null +++ b/harness/fixtures/mvp/invalid/game-logic-missing-build-graph-id-invalid.json @@ -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" + ] +} diff --git a/harness/fixtures/mvp/invalid/game-logic-missing-export-signature-invalid.json b/harness/fixtures/mvp/invalid/game-logic-missing-export-signature-invalid.json new file mode 100644 index 00000000..fd34f932 --- /dev/null +++ b/harness/fixtures/mvp/invalid/game-logic-missing-export-signature-invalid.json @@ -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" + ] +} diff --git a/harness/fixtures/mvp/invalid/game-logic-missing-runtime-sdk-contract-version-invalid.json b/harness/fixtures/mvp/invalid/game-logic-missing-runtime-sdk-contract-version-invalid.json new file mode 100644 index 00000000..db9f3e5f --- /dev/null +++ b/harness/fixtures/mvp/invalid/game-logic-missing-runtime-sdk-contract-version-invalid.json @@ -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" + ] +} diff --git a/harness/fixtures/mvp/invalid/game-logic-string-source-ref-invalid.json b/harness/fixtures/mvp/invalid/game-logic-string-source-ref-invalid.json new file mode 100644 index 00000000..fb8f79cc --- /dev/null +++ b/harness/fixtures/mvp/invalid/game-logic-string-source-ref-invalid.json @@ -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" + ] +} diff --git a/harness/fixtures/mvp/invalid/game-logic-validated-without-report-invalid.json b/harness/fixtures/mvp/invalid/game-logic-validated-without-report-invalid.json index b7ea7918..0fe2b867 100644 --- a/harness/fixtures/mvp/invalid/game-logic-validated-without-report-invalid.json +++ b/harness/fixtures/mvp/invalid/game-logic-validated-without-report-invalid.json @@ -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" } diff --git a/harness/fixtures/mvp/invalid/game-logic-validation-report-failed-invalid.json b/harness/fixtures/mvp/invalid/game-logic-validation-report-failed-invalid.json index 0a6bea4d..f6224fdc 100644 --- a/harness/fixtures/mvp/invalid/game-logic-validation-report-failed-invalid.json +++ b/harness/fixtures/mvp/invalid/game-logic-validation-report-failed-invalid.json @@ -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" } diff --git a/harness/fixtures/mvp/invalid/game-logic-validation-report-web-only-sdk-invalid.json b/harness/fixtures/mvp/invalid/game-logic-validation-report-web-only-sdk-invalid.json index a6c002c1..1cbfe3b2 100644 --- a/harness/fixtures/mvp/invalid/game-logic-validation-report-web-only-sdk-invalid.json +++ b/harness/fixtures/mvp/invalid/game-logic-validation-report-web-only-sdk-invalid.json @@ -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" } diff --git a/harness/fixtures/mvp/invalid/node-api-usage-invalid.json b/harness/fixtures/mvp/invalid/node-api-usage-invalid.json index afa332a1..6e855079 100644 --- a/harness/fixtures/mvp/invalid/node-api-usage-invalid.json +++ b/harness/fixtures/mvp/invalid/node-api-usage-invalid.json @@ -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" } diff --git a/harness/fixtures/mvp/invalid/validation-report-checksum-mismatch-invalid.json b/harness/fixtures/mvp/invalid/validation-report-checksum-mismatch-invalid.json index 93261b9c..0d5c0d25 100644 --- a/harness/fixtures/mvp/invalid/validation-report-checksum-mismatch-invalid.json +++ b/harness/fixtures/mvp/invalid/validation-report-checksum-mismatch-invalid.json @@ -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" } diff --git a/harness/fixtures/mvp/invalid/validation-report-disallowed-import-invalid.json b/harness/fixtures/mvp/invalid/validation-report-disallowed-import-invalid.json new file mode 100644 index 00000000..ff8318bf --- /dev/null +++ b/harness/fixtures/mvp/invalid/validation-report-disallowed-import-invalid.json @@ -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" +} diff --git a/harness/fixtures/mvp/invalid/validation-report-extra-properties-invalid.json b/harness/fixtures/mvp/invalid/validation-report-extra-properties-invalid.json new file mode 100644 index 00000000..70074231 --- /dev/null +++ b/harness/fixtures/mvp/invalid/validation-report-extra-properties-invalid.json @@ -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" +} diff --git a/harness/fixtures/mvp/invalid/validation-report-missing-created-at-invalid.json b/harness/fixtures/mvp/invalid/validation-report-missing-created-at-invalid.json new file mode 100644 index 00000000..e72dbf88 --- /dev/null +++ b/harness/fixtures/mvp/invalid/validation-report-missing-created-at-invalid.json @@ -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" +} diff --git a/harness/fixtures/mvp/invalid/validation-report-missing-target-contract-invalid.json b/harness/fixtures/mvp/invalid/validation-report-missing-target-contract-invalid.json new file mode 100644 index 00000000..46e35361 --- /dev/null +++ b/harness/fixtures/mvp/invalid/validation-report-missing-target-contract-invalid.json @@ -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" +} diff --git a/harness/fixtures/mvp/invalid/validation-report-missing-validator-version-invalid.json b/harness/fixtures/mvp/invalid/validation-report-missing-validator-version-invalid.json new file mode 100644 index 00000000..674c89d2 --- /dev/null +++ b/harness/fixtures/mvp/invalid/validation-report-missing-validator-version-invalid.json @@ -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" +} diff --git a/harness/fixtures/mvp/valid/game-logic-validation-report-valid.json b/harness/fixtures/mvp/valid/game-logic-validation-report-valid.json index 6d36043b..e1f37d53 100644 --- a/harness/fixtures/mvp/valid/game-logic-validation-report-valid.json +++ b/harness/fixtures/mvp/valid/game-logic-validation-report-valid.json @@ -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" } diff --git a/harness/fixtures/mvp/valid/simulation-game-logic-module-valid.json b/harness/fixtures/mvp/valid/simulation-game-logic-module-valid.json index 02f1fff1..8ca6fe97 100644 --- a/harness/fixtures/mvp/valid/simulation-game-logic-module-valid.json +++ b/harness/fixtures/mvp/valid/simulation-game-logic-module-valid.json @@ -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" + ] } diff --git a/harness/schemas/game-logic-module.schema.json b/harness/schemas/game-logic-module.schema.json index 9f69b4af..3aae4111 100644 --- a/harness/schemas/game-logic-module.schema.json +++ b/harness/schemas/game-logic-module.schema.json @@ -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 } diff --git a/harness/schemas/validation-report.schema.json b/harness/schemas/validation-report.schema.json index 26903013..3cd650d0 100644 --- a/harness/schemas/validation-report.schema.json +++ b/harness/schemas/validation-report.schema.json @@ -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 diff --git a/harness/scripts/validate-harness.mjs b/harness/scripts/validate-harness.mjs index 772aaee9..d9d944a2 100755 --- a/harness/scripts/validate-harness.mjs +++ b/harness/scripts/validate-harness.mjs @@ -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}`); }