feat(capability): add auditable external evidence lifecycle - #4813
Conversation
|
Windows CI root cause was the managed Effect runtime package boundary, not the contract logic: the runtime staging test copies only |
|
Added the missing typed discovery step at exact head What changed:
Validation:
Product surfaces: CLI and managed Turn changed in this slice. Frontend/Lark remain a companion because there is still no persisted external-evidence configuration/readback owner for those surfaces; adding form state now would create a second source of truth. They should consume this same discovery/plan/admission projection once that owner exists. |
ce7d782 to
a99a8db
Compare
|
Rebased onto current Post-rebase validation:
The contract remains unchanged: discovery reports inventory/readiness only and does not claim provider execution or evidence coverage. Fresh CI is running. |
|
Added the provider-execution observation boundary at exact head This keeps execution with the existing method/connector owner while adding the shared TypeScript Validation:
This consumes the parent calibration constraint for |
huangruiteng
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
Reviewed exact head: 7cd3dc5ad373adbc4fc19b3e04ccd3b08b2956ee
动机
这个 PR 要把分散的 host research method、connector inventory 和下游证据消费统一成一个可审计的 discover → plan → execute → receipt → admit/reject → retire 生命周期。方向是对的:它明确区分“注册表里存在”“当前 ready”“已经执行”“证据覆盖”“被父调用方接纳”这些过去容易混在一起的语义,并且不把 provider 执行权、凭据或后续动作权限收进 Core。
当前 blocker 不在方向,而在这个生命周期最核心的承诺——receipt/admission 必须绑定到“原先的 exact ready plan”。现有实现只比较调用方重新提交的可变 plan 对象里的几个字符串,没有证明它仍然是最初生成的 plan。
改动思路
TypeScript 的 external_evidence.ts 负责 request/provider 规范化、ready provider 选择、receipt/admission 校验和 retirement 投影;Python external_research/cli.py 只读取有界 JSON 并通过 Effect runtime 调用同一 typed owner。connector registry 被保留为 inventory,provider 真正执行仍由原 method/connector owner 完成;这符合现有所有权边界。
正向路径是完整且内聚的。但负向路径有断层:planExternalEvidenceRequest 会为规范化 request 计算 request_id,之后 normalizeExecutionReceipt 却不重新规范化 request、也不校验完整 plan digest。调用方可以修改 objective/decision/constraints 或 provider 状态,只要保留旧 request_id 和 provider 字段,receipt 仍会被认为“绑定 exact plan”。最小修复应是对完整规范化 ready plan 生成并校验 plan_id,或在后续 reducer 中完整重建并核验所有 plan 不变量。
具体改动
- 新增 7 个 v0 schema 与五个 CLI/Effect 方法,覆盖 discovery、planning、execution receipt、admission、retirement。
- 新增 built-in capability catalog、双语 RFC/README 和 roadmap 登记。
- 新增 10 个 TypeScript 单测和 Python CLI/runtime 包装测试;现有 stale receipt identity、file provenance、subset admission 与 retirement coverage 都有覆盖。
关键代码讲解
planExternalEvidenceRequest正确地把规范化 request hash 成request_id,并只选择declared && installed && enabled && ready的 provider。normalizeExecutionReceipt是后续审计边界,但它直接信任传入plan.request.request_id与plan.selected_provider,没有验证 plan 自身是否被改写。recordExternalEvidenceExecution在这个弱校验后输出provider_execution_observed=true,因此问题不是少一个防御性字段,而是公开 truth contract 可能说错话。handle_external_evidence_command把文件里的 plan/receipt 直接交给 reducer;CLI 文件边界使篡改场景成为真实公共入口,而不只是内部函数误用。
对主干的风险
P1:可变 plan 可以绕过 exact-plan 绑定
我在 exact head 上先生成合法 plan,随后只把 plan.request.objective 从原值改为另一语义,保留旧 request_id,再提交匹配该 id 的 succeeded receipt。实际输出仍是:
status=succeeded, provider_execution_observed=true
这会让后续 admission/retirement 产生看似一致的 digest/id,但这些 id 证明的是调用方当下递交的对象组合,不是原始 ready plan 未被篡改。现有测试只改 receipt 的 request/provider id,因而全部通过但没有命中这个反例。
最低修复:
- 对完整规范化 ready plan(request、selected provider、execution envelope 等)生成不可歧义的
plan_id; - receipt/admission 必须重新验证该 plan 或校验
plan_id,不能只比较对象内自报字段; - 增加 objective、decision、constraints、provider readiness、execution envelope 的逐字段 mutation regression。
验证结果:control-plane typecheck 通过;10 个 TS 测试通过;7 个 Python CLI/runtime 测试通过;Ruff check/format 与 git diff --check 通过。按 capability packet 的 wait_for_ci=false,未轮询 GitHub CI。失败证据来自真实 exact-head TypeScript reducer,不依赖 mock。
语义与 CI 对齐
RFC 的“exact ready plan binding”和运行时当前能保证的“与传入 plan 对象字段相等”不一致。新增 mutation regression 后,应让旧实现失败、修复 head 通过,才能把这个 v0 audit vocabulary 视为成立。
我的整体评价
能力归属、provider 权限边界、inventory/readiness 分离以及 Python→TypeScript owner 的架构选择都合理,1694 行也围绕一个完整 preview 生命周期展开,不是无关拼装;future-facing pass 最值得做的是抽出一个唯一 canonical plan decoder/digest verifier,让 plan、receipt、admission 共用同一不变量,而不是再增加第二套校验。
但 exact-plan 绑定是这项能力存在的核心价值,当前反例会直接让审计语义失真,所以本 head 需要修改后复审。修复后仍应把一个真实 host method 与一个 connector 的端到端 acceptance 保留为明确 companion evidence,不能用 reducer 单测替代。
English verdict: REQUEST_CHANGES - head 7cd3dc5 accepts a semantically mutated plan while still reporting succeeded observed execution; typecheck, 10 TS tests, 7 Python tests, Ruff and diff checks pass, but canonical exact-plan integrity needs a plan digest plus mutation coverage.
7cd3dc5 to
8b016ba
Compare
258255e to
b34f14a
Compare
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
b34f14a to
6ca2b48
Compare
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Reviewed exact head: f0862fb51b604d33b15cd76b5418d7553cc6b1ed
动机
这个 PR 要解决一个真实的边界缺口:仓库已经能表达“正在等待外部证据”和 connector inventory,却没有一个 provider-neutral、可审计的契约来区分“目录里存在”“当前 ready”“调用方出示了 receipt”“证据被父决策接纳”“证据已经被下游覆盖、可以退休”。基线 09f10c4e 的公开 CLI 会把 external-evidence 视为未知命令,也没有对应的 managed-Turn Effect owner。
本次交付不是整个外部研究旅程的完成态,而是一个成立的、可独立使用的增量:CLI 和 Effect caller 已能走完 discovery → plan → receipt observation → admission → retirement;真实 host method/connector 执行、前端/Lark 投影仍是明确的 companion work。这个边界可独立测试、没有持久化迁移,并且不会把 provider 凭据、执行重试或后续动作权限搬进 Core。
改动思路
TypeScript external_evidence.ts 是唯一的生命周期和状态判定 owner;Python CLI 只做有界 JSON 读取和 Effect 适配。connector registry 只提供 inventory,只有同时满足 declared/installed/enabled/ready 的 provider 才能被 plan 选择;provider 的真实执行仍由原 host/connector owner 负责。
最强的反对意见是:约 2K 行的新 built-in surface 可能重复 Decision Context,并且早于真实 provider adoption。对 base/head 的 bounded search 表明两者的状态和权责不同:Decision Context 是 default-off、goal-scoped、带 profile/review/outcome 的持久化决策闭环;这个 PR 是无持久状态的单次 provider 选择、receipt/provenance admission 和 retirement boundary。把它塞进 connector registry 又会错误地让 inventory owner 负责 live readiness。因此,独立 capability 是合理的,但必须继续保持 provider execution outside Core。
正向路径把完整 normalized request、provider candidates、selected provider 与 execution envelope content-address 成 plan_id;receipt 只形成“调用方 receipt observation”,明确不证明 provider execution;admission 对精确 source subset 生成完整 admission_id;retirement 先重建 admission,再检查 downstream source coverage。
负向路径 fail closed:任何 request/decision/constraint/readiness/envelope 变更都会破坏 plan identity;任何 disposition/source/projection 变更都会破坏 admission identity。调用方需要重新生成 canonical object,Core 不会静默修补或替 provider 重试。
具体改动
- 新增五个 CLI 子命令和五个 managed-Turn Effect 方法,输出 discovery、plan、receipt-observation、admission、retirement 的 v0 projection。
- 新增 capability catalog、README、双语 RFC 和 roadmap 登记;公开文本明确 inventory ≠ readiness、receipt observation ≠ provider execution attestation、admission ≠ downstream authority。
- 新增 13 个 typed reducer 测试和 Python CLI/runtime 覆盖;验证 connector-only inventory、ready selection、stale identity、semantic mutation、file provenance、source subset/uniqueness、retirement coverage。
- 把 capability protocol 注册为结构化 catalog contract,补齐内置 capability 顺序契约,并用 catalog 回归测试固定 receipt observation 不等于 provider execution 的公开语义。
- 自修复把原先只比较可变对象中少数字段的做法改为 canonical
plan_id;随后又把完整 admission content-address,并将过宽的 execution-observed 命名收窄为 receipt observation。 - 修复 source checkout 与 Python distribution 的差异:把
control_plane/capabilities声明为 package,并显式把external_evidence.ts纳入 wheel。
关键代码讲解
planExternalEvidenceRequest(external_evidence.ts:195)规范化 request/provider,只有完整 lifecycle-ready 的候选能被选择;返回的 plan 把完整语义绑定到plan_id,不执行 provider。normalizeReadyPlan(external_evidence.ts:248)是第一道信任边界:它从 untrusted plan JSON 重建 request id、候选、selected provider、envelope 和 plan digest。receipt/admission 只能消费通过这道校验的 plan。evaluateExternalEvidenceAdmission(external_evidence.ts:431)验证 receipt 与 plan identity、非本地 provenance、精确且唯一的 source subset,只把 compact provenance 放入 downstream projection。projectExternalEvidenceRetirement(external_evidence.ts:612)先由normalizeAdmission验证完整 admission digest,再区分retained与retire_ready;它本身不删除证据。handle_external_evidence_command(external_research/cli.py:171)只把五个公开命令映射到同一 typed owner,没有在 Python 侧复制第二套状态机。
对主干的风险
此前 exact head 7cd3dc5a 的核心风险已经被真实反例证实:修改 plan objective、保留旧 id,仍可能得到 succeeded/observed 的错误审计结论。当前 head 通过共享 canonical decoder/digest 修复了同根问题;进一步检查又发现并修复了可编辑 admission 绕过 retirement coverage,以及 caller receipt 被误报为 provider execution observed 的语义过宽。
最终 CI 还暴露了源码测试未覆盖的发布缺口:新的 TypeScript owner 位于一个没有 Python package marker/package-data 声明的目录,因此 wheel 没有包含 external_evidence.ts,安装后的 Effect runtime 无法 ready。修复后我实际构建并检查 wheel、隔离安装,collect_effect_runtime_readiness(deep=True) 返回 status=ready / semantic_probe=passed,doctor --deep 返回 typescript_effect_runtime_ready=ready 与 release_candidate=true。这条证据验证的是产物本身,不是源码 mock。
我在当前 exact head 通过公开 CLI 重放了两个关键反例:合法 receipt 返回 succeeded 且 provider_execution_attested=false;修改 plan objective 后退出码为 1。合法且已覆盖的 admission 返回 retire_ready;修改 projection finding 后同样退出码为 1。它们不是 mock 结果。focused TS 为 13 passed,Python CLI/runtime 为 7 passed,control-plane typecheck、Ruff、diff check 通过。完整 control-plane 套件为 2208 passed / 18 skipped,唯一失败是一个 NoKV 子进程在 90 秒截止时未退出;同一精确测试随即独立重跑 3/3 通过(2.117s、1.011s、0.962s),前一修复 head 则为 2209 passed / 18 skipped / 0 failed。premerge canary 的 19 个选中检查全部通过,0 failures、0 manual holds;最终 GitHub 分片是这次瞬时超时的独立裁决。
CI 还暴露了两个 catalog 集成缺口:implemented_protocols 误用了字符串而不是结构化 protocol record,内置 capability 的顺序契约也没有登记新 id。两者已修复,相关 documentation/catalog/external-evidence 测试为 33 passed;新测试同时固定 caller-presented receipt 不得被描述成 provider execution。
最低 Node 完整套件过去成功样本已耗时 9:17–9:43,而 10 分钟任务上限在最新 main 和本 PR 都以 0 测试失败被取消。本次把任务上限校准为 15 分钟,保留全部兼容性用例和单测 deadline,没有通过删测试或放宽断言换绿。
当前没有 blocking finding。剩余风险是还没有真实网络 provider 的真实性/E2E 证明;这不会让当前 contract 说错话,因为 truth contract 明确把 execution attestation、coverage、automatic admission/promotion 全部标为 false,但 host/connector adoption 前仍应完成一个 method 和一个 connector 的端到端 acceptance。前端/Lark 也尚未交付;本 PR 没有新增持久化配置 owner,因此现在强行做 UI 会制造第二套事实源。
语义与 CI 对齐
这个 PR 有意创建 external_evidence_research_v0 vocabulary,而不是重解释现有 waiting_on=external_evidence 或 Decision Context 值。schema/export/CLI help/RFC 已统一使用 receipt observation,catalog 与双语 roadmap 同步登记。最终 exact-head GitHub 检查为 27 success、3 个符合 PR 语境的 deploy/publish skip,没有 pending、failed 或 cancelled check。
我的整体评价
这是一个比例合适的完整 stage:约一半 diff 是测试和文档,生产 surface 有真实 CLI/Effect caller,无持久状态、迁移或自动执行。后续最相关的 future-facing boundary 已在本 PR 内完成——plan/admission 都只有一个 canonical normalizer,Python 没有第二套规则;再拆 helper 暂时只会增加跳转而没有新 consumer。
结论是 exact head 可批准,且没有未解决的 P0/P1/P2/P3 finding。由于作者账号无法 formal self-approve,本记录使用 COMMENTED approval conclusion;同时,这个 PR 修改 control-plane/runtime public contract,仓库规则要求由独立维护者执行最终合并,不能由作者自合并。
English verdict: APPROVE - exact head f0862fb closes the reproduced plan/admission, wheel-packaging, and catalog-contract defects, narrows receipt authority semantics, preserves the complete minimum-Node qualification suite with an evidence-based timeout budget, passes installed-artifact and public-boundary validation, and has no blocking finding; real provider end-to-end acceptance remains explicit residual integration work.
Summary
external_evidence_research_v0request, discovery, plan, receipt-observation, admission, and retirement lifecycleexternal-researchand connector providers behind one auditable capability boundaryloopx external-evidence discover|plan|receipt|admit|retire, plus capability catalog, bilingual RFC, and roadmap registrationplan_id, and bind retirement to the complete normalized admission identityProduct delivery
Validation
npm run typecheck:control-planenode --no-warnings --experimental-strip-types --test tests/control_plane_ts/external_evidence_research.test.ts(13 passed)uv run --extra test python -m pytest -q tests/capabilities/test_external_evidence_cli.py tests/control_plane/test_effect_runtime_integration.py::test_runtime_decode_change_rotates_identity_and_starts_replacement(7 passed)uv run --extra test python -m pytest -q tests/capabilities/test_capability_documentation.py tests/capabilities/test_capability_extension_registry.py tests/capabilities/test_external_evidence_cli.py(33 passed; structured protocol record, built-in order, documentation ownership, and receipt-observation wording)uv run --extra test npm run test:control-plane(2208 passed, 18 skipped, plus one 90-second NoKV child-process timeout; the identical case immediately passed 3/3 in isolation at 2.117s, 1.011s, and 0.962s; the preceding repaired head passed 2209, skipped 18, failed 0)uv run --extra test ruff check loopx/capabilities/external_research tests/capabilities/test_external_evidence_cli.pyuv run --extra test ruff format --check loopx/capabilities/external_research tests/capabilities/test_external_evidence_cli.pyuv run --extra test loopx canary premerge --from-git-diff(19 selected checks passed, 0 failures, 0 manual holds)external_evidence.tsis present, the Effect semantic probe passed, anddoctor --deepreportedtypescript_effect_runtime_ready=readygit diff --check origin/main...HEADThe minimum-Node lane preserves the complete conformance suite. Its prior successful runs already took 9:17–9:43, and both current
mainand the prior exact PR head were cancelled at the 10-minute job ceiling with zero reported test failures. The job budget is therefore 15 minutes, without removing cases or weakening individual test deadlines.The full Python shard also caught two catalog integration gaps:
implemented_protocolsused a string instead of the repository's structured protocol record, and the built-in ordering contract omitted the new id. Both are repaired, and the focused catalog regression additionally fixes the public wording so a caller-presented receipt cannot be read as provider execution.Running the control-plane suite outside the source-checkout
uvenvironment resolves subprocesses to the system Python 3.9 and fails on the repository's existing@dataclass(slots=True)usage. The requireduv run --extra testsource environment uses Python 3.13.