feat(goals): show acceptance gaps and evidence-backed progress - #4248
Conversation
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>
Signed-off-by: song <liusongstep@gmail.com>
songoow
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
#4248 自审补充:部分验收观察与真实生产来源
审查范围:6c0a43c340fe4b58e8ba477d25d7250508ddd4f0...d7a3f8da370ff56fa5d9bf760b0e980c5256d9e6。作者自审记录,不冒充独立 APPROVE。当前结论:REQUEST_CHANGES / 等待证据闭环;未发现尚未修复的具体代码阻塞,但最终提交的托管 CI 尚在运行。以下将这些证据缺口与已修复问题区分。
动机
此前 Goal 详情主要展示当前状态和用量;即使 Agent 已通过现有 vision 写入验收要求,用户仍需分别查看运行记录和 Todo,才能理解任务完成之后还缺什么证据。这个差异有实际意义:实现任务已经勾选完成、过去存在批准记录,都不能说明当前 Goal 已被完整验收。本 PR 把已有要求、对应 Agent、待处理门禁和下一步放到同一详情入口,并明确未知的人类决策责任人。此次自审重新从生产来源检查功能价值,而不是把新增卡片或合成 JSON 能显示当作交付完成。它解决的是信息缺失和误读风险,尚无使用频率的量化证据,也没有实现完整里程碑管理系统。
改动思路
方案复用现有 status 已读取的 Goal 历史和 Todo 投影,在展示截断前派生一个只读观察对象,再由 Dashboard 与 Markdown 消费。验收分类继续归属于现有 acceptance_gaps_from_agent_vision,门禁识别继续使用 Todo owner;不增加每个 Goal 的 quota 查询,不复制完成或调度规则。有限历史只能支持部分观察,因此协议固定 acceptance_assessed=false,缺口为空时也不宣布验收成功。
自审发现旧实现额外接收 frontier 的分支只有手填测试覆盖:普通收集尚未附加 frontier,相关 CLI 则在此次派生之后才附加它。因此已删除重复合并与固定“frontier 缺失”提示,压缩约二十余行生产代码,并保留真实已有的下一步投影。完整执行前沿审计仍未完成。文档同步说明 RFC #4172 提出的材料声明和绑定版本的阅读回执未实现,不能从历史执行反推出当前方向材料已经阅读。
具体改动
关键代码讲解
loopx/control_plane/goals/artifact_lifecycle.py:63的build_goal_artifact_lifecycle按 Goal 过滤、按 Agent 选择最新 vision,再调用既有验收 helper。原始事实先参与缺口分类,输出文本随后验证和压缩;仅有不安全验收文本时保留缺口并隐藏正文,避免脱敏把“缺证据”变成“没有缺口”。关闭阶段需要后继 vision 时保留规范原因,不被旧 patch 的说明覆盖。loopx/control_plane/status/collection.py:64的collect_status在既有组装结束后调用附加器,使用展示截断前已读取的 history。真实refresh_state_run(dry_run=False, sync_global=False)现在替代测试中手写 journal,证明正常生产 writer 输出能经过真实 collector 到达详情;没有触碰活动 Goal 或增加持久化状态。apps/presentation/dashboard/src/features/personal-workspace/goal-acceptance-card.tsx:4的GoalAcceptanceCard只在选中 Goal 的详情内渲染。缺失、加载失败、Goal ID 不匹配时显示不可用;要求、Agent、门禁范围和未知审批人分开呈现,旧生命周期记录折叠为历史。组件没有审批或执行按钮,旧来源缺失可选 schema 时不会阻断整个 status。loopx/presentation/renderers/goal_acceptance_markdown.py:10的append_goal_acceptance_markdown提供 CLI 摘要,保持“部分观察,不构成完成证明”。该小型提取让现有大型 renderer 只增加调用,维护性上限未放宽。
验证覆盖 16 项 pytest、真实 producer/collector 的中英文桌面与移动详情、开发版及打包版完整工作区浏览器、TypeScript 与双构建、status Markdown/readmodel、文档治理和维护性 ratchet。冻结最终提交后 Canary 标准集 18 项全部通过;首次运行因同时格式化和替换资源触发 tracked-side-effect 检测,已由稳定提交上的完整重跑替代。仅替换本 PR 的旧 bundle,保留主干资源。
对主干的风险
最强失败场景是有限历史遗漏另一条 Agent 通道,而展示却把“没有可见缺口”解释成“已验收”。当前输出固定未评估,并保留部分/不可用状态;测试还覆盖完成任务、旧批准、关闭阶段、未知身份、其他 Goal、延期和已替代门禁,以及先脱敏后错误分类的反例。既有 user-gate helper 对缺少显式 task_class 的旧 action_kind 保留字符串回退,这可能使旧标识误归为门禁;本 PR 没有增加该规则或授予任何执行权,显式类型仍优先。
同一份真实写入 fixture 在精确主干 collector 与当前公共 collect_status 上执行:去掉唯一新增投影后既有输出相等,读取前后文件哈希相等;display limit 0 与 5 的新投影相等。故意令公共读路径返回“已评估验收”的变异被独立断言拒绝。再通过生产 writer 写入目标 Goal 的 25 条 Agent vision 和另一 Goal 的 22 条 vision,超出历史与展示上限,确认仍只展示 12 条部分观察,其他 Goal 不改变目标内容,scoped/unscoped 查询一致。新增部分只消费既有收集快照,不增加并发 writer、锁或来源读取,因此没有新的写入竞争契约;这不声称原收集器具备新的原子性,也不等于部署验收。浏览器读取真实收集的 JSON,但无关网络接口为 fixture,最终托管 CI 尚待结果。回滚只需移除派生视图并重建资源,没有数据迁移。
我的整体评价
这是一项有明确日常入口的展示补齐,范围在此次审查中缩小了:现有权威规则不变,删除无真实调用的兼容分支,测试从运输形状提升到生产 writer 的实证。它值得作为独立阶段评审,但不能把完整 frontier 审计、声明式里程碑或 #4172 的材料阅读证明计入已实现。当前没有经验证仍待修复的代码问题;保留 REQUEST_CHANGES 是因为最后提交的托管检查尚未闭环,不是声称发现了新的运行缺陷。应在这些证据补齐后更新结论,而非以本地绿色测试替代最终审核。
English verdict: REQUEST_CHANGES — evidence hold, not an outstanding confirmed code defect. The reviewed head is d7a3f8da370ff56fa5d9bf760b0e980c5256d9e6. The unreachable frontier merge was removed; real refresh_state_run production output now feeds collector and browser validation. All 16 focused tests, development and packaged full workspace/browser checks, builds, and the frozen-head 18-check canary passed. Existing status output and source files remained unchanged in a paired baseline/head replay. Public-caller replay also covered 25 target Agent visions and 22 foreign Goal visions beyond collection/display caps, scoped queries and display-limit parity. No new writer or snapshot-read contract is introduced. Hosted CI was still pending. Complete frontier auditing, authored milestone catalogs and RFC #4172 material-use receipts remain outside this PR. This is an author-owned self-review, not an independent formal approval.
Signed-off-by: song <liusongstep@gmail.com> (cherry picked from commit b908830) Signed-off-by: song <liusongstep@gmail.com>
songoow
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
#4248 自审补充:部分验收观察与真实生产来源
审查范围:6c0a43c340fe4b58e8ba477d25d7250508ddd4f0...733364c220ab305e6ad6dab13d17d8351985dead。作者自审记录,不冒充独立 APPROVE。当前结论:REQUEST_CHANGES / 等待证据闭环;未发现尚未修复的具体代码阻塞,但最终提交的托管 CI 尚在运行。以下将这些证据缺口与已修复问题区分。
动机
此前 Goal 详情主要展示当前状态和用量;即使 Agent 已通过现有 vision 写入验收要求,用户仍需分别查看运行记录和 Todo,才能理解任务完成之后还缺什么证据。这个差异有实际意义:实现任务已经勾选完成、过去存在批准记录,都不能说明当前 Goal 已被完整验收。本 PR 把已有要求、对应 Agent、待处理门禁和下一步放到同一详情入口,并明确未知的人类决策责任人。此次自审重新从生产来源检查功能价值,而不是把新增卡片或合成 JSON 能显示当作交付完成。它解决的是信息缺失和误读风险,尚无使用频率的量化证据,也没有实现完整里程碑管理系统。
改动思路
方案复用现有 status 已读取的 Goal 历史和 Todo 投影,在展示截断前派生一个只读观察对象,再由 Dashboard 与 Markdown 消费。验收分类继续归属于现有 acceptance_gaps_from_agent_vision,门禁识别继续使用 Todo owner;不增加每个 Goal 的 quota 查询,不复制完成或调度规则。有限历史只能支持部分观察,因此协议固定 acceptance_assessed=false,缺口为空时也不宣布验收成功。
自审发现旧实现额外接收 frontier 的分支只有手填测试覆盖:普通收集尚未附加 frontier,相关 CLI 则在此次派生之后才附加它。因此已删除重复合并与固定“frontier 缺失”提示,压缩约二十余行生产代码,并保留真实已有的下一步投影。完整执行前沿审计仍未完成。文档同步说明 RFC #4172 提出的材料声明和绑定版本的阅读回执未实现,不能从历史执行反推出当前方向材料已经阅读。
具体改动
关键代码讲解
loopx/control_plane/goals/artifact_lifecycle.py:63的build_goal_artifact_lifecycle按 Goal 过滤、按 Agent 选择最新 vision,再调用既有验收 helper。原始事实先参与缺口分类,输出文本随后验证和压缩;仅有不安全验收文本时保留缺口并隐藏正文,避免脱敏把“缺证据”变成“没有缺口”。关闭阶段需要后继 vision 时保留规范原因,不被旧 patch 的说明覆盖。loopx/control_plane/status/collection.py:64的collect_status在既有组装结束后调用附加器,使用展示截断前已读取的 history。真实refresh_state_run(dry_run=False, sync_global=False)现在替代测试中手写 journal,证明正常生产 writer 输出能经过真实 collector 到达详情;没有触碰活动 Goal 或增加持久化状态。apps/presentation/dashboard/src/features/personal-workspace/goal-acceptance-card.tsx:4的GoalAcceptanceCard只在选中 Goal 的详情内渲染。缺失、加载失败、Goal ID 不匹配时显示不可用;要求、Agent、门禁范围和未知审批人分开呈现,旧生命周期记录折叠为历史。组件没有审批或执行按钮,旧来源缺失可选 schema 时不会阻断整个 status。loopx/presentation/renderers/goal_acceptance_markdown.py:10的append_goal_acceptance_markdown提供 CLI 摘要,保持“部分观察,不构成完成证明”。该小型提取让现有大型 renderer 只增加调用,维护性上限未放宽。
验证覆盖 16 项 pytest、真实 producer/collector 的中英文桌面与移动详情、开发版及打包版完整工作区浏览器、TypeScript 与双构建、status Markdown/readmodel、文档治理和维护性 ratchet。此前生产提交 d7a3f8da3 的 Canary 标准集 18 项全部通过;首次运行因同时格式化和替换资源触发 tracked-side-effect 检测,已由稳定提交上的完整重跑替代。仅替换本 PR 的旧 bundle,保留主干资源。
随后托管 CI 发现共享浏览器测试的“正在整理…”定位同时匹配消息 span 和 aria-live region。当前提交只为该行增加 exact: true,没有修改生产源码或打包资源;已用完整提交差异核实。上述 pytest、浏览器及主干对照收据保留实际执行提交 d7a3f8da3,不改写成新提交的执行记录。新提交 Canary 标准集重跑已通过,18 项执行、0 项失败;组合打包版完整浏览器由并行验证补充,托管 CI 仍不宣称通过。
对主干的风险
最强失败场景是有限历史遗漏另一条 Agent 通道,而展示却把“没有可见缺口”解释成“已验收”。当前输出固定未评估,并保留部分/不可用状态;测试还覆盖完成任务、旧批准、关闭阶段、未知身份、其他 Goal、延期和已替代门禁,以及先脱敏后错误分类的反例。既有 user-gate helper 对缺少显式 task_class 的旧 action_kind 保留字符串回退,这可能使旧标识误归为门禁;本 PR 没有增加该规则或授予任何执行权,显式类型仍优先。
同一份真实写入 fixture 在精确主干 collector 与当前公共 collect_status 上执行:去掉唯一新增投影后既有输出相等,读取前后文件哈希相等;display limit 0 与 5 的新投影相等。故意令公共读路径返回“已评估验收”的变异被独立断言拒绝。再通过生产 writer 写入目标 Goal 的 25 条 Agent vision 和另一 Goal 的 22 条 vision,超出历史与展示上限,确认仍只展示 12 条部分观察,其他 Goal 不改变目标内容,scoped/unscoped 查询一致。新增部分只消费既有收集快照,不增加并发 writer、锁或来源读取,因此没有新的写入竞争契约;这不声称原收集器具备新的原子性,也不等于部署验收。浏览器读取真实收集的 JSON,但无关网络接口为 fixture,最终托管 CI 尚待结果。回滚只需移除派生视图并重建资源,没有数据迁移。
我的整体评价
这是一项有明确日常入口的展示补齐,范围在此次审查中缩小了:现有权威规则不变,删除无真实调用的兼容分支,测试从运输形状提升到生产 writer 的实证。它值得作为独立阶段评审,但不能把完整 frontier 审计、声明式里程碑或 #4172 的材料阅读证明计入已实现。当前没有经验证仍待修复的代码问题;保留 REQUEST_CHANGES 是因为最后提交的托管检查尚未闭环,不是声称发现了新的运行缺陷。应在这些证据补齐后更新结论,而非以本地绿色测试替代最终审核。
English verdict: REQUEST_CHANGES — evidence hold, not an outstanding confirmed code defect. The reviewed head is 733364c220ab305e6ad6dab13d17d8351985dead. The unreachable frontier merge was removed; real refresh_state_run production output now feeds collector and browser validation. All 16 focused tests, development and packaged full workspace/browser checks, builds, and an 18-check canary passed at production head d7a3f8da3. The new head changes only one shared-browser locator to exact matching after CI exposed ambiguity with an aria-live region; source and bundle are unchanged. Original execution receipts retain their real revision, and the new-head standard canary passed all 18 selected checks. Existing status output and source files remained unchanged in a paired baseline/head replay. Public-caller replay also covered 25 target Agent visions and 22 foreign Goal visions beyond collection/display caps, scoped queries and display-limit parity. No new writer or snapshot-read contract is introduced. Hosted CI was still pending. Complete frontier auditing, authored milestone catalogs and RFC #4172 material-use receipts remain outside this PR. This is an author-owned self-review, not an independent formal approval.
The Goal acceptance smoke added by loopx-project#4248 was only reachable by invoking `node examples/dashboard-goal-acceptance-browser-smoke.mjs` by hand, so no script or CI job could select it. Every comparable Dashboard browser check in this package registers a `smoke:*` script, and several are called from workflows. Add `smoke:goal-acceptance-browser` and `smoke:goal-acceptance-packaged`, following the existing `smoke:personal-workspace*` pair: the packaged variant sets `LOOPX_GOAL_ACCEPTANCE_PACKAGED=1`, points `LOOPX_PLAYWRIGHT_PACKAGE` at the package-local Playwright, and uses a distinct port so it can run next to the development variant. Also link the new reference doc from the reference index, which is where the other machine-facing read paths are listed; the document was previously unreferenced. Validation: - `npm run smoke:goal-acceptance-browser` -> goal acceptance browser (development): passed - `npm run smoke:goal-acceptance-packaged` -> goal acceptance browser (packaged): passed - `python3 examples/docs-governance-smoke.py` -> ok This is a follow-up to loopx-project#4248 and targets that branch; it changes no product behavior. Signed-off-by: song <liusongstep@gmail.com>
huangruiteng
left a comment
There was a problem hiding this comment.
结论:REQUEST_CHANGES。这个只读“验收观测”切片本身有价值,也通过了真实 writer/collector 和 Dashboard 验证;但它把一个明显更窄、结构也不同的 payload 命名为仓库已经定义过的 goal_artifact_lifecycle_projection_v0。这是新的公开 v0 协议,当前应在兼容成本形成前修正;此外 exact head 还与当前 main 冲突且没有托管检查结果。
动机
现有 Goal 详情会展示状态、Todo 和运行信息,却不能直接回答“哪些 Agent 验收证据仍缺、当前有哪些人类门禁、下一步是什么”。这会造成一个高代价误读:Agent Todo 全部完成或历史上出现过批准记录,并不代表当前 Goal 的验收条件已经满足。
PR 的 before/after 目标是合理的:复用已经读取的 Agent Vision、Todo 和 run history,生成一个始终标注为 partial/read-only 的观测;即使缺口为空也固定 acceptance_assessed=false,缺来源、截断和不可用状态明确展示。UI-only 临时拼接会复制控制面分类并受展示截断影响,因此在 status 收集边界派生一次、由 Markdown 和 Dashboard 共同消费,是比重复实现更好的最小机制。
改动思路
collect_status 在组装完内部 history 与 attention queue 后附加派生对象。它按 Agent 选择最新 Vision,复用 acceptance_gaps_from_agent_vision;待处理门禁复用 open_user_gate_todo_items 与 decision-scope normalization;历史进展只接受固定 lifecycle flag,并经 public-safe 过滤和 12 条上限后输出。没有新增文件读取、quota 查询、持久化状态或完成判定。
真实路径是 refresh_state 写入 Agent Vision → collect_history 读取展示截断前的有界历史 → 新 helper 派生 → status JSON/Markdown 和 Personal Workspace 消费。缺少投影、Goal ID 不匹配、加载失败或 coverage unavailable 时,Dashboard 不保留旧缺口行。这个状态模型属于 derived projection,不是第二份验收意图;部分来源的缺失不能证明 canonical state 中不存在缺口。
问题出在协议边界:仓库 RFC 已把 goal_artifact_lifecycle_projection_v0 定义为含 lifecycle_phase、带 id/label/reached/reached_evidence_refs 的 milestones、带 id/blocked 的 guards,以及 next_transitions[] 的 Goal 生命周期投影;当前实现则只有部分 acceptance gaps、历史 flag、guards 和一个 next_action,而文档同时声明未实现统一阶段、声明式 milestone 与 legal transition。这不是同一 v0 合约的“小字段缺省”。
具体改动
关键代码讲解
build_goal_artifact_lifecycle从一个 Goal 和对应 attention item 派生 per-Agent acceptance gap、开放 user gate、历史 flag、missing_sources 与 next action。acceptance_assessed固定为 false,危险文本在输出前验证,结果有 12 条上限。attach_goal_artifact_lifecycles用展示截断前的 history goal 补充每个返回 Goal,再把结果附到run_history.goals[].artifact_lifecycle;因此limit=0仍能保留有界验收观测,而不是从空展示误判没有缺口。goalArtifactLifecycleSchema把 read_only/acceptance_assessed 固定为 literal,并让父级可选字段在旧来源或畸形 payload 时退化为 null;GoalAcceptanceCard对 Goal 身份、加载状态和 coverage 再做一次失效保护。- Markdown helper 只输出数量和有界证据要求,避免继续膨胀既有 renderer;Dashboard 卡片分开显示 gap、gate、next action、历史记录、缺来源和截断提示,没有动作按钮。
完整差异为 21 个文件、增加 896 行、删除 5 行,其中包括 227 行 Python 派生模块、248 行测试、真实浏览器 fixture、双语文档和生成 bundle。机制与测试总体成比例;最高价值的压缩不是删除这个产品入口,而是把它命名为实际实现的 goal_acceptance_observation_projection_v0(或同等窄名称),不要让首个 consumer 过早冻结更广的 lifecycle 协议。
对主干的风险
[P1 / blocking] 同一个公开 schema id 对应两个不兼容结构。 触发方式很直接:任何 consumer 按 RFC 识别 goal_artifact_lifecycle_projection_v0,都会期待 lifecycle phase、可达 milestone 与 legal next transitions;当前 Python producer 和 TypeScript schema 无法表达这些字段。现有测试的 oracle 都来自新 helper 自己,所以即使全部绿色,也无法发现与独立 canonical 文档的冲突。最低修复有两个选择:把当前窄切片及字段改名为 acceptance observation v0,并同步 Python/TS/文档/测试/bundle;或者在现有 id 下实现 RFC 的实际结构。考虑 PR 已明确这些广义语义不在范围内,前者更小、更诚实。
第二个阻断是当前主干资格:git merge-tree 对 origin/main 84334f794126735a83a6cfb77882dd58ca8066d9 与 exact head 报告 examples/personal-workspace-browser-smoke.mjs 内容冲突;GitHub 对该 head 没有任何 checks。需要 rebase 到当前 main,按新的 scenario runner 结构保留 Goal acceptance 断言,重建 bundle 后运行 required checks。
我实际运行了 tests/control_plane/test_goal_artifact_lifecycle.py(9 passed)、Ruff、Dashboard typecheck/双构建、presentation schema smoke 和开发版真实浏览器 smoke,全部通过。反例覆盖 completed Todo、历史批准、空缺口、跨 Goal run、终态 gate、unsafe 文本、display limit、投影删除和 Goal 身份失配。这证明实现的“部分观测”语义可用,但不能消除协议命名与当前主干组合的两个阻断。
我的整体评价
状态来源、只读边界、partial 语义和真实调用路径都做得扎实;尤其是使用展示截断前的已有 history、固定不宣称验收完成,以及让旧来源安全降级,避免了最危险的“没看到就当不存在”。Future-facing pass 也无需扩大为完整流程引擎。
现在不应批准的原因不是功能价值不足,而是公开协议名称比实现承诺更宽,并且 exact head 尚未完成当前主干集成。先收窄 schema identity、rebase 并恢复托管验证后,这个范围可以保持不变地重新评审;不需要为了保住旧名称而把整份 RFC 都塞进当前 PR。
English verdict: REQUEST_CHANGES at exact head 61bb110. The partial read-only acceptance observation is valuable and passed 9 focused tests, Ruff, Dashboard typecheck/build, schema, and browser validation, but it reuses the documented goal_artifact_lifecycle_projection_v0 id for an incompatible narrower shape; rename the contract (preferred) or implement the canonical RFC shape, then rebase the current-main browser-smoke conflict, rebuild assets, and restore hosted checks.
Signed-off-by: song <liusongstep@gmail.com>
|
已按维护者建议收窄协议并完成当前主干集成,修复提交
这是现有 Goal 读模型的命名修正,不增加执行、完成、审批或配置权限;没有新增 Lark 消费者或设置项,因此无需 Lark/config companion。完整 frontier 审计和材料阅读回执仍明确不在范围。冻结提交的 Canary 标准集已通过,18 项、0 失败;最终托管 CI 仍待确认,不以本地测试替代托管检查。 四个更新后的 PR 已在 main |
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion
审阅绑定 exact head:e961c90126440bc0939db250f6fddd7cab34bfb4。结论:APPROVE。
动机
这个 PR 要解决的是一个真实但容易被命名放大的操作问题:Goal 的 Agent Vision、Todo gate、历史进展和下一步建议原本分散在不同状态投影里,操作者需要手工拼出“当前还缺什么验收证据”。上一版更严重的问题是把这个窄读模型命名成 goal_artifact_lifecycle_projection_v0,会让公共消费者误以为它覆盖了完整 artifact lifecycle 或验收权威;当前 head 已删除这个过宽协议,统一改成 goal_acceptance_observation_projection_v0,并明确 acceptance_assessed=false、partial/unavailable 都不能证明完成。
我用同一份 disposable registry/Goal/Agent Vision/user-gate 走了 base 84334f794126735a83a6cfb77882dd58ca8066d9 与当前 head 的真实 collect_status。除新增的 acceptance_observation 字段以及临时路径/生成时间外,既有输出保持一致;head 新字段正确给出 1 个 gap、1 个 gate 与证据引用,但没有产生状态写入或完成结论。
改动思路
实现把权威留在既有 owner:每个 Agent 的最新 Vision 仍由 goal_vision_read_model.py 选择,开放用户门禁仍由 todos/user_gate.py 判断,历史记录和 attention recommendation 仍由 status collection 提供。acceptance_observation.py 只在这些已读事实之上组装一个有界、公开安全的派生视图;它在 display-only latest_runs 裁剪之前执行,因此不会把“展示为空”误判成“没有验收缺口”。
正向路径是 collect_status → attach_goal_acceptance_observations → build_goal_acceptance_observation,随后同一结构进入 Markdown renderer、Dashboard Zod schema、Personal Workspace model/card 和 packaged Chat bundle。负向路径也闭合:缺少历史时 coverage 为 unavailable;closed lane 生成 successor requirement;done/deferred/superseded gate 不再当作当前阻塞;超过 12 个 observation 会标记 truncated;错误的旧 broad schema 在 Markdown/TS 边界被拒绝。这里没有第二套 Todo/Vision 决策、验收写 API、调度变化或权限扩张。
具体改动
完整 diff 为 22 个文件、+955/-4。主要 production 体量是 227 行 Python 派生器以及 Dashboard schema/card/i18n/wiring;其余大头是 272 行 focused tests、专用 browser smoke、双语文档和重建 bundle。这个多表面 footprint 偏大,但它对应的是同一个公共 contract 从 producer 到 JSON、Markdown、development UI 与 packaged UI 的完整闭环,不是新增框架。
关键代码讲解
acceptance_observation.py:63的build_goal_acceptance_observation按 Goal/Agent 选择已有 Vision,映射 gap、open gate、历史 progress 和 next action;所有路径都保持acceptance_assessed=false,没有用 Todo done_count 或历史 approval 推断验收完成。acceptance_observation.py:212的attach_goal_acceptance_observations使用完整的 already-read history 按 goal id 组装后再交回 status payload,避免 display limit 破坏语义。goal_acceptance_observation_markdown.py:13只接受精确的窄 schema,并在文案上明确 “partial; not completion proof”;旧的goal_artifact_lifecycle_projection_v0不再被兼容性别名偷偷保留。goal-acceptance-observation-card.tsx:4是纯展示消费者,没有按钮、写接口或网络副作用;缺失/非法 projection 由既有 status parser/model 边界安全省略。
future-facing pass 已做在正确边界:复用 Vision/gate helper、给派生 contract 一个窄而真实的名字、避免 lifecycle/acceptance authority。再抽通用“artifact lifecycle framework”会把当前没有的语义提前固化,因此没有必要。
对主干的风险
没有发现 blocking finding。最强历史回归是公共名字比实现语义更宽:我在旧 head 61bb110a521232cbe77caf25a1942b19d5fa57d5 实际执行 builder,得到 goal_artifact_lifecycle_projection_v0;当前 head 同一路径得到 goal_acceptance_observation_projection_v0,并由 Python/TS/Markdown 负例拒绝 broad id。这比只看重命名 diff 更能证明旧缺陷已失效。
验证结果:focused Python tests 10/10;Ruff、diff check、presentation surface schema、Dashboard build 与生成 bundle 对齐均通过;专用 Goal acceptance development/packaged browser smoke 通过;完整 Personal Workspace development/packaged smoke 在显式空闲端口 5296/5297 通过。首次默认端口 5196 被一项无关的长驻本地服务占用,我没有终止它,而是换端口重跑通过。远端当前 exact head 的 30 项 checks 全部成功。
剩余风险是该视图有意只读取有界历史,所以它只能报告 partial,而不能给出全局 absence/completion 证明;这已经被 schema、测试和 UI 文案显式表达。现有 open_user_gate_todo_items 对旧未类型化 Todo 的文本兼容 fallback 仍可能产生保守的 gate 展示,但它属于既有迁移 owner,本 PR 没有复制该规则,也不会把它升级为验收结论。
我的整体评价
APPROVE。当前 head 修掉了上一轮真正危险的协议语义过宽问题,并把 producer、typed consumers、负向语义和真实 development/packaged 浏览器路径补成一个闭环。它只增加可省略的只读观察字段,保持既有状态输出、写权限、Todo/Vision authority、调度和完成语义不变。22 文件的体量主要来自同一 contract 的多表面验证,和问题影响相称;没有为了未来可能性引入第二套 lifecycle 状态机。
English verdict: APPROVE exact head e961c90126440bc0939db250f6fddd7cab34bfb4. The current head replaces the misleading broad artifact-lifecycle protocol with the narrow goal_acceptance_observation_projection_v0, keeps Agent Vision and Todo state authoritative, and exposes only a bounded read model that never certifies completion. Base/head collect_status parity held after removing the one intended optional field and normalizing timestamps/temp roots; old/head execution proved the broad-name regression is gone. Ten focused tests, schema/build, dedicated and full development/packaged browser smokes, Ruff, diff check, generated-bundle parity, and all 30 hosted checks passed. Remaining bounded-history and legacy untyped-gate compatibility risks are explicit and cannot produce an acceptance conclusion.
Signed-off-by: huangruiteng <huangrt01@163.com>
|
Re-reviewed the integrated head Resolution and product judgmentThe three source conflicts were additive imports shared with #4247. Both attention details and Goal acceptance observations are retained. The packaged entry point was rebuilt from the combined source; only this PR's superseded generated JavaScript was replaced, while existing main assets were retained. The full 22-file PR remains a cohesive read-only projection from existing Agent Vision, Todo gate and status history owners into JSON, Markdown and Dashboard. It helps operators see missing evidence without interpreting completed tasks or historical approvals as Goal acceptance. Review lenses: state rules reuse the existing Vision/gate helpers; no new prose classifier or denylist was introduced. The existing legacy untyped-gate fallback can conservatively over-report a gate, but is unchanged and grants no authority. The default addition to status and UI is disclosed in the reference documentation; this PR makes no opt-in/default-off claim. The observations impose no execution obligation. Future-facing pass: the existing bounded projection and shared helpers are sufficient; no additional abstraction or companion refactor is warranted for import-conflict resolution. Validation
The remaining limitation is the intentionally bounded historical coverage, which cannot prove absence of gaps or successful acceptance. No required local validation is skipped. Hosted CI for this new head is still partially running/queued; its pending checks are not claimed as passed. Local coverage exercises the affected producer, schema, Markdown, both browser distributions, integration with #4247, and broader control-plane regressions, which is sufficient for the explicitly authorized maintainer admin-bypass self-merge. No branch-protection policy is changed. Final base freshness check: main advanced to Merge decision: self-merged with owner-authorized admin bypass. GitHub confirmed merge commit |
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>
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>
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>
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>
* feat(goals): derive the Goal artifact lifecycle projection 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 #4128 Signed-off-by: song <liusongstep@gmail.com> * fix(goals): keep the lifecycle projection honest at its boundaries 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> * fix(goals): own the lifecycle rules inward and give the projection a 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> * test(goals): keep the two Goal projections distinct now that both ship CI `test-shard (2)` caught a real conflict I had missed: `#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 `#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> * fix(goals): derive lifecycle from canonical status and safety boundaries 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> * fix(goals): keep closeout reachable while a recorded gap stays unreached 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> * fix(goals): require an acceptance verdict before recommending closed 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> * fix(goals): consume owned work observations in the lifecycle readout Signed-off-by: song <liusongstep@gmail.com> * docs(goals): explain lifecycle work-observation precedence Signed-off-by: song <liusongstep@gmail.com> * fix(goals): keep lifecycle v0 terminal advice out of scope Signed-off-by: song <liusongstep@gmail.com> * docs(goals): define lifecycle advice and work observation coverage Signed-off-by: song <liusongstep@gmail.com> * test(chat): isolate unbound channel fixture from installed runtime 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>
Behavior
Goal details and status JSON/Markdown expose bounded acceptance observations: current gaps, pending gates, next action and historical progress with evidence references. Existing Agent Vision and Todo rules remain authoritative; collection reuses already-read history before display truncation.
The wire contract is
goal_acceptance_observation_projection_v0inacceptance_observation; historical entries usehistorical_progress. This intentionally differs from the full lifecycle RFC's phases, milestones and legal transitions. There is no alias using the broader RFC schema name. Missing sources and partial coverage remain explicit; completed Todos, old approval flags and empty gaps never certify acceptance.Validation
Scope and integration
Head
e961c90126440bc0939db250f6fddd7cab34bfb4includes main84334f794. The current browser scenario runner and original Goal smoke registration are retained; repository read-only selectors live in the shared typed-actions scenario.Entry points are Dashboard Goal details and status CLI. No configuration, Lark consumer, authority or action API changed. Full frontier audit, declared milestones and #4172 material receipts remain outside this bounded slice. The future-facing pass keeps the observation and renderer in their existing read-model ownership and removes the misleading lifecycle name instead of adding a second state machine.
Hosted CI and maintainer review must qualify the current head. Tests use synthetic disposable state, not a deployed Goal.