diff --git a/.claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json b/.claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json index c5f0f0d..e9b680b 100644 --- a/.claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json +++ b/.claude/skills/replay-eval/configs/writer-gate-a-deep-space-v1.json @@ -48,8 +48,8 @@ "writerInputProvenance": "preregistered_fine_outline", "oracleInputProvenance": "oracle_reference_scaffold", "maxContextChars": 140000, - "modelVersion": "pending_probe", - "adapterVersion": "pending_probe", + "modelVersion": "claude-opus-4-8[1m]", + "adapterVersion": "writer-runtime-v1|claude-cli-2.1.211|binary-sha256-5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629", "sampling": { "temperature": "unsupported", "topP": "unsupported", @@ -58,6 +58,41 @@ }, "detectorProfile": "writer-detector-report-v1" }, + "executionAuthorization": { + "runtimeProbe": { + "status": "successful", + "checkedAt": "2026-07-22T22:09:32+08:00", + "claudeExecutablePath": "/Users/qingse/.nvm/versions/node/v24.15.0/bin/claude", + "claudeExecutableSha256": "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629", + "claudeCliVersion": "2.1.211", + "modelAlias": "opus", + "resolvedModelId": "claude-opus-4-8[1m]", + "executionProfileSha256": "sha256:9812b32c14c6a95204a22555c34b84353a96d4df5e5988a00df23634022e34ca", + "executionReceiptSha256": "sha256:5e1171201771cd921c187b3bdc9d192f3bb0abbba4115a4489150721c4d72477", + "structuredOutputSha256": "sha256:4062edaf750fb8074e7e83e0c9028c94e32468a8b6f1614774328ef045150f93", + "terminalReason": "completed", + "totalCostUsd": "0.003885", + "modelMatch": true, + "exitCode": 0, + "apiErrorStatus": null, + "receiptSha256": "sha256:63de7cea7b33dd328bc526bb76175d53a3a03179f2d901badf7547cd42cf4c70" + }, + "budget": { + "status": "pending", + "reason": "Gate A 的 writer、semantic_detector、blind_judge 机械最低调用次数已登记,但本次单次执行预算尚未获得明确批准;不得创建模型 runner。", + "maxCalls": { + "writer": 15, + "semantic_detector": 15, + "blind_judge": 15 + }, + "receiptSha256": "sha256:aeb40e282c1031f40e007504edf32e783266a02cce74b3c1d32ec94d2e266be4" + }, + "rawRetention": { + "status": "pending", + "reason": "本次执行的原始 prompt/response 与 token 留存尚未获得明确批准;不得创建 raw vault。", + "receiptSha256": "sha256:9d912dcafdc2988708d4a2dd21a216382a51eccb8354c5d9f252de2f96d77d45" + } + }, "armPolicies": { "A": { "evidenceStrategy": "generic_prose_retrieval", diff --git a/.claude/skills/replay-eval/scripts/load_writer_reference_work.py b/.claude/skills/replay-eval/scripts/load_writer_reference_work.py index a92e8ff..a108308 100644 --- a/.claude/skills/replay-eval/scripts/load_writer_reference_work.py +++ b/.claude/skills/replay-eval/scripts/load_writer_reference_work.py @@ -60,6 +60,7 @@ CANONICAL_CHAPTER_STATUSES = frozenset({"published", "confirmed", "canonical"}) EXPECTED_WRITER_INPUT_PROVENANCE = "preregistered_fine_outline" EXPECTED_ORACLE_INPUT_PROVENANCE = "oracle_reference_scaffold" PREREGISTERED_MAX_CONTEXT_CHARS = 140_000 +RUNTIME_ADAPTER_VERSION_PREFIX = "writer-runtime-v1" class WriterReferenceWorkError(AdapterError): @@ -78,6 +79,29 @@ def _sha256_value(value: Any) -> str: return "sha256:" + hashlib.sha256(_safe_json(value).encode("utf-8")).hexdigest() +def _is_full_sha256(value: Any) -> bool: + """只接受不带前缀的 64 位小写 SHA-256。""" + + return isinstance(value, str) and len(value) == 64 and all( + char in "0123456789abcdef" for char in value + ) + + +def _validate_self_hash(value: Mapping[str, Any], field: str) -> None: + """校验对象的 receiptSha256 是否等于其规范 JSON 自哈希。""" + + receipt = value.get("receiptSha256") + if not isinstance(receipt, str) or not receipt.startswith("sha256:") or not _is_full_sha256( + receipt.removeprefix("sha256:") + ): + raise WriterReferenceWorkError(f"{field}.receiptSha256 必须是 canonical SHA-256") + expected = _sha256_value( + {key: item for key, item in value.items() if key != "receiptSha256"} + ) + if receipt != expected: + raise WriterReferenceWorkError(f"{field}.receiptSha256 canonical 自哈希无效") + + def _positive_chapter(value: Any, field: str) -> int: """严格接受正整数章号,不把 bool、浮点或模糊文本猜成章号。""" @@ -163,8 +187,49 @@ def _validate_loader_controls( samples = base_config.get("samples") if not isinstance(samples, list) or not samples: raise WriterReferenceWorkError("基础配置 samples 不能为空") - if common.get("modelVersion") != "pending_probe" or common.get("adapterVersion") != "pending_probe": - raise WriterReferenceWorkError("modelVersion/adapterVersion 必须保持 pending_probe") + execution_authorization = base_config.get("executionAuthorization") + if not isinstance(execution_authorization, Mapping): + raise WriterReferenceWorkError("基础配置缺少 executionAuthorization") + runtime_probe = execution_authorization.get("runtimeProbe") + if not isinstance(runtime_probe, Mapping): + raise WriterReferenceWorkError("executionAuthorization.runtimeProbe 必须是对象") + if runtime_probe.get("status") != "successful": + raise WriterReferenceWorkError("executionAuthorization.runtimeProbe 必须为 successful") + _validate_self_hash(runtime_probe, "executionAuthorization.runtimeProbe") + cli_version = runtime_probe.get("claudeCliVersion") + executable_sha256 = runtime_probe.get("claudeExecutableSha256") + if not isinstance(cli_version, str) or not cli_version.strip(): + raise WriterReferenceWorkError("runtimeProbe.claudeCliVersion 不能为空") + if not _is_full_sha256(executable_sha256): + raise WriterReferenceWorkError( + "runtimeProbe.claudeExecutableSha256 必须是完整 64 位小写 SHA-256" + ) + resolved_model_id = runtime_probe.get("resolvedModelId") + if not isinstance(resolved_model_id, str) or not resolved_model_id.strip(): + raise WriterReferenceWorkError("runtimeProbe.resolvedModelId 不能为空") + if common.get("modelVersion") != resolved_model_id: + raise WriterReferenceWorkError( + "commonControls.modelVersion 必须精确等于 runtimeProbe.resolvedModelId" + ) + expected_adapter_version = ( + f"{RUNTIME_ADAPTER_VERSION_PREFIX}|claude-cli-{cli_version}|" + f"binary-sha256-{executable_sha256}" + ) + if common.get("adapterVersion") != expected_adapter_version: + raise WriterReferenceWorkError( + "commonControls.adapterVersion 未绑定 runtimeProbe 的 CLI version 与 executable hash" + ) + for control_name in ("budget", "rawRetention"): + control = execution_authorization.get(control_name) + if not isinstance(control, Mapping): + raise WriterReferenceWorkError( + f"executionAuthorization.{control_name} 必须是对象" + ) + if control.get("status") != "pending": + raise WriterReferenceWorkError( + f"executionAuthorization.{control_name} 必须保持 pending" + ) + _validate_self_hash(control, f"executionAuthorization.{control_name}") if common.get("sampling") != { "temperature": "unsupported", "topP": "unsupported", diff --git a/.claude/skills/replay-eval/scripts/test_load_writer_reference_work.py b/.claude/skills/replay-eval/scripts/test_load_writer_reference_work.py index 12c7456..5fe6894 100644 --- a/.claude/skills/replay-eval/scripts/test_load_writer_reference_work.py +++ b/.claude/skills/replay-eval/scripts/test_load_writer_reference_work.py @@ -18,6 +18,7 @@ sys.path.insert(0, str(SCRIPT_DIR)) sys.path.insert(0, str(READ_CONTEXT_SCRIPTS)) import load_writer_reference_work as loader # noqa: E402 +import run_writer_replay as replay_module # noqa: E402 from load_writer_reference_work import ( # noqa: E402 WriterReferenceWorkError, assemble_writer_gate_config, @@ -38,6 +39,17 @@ SELECTOR_DIGEST = loader.selector_sha256(SELECTOR_PATH.read_bytes()) FILE_HASH = "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4" SOURCE_HASH = f"sha256:{FILE_HASH}" SOURCE_VERSION = f"raw-file-v1:{SOURCE_HASH}" +EXPECTED_CLAUDE_EXECUTABLE_SHA256 = ( + "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629" +) + + +def _refresh_self_hash(value: dict[str, object]) -> None: + """按 loader 使用的规范 JSON 算法刷新测试夹具自哈希。""" + + value["receiptSha256"] = loader._sha256_value( + {key: item for key, item in value.items() if key != "receiptSha256"} + ) def _card_row( @@ -357,8 +369,34 @@ class LoadWriterReferenceWorkTest(unittest.TestCase): self.assertEqual(self.base_config["commonControls"]["selectorSha256"], SELECTOR_DIGEST) self.assertEqual(self.base_config["strategyVersion"], "writer-abc-single-variable-v2") - self.assertEqual(self.base_config["commonControls"]["modelVersion"], "pending_probe") - self.assertEqual(self.base_config["commonControls"]["adapterVersion"], "pending_probe") + probe = self.base_config["executionAuthorization"]["runtimeProbe"] + self.assertEqual(probe["status"], "successful") + self.assertEqual( + self.base_config["commonControls"]["modelVersion"], probe["resolvedModelId"] + ) + self.assertEqual(len(probe["claudeExecutableSha256"]), 64) + self.assertEqual( + probe["claudeExecutableSha256"], EXPECTED_CLAUDE_EXECUTABLE_SHA256 + ) + self.assertEqual( + self.base_config["commonControls"]["adapterVersion"], + "writer-runtime-v1|claude-cli-" + + probe["claudeCliVersion"] + + "|binary-sha256-" + + probe["claudeExecutableSha256"], + ) + for control_name in ("runtimeProbe", "budget", "rawRetention"): + control = self.base_config["executionAuthorization"][control_name] + self.assertEqual( + control["receiptSha256"], + loader._sha256_value( + {key: item for key, item in control.items() if key != "receiptSha256"} + ), + ) + self.assertEqual(self.base_config["executionAuthorization"]["budget"]["status"], "pending") + self.assertEqual( + self.base_config["executionAuthorization"]["rawRetention"]["status"], "pending" + ) self.assertEqual( self.base_config["commonControls"]["sampling"], { @@ -417,6 +455,100 @@ class LoadWriterReferenceWorkTest(unittest.TestCase): rows=_assembly_rows(self.base_config), ) + def test_runtime_probe_status_tamper_fails_closed(self): + """runtime probe 不是成功状态时必须在 loader 控制门阻断。""" + + tampered = copy.deepcopy(self.base_config) + tampered["executionAuthorization"]["runtimeProbe"]["status"] = "failed" + with self.assertRaisesRegex(WriterReferenceWorkError, "runtimeProbe.*successful"): + loader._validate_loader_controls( + tampered, + self.selectors, + selector_digest=SELECTOR_DIGEST, + ) + + def test_runtime_probe_self_hash_tamper_fails_closed(self): + """runtime probe receipt 的 canonical 自哈希被篡改时必须失败关闭。""" + + tampered = copy.deepcopy(self.base_config) + tampered["executionAuthorization"]["runtimeProbe"]["receiptSha256"] = ( + "sha256:" + "0" * 64 + ) + with self.assertRaisesRegex(WriterReferenceWorkError, "runtimeProbe.*自哈希"): + loader._validate_loader_controls( + tampered, + self.selectors, + selector_digest=SELECTOR_DIGEST, + ) + + def test_model_version_tamper_fails_closed(self): + """公共 modelVersion 必须精确绑定 probe 解析出的模型 ID。""" + + tampered = copy.deepcopy(self.base_config) + tampered["commonControls"]["modelVersion"] = "claude-opus-tampered" + with self.assertRaisesRegex(WriterReferenceWorkError, "modelVersion.*resolvedModelId"): + loader._validate_loader_controls( + tampered, + self.selectors, + selector_digest=SELECTOR_DIGEST, + ) + + def test_adapter_version_tamper_fails_closed(self): + """公共 adapterVersion 必须同时绑定 probe CLI 版本和完整 executable hash。""" + + tampered = copy.deepcopy(self.base_config) + tampered["commonControls"]["adapterVersion"] = "writer-runtime-v1|tampered" + with self.assertRaisesRegex(WriterReferenceWorkError, "adapterVersion"): + loader._validate_loader_controls( + tampered, + self.selectors, + selector_digest=SELECTOR_DIGEST, + ) + + def test_executable_hash_must_remain_full_and_bound(self): + """probe executable hash 截断后即使重签 receipt 也不能通过。""" + + tampered = copy.deepcopy(self.base_config) + probe = tampered["executionAuthorization"]["runtimeProbe"] + probe["claudeExecutableSha256"] = EXPECTED_CLAUDE_EXECUTABLE_SHA256[:-1] + _refresh_self_hash(probe) + with self.assertRaisesRegex(WriterReferenceWorkError, "64 位"): + loader._validate_loader_controls( + tampered, + self.selectors, + selector_digest=SELECTOR_DIGEST, + ) + + def test_assembly_preserves_pending_execution_authorization_and_blocks_before_side_effects(self): + """装配不得补批准;真实 execute 应因 pending budget 阻断且不创建 vault/runner。""" + + expected_authorization = copy.deepcopy(self.base_config["executionAuthorization"]) + config = assemble_writer_gate_config( + base_config=self.base_config, + selector_config=self.selectors, + selector_digest=SELECTOR_DIGEST, + rows=_assembly_rows(self.base_config), + ) + self.assertEqual(config["executionAuthorization"], expected_authorization) + self.assertEqual(config["executionAuthorization"]["budget"]["status"], "pending") + self.assertEqual(config["executionAuthorization"]["rawRetention"]["status"], "pending") + with tempfile.TemporaryDirectory(dir="/private/tmp") as directory: + output_dir = pathlib.Path(directory) / "run" + with patch.object(replay_module, "RawVaultManager") as vault_manager, patch.object( + replay_module.subprocess, "run" + ) as runner: + result = run_writer_replay( + config, + run_id="loader-pending-budget", + output_dir=output_dir, + execute=True, + ) + self.assertFalse(result["ok"]) + self.assertEqual(result["status"], "blocked_budget_authorization") + self.assertEqual(result["errors"], ["BUDGET_AUTHORIZATION_REQUIRED"]) + vault_manager.assert_not_called() + runner.assert_not_called() + def test_milestone_ranges_expand_and_keep_latest_three_unique_chapters(self): """明确区间按绝对章展开,重复章去重后只保留最近三章。""" diff --git a/.claude/skills/replay-eval/scripts/test_run_writer_replay.py b/.claude/skills/replay-eval/scripts/test_run_writer_replay.py index bf6f74d..ff5ef1d 100644 --- a/.claude/skills/replay-eval/scripts/test_run_writer_replay.py +++ b/.claude/skills/replay-eval/scripts/test_run_writer_replay.py @@ -1069,7 +1069,52 @@ class WriterReplayDryRunTest(unittest.TestCase): result = run_writer_replay(gate_config, run_id="gate-a-preregistered-dry-run") self.assertTrue(result["ok"]) + self.assertEqual(result["status"], "ready") self.assertEqual(len(result["samples"]), 5) + self.assertEqual(gate_config["commonControls"]["modelVersion"], "claude-opus-4-8[1m]") + self.assertIn("writer-runtime-v1", gate_config["commonControls"]["adapterVersion"]) + self.assertIn("2.1.211", gate_config["commonControls"]["adapterVersion"]) + executable_sha256 = gate_config["executionAuthorization"]["runtimeProbe"][ + "claudeExecutableSha256" + ] + self.assertEqual(len(executable_sha256), 64) + self.assertEqual( + executable_sha256, + "5a728a76198b6eca7f3c7cdbff43bab44b77b48c2108f7a3107d889773382629", + ) + self.assertIn(executable_sha256, gate_config["commonControls"]["adapterVersion"]) + self.assertNotIn("executionProfiles", gate_config) + self.assertNotIn("profileSha256", gate_config["executionAuthorization"]) + + def test_gate_a_execute_blocks_pending_budget_before_vault_or_runner(self): + """正式 Gate A 已完成 runtime probe,但预算未批准时必须在副作用前阻断。""" + + gate_config_path = SCRIPT_DIR.parent / "configs" / "writer-gate-a-deep-space-v1.json" + gate_config = json.loads(gate_config_path.read_text(encoding="utf-8")) + vault_calls: list[pathlib.Path] = [] + + def vault_factory(path): + vault_calls.append(path) + return RawVaultManager(path) + + adapters, writer, semantic, judge = _production_adapters(vault_factory=vault_factory) + with tempfile.TemporaryDirectory(dir="/private/tmp") as directory: + output = pathlib.Path(directory) / "run" + result = run_writer_replay( + gate_config, + run_id="gate-a-pending-budget", + output_dir=output, + execute=True, + production_adapters=adapters, + ) + + self.assertFalse(result["ok"]) + self.assertEqual(result["status"], "blocked_budget_authorization") + self.assertEqual(result["errors"], ["BUDGET_AUTHORIZATION_REQUIRED"]) + self.assertEqual(vault_calls, []) + self.assertEqual(writer.calls, []) + self.assertEqual(semantic.calls, []) + self.assertEqual(judge.calls, []) class WriterReplayExecuteBoundaryTest(unittest.TestCase): @@ -1540,6 +1585,28 @@ class WriterReplayProductionIntegrationTest(unittest.TestCase): self.assertEqual(writer.calls, []) self.assertNotIn("binding_verifier", inspect.signature(run_writer_replay).parameters) + def test_runtime_probe_self_hash_tamper_fails_closed_before_runner(self): + """runtime probe receipt 自哈希被篡改时,不能进入任何模型 runner。""" + + adapters, writer, semantic, judge = _production_adapters() + tampered = _production_config() + tampered["executionAuthorization"]["runtimeProbe"]["receiptSha256"] = ( + "sha256:" + "0" * 64 + ) + + result, _output = self._run( + adapters, + evaluation_config=tampered, + run_id="tampered-runtime-probe", + ) + + self.assertFalse(result["ok"]) + self.assertEqual(result["status"], "blocked_execute_authorization") + self.assertEqual(result["errors"], ["EXECUTE_AUTHORIZATION_HASH_INVALID"]) + self.assertEqual(writer.calls, []) + self.assertEqual(semantic.calls, []) + self.assertEqual(judge.calls, []) + if __name__ == "__main__": unittest.main() diff --git a/docs/2026-07-20-正文智能体正式优化设计与计划.md b/docs/2026-07-20-正文智能体正式优化设计与计划.md index dcc74cc..29fc77f 100644 --- a/docs/2026-07-20-正文智能体正式优化设计与计划.md +++ b/docs/2026-07-20-正文智能体正式优化设计与计划.md @@ -354,11 +354,11 @@ dry-run 命令: dry-run 只生成计划、manifest 和上下文摘要,不调用模型。测试注入路径用于机械验证 writer、detector 与盲评编排,但与真实执行共用 probe、预算、raw 保留和 profile 授权门;候选只进入受租约约束的 RawVault,普通输出目录不落 raw。judge 只接收独立盲化内容和共同评测参考,不能取得 raw 目录、真实臂映射或任一臂专属证据;评委顺序变化不得改变共同参考。即使全部离线测试通过,也不得据此声称真实回放或 Gate A 已完成。 -截至 2026-07-22,本版第一阶段实现已接通:A/C 原文检索与 allowlist diff、B 负对照、全评测集平衡表、oracleTruthPack、writer 联合回执、真实 semantic detector、隔离 blind judge、GateInputBuilder、confounders、RawVault、文件 CAS、Gate B receipt 签发器和细纲 real-run 入口守卫。执行 profile 绑定绝对 Claude CLI 路径/hash/版本、完整模型 ID、单次预算、schema 和系统提示词;系统失败优先于样本不足。提交前机械证据为 replay-eval 180 项、writer 8 项、上下文装配 14 项、detector 12 项、judge 13 项全部通过,Gate A dry-run 5 个样本均为 ready;独立对抗复审覆盖空内层回执、judge 合法改分后重签、测试 adapter 绕授权、错误 CLI 绑定、detector 泄漏和 CAS 非破坏恢复。该证据只证明离线编排合同实现,不等于成功探测、预算批准、真实 Gate A 或生成质量通过。 +截至 2026-07-22,本版第一阶段实现已接通:A/C 原文检索与 allowlist diff、B 负对照、全评测集平衡表、oracleTruthPack、writer 联合回执、真实 semantic detector、隔离 blind judge、GateInputBuilder、confounders、RawVault、文件 CAS、Gate B receipt 签发器和细纲 real-run 入口守卫。执行 profile 绑定绝对 Claude CLI 路径/hash/版本、完整模型 ID、单次预算、schema 和系统提示词;系统失败优先于样本不足。当前机械证据为 replay-eval 199 项全部通过,Gate A dry-run 5 个样本均为 ready;独立对抗复审覆盖空内层回执、judge 合法改分后重签、测试 adapter 绕授权、错误 CLI 绑定、detector 泄漏和 CAS 非破坏恢复。该证据只证明离线编排合同实现,不等于预算批准、真实 Gate A 或生成质量通过。 真实 CLI 极小探测还有直接证据:两次 Opus 探测分别等待 177043ms 与 185505ms 后因 API 503 失败,进程 `exit=1`;JSON envelope 却同时出现 `type=result`、`subtype=success`、`is_error=true`、`api_error_status=503`、`terminal_reason=api_error`、`stop_reason=stop_sequence`、`total_cost_usd=0`、完整零值 `usage` 和空 `modelUsage={}`。因此 `subtype=success`、合法 JSON、完整 usage 或 `stop_reason=stop_sequence` 都不能单独证明调用成功;该证据也说明每次调用必须有编排器硬 deadline。安全摘要只记录状态码和受控字段,不记录具体网关错误正文。 -该失败探测没有产生可验证的成功 `structured_output` 或完整 model ID,只能证明失败 envelope 语义。必须先完成第 11.4 节成功探测,才能冻结离线评测 adapter 所需字段;在此之前 `--execute` 继续失败关闭。 +随后完成了第 11.4 节正式成功探测:绝对 CLI 路径绑定版本 `2.1.211` 与二进制 SHA-256,实际完整模型 ID 为 `claude-opus-4-8[1m]`;进程 `exit=0`、`is_error=false`、`terminal_reason=completed`、`modelMatch=true`,严格 schema 产出 `structured_output`,单次成本 `$0.003885`。其 `structuredOutputSha256=sha256:4062edaf750fb8074e7e83e0c9028c94e32468a8b6f1614774328ef045150f93`、`executionProfileSha256=sha256:9812b32c14c6a95204a22555c34b84353a96d4df5e5988a00df23634022e34ca`、`executionReceiptSha256=sha256:5e1171201771cd921c187b3bdc9d192f3bb0abbba4115a4489150721c4d72477` 已冻结进预注册配置。探测同时暴露并修复了三处真实运行合同缺口:完整模型 ID 的能力后缀、自定义网关安全认证字段、Claude usage 中非数值元数据;对应回归测试已纳入机械门禁。 `writer_gate.py` 已按 10.1 改为系统失败优先;detector 指标只接受严格校验并绑定模型回执的真实语义报告,禁止常量、默认通过或测试替身进入 real-run。第三评委报告非法或仍不稳定时,样本和顶层都失败;GateInputBuilder 还会把 reviewer 原始 structured output 与内层 ExecutionReceipt 绑定后重算 panel,报告内容即使重签自身 hash 也不能替换模型原始结论。 @@ -443,7 +443,7 @@ vault 路径由可信编排器生成不可猜测 ID,拒绝调用方传入绝 每次调用都产生不含正文的 `ExecutionReceipt`,至少记录 `adapterRole/invocationId/requestedModelId/actualModelId/modelMatch/effort/maxBudgetUsdPerCall/totalCostUsd/usage/modelUsage/stopReason/terminalReason/isError/apiErrorStatus/exitCode/durationMs/inputSha256/structuredOutputSha256/jsonSchemaSha256`。CLI 结果未提供完整实际 model ID、`total_cost_usd`、usage、modelUsage、stop reason 或 terminal reason,或任一字段无法归一化时,回执非法,不能把该调用算作成功。 -`envelope.structured_output` 是唯一业务对象,必须直接通过该角色的严格 JSON schema;`envelope.result` 只作审计和受控错误分类,禁止解析为业务 JSON,也禁止作为 structured_output 缺失时的 fallback。当前 503 探测没有证明 2.1.211 成功响应的 `structured_output` 字段形状;实现前必须完成一次极小成功探测并冻结 envelope schema/hash。该探测未成功前 Gate A 不得启动。 +`envelope.structured_output` 是唯一业务对象,必须直接通过该角色的严格 JSON schema;`envelope.result` 只作审计和受控错误分类,禁止解析为业务 JSON,也禁止作为 structured_output 缺失时的 fallback。版本 2.1.211 的成功探测已经证明并冻结 `structured_output`、完整模型 ID、usage/modelUsage 与终止字段的形状和回执 hash;任何后续运行与该探针绑定不一致仍须失败关闭。 调用成功必须联合满足:进程 `exitCode=0`;envelope `type=result` 且 `is_error=false`;`terminal_reason` 属于该角色预注册的正常终止集合;业务输出通过严格 schema;`modelUsage` 能证明实际模型且与请求模型一致;`usage` 结构和数值合法并可核账。`subtype=success` 与 `stop_reason` 只作审计字段,不能覆盖非零退出、`is_error=true`、API error、空 `modelUsage` 或 schema 失败。 @@ -475,7 +475,7 @@ vault 路径由可信编排器生成不可猜测 ID,拒绝调用方传入绝 Gate A real-run 只允许使用满足本节合同的离线评测 adapter。执行前必须机械证明:成功 structured_output 探测已冻结;Gate A 总预算与单次预算已确认;A/C 执行配置、事实约束和原文字符预算相等;全评测集顺序表平衡;完整上下文 allowlist diff 通过;temperature/topP/seed 为 `unsupported/not_claimed`;三类 schema、oracleTruthPack、GateInputBuilder、泄漏审计与回执测试通过;judge 隔离无法发现 raw、映射和臂名;raw lease、vault 权限/逃逸/保留/清理测试通过;文件 CAS journal 的并发、崩溃和迟到结果测试通过。Gate B 启动前另行预注册并批准独立预算,不得沿用 Gate A 批准。 -本次设计先定义合同,当前 adapter 已按合同实现并通过离线机械测试,但这仍不等于 Gate A 已运行或通过。成功 `structured_output` 探测和完整模型 ID 尚未取得,Gate A 总预算与 raw 保留期也尚未批准,因此仓内配置保持 `pending_probe`,真实 `--execute` 机械返回 `blocked_runtime_probe`,且在阻断前不创建 raw 目录、不调用模型。 +本次设计先定义合同,当前 adapter 已按合同实现并通过离线机械测试,成功 `structured_output` 探测和完整模型 ID 也已冻结,但这仍不等于 Gate A 已运行或通过。Gate A 总预算与 raw 保留期尚未批准,因此仓内 `executionAuthorization.budget/rawRetention` 保持 `pending`;真实 `--execute` 必须在创建 runner、raw 目录或调用模型前失败关闭。 ## 12. 用户闭环与下游交接(Gate B 后第二阶段) @@ -507,7 +507,7 @@ Gate A real-run 只允许使用满足本节合同的离线评测 adapter。执 - 本修订版通过产品流程与技术合同两类独立评审。 - 用户确认本文为正文智能体实验台任务 SoT。 -- 第 11 节离线评测 adapter 已实现并通过离线机械测试;成功探测、预算或 raw 授权任一缺失时,真实 `--execute` 仍保持失败关闭。设计评审与测试通过都不代表 Gate A 完成。 +- 第 11 节离线评测 adapter 已实现并通过离线机械测试,成功探测已冻结;预算或 raw 授权任一缺失时,真实 `--execute` 仍保持失败关闭。设计评审、测试与探针通过都不代表 Gate A 完成。 - Gate A real-run 前,用户明确批准总预算、三类单次预算、最大调用次数和 raw 最长保留期。 - Gate B real-run 前,按其样本集另行批准独立总预算、三类单次预算、最大调用次数和最坏总额。 - writer、semantic detector、blind judge、oracleTruthPack、GateInputBuilder、Gate B receipt 签发器与细纲入口守卫的严格 schema、隔离、回执、路径安全、文件 CAS 和失败码均有机械测试证据。