Skip to content

feat(goals): derive the Goal artifact lifecycle projection - #4360

Merged
huangruiteng merged 21 commits into
loopx-project:mainfrom
songoow:codex/goal-artifact-lifecycle-projection
Sep 16, 2026
Merged

huangruiteng merged 21 commits into
loopx-project:mainfrom
songoow:codex/goal-artifact-lifecycle-projection

Conversation

@songoow

@songoow songoow commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

Behavior

Adds the RFC #4128 Goal artifact lifecycle readout to status JSON and Markdown: observed phase, evidence milestones, owner/evidence guards, and next steps. Observed required work takes precedence over inferred closeout, including when completed Todos and historical progress would otherwise suggest closing.

The projection is read-only and grants no execution or completion authority. It remains distinct from the bounded acceptance-observation projection.

Implementation

  • The lifecycle consumes WorkLaneObservation from existing work-lane and session-runtime owners. Legacy field decoding stays in those owners. Status reads the current Goal's nested session_runtime_projection, validates its schema and Goal identity, and uses the observation before display trimming. There is no new direct legacy-field reader or increased retirement budget.
  • Uses per-Goal history, including retained semantic evidence, and the canonical delivery-outcome rules. outcome_gap remains an unreached marker and cannot satisfy closeout. Batch size cannot promote a non-progress outcome.
  • Reuses acceptance observations for open gates and evidence gaps. Lifecycle v0 never recommends a terminal transition. closing remains a verification step carrying the existing machine-readable acceptance_unverified reason code. An already-terminal Goal still displays closed with no next steps. A future acceptance producer change cannot silently add terminal advice.
  • Declared milestone authoring has no shipped producer and is not implemented here. Missing Todo coverage is not interpreted as zero work.
  • Emitted labels and evidence references pass existing public-safety checks before truncation. Renderers reject the other projection's schema. Shared classifier changes remain outside this PR.
  • Regenerates the semantic inventory against the integrated main tree. Existing vocabulary, retirement, and runtime-twin budgets remain unchanged.

Bounded refactor: separates a three-field immutable work read model from legacy protocol decoding; no new production module, persistent store, scheduling rule, or provider is introduced.

Validation

Tested revision: d70419e42583c3ac287e4ce690638f27cbfe0aff
Run state: finished

  • 61 lifecycle/work-observation, semantic inventory/drift and new-main Turn-lane fence tests passed.
  • 25 manager-channel and real shadow-CLI tests passed. The existing four-line channel-fixture correction from fix(runtime): discover Python interpreters without fixed minor lists #4482 is reused with attribution to remove dependence on whether DSH is installed.
  • The old-head shadow-CLI crash-gap failure passed on isolated rerun; its complete module also passes on this head. The CI log alone did not establish a root cause, so new-head CI must confirm the outcome.
  • Combined candidate d8c8988b35a40d7f9784462967d3b14391d6ce75, based on main 675a9e50b: 467 Python tests plus 187 DSH plugin tests pass; Ruff, kernel mypy, plugin typecheck/build/artifact smoke, docs governance and lifecycle smoke pass.

Affected entry points remain status JSON/Markdown. Session-runtime observation coverage is bounded by the supplied schema and Goal id; missing work observations do not certify completion. No new acceptance authority, permission, settings field or packaged lifecycle UI is introduced. The three semantic siblings are coordinated as #4457 → #4459 → #4480; this feature and #4482 remain independent. All source heads were composed and tested together, but no PR has been merged into main. Fresh remote CI/review remains required.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:REQUEST_CHANGES

审阅绑定的 exact head:367de99e659948efc893de7a80eccfd1d8ec5a86

动机

把 Goal 的阶段、里程碑、阻塞 guard 与下一步统一投影出来,产品方向有价值:当前 operator 确实需要从 Todo、run history、quota/frontier 等分散信号中手工重建状态。但这个 PR 当前只落了 356 行 production helper 和 321 行 example smoke,正文又明确把 status markdown/dashboard 两个实际 consumer 留到后续。因此 exact head 并没有改变任何用户或 operator 的 shipped behavior,也没有建立输入完整性、失败重试与显示语义的真实 owner。

改动思路

helper 试图把已收集的 goal、user/agent Todo summary、run history、work-lane contract、acceptance gaps 合成为 goal_artifact_lifecycle_projection_v0。它把 milestone 分成 declared/evidence 两类,从 owner gates 和 acceptance gaps 生成 guards,再根据 terminal status、open_count、milestone reachability 和 lane 推导 phase/next transition。纯投影、不写状态、不授予权限的原则是正确的;问题在于实现没有沿用仓库已有的 typed delivery semantics 和依赖方向,而且没有真实 consumer 来验证这些输入是不是完整、相互一致、适合决定 closeout。

具体改动

关键代码讲解

  1. _evidence_milestones(artifact_lifecycle.py:119)本地维护 _MATERIAL_OUTCOMES,并允许 delivery_batch_scale == multi_surface 独立把一条 run 变成 reached milestone。随后 milestone id 又优先取 delivery_outcome。
  2. _compact_text(:55)从 control-plane 向外导入 presentation.public_safety,同时再维护一套 provider-token regex;这直接违反既有 import boundary,也让 public-safety classification 出现第二份规则。
  3. build_goal_artifact_lifecycle_projection(:301)导出一个新的 v0 contract,但仓库级 caller search 只找到 example smoke;status/dashboard 均未加载它。

本地 fixture smoke 能通过,但同一 exact head 上 Ruff 对 smoke 报 E402。更重要的是,远端 test-shard (1) 因 control-plane -> presentation 依赖失败,test-shard (3) 因同一 dependency debt 触发 maintainability ratchet,最终 pytest 和 merge-gate 都失败。

对主干的风险

有三个阻塞问题:

  1. 依赖方向错误。 loopx.control_plane.goals.artifact_lifecycle -> loopx.presentation.public_safety 是仓库明令禁止的 outward dependency,现有 required checks 已经精确捕获。应把可复用 redaction primitive 放在 inward-safe owner,或让 presentation consumer 自己做最后一层适配;不能登记新 debt 来绕过。
  2. material outcome 语义被错误扩大。 我直接调用 exact-head helper,输入 delivery_outcome=surface_only、delivery_batch_scale=multi_surface,结果得到 id=surface_only, reached=true 的 milestone。batch scale 只描述交付范围,不能把非进展 outcome 升格为 Goal evidence;仓库的 typed MATERIAL_DELIVERY_OUTCOMES 明确不包含 surface_only。请复用 canonical typed rule,并为所有 outcome × scale 组合加负向矩阵。
  3. 没有 production caller。 当前只有 example smoke 调用新模块;356 行 production contract、phase vocabulary 和 v0 schema 没有任何用户入口,也无法通过真实 consumer 验证 incomplete input、gate scope、declared milestone identity 与 closeout。请要么在本 PR 中落下最小、内聚的真实 status/dashboard consumer,要么先移除/暂缓 production module,等 consumer 到来时从已有 frontier/status owner 提取被真实路径证明的最小 rule group。

我的整体评价

这个方向值得做,但当前批次把设计可能性提前冻结成了较大的未使用生产 surface,并且已经出现 typed semantics 漂移和 required CI 红灯。第二个 fix commit 解决了部分 closeout/redaction 症状,却没有解决 rule ownership、consumer demand 与依赖层级。建议先把 outcome materiality 收回 canonical typed owner、修复依赖边界,然后以真实 consumer 驱动最小投影;复审时需要看到 consumer-level readback、complete/incomplete source counterfactual 以及全绿 merge gate。

English verdict: REQUEST_CHANGES — the exact head has a forbidden control-plane-to-presentation dependency, misclassifies surface_only + multi_surface as reached evidence, and ships a 356-line production contract with no active consumer.

@songoow
songoow force-pushed the codex/goal-artifact-lifecycle-projection branch from 367de99 to 8441fcd Compare September 14, 2026 09:14
@songoow

songoow commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto current main and addressed all three blocking points at new head 8441fcdd2.

  1. Forbidden dependency direction. You were right: loopx.control_plane.goals.artifact_lifecycle imported loopx.presentation.public_safety, and the boundary test caught it. The module no longer imports presentation at all. It keeps a bounded, inward-safe rewrite for local path shapes and reuses loopx.public_safe_text.find_private_text_match (a top-level module the control plane may import) for classification, so the shared private-text contract still has exactly one owner. A smoke assertion now walks the module's AST and fails if any presentation import returns.

  2. Material outcome semantics. Reproduced your counterexample: driving the helper with delivery_outcome=surface_only plus delivery_batch_scale=multi_surface returned a reached milestone with id=surface_only. The local _MATERIAL_OUTCOMES set and the scale promotion are gone; the module now consumes the canonical MATERIAL_DELIVERY_OUTCOMES and normalize_delivery_outcome from control_plane.work_items.delivery_outcome. Batch scale no longer promotes anything. The smoke pins the full outcome x scale matrix in both directions: every non-material outcome (surface_only, empty, unknown, and even the literal multi_surface) yields no milestone at either scale, and every canonical material outcome still counts at either scale.

  3. No production consumer. The RFC's smallest slice requires one readout in status markdown, and you were right that the previous head changed no shipped behavior. Status collection now attaches the projection (attach_goal_artifact_lifecycle_projections) and a new presentation renderer prints phase, milestone and guard counts plus the blocking guards and the next transition. Verified end to end through the product CLI: loopx status --goal-id <goal> now carries artifact_lifecycle on each projected Goal, and --format markdown prints - artifact lifecycle: phase=starting milestones=0/0 guards=0. The projection derives only from payloads collection already gathered, so the readout costs no extra IO. The smoke drives the same seam end to end, including a blocking owner gate that renders as blocked by owner_decision (user): todo_gate.

Mutation evidence: restoring the scale-promotion rule fails the smoke with your exact surface_only + multi_surface counterexample, and restoring the presentation import fails the AST assertion.

Verification at 8441fcdd2: import-boundary suite 15/15; presentation + architecture 29/29; docs governance passes; the four status smokes pass; Ruff reports nothing on the changed files (the 14 findings in status_markdown.py are unchanged from baseline).

One scope note on your third point. RFC section 3 lists the dashboard as a later item and section 7's smallest slice names status markdown; I implemented the status markdown readout and left the dashboard consumer for a follow-up. If you would rather have both in this PR, say so and I will add the dashboard view on top.

@songoow
songoow force-pushed the codex/goal-artifact-lifecycle-projection branch from 8441fcd to 55e74cc Compare September 14, 2026 09:28
@songoow

songoow commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

CI caught a real conflict on the previous head that I had missed, now fixed at b9d7160f9.

test-shard (2) failed with 2100 passed, 1 failed:

FAILED tests/control_plane/test_goal_acceptance_observation.py::test_real_collection_preserves_acceptance_before_display_run_trimming
AssertionError: assert 'artifact_lifecycle' not in {'id': 'acceptance-demo', ...}

That assertion is not stale — it came from #4248, which shipped goal_acceptance_observation_projection_v0 as bounded historical evidence and deliberately guarded that it is not the full lifecycle contract. It asserted the collected Goal carried no artifact_lifecycle key at all, which was true while the full contract had no producer. Attaching the real projection broke it.

The guard's intent is the distinction between the two contracts, not the absence of one of them, so I preserved the intent rather than deleting the assertion. The collection test now requires both projections present under their own keys with their own schema versions, and requires the phase/milestone/transition vocabulary to belong to the lifecycle projection alone.

I also gave the new renderer the symmetric check #4248 established for its own: each renderer must refuse the other's schema rather than print a half-understood payload under its own heading. Mutation-verified — relaxing the lifecycle renderer's schema check to "any non-empty payload" makes it print phase=unknown milestones=0/0 guards=0 for an acceptance observation, and the new smoke assertion fails.

test_markdown_rejects_the_distinct_full_lifecycle_contract is untouched and still passes.

Verification at b9d7160f9: test_goal_acceptance_observation.py 10/10; architecture + presentation 39/39; the lifecycle smoke passes including the new distinctness assertion; Ruff clean on both changed files.

One unrelated red I want to name rather than leave ambiguous: examples/docs-governance-smoke.py currently fails with RFC missing required Chinese mirror: manager-runtime-profile-v0.md. I reproduced that on a clean upstream/main checkout — it arrived with #4337 and is not from this branch.

@songoow
songoow force-pushed the codex/goal-artifact-lifecycle-projection branch from b9d7160 to 2d0617d Compare September 14, 2026 10:07

@songoow songoow left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes conclusion (author-owned PR; GitHub blocks formal self-review)

审查 head:2d0617d0dd9eadc48034290d122084ec8459a1a1。结论:REQUEST_CHANGES。

动机

Goal 生命周期读出应让操作员看到真实阶段、里程碑与阻塞项。新增一个 schema 或能打印一行 markdown 还不能证明这一价值:它必须消费已有状态的真实形状,保留来源完整性,并服从现有 lane/frontier。较小且更可靠的方案是复用 acceptance observation 的数据提取、user-gate/vision owner 和既有 public-safety 边界,再增加必要的生命周期词汇;不应复制一套缺省为空的推导链。

改动思路

当前 consumer 为 collect_status → attach_goal_artifact_lifecycle_projections → pure builder → run_history.goals[].artifact_lifecycle → status markdown。相对上次评审,已去掉 control-plane → presentation 导入,materiality 也改用 canonical MATERIAL_DELIVERY_OUTCOMES,并接入真实 collection/rendering。两个投影的 schema 隔离有对称测试。这些修复有效,但新消费者的输入接线、脱敏边界与 lane 优先级仍有问题。

具体改动

整个 diff 为 6 个文件:423 行生命周期模块、47 行 renderer、collection/status markdown 两处接入、485 行 fixture smoke,以及 acceptance observation 集成测试的并存断言。没有新增持久状态或执行权。Dashboard 没有新显示消费者;这不应被描述为完成了 dashboard 功能。

关键代码讲解

  • attach_goal_artifact_lifecycle_projections(artifact_lifecycle.py:395-406)读取 user_todo_summary/agent_todo_summary,并把整个 run_history 汇总对象交给 builder。
  • _evidence_milestones 实际只读取输入中的 latest_runs;_guards 只读取 gate_open_items 以及独立 gaps。
  • _compact_text(:82-90)先用本地 regex 重写/截断,再以另一套 private-text/token 规则判断是否保留。
  • _next_transitions(:284-310)仅在 phase 不是 closing 时服从 lane,否则直接建议 closed。

对主干的风险

[P1] 真实状态接线错误,当前 guards 与运行证据被丢失。

运行仓库现有 collect_fixture,它通过真实 refresh_state_run/collect_status 构建状态:acceptance_observation 有 1 个 user gate 和 1 个 acceptance gap,新 lifecycle 却为 starting、guards=[]、milestones=[]。真实 attention 字段为 user_todos/agent_todos,summary 的 gate 需要现有 open_user_gate_todo_items 提取;顶层 run_history 有 goals/recent_runs,没有 latest_runs。新 smoke 的 consumer fixture 手填了只有新实现才认识的字段,所以无法发现接线错位。请使用每个 Goal 未经过 display limit 裁剪的 history,复用现有 user-gate/agent-vision 提取,并在真实 collection fixture 断言 guard、gap、evidence 和 markdown 的值,而非只检查 schema/key 存在。

[P1] “public-safe”仍放行已有公共边界明确禁止的值。

用合成的 Windows 用户路径、Linux 配置/工作区绝对路径,以及 access_key=<synthetic-value> 测试:_compact_text 全部原样返回,而仓库现有 runtime.public_safety.validate_public_safe_value 全部拒绝。Windows regex 匹配的是重复反斜杠,常见路径不匹配;本地 Linux 前缀集和凭据规则也比 canonical validator 窄。应复用已经位于 control-plane 内的 public-safety owner,并在截断前校验完整输入;不要维护第三份缩减规则。negative coverage 应调用 canonical validator 校验整个输出,覆盖 label 和 evidence ref。

[P2] closing 路径覆盖现有强制 work lane。

用真实 lark_inbox_reply_due_work_lane_contract 构造 must_attempt_work=true 的待回复 lane,配合零 open Todo 和已有 material run,builder 仍返回 next=closed、reason_codes=[no_open_agent_work]。这与 PR 宣称的 lane 优先相反,也不符合 RFC 要求“从既有 frontier/lane 推导合法下一步”。这是只读建议错误,不是已经修改 Goal 完成状态;修复应服从现有权威 lane/frontier,避免另建 completion gate。

我的整体评价

本轮 fixture smoke 与 10 个 acceptance tests 均通过,以上三个独立反例也均复现。当前 CI 通过说明旧依赖错误已消除,不能证明产品读出的事实正确。423 行生产模块与 485 行 smoke 的核心成本仍应通过复用既有数据提取和安全规则降低,而非继续追加手工 payload。

PR 正文也需要按最终实现重写:它仍说 status consumer 是后续,并声称使用已被删去的 redactor。当前不建议合并;修复真实输入接线、安全合同及 lane 优先级后再做端到端复审。本轮未作 packaged dashboard 验收,也不将不完整历史等同于 Goal 已完成。

English verdict: REQUEST_CHANGES at 2d0617d0dd9eadc48034290d122084ec8459a1a1. Real status collection loses the existing user gate, acceptance gap and per-goal run evidence due to mismatched input fields; the local sanitizer retains values rejected by the canonical public-safety validator; closing overrides a mandatory existing work lane. The fixture smoke and 10 acceptance tests pass but miss these reproduced counterexamples. Reuse the existing extraction/safety owners and test actual collection output.

@songoow

songoow commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

修复提交:155c20760d2755974ddb145451b8426c900c3a0c

已修复真实字段接线、共享安全边界和强制 lane 优先级。复用 acceptance-observation 保留人工/Controller gate 与 vision gaps,读取 display trimming 前的 Goal history 和 semantic retention;补齐 quoted credentials、Windows/UNC/data-root 负例。安全值和公开 HTTPS 引用仍可使用。

验证:137 项相关测试、fixture smoke、Ruff、公私边界及 LoopX premerge 通过;独立候选复查发现的同边界遗漏也已加入回归。

已重写 PR 正文以反映当前实现与边界。全部提交带 DCO sign-off;新的 CI 已触发,尚未宣告合并或批准。请按此 head 复审。

Fix follow-up: the reproduced findings are addressed at this exact head with regression coverage and repository-native premerge validation. CI is rerunning; this is a fix report, not an approval.

@steven-kid steven-kid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动机

REQUEST_CHANGES。 审查 head:155c20760d2755974ddb145451b8426c900c3a0c;完整 base:e743f2404d4a17d52cbf2d15db1f879ff46caea8。

RFC #4128 希望 operator 从一个 Goal 读出阶段、里程碑、阻塞 guard 与下一步,而不是手工拼接 Todo/run/quota。RFC 的最小切片允许 pure projection、fixture smoke 和 status markdown,现有 consumer 现在满足这一最小使用场景,不需要为本轮批准强制增加 dashboard。

相对旧评审 head 2d0617d0dd9eadc48034290d122084ec8459a1a1,真实字段接线、shared safety 与 mandatory lane 优先级已修复,旧 outward import 和 scale promotion 也没有回归。但是“material history”仍被当作“可关闭 Goal”的证据,越过了现有 acceptance observation 对不完整来源的明确限制。复用更多字段和全部 fixture 通过,不能证明这一步推断成立。

改动思路

当前生产链路为 collect_status → attach_goal_artifact_lifecycle_projections → builder → run_history.goals[].artifact_lifecycle → status markdown。attach 读取 display trimming 前的 per-Goal history,复用 semantic retention,真正使用 attention 的 user_todos/agent_todos。guards 与 vision gaps 通过已有 acceptance-observation owner 提取,materiality 使用 canonical DeliveryOutcome;public safety 也改为 inward-safe owner。

我比较了 base/head 的 acceptance observation、history/semantic retention、frontier/work-lane 和 shared safety。保留原 acceptance observation 的“partial、acceptance_assessed=false”是合理的不同视图;但新投影应派生相同权威事实,不能在缺少 acceptance 来源时另行推出 closeout。最小修复应收窄 closing 的证明条件并沿用已有 frontier/验收语义,不需要新存储或新的完成引擎。

正向真实 collection 测试已证明 user gate、vision gap、material run 在 display limit 0/5 下保留,markdown 可读;deferred/completed gates、foreign Goal run 和强制 Lark reply lane 有负向覆盖。新增 readout 没有执行完成或修改 quota 的权力;下面报告的是面向 operator 的错误阶段及行动建议。

具体改动

关键代码讲解

  1. artifact_lifecycle.py::attach_goal_artifact_lifecycle_projections/build_goal_artifact_lifecycle_projection 连接当前用户/agent summary 与未裁剪 Goal history,补入 retained material/vision run,并过滤 foreign Goal。已消除上一轮手填字段才能工作的问题;没有文件写入。
  2. _evidence_milestones/_lifecycle_phase/_next_transitions 把 material outcome 转成 reached marker,再以零 open agent Todo 和没有 unreached marker 判定 closing,并建议 closed。这里混淆了 materiality 与验收/进展:canonical MATERIAL_DELIVERY_OUTCOMES 包含 outcome_gap,而 PROGRESS_DELIVERY_OUTCOMES 刻意不包含它。
  3. _guards 复用 acceptance-observation 的 owner gate 和 gap,但 builder 丢掉 observation 的 coverage/missing_sources/acceptance_assessed。这使“未观察到 gap”继续被当成“没有未满足验收”,尤其在缺少 agent vision 时。
  4. runtime/public_safety.py 扩大 Windows drive/UNC/data-root 与带引号凭据识别;_compact_text 在截断前调用完整 validator,再保留已有 private-text/provider-token 的更严格检查。作用域是所有 shared-safe-text 调用者,PR 正文已经披露这种更严格拒绝;仍需处理下面实际失败的已有契约测试。
  5. goal_artifact_lifecycle_markdown.py 只接受自身 schema,输出阶段、milestone 数、guard 与 next transition;status renderer 接入,接受观察的对称 schema 测试保留。481 行 fixture smoke 与扩展 acceptance tests 覆盖正反路径,但部分 closeout oracle 本身就允许缺少验收证据的关闭建议,不能作为该语义的独立证明。

全部 7 文件 +1107/-8 已审查:423 行 pure module、47 行 renderer、collection/render 接入、shared safety 和两份测试资产。没有新 settings、自动安装指令或 feature flag。_declared_milestones 的实际 authoring→collection 路径仍缺少证明:collect_history 的 Goal 字段选择没有 acceptance/milestones,手填 helper payload 不能证明普通用户声明会到达 readout。请接入既有真实 marker 来源或收窄未交付的声明,而不是再增加手工同步状态。

对主干的风险

[P2,功能性缺陷] 不完整验收加 outcome_gap 被显示为 closing,并建议 closed。 定位:loopx/control_plane/goals/artifact_lifecycle.py:238–240,结合 _evidence_milestones 和 _next_transitions。

使用真实 refresh_state_run 写入一条 delivery_outcome=outcome_gap、建议“Missing independent verification”的记录;Goal active,无 open Todo,也没有 agent vision。然后通过真实 collect_status 和 status markdown,在 base/head 分别运行完全相同输入,display limit 均测试 0/5:

  • base 的 acceptance observation 为 coverage=partial, missing_sources=[agent_vision], acceptance_assessed=false,现有 next action 仍是继续只读检查;不存在生命周期关闭读出。
  • head 保留上述 partial observation,却同时输出 lifecycle_phase=closing、outcome_gap reached=true、guards=[]、next_transitions.target_phase=closed;markdown 明确显示 next: closed (record the terminal no-follow-up outcome)。

这不是 Todo 已经被关闭,也不是测试伪造新投影结果;实际 collector/backend 自己产生了矛盾的公开建议。独立 oracle 来自 canonical outcome 对 material/progress 的区分,以及已有 observation 对 missing source 的限制。将 gap 记作历史证据可以有价值,但不能据此推导验收已足够关闭。修复时保留 incomplete/unknown coverage,并从已有 frontier/验收依据决定 closeout;仅从 material 集合删除一个值还不能证明 outcome_progress 就足够完成整个 Goal。

现有 required check 仍失败。 本地复现 tests/test_loopx_turn_executor.py::test_enabled_host_result_rejects_path_shaped_opaque_refs[worker_ref-C:/workspace/private/worker.json]:期待 opaque-reference 错误,现在返回 absolute-local-path 错误。base 聚焦两个用例通过,head 同一用例失败;这与远端 test-shard(4)/pytest/merge-gate 一致。安全值仍被拒绝,并非敏感值泄漏;请明确保留旧诊断优先级,或按有意变更更新契约断言,并覆盖 shared consumers,不能将 CI 红灯当作无关。

验证:acceptance + turn executor 70 passed、1 failed;architecture/import + retained-history 24 passed;fixture smoke、按项目范围 Ruff、diff check 通过。base 相关 opaque-reference 用例 2 passed。没有运行 packaged dashboard、全仓库 suite 或真实服务;不把 smoke 和局部绿灯等同于完整产品验证。

我的整体评价

本轮确实解决了上一轮接线、安全和 lane 的具体问题,也有最小真实 consumer;但还未证明新增 closing 推断与 canonical completion/acceptance 语义一致。当前应请求修改,优先消除上述真实 readout 矛盾、修复 shared-consumer 红灯,并补齐声明来源或缩减承诺。建议把回归放在真实 collection/markdown 路径,继续以不完整数据为 unknown,而不是继续扩展手填 fixture 来匹配当前实现。未批准、未合并。

English verdict: REQUEST_CHANGES at 155c207. A real refresh_state_run → collect_status → markdown probe with only outcome_gap and no agent vision produces closing / next: closed, while the existing acceptance observation still says partial, missing agent_vision, acceptance_assessed=false. Material history is not completion evidence; preserve missing-source uncertainty and derive closeout from the existing acceptance/frontier owner. This is a read-only recommendation defect, not an executed Goal completion. Prior wiring, safety and mandatory-lane fixes are present. A shared turn-executor diagnostic contract also fails locally and in required CI: 70 related tests passed / 1 failed; 24 architecture/retention tests, lifecycle smoke, Ruff and diff checks passed. Baseline opaque-reference cases pass. No merge performed.

@steven-kid

Copy link
Copy Markdown
Collaborator

English verdict: REQUEST_CHANGES at 155c207. A real refresh_state_run → collect_status → markdown probe with only outcome_gap and no agent vision produces closing / next: closed, while the existing acceptance observation still says partial, missing agent_vision, acceptance_assessed=false. Material history is not completion evidence; preserve missing-source uncertainty and derive closeout from the existing acceptance/frontier owner. This is a read-only recommendation defect, not an executed Goal completion. Prior wiring, safety and mandatory-lane fixes are present. A shared turn-executor diagnostic contract also fails locally and in required CI: 70 related tests passed / 1 failed; 24 architecture/retention tests, lifecycle smoke, Ruff and diff checks passed. Baseline opaque-reference cases pass. No merge performed.

@songoow
songoow force-pushed the codex/goal-artifact-lifecycle-projection branch from 155c207 to 55f9ee4 Compare September 15, 2026 05:52
songoow added a commit to songoow/loopx that referenced this pull request Sep 15, 2026
…d quoted secrets

The shared local-path rule only treated `X:\Users` and `X:\Documents and
Settings` as local paths, so any other drive-qualified path, every UNC
share and `/data/...` roots passed the public boundary. The secret rule
required the key to touch its separator, so JSON-shaped and quoted forms
such as `"token": "..."` and `access_key='...'` were never classified.

Both rules are widened. The lookbehind still excludes URL schemes, clock
times and ratios; a boundary test pins those shapes on both sides.

Behavior change, disclosed: a drive-qualified path-shaped opaque ref is
now reported as an absolute local path before the opaque-reference shape
check. The value was rejected before and is rejected now; only the
diagnostic differs. The turn-executor contract records the intended
diagnostic per shape.

Split out of loopx-project#4360, which only needs the rules that already exist.

Signed-off-by: song <liusongstep@gmail.com>
songoow added a commit to songoow/loopx that referenced this pull request Sep 15, 2026
…d quoted secrets

The shared local-path rule only treated `X:\Users` and `X:\Documents and
Settings` as local paths, so any other drive-qualified path, every UNC
share and `/data/...` roots passed the public boundary. The secret rule
required the key to touch its separator, so JSON-shaped and quoted forms
such as `"token": "..."` and `access_key='...'` were never classified.

Both rules are widened. The lookbehind still excludes URL schemes, clock
times and ratios; a boundary test pins those shapes on both sides.

Behavior change, disclosed: a drive-qualified path-shaped opaque ref is
now reported as an absolute local path before the opaque-reference shape
check. The value was rejected before and is rejected now; only the
diagnostic differs. The turn-executor contract records the intended
diagnostic per shape.

Split out of loopx-project#4360, which only needs the rules that already exist.

Signed-off-by: song <liusongstep@gmail.com>
@songoow

songoow commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Reworked at 55f9ee4ac in response to the third review (155c207). History was rewritten: the previous third commit is replaced, the second commit no longer carries the shared classifier change.

P2 — outcome_gap treated as closeout evidence. Fixed as suggested: milestone reachability now uses PROGRESS_DELIVERY_OUTCOMES; a gap stays a visible unreached marker. Reproduced defect (zero open agent Todos over a gap run → closing / next: closed) is pinned in the smoke and in a unit test; it now reads qualifying / milestone_unreached.

On "defer to the acceptance owner". The first attempt gated closing on acceptance_assessed + coverage == complete. That owner is bounded by contract (acceptance_observation.py:198-199, dashboard z.literal(false)), so the gate was unsatisfiable and closing became unreachable in every scenario. Reverted. Closing keeps the todo-completion reading RFC §2 adopts; when the observation names sources it could not read, the closeout step carries them in its precondition with acceptance_unverified instead of implying a verified acceptance.

Declared milestones stay readable (RFC §3.2 lists them in the smoke, §9 keeps their source open). No producer yet; not resolved here.

P1 — shared turn-executor contract red. The public_safety.py widening that caused it is out of this PR and in #4426 with its own boundary tests and the disclosed diagnostic-ordering change. tests/test_loopx_turn_executor.py is untouched here and passes.

Validation: 73 tests + lifecycle smoke, Ruff, public-boundary scan, maintainability ratchet; rebased on cd9cfa6d4.

huangruiteng
huangruiteng previously approved these changes Sep 15, 2026

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

队列前言(loopx pr-review --state open,policy_revision=3,other-developers-first):本卡是 rank 2 的 #4360(社区贡献者 @songoow,tier 0 社区反馈槽位)。我在开始审查时读出 packet 记录的 head 是 55f9ee4ac,随后远端已前进到 692c1b8f0,因此本轮全部证据都在最新 head 上重新取得,未继承 @steven-kid 上一轮的结论。

结论先行:在这个 exact head 上通过(一个非阻塞 P2)。 上一轮 REQUEST_CHANGES 的两条实质问题都已在真实路径上被修复:outcome_gap + 零 open agent Todo 不再产生 closing / next: closed;shared turn-executor 诊断契约的回归也消失了。我另提一个非阻塞 P2:终态建议分支目前没有可达的生产触发条件。

动机

RFC #4128 希望 operator 从一个 Goal 直接读出阶段、已达里程碑、阻塞 guard 与下一步,而不是自己从 Todo 计数、run 分类和 quota 里拼。改动前后是可测的:在 base cd9cfa6d4 上用同一份一次性 fixture 跑真实 loopx status,两个 Goal 都得到 artifact_lifecycle: ABSENT;在 head 上同一份 fixture 得到 lifecycle_phase、带 reached 的 milestones、带 owner/blocked 的 guards、以及带 target_phase/precondition/reason_codes 的 next_transitions,markdown 也渲染出对应区块。

这块不是「已经能工作还要再包一层」:上一轮的真实探针证明,自建推断会把 run 级 outcome_gap 读成可以关闭 Goal 的证据。所以本轮的判断标准不是「新增了字段」,而是「新增的字段在证据不足时是否还会给出错误动作」。

改动思路

链路是 collect_status → attach_goal_artifact_lifecycle_projections → build_goal_artifact_lifecycle_projection → run_history.goals[].artifact_lifecycle → status markdown。投影只读已经收集好的 payload:Goal 记录、run_history 的 latest_runs 与 collector 保留的 semantic_history、attention item 的 user_todos/agent_todos/work_lane_contract,以及 frontier 的 acceptance_gaps。它不读文件、不写状态、不花 quota。

规则的归属是这轮最值得肯定的部分:materiality 用 canonical PROGRESS_DELIVERY_OUTCOMES(而不是自建集合),里程碑可达性只由 progress outcome 决定、outcome_gap 保留为「可见但未达」的标记;guards 与 coverage 来自既有 acceptance-observation owner;lane 优先级来自 attention item 的 work_lane_contract;保留证据来自 collector 的 semantic retention,而不是放宽 display limit。_next_transitions 的顺序是固定的:open guard → 已有 lane 约束 → 未达里程碑 → 最后才是 closing,所以 lane owner 永远压过投影自己的推断。

正向路径:progress 证据把里程碑标为 reached,阶段进入 closing,此时 closeout 步骤不直接给终态,而是要求 acceptance owner 自己的裁决;不满足就把步骤留在 closing,并在 precondition 里点名读不到的来源。负向路径:outcome_gap 只产生未达标记,阶段是 qualifying,下一步是 milestone_unreached;已终结的 Goal 直接 phase=closed 且没有下一步。

具体改动

6 个文件 +1215/−3:445 行纯投影模块、47 行渲染器、两处各 1 行的接入(status collection 与 status markdown),以及 497 行 fixture smoke 与 225 行行为测试。没有新增持久状态、没有 feature flag、没有新命令。

关键代码讲解

  1. attach_goal_artifact_lifecycle_projections(loopx/control_plane/goals/artifact_lifecycle.py:389):只把已经收集到的 payload 组装成输入,成本为零额外 IO;推导不出来的 Goal 直接没有该字段,而不是给一个占位值。
  2. _acceptance_supports_closeout(:178):终态建议必须由 acceptance owner 自己的裁决字段支撑(acceptance_assessed is True 且 coverage == "complete" 且无未读来源),不再从「没有可点名的缺失」推断裁决。这是我下面 P2 的来源。
  3. _next_transitions(:245):用既有 lane/guard/里程碑顺序派生下一步,而不是第二套状态机;没有裁决时把步骤留在 closing,并把 acceptance_unverified 与读不到的来源写进 precondition。
  4. _evidence_milestones(:88):只有 progress outcome 能把里程碑标为 reached;delivery_batch_scale 不改变 materiality;outcome_gap 保留为未达标记。
  5. append_goal_artifact_lifecycle_markdown(loopx/presentation/renderers/goal_artifact_lifecycle_markdown.py:13):schema 不匹配就完全不输出,避免把别的投影当成本投影渲染。

我的验证

  • 真实路径(base 对比 head,一次性 fixture,真实 loopx status):outcome_gap + 零 open agent Todo → phase=qualifying、outcome_gap reached=false、next=[qualifying, milestone_unreached];progress + 零 open work → phase=closing、next=[closing, acceptance_unverified],precondition 点名 agent_vision, todo_projection;base 上两者都无该字段。acceptance observation 在 base 与 head 上取值完全一致,说明这不是靠改动既有投影来凑结果。
  • tests/control_plane/test_goal_acceptance_observation.py 20 passed;tests/test_loopx_turn_executor.py -k opaque 2 passed(上一轮报告的 shared-consumer 红灯已消失);goal-artifact-lifecycle-projection-smoke ok;改动文件 Ruff 通过。
  • 依赖方向已修正:新模块只导入 control_plane/runtime/public_safety 与根级 public_safe_text,不再有 control-plane → presentation 的外向依赖;rg 也确认仓库里没有任何生产者写 goal.acceptance.milestones,与「删除声明式里程碑读取器」的说明一致。
  • 未验证:head 的完整 CI 矩阵当时仍在 queued/in progress(Sign-off 已 SUCCESS,无失败项);dashboard UI 与 periodic-report consumer 是本 PR 声明的后续;我的探针用的是合成 Goal/run 数据,没有触碰任何真实 goal。

对主干的风险

最强的回归场景不是崩溃,而是 readout 给出证据不支持的动作。第一类(把非进展 outcome 当关闭证据)和第二类(把有界观测的沉默当已验收)在 head 上都已被阻断,我在真实 collector 路径上复现了两者的输入形状,结果符合预期。

第三类是共享面:collect_status 与 status markdown 都被大量消费者使用,所以隔离不能靠读 diff。我用 base/head 同 fixture 对比确认:既有字段取值不变(acceptance observation 完全一致),新增字段只在能推导时出现,渲染器对非本 schema 静默返回。

P2(非阻塞,建议后续处理):终态建议分支目前没有可达的生产触发条件。 定位:artifact_lifecycle.py:178(guard)与 :292-303(target_phase: closed 分支)。触发条件:任何走真实 collect_status 的 active Goal,包括「无 open agent 工作且所有里程碑已达」的那一种。结果:唯一的生产者 build_goal_acceptance_observation 把 acceptance_assessed 硬编码为 False(acceptance_observation.py:198)、coverage 只会是 partial/unavailable(:199),dashboard 契约把该字段钉成 z.literal(false),本 PR 自己的测试也断言 coverage != "complete";因此 closed 建议永远不会触发,也没有任何 fixture 覆盖它,而同一个 head 刚刚以「没有生产者」为由删掉了声明式里程碑读取器。最小修复:要么删掉这个不可达分支、明确读出停在 closing 直到 acceptance 裁决 owner 出现,要么保留但在注释与测试中显式标注当前不可达(现有那条 coverage != "complete" 断言就是很好的起点)。我把它判为非阻塞,因为方向是 fail-safe、读出不误导,而且它正是上一轮要求的「不自己发明完成规则」的直接结果——但它值得在 acceptance 裁决生产者落地时一起收口。

残余风险:head 的完整 CI 矩阵在本轮审查时仍在排队;dashboard 与 periodic-report 消费面未演练;关闭建议的可达性取决于未来的 acceptance 裁决生产者。

我的整体评价

可观测语义是明确的新增,并且相对上一个 head 是有意的语义修正:base 上没有该字段,head 上同一份真实输入给出自洽的阶段/里程碑/guard/下一步,而上一轮报告的具体矛盾(gap + 无 open work → closing/next: closed)在真实路径上已不可复现。代码体量与问题相称:一个纯投影加一个渲染器与两处一行接入,没有新持久状态、没有新命令、没有第二套完成规则;规则都留在原有 owner 里。scope fit 有真实调用点(collect_status),并且这一轮真的删掉了没有生产者的声明式里程碑读取器。因此我给出通过结论;上面的 P2 不阻塞,合并仍走 loopx-pr-merge,本评论不构成合并授权。

English verdict: APPROVE at exact head 692c1b8 — the derived lifecycle readout reaches the real loopx status JSON and markdown (base: field absent, head: phase/milestones/guards/next present), and the previous round's reproduced defect is gone: on a disposable fixture whose only run is outcome_gap with zero open agent Todos the real collector now reports phase=qualifying, outcome_gap reached=false, next=[qualifying, milestone_unreached], while a progress Goal stays in closing with acceptance_unverified naming agent_vision, todo_projection. Validation on this head: 20 goal-acceptance tests, 2 shared turn-executor tests (the earlier shared-consumer failure is resolved), the lifecycle smoke, Ruff, and my base/head probe. One non-blocking P2: the target_phase: closed branch is currently unreachable because the only acceptance-observation producer hardcodes acceptance_assessed=False and never reports complete coverage, so it should either be removed or explicitly pinned as awaiting a producer. Full CI was still queued at review time; dashboard and periodic-report consumers are declared follow-ups.

Implements the smallest useful slice of the Goal artifact lifecycle RFC: one
pure derivation module plus a fixture smoke.

An operator can see todo counts, quota state and the latest classification for
a long-running Goal but must reconstruct three answers from them: where the
Goal sits in its lifecycle, which milestones it has reached, and which guard
blocks the next step and who owns it. This derives those from state LoopX
already owns.

The projection reads no files, writes no state and grants no authority.
Milestone reachability starts from markers the Goal declares and falls back to
material evidence already recorded in the run history; a declared marker is a
claim, not proof, so it counts only when evidence records it. Guards are open
owner decisions and unmet evidence preconditions. Next transitions reuse the
existing frontier/lane derivation rather than a second state machine.

The fixture smoke covers the RFC's negative cases: an unreached declared
milestone, a blocking owner gate that admits no other transition, an
evidence guard owned by the agent, the closing/closed boundary, projection
purity, and the public-safe boundary.

Refs loopx-project#4128

Signed-off-by: song <liusongstep@gmail.com>
Review found two defects in the derived projection.

Closeout ignored milestone reachability: a Goal whose declared acceptance
marker was still unreached, with no open agent work, was reported as closing
with a next transition of closed. Running out of open work is not the same as
having reached acceptance, so an unreached milestone now keeps the Goal in
qualifying with a milestone_unreached reason. An existing work-lane constraint
also outranks this projection's own reading of remaining work.

The compact label helper claimed public safety without providing it: it only
collapsed whitespace and truncated, so a private absolute path in a run
history reference reached the projection verbatim. It now reuses the shared
redaction rule and drops a value that still matches a private-text or provider
token shape.

Both are covered by negative cases, and each case kills its mutant: removing
the closeout guard or the redaction rule fails the smoke.

Signed-off-by: song <liusongstep@gmail.com>
…consumer

Three review findings, all reproduced at the previous head:

1. Forbidden dependency direction. The module imported
   `loopx.presentation.public_safety`, an outward dependency the control plane
   may not take. Redaction is now bounded and inward-safe inside the module and
   reuses `loopx.public_safe_text.find_private_text_match` for classification,
   so the shared private-text contract still has exactly one owner.

2. Material outcome semantics drifted. Evidence milestones kept a local
   outcome set and let `delivery_batch_scale == "multi_surface"` promote a run on
   its own. Driving the helper with `delivery_outcome=surface_only` plus
   `delivery_batch_scale=multi_surface` returned a reached milestone, but batch
   scale describes delivery width and the canonical typed rule
   (`MATERIAL_DELIVERY_OUTCOMES`) excludes `surface_only`. The module now
   consumes that canonical rule directly, and the smoke pins the full
   outcome x scale matrix in both directions.

3. No production consumer. The RFC's smallest slice requires one readout in
   status markdown. Status collection now attaches the projection and the
   presentation renderer prints phase, milestone and guard counts plus the
   blocking guards and the next transition. `loopx status` shows the readout
   without any extra IO: it derives only from payloads collection already
   gathered.

Verified: the smoke fails when the scale-promotion rule is restored and when the
presentation import is restored; the import-boundary suite is 15/15; status and
architecture suites are 29/29; docs governance passes; the four status smokes
pass; Ruff reports nothing new on the changed files.

Signed-off-by: song <liusongstep@gmail.com>
CI `test-shard (2)` caught a real conflict I had missed: `loopx-project#4248` shipped
`goal_acceptance_observation_projection_v0` as bounded historical evidence and
guarded that it is *not* the full lifecycle contract, partly by asserting the
collected Goal carries no `artifact_lifecycle` key at all. Attaching the real
lifecycle projection made that guard fail (2100 passed, 1 failed).

The guard's intent was the distinction, not the absence, so this preserves the
intent under the new reality instead of deleting the assertion. The collection
test now requires both projections to be present under their own keys with
their own schema versions, and requires the phase/milestone/transition
vocabulary to belong to the lifecycle projection alone.

The smoke gains the symmetric renderer check `loopx-project#4248` established for its own
renderer: each renderer must refuse the other's schema rather than print a
half-understood payload under its own heading. Verified by mutation — relaxing
the lifecycle renderer's schema check to "any non-empty payload" makes it print
`phase=unknown milestones=0/0` for an acceptance observation, and the new
assertion fails.

`test_markdown_rejects_the_distinct_full_lifecycle_contract` is untouched and
still passes.

Signed-off-by: song <liusongstep@gmail.com>
Own the Goal status vocabulary and the public-safety boundary from their
canonical modules instead of local copies, and give every emitted string
the same redaction the rest of the control plane applies.

The shared public-safety rule widening (drive-qualified and UNC paths,
quoted secret keys, `/data/` roots) that this change originally carried
is now a separate PR, because it changes a rule other owners consume and
alters the turn-executor diagnostic contract. This projection only needs
the rules that already exist.

Signed-off-by: song <liusongstep@gmail.com>
Two corrections to the lifecycle readout, both bounded by owners this
projection already consumes.

Material history is not progress. `MATERIAL_DELIVERY_OUTCOMES` decides
what run history retains and deliberately includes `outcome_gap`, while
`PROGRESS_DELIVERY_OUTCOMES` deliberately excludes it. The evidence
milestones used the material set to mark markers reached, so zero open
agent Todos over a recorded gap read as closing / next: closed. A gap
now stays a visible but unreached marker, and that Goal stays qualifying
with `milestone_unreached`.

Closing keeps the todo-completion reading this RFC adopts (no open agent
work and every marker reached); it does not become an acceptance gate.
`goal_acceptance_observation_projection_v0` is bounded by contract
(`acceptance_assessed` is always false, coverage is never complete), so
requiring its verdict would make `closing` unreachable. Instead, when
that owner names sources it could not read, the closeout step carries
them in its precondition with `acceptance_unverified`, and the reader
keeps the decision.

Declared milestones stay readable: RFC §3.2 lists them in the smoke and
§9 keeps their source an open question. They have no producer yet; that
is tracked there, not resolved here.

The shared public-safety rule widening is a separate PR.

Signed-off-by: song <liusongstep@gmail.com>
Builds on the recorded-gap fix rather than replacing it: `outcome_gap`
staying an unreached marker is kept, and so is naming the acceptance
sources the bounded observation could not read.

Two gaps remained in that reading.

An empty `missing_sources` was treated as an acceptance verdict.  It only
means the observation read every source it knows about; the projection
still reports `acceptance_assessed=False` and a coverage that is
`partial` or `unavailable`, never `complete`.  So a Goal with both an
attention item and agent vision present produced a bare `next: closed`
with no disclosure at all -- the same defect as the reported one, moved
to a fully observed input.  `_acceptance_supports_closeout` now reads the
verdict fields directly instead of inferring one from silence.

Annotating the reason codes did not undo the recommendation.  The
reported defect is an actionable wrong step shown to an operator, and
`target_phase: closed` remained that step even with `acceptance_unverified`
attached.  Closing stays reachable as the todo-completion reading, but
without a verdict the step stays inside closing and asks for the
acceptance the existing owner has not given.

Declared acceptance markers are removed.  No producer writes
`goal.acceptance.milestones` or `goal.milestones` anywhere in the
repository, so the reader was unreachable in production while carrying
the only guard able to hold back a closeout, and the fixtures exercising
it could not show that a user declaration reaches the readout.  Add it
back together with the producer that writes it.

Signed-off-by: song <liusongstep@gmail.com>
@songoow
songoow force-pushed the codex/goal-artifact-lifecycle-projection branch from 692c1b8 to 9d04037 Compare September 15, 2026 13:05
@songoow

songoow commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto upstream/main (53fed471e) to clear the merge conflict. New exact head: 9d040379a4f9f36df451f9cfdf5f33e8d4296b06. The previous approval was bound to 692c1b8f0, so this head needs a fresh exact-head pass.

Conflict and resolution. One file: tests/control_plane/test_goal_acceptance_observation.py. Upstream 7b276b793 added a missing_claim keyword to collect_fixture; this PR's 5137c1c7b added display_limit and delivery_outcome to the same signature. Resolved by keeping all three keyword-only parameters; both bodies auto-merged (the missing_claim second run remains, the first run still takes delivery_outcome). No other hunk changed, and the seven commits carry the same content as before.

Validation at this head (public-safe summary).

Check kind Result Evidence
unit passed python -m pytest tests/control_plane/test_goal_acceptance_observation.py, 20 passed
unit passed python examples/control_plane/goal-artifact-lifecycle-projection-smoke.py, ok
static passed ruff check on the six changed Python files

变基到最新 upstream/main 解决冲突,唯一冲突在 collect_fixture 签名:上游新增 missing_claim,本 PR 新增 display_limit/delivery_outcome,已合并保留三者。七个提交内容不变,DCO trailer 完整。新 head 需按 exact-head 规则重审。

huangruiteng
huangruiteng previously approved these changes Sep 15, 2026

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动机

loopx status 目前只能告诉 operator "有哪些 Goal、跑了什么、quota 还剩多少",不能回答长周期 Goal 最常被问的两个问题:这条 Goal 现在处在哪个阶段,下一步该做什么、被谁阻塞。run_history.goals[] 里没有任何生命周期读出(base 上没有 artifact_lifecycle 键),于是阶段、已达里程碑、阻塞者只能靠人工从 Todo 计数、run 分类和 quota 里推断。推断错的代价不是"少看一眼",而是按错误阶段行动——上一轮 review 的真实探针正是如此:outcome_gap(material 但未推进)被读成可以关闭 Goal 的证据。这个方向我认可:把 RFC #4128 的生命周期读出投影到既有 status 面上,并且明确它只读、不授予完成权限、不新增存储。

改动思路

  1. 新增 loopx/control_plane/goals/artifact_lifecycle.py(445 行):纯函数派生 lifecycle_phase、带 reached 的 milestones[]、带 owner/blocked 的 guards[],以及带 target_phase/precondition/reason_codes 的 next_transitions[]。
  2. 新增渲染器 goal_artifact_lifecycle_markdown.py,并在 status_markdown.py 接线;collection.py 在既有 acceptance observation attach 之后挂上本投影。
  3. 用 497 行 fixture smoke 与 tests/control_plane/test_goal_acceptance_observation.py(+231/-3)把"material 不等于 progress"这条规则钉住。
  4. 本 head 相对上一轮受审 head 692c1b8f0 的三处修正:reached 改由 PROGRESS_DELIVERY_OUTCOMES 判定(outcome_gap 保持"可见但未达");新增 _acceptance_supports_closeout 门;删掉没有 producer 的 _declared_milestones。

具体改动

  • loopx/control_plane/goals/artifact_lifecycle.py(新增 445 行)::129 的 "reached": outcome in PROGRESS_DELIVERY_OUTCOMES、:178 的 _acceptance_supports_closeout、:245 的 _next_transitions、:361 的 attention item 归一化。
  • loopx/presentation/renderers/goal_artifact_lifecycle_markdown.py(新增 47 行):按 schema_version 拒绝外来 schema 后再渲染。
  • loopx/control_plane/status/collection.py(+2,:12/:247)与 loopx/presentation/renderers/status_markdown.py(+2):status JSON 与 markdown 两个消费面。
  • examples/control_plane/goal-artifact-lifecycle-projection-smoke.py(新增 497 行)与 tests/control_plane/test_goal_acceptance_observation.py(+231/-3)。

关键内容讲解

  1. material 不等于 progress 这条规则现在有真实探针。:129 只把 canonical progress outcome 判为已达;我把该行改回 "reached": True 后,smoke 在 assert_outcome_gap_is_material_but_not_reached 立刻失败(reached == {'outcome_gap': False} 断言被打破),恢复后 git status 干净。这正是上一轮 review 提出的读错风险,本 head 已经堵住。
  2. 规则 owner 没有重复。materiality 归 control_plane/work_items/delivery_outcome,验收来源与 guard 归 acceptance_observation,public safety 归 runtime/public_safety;本模块只派生阶段与下一步,两个投影 schema 不同且互相拒绝,没有出现第二份完成规则或第二份持久状态(阶段不是存储枚举,注释已写明 Goal 可在阶段间移动而无 durable transition)。
  3. 验证证据:pytest tests/control_plane/test_goal_acceptance_observation.py tests/test_loopx_turn_executor.py → 77 passed;pytest tests/control_plane/test_cli_output_budget.py tests/control_plane/test_cli_output_differential.py tests/test_goal_portfolio.py → 97 passed;maintainability ratchet unreviewed=0;lifecycle fixture smoke ok。

对主干的风险

P2(读出的"观测到什么"会少报一项,不阻塞但要修)。artifact_lifecycle.py:361 把缺失的 attention item 归一化成 {"user_todos": user_summary} 这个真值 dict。于是调用导出 builder 的路径(本 PR 的 smoke 与若干测试正是这样用)内部得到的是 coverage=partial 且不含 attention_queue,而 owner 侧的 attach_goal_acceptance_observations 对同一个 Goal 报的是 missing_sources=['attention_queue','agent_vision']、coverage='unavailable'——两条路径对"同一份输入观测到了什么"给出不同答案。我用直接调 builder 与调 attach 对同一 payload 对比复现。最小修法:始终把 attention_item 原样传下去(owner 已正确处理 None),或在 coverage 语义里显式声明"{"user_todos": ...} 等于没有 attention item";并在 smoke 的 closing 断言里补上 attention_queue。

P3(closed 分支当前不可达)。_acceptance_supports_closeout 要求 acceptance_assessed is True 且 coverage == 'complete',但 owner 侧唯一生产者 acceptance_observation.py:211 硬编码 acceptance_assessed=False、coverage 只可能是 partial/unavailable,所以 :299 的 target_phase=closed 分支在本 head 上永远走不到;smoke 也只断言 target_phase != PHASE_CLOSED。这不是错误(保守失败方向是对的),但读代码的人会以为存在可达的"建议关闭"路径。最小修法:在门函数上写明这是为 M1 预留的保守门,或在注释里点名唯一生产者当前不可能返回 True。

P3(第三份 provider-token 规则)。artifact_lifecycle.py:53 的 _TOKEN_SHAPES 与 loopx/control_plane/runtime/public_safety.py 的 SECRET_LIKE_SURFACE_PATTERN 是一组语义相近但阈值不同的规则,属于同一知识的两份副本。最小修法:要么复用 public_safety 的 owner,要么在注释里写明为什么这里的形状判定必须更严/更松。

其余残余风险:本 head 相比上一轮受审 head 已经修掉了最关键的一处读错(reached),并删掉了没有 producer 的 _declared_milestones,我认可现在的收敛方向;投影是派生只读、无状态残留,回滚面就是这一个键与两个消费面。继承性红灯(Frontstage Pages build 缺 long-horizon-control/position-en.svg/index.html、本地 [sqlite] canonical authority 测试因 Node 25.5.0 与要求 22.22.3 不符)在 base/main 上同样失败,与本 PR 无关。

我的整体评价

APPROVE。这个 PR 把"我在哪、下一步是什么、被谁挡住"做成 loopx status 上的一等读出,并且没有拿完成权限去换可读性:它只是派生投影,materiality、验收来源、public safety 各自的 owner 都没被复制;上一轮指出的真实误读(outcome_gap 被当成可达里程碑)在本 head 上已经有可复现的探针守住。剩下的三条都是局部可修的小问题——最值得顺手改的是 :361 那处 attention item 归一化,它会让"我观测到了什么"在两条路径上不一致(P2)——但都不改变这个投影该不该合并的判断,也不引入新的状态或权限。不阻塞合并。

English verdict: APPROVE at 9d04037. The PR adds a read-only Goal artifact-lifecycle projection (phase, milestones with reached, guards with owner/blocked, next transitions) to the status JSON and markdown, with no new durable state and no completion authority; the materiality, acceptance-source and public-safety rules stay with their existing owners. I re-verified the head delta against the previously reviewed 692c1b8f0: reached now uses PROGRESS_DELIVERY_OUTCOMES, so outcome_gap stays visible but unreached (mutation probe: reverting line 129 makes the fixture smoke fail at assert_outcome_gap_is_material_but_not_reached), a conservative _acceptance_supports_closeout gate was added, and the producer-less _declared_milestones was deleted. Validation at this head: 77 passed (test_goal_acceptance_observation.py + test_loopx_turn_executor.py), 97 passed (CLI output budget/differential + goal portfolio), maintainability ratchet unreviewed=0, lifecycle smoke ok. Non-blocking findings: artifact_lifecycle.py:361 normalizes a missing attention item into a truthy dict, so the exported builder reports coverage=partial without attention_queue while the owner attach path reports missing_sources=['attention_queue','agent_vision']/unavailable for the same Goal (P2); _acceptance_supports_closeout can never be True at this head because its only producer hardcodes acceptance_assessed=False, leaving target_phase=closed unreachable (P3); and _TOKEN_SHAPES is a third provider-token rule alongside public_safety's SECRET_LIKE_SURFACE_PATTERN (P3).

@huangruiteng

Copy link
Copy Markdown
Collaborator

Merge-readiness qualification — 4360@9d040379a4f9 (read-only gate)

loopx pr-review --check-merge-readiness 4360@9d040379a4f9f36df451f9cfdf5f33e8d4296b06 → ready=false(generated_at 2026-09-15T14:36:37Z;只读门,无 GitHub 写入,未执行任何合并动作)。

1. 必需检查红(阻塞;可复现;由本 PR 引入)

head 上 26 个检查:22 success / 4 failure,失败项是 test-shard (2)、test-shard (3)、pytest、merge-gate。dashboard-acceptance、postgresql-authority (real server)、node-minimum/forward-compatibility、stage2c*、kernel-static-checks 均通过。

级联关系:pytest 只是 shard 的聚合(日志里是 SHARDS_RESULT: failure),merge-gate 又因 pytest must be success 失败。两个 shard 的根因是同一个:loopx/semantics/inventory_v0.json 过期(tests/architecture/test_semantic_inventory.py 报 "stale semantic inventory",tests/architecture/test_semantic_vocabulary_drift.py 报 "semantic vocabulary drift smoke failed")。

我在 merge ref 9cf255654(本 head 9d040379 + base main 53fed471)上本地复现:python scripts/generate_semantic_inventory.py --check 报过期;重新生成要求 source_files 1174 -> 1176、named_string_constants 2030 -> 2038、schema_version_names 756 -> 757。base main 53fed471 与当前 main 9719dc0d 单独跑同一检查都是 up to date,所以这份漂移来自本 PR 新增的两个模块(loopx/control_plane/goals/artifact_lifecycle.py、loopx/presentation/renderers/goal_artifact_lifecycle_markdown.py,正好对应 +2 source files)。

修法:在当前 main 上重跑 scripts/generate_semantic_inventory.py,把更新后的 loopx/semantics/inventory_v0.json 一起提交。

2. GitHub 评审决定仍是 CHANGES_REQUESTED(阻塞)

本 head 上的精确结论是 APPROVE(submitted_at 13:25:58Z,valid=true),但 review_decision=CHANGES_REQUESTED:steven-kid 在旧 head 155c20760d 上的 changes-requested 仍然在册(既未 dismiss,也未在新 head 上重新评审)。合并门不接受"新 head 已有 APPROVE"来顶掉这条旧结论——需要该 reviewer 在新 head 上重新评审,或由 owner 显式 dismiss 并写明理由。

3. merge tree 未验证

mergeStateStatus=BEHIND(head 落后当前 main),merge_state=UNKNOWN,GitHub 端合并树结果当前不可读;rebase 之前这个门不会给出可合并状态。

结论:本轮不做合并动作。 这个 head 未通过只读合并门,且不需要新的评审结论——审核层在 9d040379 上的 APPROVE 仍然有效。要做的是:① rebase 到当前 main 并重新生成 loopx/semantics/inventory_v0.json;② 让 steven-kid 在新 head 上重评,或显式 dismiss 旧结论;③ 重跑 loopx pr-review --check-merge-readiness <new-head>。head 一变即重新评审,届时我会在新 head 上重来一遍。

English: the read-only merge-readiness gate for 4360@9d040379a4f9f36df451f9cfdf5f33e8d4296b06 returns ready=false. Four required checks fail (test-shard (2), test-shard (3), pytest, merge-gate) and they collapse to one root cause: loopx/semantics/inventory_v0.json is stale because this head adds two modules without regenerating the semantic inventory. I reproduced it on the merge ref 9cf255654: regeneration wants source_files 1174 -> 1176, named_string_constants 2030 -> 2038 and schema_version_names 756 -> 757, while the base main 53fed471 and the current main 9719dc0d are both clean. GitHub's review decision is still CHANGES_REQUESTED because a changes-requested review left on an earlier head was never superseded at this head, and mergeStateStatus=BEHIND with merge_state=UNKNOWN leaves the merge tree unverified. Regenerate the inventory on current main, rebase, clear the standing change request, then re-run the gate. No merge performed, and no new review conclusion is needed for this head.

huangruiteng
huangruiteng previously approved these changes Sep 15, 2026

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动机

APPROVE。 审查 head:4c1f815c4106369b1816768a69bf02170d7f3d34,与当前 main(9719dc0d4)的合并基就是当前 main,合并为快进,无冲突。

这个 PR 给 status 增加了一份只读的 Goal artifact lifecycle 读出(阶段、证据里程碑、门禁、下一步),挂在 run_history.goals[].artifact_lifecycle 并在 Markdown 摘要里渲染,免去 operator 手工拼 Todo/run/quota。上一轮我提出的两点(语义 inventory 陈旧、当时被 CI 卡住的共享 turn-executor 诊断契约)与独立评审 steven-kid 的 P2(把 material history 当成完成证据)都已在本 head 修好,我用独立探针重新验证过,不是只看作者的自测。

改动思路

相对上一轮评审的 head(9d040379a),本 head 只有两个提交 + 一次上游 main 合并,主题收敛:

  • 工作事实改为从真实产出者读取:新增 WorkLaneObservation / observe_work_lane(放在 lane 归属模块 work_items/work_lane.py)与 session_runtime_work_observation 适配器(放在 session-runtime 模块),attach_goal_artifact_lifecycle_projections 改为读取当前 Goal 自己的 session_runtime_projection。旧代码链式查 attention/asset/source["work_lane_contract"],而 status 里没有任何生产者在 attention item 顶层写这个键,所以这条 lane 事实上从未到达读出。
  • 关闭判定要求验收结论:_acceptance_supports_closeout 只在 acceptance_assessed is True 且 coverage == "complete" 且无未观测来源时才给出 target_phase: closed;否则留在 closing,并在 precondition 中点名未观测到的来源。
  • 文档披露:docs/reference/goal-acceptance-observations.md 中英双语补上新读出、工作优先于 Todo 完成、缺少工作观察不凭空产生要求、closing 不等于验收通过。

具体改动

关键代码讲解

  1. loopx/control_plane/work_items/work_lane.py:11-35:WorkLaneObservation(frozen dataclass)+ observe_work_lane 只做字段解码(lane/must_attempt_work/obligation 或 fallback next_action),must_attempt 只对字面 True 成立,非 mapping 返回 None,不修改输入。它位于既有 lane 归属模块,不复制该模块里更窄的策略谓词 work_lane_contract_requires_current_agent_attempt(后者只覆盖 monitor 派生义务)。
  2. loopx/control_plane/runtime/session_runtime.py:271-288:session_runtime_work_observation 只在 schema_version 匹配且 goal_id 相等时返回观察,否则 None;负例有 3 个参数化用例。
  3. loopx/control_plane/goals/artifact_lifecycle.py:179-198, 216-320:阶段由 Goal status、guard kind、work.must_attempt、agent Todo open_count(必须是 int)与 milestone reached 推导;outcome_gap 作为未达成里程碑(milestone_unreached);未取得验收结论时 target_phase 留在 closing 并带 acceptance_unverified。
  4. loopx/presentation/renderers/goal_artifact_lifecycle_markdown.py:13-47:只接受本投影 schema,输出一行阶段/里程碑/门禁与一行 next;status_markdown.py 接入两行。
  5. 全部 11 文件 +1416/-7 已审查:投影 445 行、renderer 47 行、适配与解码 48 行、接线 4 行、inventory 计数 3+3、文档 17 行、smoke 500 行、测试 355 行。没有新增 CLI、配置、存储或调度面。

独立验证(exact head 4c1f815c4,worktree /private/tmp/pr4360-mr19)

  • 强制 lane 贯通:用真实 collect_status(含真实 run index、真实 session-runtime 投影、真实 Markdown)在旧 head 与新 head 跑同一 fixture(投影带 must_attempt_work: true、Todo 全完成)。旧 head:item 顶层没有 work_lane_contract,读出 closing;新 head:qualifying,next_transitions=[{target_phase: qualifying, precondition: "Verify the remaining evidence", reason_codes: [work_lane_selected]}]。这是本轮真实修复,不只是 fixture 对齐。
  • gap-only 不再读成收尾:只写一条 delivery_outcome=outcome_gap、无 agent vision、无未完成 Todo → qualifying + milestone_unreached。
  • 无验收结论不推荐终态:outcome_progress 且无 agent vision → closing,Markdown 为 next: closing (verify the declared acceptance with its existing owner; this readout could not observe agent_vision),不含 next: closed。
  • 测试与检查:pytest -q tests/control_plane/test_goal_acceptance_observation.py tests/control_plane/test_goal_artifact_work_observation.py 29 passed;tests/test_loopx_turn_executor.py 57 passed(上一轮该处 1 failed);投影 smoke ok;scripts/generate_semantic_inventory.py --check 在 head 与 head 并入当前 main 后均 up to date;gh pr checks 4360 全部通过(test-shard 1-4、pytest、merge-gate、kernel-static-checks、stage2c e2e/installed/mutants、postgresql-authority、dashboard-acceptance、windows-powershell)。本地跑整个 tests/control_plane 得到 158 passed / 1 failed,该失败在干净 main 9719dc0d4 上完全同样复现(本机 Node v25.5.0 环境问题),不归因于本 PR。

对主干的风险

[P3,结构性缺陷] closed 终态在当前代码里不可达。 _acceptance_supports_closeout(artifact_lifecycle.py:179-198)要求 acceptance_assessed is True 且 coverage == "complete",而唯一的产出者 acceptance_observation.py:211-212 把 acceptance_assessed 固定为 False、coverage 只会是 partial/unavailable,仓库里没有任何测试构造过 True/complete(两个 smoke 只断言 != PHASE_CLOSED)。结果是 target_phase: closed 与 no_open_agent_work 这条分支永远不会执行。这不影响正确性(fail-closed),但等于把"看起来能收尾、实际结构上不能收尾"的能力留在代码里,且未来某个产出者一旦开始写这两个字段就会静默改变行为而没有任何测试绑定。最小修复:删掉 closed 迁移与 _acceptance_supports_closeout(并在文档说明该读出没有终态建议),或给 acceptance owner 增加带产出侧测试的 typed 验收结论。

[P3] "未验证的收尾"只存在于散文里。 precondition 字符串是唯一区分"closing 因为验收未验证"与"closing 因为验收已验证"的地方(artifact_lifecycle.py:300-320),投影不暴露 coverage/missing_sources/acceptance_assessed。机器消费者只能做英文子串匹配,正是本仓 review 契约要求避免的 prose-only 分类。最小修复:随 next_transitions 发布一个紧凑的 typed 字段(例如验收状态或可达的最高阶段),或用 reason code 区分两种 closing。

[P3] 必需工作信号只在 session-runtime adapter 下交付。 工作事实的唯一产出者是 session_runtime_work_observation(session_runtime.py:271-288);其它 adapter 的 Goal 拿不到工作观察,仍可能读出 closing,而 quota/lane owner 认为仍有必须执行的工作。这不是回归(旧查找本来就没产出者),文档也已披露"缺少工作观察不凭空产生要求",但目前这只是产品边界,payload 上不可见。最小修复:在文档页写明哪些 adapter 类型能交付工作观察,或由 status owner 统一投影该事实。

主机侧合并门禁(非代码缺陷,但影响能否落地): mergeStateStatus=BLOCKED、reviewDecision=CHANGES_REQUESTED,来自独立评审在旧 head 155c20760d 上仍未撤回的 review。本结论不替代该评审:需要其在新 head 上重审或撤回,PR 才可能进入合并流程。我不会执行合并。

**残余风险:**本读出是只读、追加式投影,所有写入面(quota/todo/settlement/scheduler)未受影响,输入未被修改(新测试逐字节比对)。最强的缺失观测是"真实多 agent Goal 经打包 Dashboard 渲染"这一条我只在本地 renderer 上验证过,打包面由 CI 的 dashboard-acceptance job 覆盖。

我的整体评价

这是本轮两轮 review 之后收得比较干净的一个 head:上一轮的两个阻塞点(material history 被当作完成证据、共享 turn-executor 诊断契约红灯)都在真实路径上修好了,而且我复核的是"旧 head 真的会漏掉强制 lane"这一前后对照,而不是作者的自测结论;语义 inventory 同步、必需 CI 全绿、与当前 main 快进合并。剩下的三条 P3 都是"能力/合同精度"问题而非行为错误(不可达的终态分支、散文承载的合同、adapter 覆盖面),不构成阻塞。建议按最小修复至少处理第 1 条,因为它同时是死代码和未来行为漂移的风险点。批准,不合并。

English verdict: APPROVE at 4c1f815. Both previous blockers are verified closed on the real collection path: an independent probe shows the old head 9d04037 reads closing for a Goal with a mandatory session-runtime lane while the new head reads qualifying with the lane's own next action, and gap-only material history no longer reads as closing (outcome_gap stays an unreached milestone), while an unassessed acceptance keeps the reader inside closing instead of recommending closed. 29 lifecycle tests pass, the previously failing turn-executor module is green (57 passed), the projection smoke passes, the semantic inventory is up to date at the head and at the merge ref, and every required check is green at 4c1f815. Three non-blocking P3 findings remain: the closed transition is unreachable with any producer (acceptance_assessed is hard-coded False), the unverified-closeout caveat is prose-only, and the required-work signal is delivered only for session-runtime-adapter Goals. Merge additionally stays BLOCKED by the standing CHANGES_REQUESTED from an independent review at the older head 155c207. No merge performed.

Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
@songoow

songoow commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

已处理本轮三项 P3 — c9efe804d4f2909a83a4926e8ef5faf2d5c343c2

对应 review 5212454576。代码修复在 e67775a3f,文档在 33f30d2c3;最终 head 同步 main 2e310d768 并重新生成 inventory,未修改语义预算。

  1. 不可达终态建议:已删除。 移除 _acceptance_supports_closeout 和 target_phase: closed 建议分支。v0 明确不提供终态建议;已有终态 Goal 仍显示 lifecycle_phase: closed、next_transitions: []。新增产出者反事实测试:注入当前 owner 不会产出的 True/complete/无缺失来源,旧实现会突然建议 closed 而失败,新实现保持 closing 核验步骤。终态展示另有正例,避免删除正常读出。
  2. 机器可读的未验收语义:复核已有 reason code,补充合同说明与回归保护。 上一 head 已在 closing 核验步骤提供 reason_codes: [no_open_agent_work, acceptance_unverified];这点并非只有散文。保留该 wire shape,明确 acceptance_unverified 表示本读出未验证验收,precondition 仅作解释。缺少来源、全部有界来源已观察、以及产出者反事实均校验该 code;删除原来不可达的“已验收 closing”分支后,不再暗示第二种可达的验收结论。未新增重复字段或新的验收权威。
  3. 工作观察覆盖:文档明确,真实路径矩阵锁定。 中英文均列出来源覆盖表。判定基于实际提供的 session-runtime projection(schema、Goal id、work facts),而不是 adapter.kind 名称;其它 adapter 也可提供同一投影。没有该投影的 adapter 不会触发额外 quota/lane 查询,closing 不证明所有通道工作已完成。持久化 fixture 覆盖两种 adapter 名称 × 投影有/无 × 展示 limit 0/5,验证 JSON/Markdown 与源文件只读性。

最终 head 验证: lifecycle/work-observation 37 passed;semantic inventory/vocabulary drift 20 passed;lifecycle smoke、CI 范围 Ruff、kernel mypy(22 files)、docs governance、候选公开边界扫描及 diff 检查通过。反事实测试在修复前确实因 closed != closing 失败,修复后通过。生产代码净减少 39 行。

已正常推送并更新 PR 正文;远端 CI/评审需要绑定新 head,未执行合并。该 review 没有 inline threads,因此以此回帖逐项记录处理结果。

English verdict: addressed the three P3 items at c9efe804d4f2909a83a4926e8ef5faf2d5c343c2. Removed unreachable terminal advice while preserving already-terminal status display; documented and regression-tested the existing acceptance_unverified reason code; specified projection-based work-observation coverage with an eight-case persisted-status matrix. A future-producer counterfactual fails before the change and passes after it. Final-head validation: 37 lifecycle/work-observation plus 20 semantic tests, smoke, Ruff, mypy and docs checks pass. No budget increase, new acceptance authority, or merge.

huangruiteng
huangruiteng previously approved these changes Sep 15, 2026

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动机

APPROVE。 复审 head:c9efe804d4f2909a83a4926e8ef5faf2d5c343c2(分支已并入当时 origin/main 2e310d768);我上一轮在 4c1f815c4 的复审(review 5212454576)提出 3 条 P3,本 head 全部关闭。

问题依旧是真的:loopx status 以前没有任何"这个 Goal 走到哪一步"的派生读出,operator 只能手工拼 Todo/run/quota,而早期的 head 会在验收没有任何结论的情况下给出终态建议——方向正确但推论越界。本 head 不再补产出方,而是删掉那条够不着的终态分支,并把"未验证验收"降为机器可读字段加按来源的覆盖说明。这正是我上一轮建议的最小修法。

改动思路

  • 删除不可达分支:_acceptance_supports_closeout 与 _next_transitions 里返回 target_phase: closed 的分支一并删除(loopx/control_plane/goals/artifact_lifecycle.py)。现在唯一的 lifecycle_phase: closed 来自 Goal 自身已记录的终态(_is_closed),而不是本投影的推断。
  • 把提示变成机器可读:closing 的下一步恒为 target_phase: closing,reason_codes 固定带 no_open_agent_work 与 acceptance_unverified;英文 precondition 只做解释,消费者读 code。
  • 按来源写覆盖边界:docs/reference/goal-acceptance-observations.md 用表格说明哪些 status 来源能提供工作观察、哪些不能,中英同步。

具体改动

关键代码讲解

  1. loopx/control_plane/goals/artifact_lifecycle.py:179 _unobserved_acceptance_sources:只把"本读出没读到的验收来源"列进 precondition,不再据此推导完成。
  2. loopx/control_plane/goals/artifact_lifecycle.py:196-222 _lifecycle_phase:outcome_gap 仍是未达成的里程碑(reached=False),因此带着 gap 的 Goal 停在 qualifying,不会被读成 closing。
  3. loopx/control_plane/goals/artifact_lifecycle.py:272-292 closing 分支:注释明确写出"v0 读出永不建议终态迁移,未来要加必须另外改契约并验证产出侧",把这条约束钉在代码里而不是只写在文档里。
  4. docs/reference/goal-acceptance-observations.md:38-56(中文 88-104):never-terminal 规则、acceptance_unverified 的读法、按来源的工作观察覆盖表;并说明缺少工作来源时 closing 或缺少 work_lane_selected 都不证明工作完成。
  5. 合计 11 文件 +1460/-7(相对当前 main):投影 414 行、renderer 47 行、adapter/decoder 48 行、两份测试 401 行、fixture smoke 498 行、文档与 inventory 计数。

独立验证(exact head c9efe804d,worktree /private/tmp/pr4360-r20)

  • pytest -q tests/control_plane/test_goal_acceptance_observation.py tests/control_plane/test_goal_artifact_work_observation.py → 37 passed(我本地在 exact head 重跑)。
  • 我自己写探针直接调用 build_goal_artifact_lifecycle_projection + append_goal_artifact_lifecycle_markdown,只喂一条 run 和零 open Todo:
    • delivery_outcome=outcome_gap → qualifying,milestones=[(outcome_gap, reached=False)],next_transitions=[(qualifying, [milestone_unreached])],markdown next: qualifying (...);
    • delivery_outcome=outcome_progress(无 agent vision)→ closing,next_transitions=[(closing, [no_open_agent_work, acceptance_unverified])],precondition 追加 this readout could not observe agent_vision,markdown next: closing (...)。
    • 两条路径都没有 target_phase: closed。这同时覆盖了 steven-kid 在旧 head 的 P2(gap 被读成 closing 并建议 closed)。
  • scripts/generate_semantic_inventory.py --check → up to date;投影 smoke 通过;分支已并入当前 main,合并无冲突。

对主干的风险

未发现合并阻塞项。 本轮我只剩残余观察,不构成 REQUEST_CHANGES:

  • [P3,观察] examples/control_plane/goal-artifact-lifecycle-projection-smoke.py 498 行,正好压在仓库约 500 行的 smoke 阈值下。它目前确实在断言 shipped 行为(真实 collection + markdown),暂不需要拆分;下次再往里加断言时,应先按仓库惯例把可复用逻辑移进产品模块并收窄断言面。
  • [P3,观察] 契约对消费者的要求从"读英文 precondition"变成"读 reason_codes"。这是本 head 有意为之的改进,但旧消费者若把 next_transitions 当终态信号,需要改读 lifecycle_phase 与 reason code。文档已写清楚,属于必要的行为变更披露。
  • 合并门禁现状(非本 head 的缺陷):GitHub 仍显示 review_decision=CHANGES_REQUESTED,来源是 steven-kid 在更早 head 155c20760 的 review 5205028401;该 review 的两条 P2(gap 被读成 closing 并建议 closed、test_loopx_turn_executor 的 opaque-ref 诊断优先级)在当前 head 上第一条已由删除终态分支解决,第二条在 CI 里已不再复现。这条 stale review 需要其作者复审或由维护者 dismiss 才能解锁合并门,我不会代为处理,也不会执行合并。

我的整体评价

这一版把上一轮的问题修在了正确的位置:不是给"未验证验收"补一个产出方,而是删掉那条够不着的终态分支,并把结论降级成机器可读的 acceptance_unverified 加一份按来源的覆盖表。代码净减、约束写进注释、中英文档与实现一致,我的独立探针与 37 个测试在 exact head 上全部与文档描述相符。本 PR 已并入当前 main,合并无冲突。我同意合入这个 head;但请注意 GitHub 的合并门仍被一条指向旧 head 的 CHANGES_REQUESTED 挡住,需要其作者复审或 dismiss。

English verdict: APPROVE at c9efe80. All three P3 findings from my previous review at 4c1f815 are closed at this head: _acceptance_supports_closeout and the unreachable target_phase: closed branch are deleted (the only lifecycle_phase: closed now comes from the Goal's own recorded terminal status), the acceptance caveat is machine-readable via reason_codes: [no_open_agent_work, acceptance_unverified], and the work-observation coverage is documented by source in English and Chinese. Independently reproduced at the exact head: 37 tests pass, generate_semantic_inventory.py --check reports up to date, the projection smoke passes, and my own probe over build_goal_artifact_lifecycle_projection plus the markdown renderer shows outcome_gap -> qualifying/milestone_unreached, outcome_progress (no agent vision) -> closing with acceptance_unverified, and no path emitting target_phase: closed. That also resolves steven-kid's old-head P2 about a gap being read as closing. Residual risk is only that consumers must switch from English precondition text to reason_codes, and the 498-line projection smoke sits just under the repository's ~500-line threshold - worth watching, not blocking. The GitHub merge gate is still blocked by a stale CHANGES_REQUESTED from steven-kid at commit 155c207, which its author or a maintainer must re-review or dismiss. No merge performed.

@huangruiteng

Copy link
Copy Markdown
Collaborator

合并门禁复核(head c9efe804d,非阻塞结论)

loopx pr-review --check-merge-readiness 4360@c9efe804d4f2909a83a4926e8ef5faf2d5c343c2 结果 ready: false,两条阻塞原因,都与上面 review 5212852952 的内容无关:

  1. github_review_decision_not_approved —— GitHub 上仍是 CHANGES_REQUESTED,来源是 steven-kid 在更早 head 155c20760 的 review 5205028401。该 review 的两条 P2(outcome_gap 被读成 closing 并建议 closed、test_loopx_turn_executor 的 opaque-ref 诊断优先级)在当前 head 上第一条已由删除终态分支解决,第二条 CI 已不再复现。需要其作者复审或由维护者 dismiss。
  2. merge_state_requires_update —— 我审的 c9efe804d 当时包含了当天的 main;此后 main 前进到 f22879d50,gh pr view 4360 现在报 mergeStateStatus: DIRTY / mergeable: CONFLICTING,需要先更新分支并与 main 解决冲突,之后会是一个新的 exact head,需要重新出结论。

复核时 review_threads.unresolved_count = 0,author_owned_commented_approval = false,不需要 admin bypass。我不会执行合并。

Signed-off-by: song <liusongstep@gmail.com>
@songoow

songoow commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator Author

Maintenance update at d2287a2e32009a3958c3d8327bf1329c4cfe3e59.

Merged main edcddc7aa and regenerated loopx/semantics/inventory_v0.json from the complete resulting source tree. The only conflict was the generated inventory; no lifecycle rule or semantic budget changed. Validation: 57 lifecycle/work-observation/inventory/drift tests passed; lifecycle smoke passed. A merge-tree check against the newer main 1d8a72522 is also conflict-free.

GitHub now reports MERGEABLE. CI and reviews must qualify this exact head; earlier approval is not being reused. No merge or force push was performed.

Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
(cherry picked from commit db58562)
Signed-off-by: song <liusongstep@gmail.com>
Signed-off-by: song <liusongstep@gmail.com>
…-lifecycle-projection

Signed-off-by: song <liusongstep@gmail.com>

# Conflicts:
#	loopx/semantics/inventory_v0.json
@songoow
songoow force-pushed the codex/goal-artifact-lifecycle-projection branch from a805b2b to e2a44d4 Compare September 16, 2026 04:27
@huangruiteng

Copy link
Copy Markdown
Collaborator

是每个 PR 目前都需要 agent 去改“‎loopx/semantics/inventory_v0.json“ 么? 如果是这样的话,感觉对开发者不太友好,看能不能,动态的内容不显式存在仓库里

@songoow

songoow commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator Author

已按这个方向调整,改动放在 #4494(Q9 提交:e07ae5056、5dd0493d6),尚未合并。

此前不是每个 PR 都必须修改清单,但新增枚举/常量集合、或同步主干,都可能触发快照过期。这确实给没有语义问题的改动增加了维护成本。现在的实现是:

  1. 删除已提交的 loopx/semantics/inventory_v0.json;旧报告路径加入精确的忽略规则,避免误加回仓库。
  2. 每次语义检查直接扫描完整的、已跟踪的 loopx/ 源码树,在内存中计算清单,并复用它检查 owner、scope、分叉及预算。不是只扫描 PR diff。
  3. 人工维护的词表、owner、生产者声明、覆盖下限和预算锚点继续入库,原有语义约束不放宽。生成的 TS 绑定和术语表仍保留其新鲜度检查。
  4. 生成器默认只向 stdout 输出 JSON;需要诊断报告时才显式使用 --output .local/semantic-inventory.json。--output <文件> --check 只校验该报告,报告不会成为 CI 的输入。
  5. 中英文 RFC 的 Q9、测试指南及 PR review 修复指引已同步更新,不再要求开发者为了普通载体变化提交动态清单。

验证中特别覆盖了“新增文件与未修改文件产生分叉”:报告缺失、过期或损坏都不能绕过检查。186 项定向测试及 15 项 premerge 检查通过,提交前独立复审通过并修复了措辞建议。新提交的项目原生 review 与远端 CI 单独核验,不把本地通过视为已经合并就绪。

当前也在同步新推进的 main;结构快照的修改/删除冲突按 Q9 保持删除,其他主干改动保留,整合版本会重新验证。

…-lifecycle-projection

Signed-off-by: song <liusongstep@gmail.com>

# Conflicts:
#	loopx/semantics/inventory_v0.json
@songoow

songoow commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator Author

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

审查 head:499c85ab913423d92a558b11a6d63bb33cf6bc1c(base main)。审查按能力的 decision_procedure 执行:挑战设计、反驳声明、检查实现、核对结论。

动机

RFC #4128 要求 operator 能从一个 Goal 直接读出所处的生命周期阶段、已达成的里程碑、阻塞的 guard 及其 owner、以及下一步动作,而不必从 Todo 计数、quota 状态和最近一次 run 分类里手工拼装。这三件事在改动前没有任何 owner:既不在既有 acceptance observation 的返回里,也不是某个已存字段。改动把它们作为派生投影引入,而不是新增一份需要人工同步的持久状态。

受影响的调用方是读 status 的 operator 和 status markdown 的读者;没有新的执行路径、没有新的权限面。改动前 operator 只能看到碎片,改动后同一份已采集的 payload 会附带一份有界读模型。

改动思路

核心设计是只读投影:build_goal_artifact_lifecycle_projection 是纯函数,输入全部是调用方已经采集好的 payload,自身不读文件、不写状态、不新建权威。

四类事实各有其既有 owner,本改动只做读取与命名转换:

  • 里程碑 取自 canonical delivery outcome,而不是 run history 的自由文本。这里的关键判断是 MATERIAL_DELIVERY_OUTCOMES 与 PROGRESS_DELIVERY_OUTCOMES 的区别:outcome_gap 属于前者而不属于后者,因此它作为"记录过一次 gap"的不可达里程碑保持可见,但不能满足收尾条件。
  • guard 与 gap 复用 build_goal_acceptance_observation 这个既有 owner,不另建第二套判定。投影只补充读取该 observation 自己报告的 missing_sources,把这些未读来源写到核验步骤上,而不是替它下一个验收结论。
  • 工作线索 复用既有 lane owner:新增的 WorkLaneObservation 与 observe_work_lane 把旧字段解码留在 lane owner 内部,消费方只拿到 lane / must_attempt / next_action 三个事实,不复制协议形状。
  • 下一步动作 复用既有 frontier/lane 派生,明确不建第二套状态机。

与"什么都不做"相比:不做的代价是 operator 继续手工拼装,且 outcome_gap 与已达成的进展在读数上无法区分。与"在既有 acceptance observation 内扩展"相比:该 observation 的契约是"报告读到什么、没读到什么",不产出阶段或下一步建议;把阶段判定塞进去会让它同时承担两个问题。当前放置(新的纯投影 + 复用既有 owner)符合"派生状态优先于人工同步""复用既有 owner 优先于第二权威"。

具体改动

11 个文件、+1460/−7。生产代码 516 行(含 artifact_lifecycle.py 414 行,其中约 340 行为可执行代码),测试与 smoke 896 行,文档 48 行。

loopx/control_plane/goals/artifact_lifecycle.py(新增 414 行) — 本次改动的全部推导逻辑。

  • _evidence_milestones:只接受 canonical material outcome,按 id 去重,用 PROGRESS_DELIVERY_OUTCOMES 决定 reached。outcome_gap 因此保持 reached=False 并携带 source="evidence"。docstring 明确说明刻意不读 goal.acceptance.milestones / goal.milestones,因为没有任何 authoring 或 collection 路径会写入它们——读它们等于承诺一个用户声明无法到达的读数。
  • _lifecycle_phase:五个派生标签(starting / qualifying / waiting_owner / closing / closed),是读数标签而非存储枚举。注意 open_count 缺失时不被当作"无剩余工作",而是回退到 qualifying。
  • _next_transitions:优先级为 owner decision → 工作线索 → 未达里程碑 → 核验步骤。终止建议被硬性封顶在 closing,附 reason_codes: ["no_open_agent_work", "acceptance_unverified"],并在注释里写明 v0 永不建议终态转换。
  • _compact_text / _TOKEN_SHAPES:在任何公开标签进入投影前做完整校验,先 validate_public_safe_value,再叠加既有的 private-text 与 provider-token 检查,任一步失败即丢弃该值(fail-closed,不回落)。
  • attach_goal_artifact_lifecycle_projections:只为能从输入派生出结果的 Goal 附加投影,派生不出时不写占位符。同时从 semantic_history 补入已保留的 material/vision run,并过滤 foreign Goal(goal_id 必须匹配)。

loopx/control_plane/work_items/work_lane.py(+26) — 新增 frozen dataclass WorkLaneObservation 与 observe_work_lane。这是本 PR 处理旧协议依赖的关键手法:把 work_lane_contract 的解码留在它的 owner 内,新消费方不直接触碰旧字段。既有契约函数一行未删。

loopx/control_plane/runtime/session_runtime.py(+22) — 新增 session_runtime_work_observation,从本 Goal 已有的 session-runtime 投影读出工作事实。它校验 schema_version 与 goal_id 双重匹配,不匹配即返回 None,避免读到别的 Goal 的投影。

loopx/control_plane/status/collection.py(+2)/ loopx/presentation/renderers/status_markdown.py(+2) — 两处单行接线,把投影挂到 status 收集与 markdown 渲染上。goal_artifact_lifecycle_markdown.py(+47) 只接受自身 schema 并输出阶段、里程碑计数、guard 与下一步。

smoke(+498)与两份测试(+401) — 正反路径覆盖,含真实 collect_status 链路。

对主干的风险

未发现阻断项。 以下是我实际反驳过的点及其结果。

只读性成立。 对模块 grep open(|.write(|mkdir|rmtree|subprocess|os.remove|shutil 与 requests|urllib|http|Popen 均零命中;模块级无可变状态。投影不写任何状态、不新建权威。

不存在第二权威。 delivery_outcome.py 里 MATERIAL_DELIVERY_OUTCOMES / PROGRESS_DELIVERY_OUTCOMES 各只有一处定义,本改动只 import 消费。guard 与 gap 仍由既有 acceptance-observation owner 判定。scope_declarations / work_lane_contract 由既有注册表与 lane owner 持有。无新增持久化字段。

终止建议不可达(关键反证)。 我按"当作者的测试通过时仍可能为假"的思路构造反例:直接向投影喂入自称验收完成的 observation(acceptance_assessed=True, coverage=complete, status=accepted),以及空 missing_sources 的完成态。两种情况都停在:

phase=closing  targets=['closing']  reason=[['no_open_agent_work','acceptance_unverified']]

即即使未来的验收产出者开始返回"已完成",v0 也不会开始建议 closed。已关闭 Goal 仍正常显示 phase=closed、transitions=[]。这条规则由 4 处测试断言锁住(test_goal_artifact_work_observation.py:88,167、test_goal_acceptance_observation.py:469,516)。

语义预算未放宽。 work_lane_contract 的 Python 模块数为 32/32,正好等于预算上限。

一条继承自主干的必需检查失败(非本 PR 引入)。 tests/test_loopx_turn_journal_inspection.py::test_inspect_journal_cli_branches_before_live_or_write_paths 在 head 上失败,因为我把它归因于主干:同一测试在干净 upstream/main 上以相同错误失败——loopx.cli_commands.turn 缺少 build_lark_operator_inbox_urgency_projector,该符号由 d1aa65942(#4496)移除。loopx/cli_commands/turn.py 在本 PR 的改动集之外。这使主干 CI 在 079643038 上为红,但它不是本改动的证据,也不改变本 head 的结论。

静态检查无法用于比对。 本地 python -m mypy 在干净 main 与本 head 上同样报 1051 个错误(未归一化时两侧各有 21 条位置漂移项,归一化后 417 种完全相同),说明这是本地环境与 mypy 解析的差异,不构成本 PR 的证据。本 head 的静态结论以 CI 的 kernel-static-checks 为准。

残余证据边界。 本轮的评测包给出 wait_for_ci 语义下我未取远程 CI 结论;上述继承失败是我为归因而单独查证的,不属于本 head 的证据。smoke 的 498 行我只验证了它可运行并输出 ok,未逐行审阅其断言强度。

我的整体评价

结论:APPROVE。 证据如下:

证据项 结果
架构 + 生命周期测试 73 通过
smoke goal-artifact-lifecycle-projection-smoke ok
只读性(写/网络/可变状态) 零命中
第二权威 无,delivery outcome 集合单一定义
终止建议可达性反证 不可达,测试锁住
work_lane_contract 预算 32/32,未放宽
领域中立性 artifact_lifecycle.py 与 renderer 均无平台耦合
变更比例 生产 516 / 测试+smoke 896 / 文档 48

方向与仓库既有架构一致:派生状态而非人工同步、复用既有 owner 而非第二权威、把旧协议解码留在其 owner 内。上一轮评审提出的两点(outcome_gap 被读成可关闭证据、builder 丢弃 coverage/missing_sources)在本 head 各自的提交里修复并有负向测试冻结。scope_fit 上,本 PR 交付的是 RFC #4128 的最小切片,未越界引入 dashboard 或完成引擎。

非阻断建议(不改变结论):

  1. work_lane_contract 已顶到 32/32。本改动把解码移到 owner,方向正确且未超预算,但这条路径从此零余量——后续任何在 .py 模块里新增对该字段的引用都会直接变红。建议在退休计划里显式记录这一点。
  2. _next_transitions 中未达里程碑的措辞是 "reach the unreached acceptance milestones with evidence",而 _evidence_milestones 的 docstring 明确这些是 evidence milestone、且刻意不读用户声明的 acceptance milestone。措辞把两者混在一起,读者容易误以为存在一条用户声明验收路径。建议改为 "evidence milestones" 以对齐模块自身的用词。

English verdict

English verdict: APPROVE — exact head 499c85ab913423d92a558b11a6d63bb33cf6bc1c of #4360. The read-only claim holds (zero writes, no network, no module-level mutable state), there is no second authority (the canonical MATERIAL_/PROGRESS_DELIVERY_OUTCOMES sets keep their single definition in delivery_outcome.py; guards and gaps still come from the existing acceptance-observation owner), and the terminal-advice boundary is enforced rather than merely documented: feeding the projection an observation that claims completed acceptance still yields target_phase=closing with acceptance_unverified, never closed, and that rule is frozen by four assertions. work_lane_contract sits at 32/32, so no semantic budget was raised. Validation: 73 architecture and lifecycle tests pass, the 498-line smoke prints ok, and the module is platform-neutral. One required check fails on this head, but it is inherited from main and not this PR's evidence: test_inspect_journal_cli_branches_before_live_or_write_paths fails identically on clean upstream/main because loopx.cli_commands.turn lost build_lark_operator_inbox_urgency_projector in d1aa65942 (#4496), a file outside this PR's change set. Local mypy reports the same 1051 errors on clean main and on this head, so it carries no signal here; the static verdict rests on CI's kernel-static-checks. Two non-blocking suggestions: record that the work_lane_contract budget now has zero headroom, and rename the unreached-milestone precondition to "evidence milestones" so it stops implying a user-declared acceptance path.

…-lifecycle-projection

Signed-off-by: song <liusongstep@gmail.com>

# Conflicts:
#	loopx/semantics/inventory_v0.json
@songoow
songoow force-pushed the codex/goal-artifact-lifecycle-projection branch from aef5b02 to aee2796 Compare September 16, 2026 10:51
@songoow

songoow commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed aee2796d7: merged upstream/main@4aaad69bd (the only conflict was loopx/semantics/inventory_v0.json, which main deleted under Q9 — took the deletion) and signed off the merge commit, which is what the failing Sign-off check was reporting.

English — the standing CHANGES_REQUESTED is steven-kid's review on 155c20760, two heads ago. The two later reviews on this PR (yours, on 4c1f815c4 and c9efe804d) both concluded APPROVE and both were invalidated only for head binding and the standalone bilingual format — not for a substantive finding. Please re-review at aee2796d7 so the merge gate reflects the current head.

中文 — 当前 CHANGES_REQUESTED 是 steven-kid 在 155c20760(已隔两个 head)留的。 其后两次 review(你在 4c1f815c4 与 c9efe804d)结论均为 APPROVE,被判无效的原因只是未绑定当前 head 与缺少独立双语格式,并非实质发现。请在 aee2796d7 上重审,让合并门禁反映当前 head。

Local evidence at aee2796d7: tests/control_plane/test_goal_acceptance_observation.py + test_goal_artifact_work_observation.py → 37 passed.

On the inventory_v0.json developer-experience question you raised: main now computes the inventory per run and no longer commits it, so PR authors no longer need to regenerate that file. That resolves it.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

动机

在此之前,status 里没有任何一处"这个 Goal 的交付物生命周期走到哪了"的读模型:接受证据(acceptance observation)和 work-lane 契约各自暴露,读者要自己把 phase、里程碑、阻塞守卫和下一步拼出来。后果是把"Todo 全完成 + 历史有进度"误读成"可以收尾"——即便接受证据尚未达到、或 owner gate 还开着。本 PR 增加一个只读派生投影来消除这种拼接。

改动思路

新增 loopx/control_plane/goals/artifact_lifecycle.py,把已有 payload 组合成一个派生读模型:phase(starting/qualifying/waiting_owner/closing/closed)、evidence milestones、guards(owner_decision / evidence_precondition)与 next_transitions。关键的架构选择是"不新建规则引擎":接受规则仍由 build_goal_acceptance_observation 拥有,work 事实的解码放在 lane 自己的 owner(work_lane.observe_work_lane),next_transitions 复用既有 lane/frontier 推导。消费侧只在 status collection 挂载、并在 Markdown 里多打一行。

具体改动

  • loopx/control_plane/goals/artifact_lifecycle.py(新增 414 行):build_goal_artifact_lifecycle_projection(:294)与 attach_goal_artifact_lifecycle_projections(:358),以及 phase/guard 常量;_lifecycle_phase(:196)决定阶段,_next_transitions(:225)复用既有推导。
  • loopx/control_plane/work_items/work_lane.py(+26):新增 WorkLaneObservation 与 observe_work_lane(:14),把 legacy 字段解码留在 lane owner 内,并写明"只读、非调度决定"。
  • loopx/control_plane/runtime/session_runtime.py(+22):session_runtime_work_observation(:271)从 session-runtime 投影里读 work 事实,schema 或 goal_id 不匹配就返回 None。
  • loopx/control_plane/status/collection.py(+2)、loopx/presentation/renderers/status_markdown.py(+2)、新渲染器 goal_artifact_lifecycle_markdown.py(+47):挂载与展示。
  • examples/control_plane/goal-artifact-lifecycle-projection-smoke.py(+498)、两个测试模块(+233/+168)、docs/reference/goal-acceptance-observations.md(+49)。

关键代码讲解

  1. loopx/control_plane/goals/artifact_lifecycle.py:294 — build_goal_artifact_lifecycle_projection:docstring 直接写明"每个输入都是调用方已经收集好的 payload,本函数自己什么都不读、也不授予任何权限",并在 _evidence_milestones 里刻意不读 goal 自带的里程碑声明,理由写得明白——没有任何生产路径会写入 goal.acceptance.milestones,读它等于承诺一个用户声明永远到不了的读数,同时却握着唯一能挡住收尾的守卫。这是我这次最认可的一处克制度:宁可少一个特性,也不留一个永不可达的守卫。
  2. loopx/control_plane/goals/artifact_lifecycle.py:196 — _lifecycle_phase:终态 goal status 优先 → owner_decision 守卫 → 任意守卫或 must_attempt work → qualifying;特别值得肯定的是 open_count 缺失时不当作 0,注释写明"被省略/被截断的来源不是没有工作的证据"。这正是把"没有证据"与"证据为零"区分开的正确写法。
  3. loopx/control_plane/work_items/work_lane.py:14 — observe_work_lane / WorkLaneObservation:把 legacy 字段解码放在 lane owner 模块内,而不是让新消费者各自解析 work_lane_contract;dataclass 的 docstring 明确"既不是调度决定,也不是执行授权"。这符合仓库"契约语义跟随 owner"的要求。
  4. loopx/presentation/renderers/goal_artifact_lifecycle_markdown.py:10 — append_goal_artifact_lifecycle_markdown:schema 不匹配即静默返回;只打印 phase、reached/total 里程碑数、守卫数,并把被阻挡的守卫与 next transition 逐行列出——展示层不重算规则,只渲染派生结果。

对主干的风险

没有阻塞项。 这是纯派生、只读的读数:不写状态、不授予权限、不推荐终态转换(文档明确 closing 是验证步骤,并给出 acceptance_unverified 这个机器可读 reason code,消费者应看它而不是英文 precondition)。文档还显式列出覆盖边界:work 观测只有在上游提供了匹配 schema/goal_id 的 session-runtime 投影时可用,缺失时读数可能显示 closing 但外部 lane 仍有工作,且 lane/完成决定权仍属各自 owner。

P3(非阻断,知识归属):loopx/control_plane/goals/artifact_lifecycle.py:54 的 _TOKEN_SHAPES 用局部正则补充了 provider token 形状(gh?_…/sk-…/AKIA…)。注释解释了它为什么是局部的(共享私密文本规则没覆盖这些前缀),但没留下后续动作,因此在共享 owner(loopx/public_safe_text.py 的 find_private_text_match)里仍然不知道这些形状——我核对过,那两个公共安全模块里确实没有这些前缀定义。仓库规则要求这类补充性启发式既有书面理由、也有类型化后续;最小修复是把这些形状并入共享语料,或在注释里点名要更新的 owner。

验证(在 aee2796d 上跑):python examples/control_plane/goal-artifact-lifecycle-projection-smoke.py → goal-artifact-lifecycle-projection-smoke ok;pytest -q tests/control_plane/test_goal_artifact_work_observation.py tests/control_plane/test_goal_acceptance_observation.py → 37 passed;git merge-tree --write-tree HEAD origin/main 干净(head 本身就是把 upstream/main 合进来的 merge commit)。按本 lane 配置不拉取 CI;我没有跑真实浏览器的 dashboard acceptance smoke,这一层属残余证据缺口。

我的整体评价

APPROVE。这个 PR 的价值不在"多一个字段",而在把生命周期读数做成派生而非第二份权威:接受规则、lane 解码、公共安全都留在各自 owner,读模型只做组合与呈现,文档还把"不推荐终态、缺失观测不等于没有工作"这两条最容易被误读的边界写成明文。我特别认可两处细节——刻意不读没有生产者的 goal 里程碑(避免永不可达的守卫),以及 open_count 缺失时不推断为 0(避免把截断当成完成)。近 1500 行看起来不小,但构成是 414 行投影 + 401 行测试 + 498 行缺陷复现型 fixture smoke + 文档,属于可复审的完整包;唯一想提醒的是 smoke 已接近仓库 500 行的复审线,继续增长时应把 fixture 移入测试模块、让 smoke 退回薄渲染检查。剩下一条 P3 是 token 形状知识的归属,不影响正确性。

English verdict: APPROVE — exact head aee2796d74dcb537e5197d91d3ddd733f8a9f100 of #4360. The PR adds a read-only, derived Goal artifact lifecycle projection (phase, evidence milestones, guards, next transitions) that composes the existing acceptance observation and work-lane owners instead of creating a second rule engine: build_goal_artifact_lifecycle_projection reads nothing itself, _lifecycle_phase refuses to treat an omitted open_count as "no work remains", goal-declared milestones are deliberately not read because no producer writes them, and observe_work_lane keeps legacy field decoding next to the lane owner. Validation at this head: examples/control_plane/goal-artifact-lifecycle-projection-smoke.py prints ok, tests/control_plane/test_goal_artifact_work_observation.py plus test_goal_acceptance_observation.py pass 37 tests, and the branch merges cleanly with current main. No blocking findings; the docs state the readout never recommends a terminal transition and that a missing work observation does not prove completion. One non-blocking P3: the local _TOKEN_SHAPES secret-prefix regex supplements the shared public-safety corpus without recording a follow-up to move those shapes into loopx/public_safe_text.py.

@huangruiteng
huangruiteng merged commit 5d66197 into loopx-project:main Sep 16, 2026
20 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants