docs(catalog): add IP-033 recorded rejection is not absent authority - #4550
huangruiteng merged 1 commit into
Conversation
The standing-decision contract treats an explicit `reject` or `cancel` as a recorded receipt whose scope, owner and chronology survive exactly like an approval's, while activation stays bound to `decision_outcome === "approve"`. No catalog pattern named that boundary, so a surface could render "no active approval" as "no decision was made" and re-ask a scope the operator already refused, or over-read a retained, done, global todo as the approval it structurally resembles. Add the pattern under Human Decision with its trigger, three expected-behavior rules, visual model, bad smells and validation; list it in the Pattern-To-Canary matrix; and pin the entry in examples/interaction-pattern-catalog-smoke.py. The fixture slice that exercises the invariant lands with GH-C102 (loopx-project#4541). Refs GH-C37 Signed-off-by: superwesleyhys-ux <251160695+superwesleyhys-ux@users.noreply.github.com>
19d67f9 to
fd2096a
Compare
steven-kid
left a comment
There was a problem hiding this comment.
评审提交:fd2096a90c3a20ed4b47420ca4902d12e8e2cc86。结论:APPROVE,无阻塞问题;以下 chronology 表述建议为非阻塞 P2。
动机
本 PR 补充“拒绝已经发生,但没有授予权限”这一交互语义。若界面仅展示 active authority,已拒绝的 scope 会被误读为尚未决策,导致重复询问;反过来,仅凭 done、global 或 archive retained 又可能把拒绝误当批准。现有 IP-014 解释决策写入,IP-032 解释归档保留,这两者没有集中解释拒绝的后续呈现。因此新增条目有明确用户价值,而不只是为新测试登记一个编号。维持现状需要读者自行拼接这些区别;直接扩写 IP-032 更短,但会把归档生命周期和决策结果混为一个主题。新增条目的范围仍然受现有 standing decision 实现约束,不新增权限。
改动思路
权威输入仍是具有合格 typed scope 的已完成 user gate。isStandingDecisionReceipt 决定是否保留为 receipt,projectStandingDecisions 按 scope、agent 和 chronology 选择结果,archive selector 使用相同 eligibility。正向路径是合格 reject/cancel 被保留、投影为 inactive,后续消费者读取 outcome;负向路径是缺失 typed scope、未完成或带单次 unblock 关联的记录不成为 standing authority,冲突也不能产生默认批准。
文档把这条既有路径组织成 trigger、三条规则、流程图、bad smell 和 validation references。核对 IP-014、IP-032、standing decision smoke、archive 测试和原生投影测试后,重复部分主要是必要的上下文,新增重点是“已拒绝”和“未决策”的用户区别。作者本批只有 #4549 与 #4550,间隔 44 秒,但前者是共享生产规模 fixture 的回归测试,后者是交互说明,不能仅凭时间接近判为重复 smoke。文档注明 GH-C102 fixture 尚待到达;#4540 确实是该任务 issue,实际实现 PR 为 #4549。
具体改动
完整 diff 为两个文件 +83/-1,没有运行时代码或生成物。docs/concepts/interaction-pattern-catalog.md 更新 Human Decision canary 覆盖表、索引和 IP-033 详细条目;examples/interaction-pattern-catalog-smoke.py 在既有 smoke 中增加标题、核心句和 inactive_count 三个字符串检查。复用既有 smoke 比另建脚本更轻,但字符串检查只能防文档条目丢失,不能证明权限语义;运行时语义由现有原生和 CLI 测试承担。
关键内容讲解
- IP-033 trigger 与 record/activate 区分:仅当记录本来符合 standing receipt 条件时,reject/cancel 才保留为 inactive receipt。它不是对任意含有
decision_outcome的 Todo 授予 standing 身份。 - 流程图与 chronology:outcome 为 approve 是激活条件,但应先完成同一 scope 的候选选择。图中的 chronology 分支目前过宽,见下方建议;单条 receipt 的 outcome 与多个候选的排序不能混成同一维度。
- Validation 与 smoke:指向 canonical predicate/projection 和共享 fixture;后者属于 #4549 的配套覆盖,不应宣称已经在本 head 内运行。现有
standing_decision.test.ts足以验证核心的拒绝、撤销、冲突和归档资格;本 PR 的三条 smoke pin 是目录维护约束。
对主干的风险
没有运行时行为变化,主要风险是后续消费者按过宽文案实现排序策略。P2,非阻塞:请把第 2 条和 Mermaid 的“mixed or undated / missing chronology 必须 conflict”收窄为“不同 outcome 且无法可靠确定先后的候选才 conflict”。 当前 latest 对单条无日期记录直接选择,对多个相同 outcome 也不制造冲突;legacy source order 还可以合法决定先后。已运行的原生测试明确覆盖这些例外。最小修改是同时修正文句和图中条件,并引用现有 chronology tests,不需要改变运行时来迁就文案。
验证:目录 smoke 通过;node --no-warnings --experimental-sqlite --experimental-strip-types --test tests/control_plane_ts/standing_decision.test.ts 的 8 项测试通过;uv run --no-project --with pyyaml python examples/control_plane/todo-standing-decision-authority-smoke.py 通过;git diff --check 通过;两个变更文件的 public boundary scan 干净。后续 #4549 的新测试文件未包含在此 head,未把它当成本次已执行证据。未运行全库测试,这与本次文档及三条 smoke pin 的范围相称。
我的整体评价
批准。本 PR 为既有权限事实补充清楚的用户交互含义,目录结构和复用 smoke 的成本与价值相称,没有引入第二套决策 owner、默认开关或新的写权限。核心原则已在原生投影与 CLI smoke 中验证;chronology 的文字例外需要收窄,但不会改变本 PR 的运行时权限,作为非阻塞建议保留。批准仅针对上述 exact head 的两个变更文件,不代替 #4549 的代码评审,也不代表已执行合并。
English verdict: APPROVE at fd2096a. The catalog usefully distinguishes a recorded refusal from missing authority and reuses the existing smoke. Non-blocking P2: narrow the chronology wording and diagram; undated single or same-outcome receipts, and valid legacy source order, do not necessarily conflict. The catalog smoke, standing-decision CLI smoke, eight native projection tests, diff check and touched-file public-boundary scan passed. The companion production-scale tests belong to #4549 and were not executed as part of this head. No merge performed.
|
English verdict: APPROVE at fd2096a. The catalog usefully distinguishes a recorded refusal from missing authority and reuses the existing smoke. Non-blocking P2: narrow the chronology wording and diagram; undated single or same-outcome receipts, and valid legacy source order, do not necessarily conflict. The catalog smoke, standing-decision CLI smoke, eight native projection tests, diff check and touched-file public-boundary scan passed. The companion production-scale tests belong to #4549 and were not executed as part of this head. No merge performed. |
huangruiteng
left a comment
There was a problem hiding this comment.
Reviewed exact head: fd2096a90c3a20ed4b47420ca4902d12e8e2cc86
动机
standing decision 契约把显式的 reject/cancel 记成与 approve 同级的 receipt(scope、owner、chronology 一视同仁),但激活只认 decision_outcome === "approve"。而 Human Decision 家族此前只有 IP-004、IP-014、IP-017、IP-027、IP-030,没有任何一行描述“已被明确拒绝的 scope”。后果很具体:只看 active authority 的界面会把“没有 active approval”渲染成“从未做过决定”,于是操作者拒绝过的宽 write scope 两个 turn 之后又被重新提案——操作者体验是“我说过不行”接着“为什么又问”。这个 PR 就是把这条边界写进目录:触发条件(typed decision_scope + 显式 reject/cancel,且 gate 足够宽,若换成 approve 就会成为 standing authority)、三条期望行为(记录它、不要激活它、读结果而不要推断)、visual model、bad smell 与 validation,并把它接进 Pattern-To-Canary 矩阵与 smoke。改动面很小(2 文件 +83/-1),且 predicate 早已存在于 loopx/control_plane/todos/standing_decision.ts,所以这是文档补齐而不是新增机制。
改动思路
入口是目录文档本身:docs/concepts/interaction-pattern-catalog.md 的 Human Decision 家族表 + Pattern-To-Canary 矩阵 + 新增 IP-033 小节;权威状态是 standing_decision_receipt_v0,由 typed todo 的 decision_scope 与 decision_outcome 推导;decision owner 仍是 standing_decision.ts(OUTCOMES 集合、逐条 active: decision_outcome === "approve"、active_count/inactive_count)。这个 PR 没有新增状态或转换,只把它写清楚,并用既有 smoke 钉住入口。
正向路径:我按读者顺序走了矩阵行 → IP-033 小节 → smoke pin,并在 head 上执行 examples/interaction-pattern-catalog-smoke.py,输出 ok;作者也明确划了边界(IP-014 拥有“怎么写/预览决定”,IP-032 拥有“todo 离开 active window 后 durable decision 怎么办”,IP-033 只拥有“被拒绝的决定是什么意思”),没有把三者混成一条规则。复用判断:smoke 只是往既有 pin 列表里加一行,没有新建 smoke 家族;pattern 编号与 P1 排序也符合该家族既有约定(P1 行插在 IP-030 之后、P2 之前)。
具体改动
docs/concepts/interaction-pattern-catalog.md(+81/-1):家族表加入 IP-033;矩阵 Human Decision 行补上 IP-033;新增 #### IP-033 Recorded Rejection Is Not Absent Authority(Trigger / 三条 Expected behavior / mermaid Visual Model / Bad smell / Validation)。examples/interaction-pattern-catalog-smoke.py(+3):把 IP-033 加进既有 pin 列表。
关键内容讲解
- 三条规则的分工:第 1 条把
reject/cancel钉成standing_decision_receipt_v0(并说明 archive 与retained_standing_decision_count一视同仁);第 2 条把激活条件收紧为仅approve,并要求“无 active approval”不得渲染成“没有决定”、混合或缺失 chronology 必须报 conflict 而不是静默取 approve;第 3 条禁止消费者把“缺少拒绝记录”当作批准、也禁止把 retained/done/global todo 当作它结构上相似的 authority。三条合起来正好覆盖了 bad smell 里的两个方向:拒绝消失被重复提问、以及过度解读 retained 记录。 - Visual Model 明确三条分支(approve → active、reject/cancel → inactive、missing/mixed →
standing_decision_order_unresolved),与代码里的active_count/inactive_count投影一一对应,读目录的人不需要再去猜 conflict 分支的落点。 - 两份 Validation 指针(本次 review 提出,见下):一条指向尚未落地的
tests/control_plane_ts/production_scale_rejected_decision.test.ts,并把它标为随 GH-C102 fixture slice 到达;另一条声称authority_store_conformance.ts断言inactive_count。前者在该 head 不存在,后者在该 head 不成立——这条 bullet 需要收窄到现状或在 fixture 落地时一起补。
对主干的风险
文档改动本身不改变运行时行为,回归风险集中在“目录作为权威说明是否准确”。最强的回归场景是:后续有人依据 Validation 一节去改 standing_decision.ts 的谓词,却以为“拒绝不激活”这条已被既有测试覆盖——实际上只有 examples/interaction-pattern-catalog-smoke.py 保证条目存在,不保证 cited 路径可验证(catalog smoke 只检查条目与矩阵接线,所以指针写错也会通过)。负向证据我实测过:rg -n 'inactive_count' tests/control_plane_ts/authority_store_conformance.ts 无匹配(该文件在 :240 断言的是 decisions.active_count),inactive_count 仅出现在 loopx/control_plane/todos/standing_decision.ts:110;ls tests/control_plane_ts/production_scale_rejected_decision.test.ts 也失败。最小修复:Validation 只引一个 GH-C102 issue(文档写 #4540、commit/PR body 写 #4541,两个是标题相同的 open issue),并把未落地的测试标成 pending,或改成引用今天存在的 predicate owner。
这两条我作为 P2(非阻塞) 提出:PR body 已声明 fixture slice 随后落地,且目录中另有 3 条既有条目同样引用了不存在的路径(examples/worker-bridge-*、examples/benchmark-lifecycle-state-smoke.py),说明这是目录级的历史漂移,值得单独做一次 catalog lint,而不是阻塞本 PR。除此之外矩阵接线、编号与优先级、以及“与 IP-014/IP-032 的边界划分”都成立,smoke 在 exact head 通过。
我的整体评价
APPROVE。 这条模式填补的是一处真实的授权语义空白:拒绝是“已决定”,不是“没决定”,而激活只看显式 approve——目录现在把这一点连同 conflict 分支、bad smell 与验证入口一起写清楚了,且明确与 IP-014/IP-032 分工,没有把三条规则糊成一条。证据:exact head fd2096a90 上 examples/interaction-pattern-catalog-smoke.py 输出 interaction-pattern-catalog-smoke: ok;谓词与投影确实由 loopx/control_plane/todos/standing_decision.ts 拥有(OUTCOMES、逐条 active、active_count/inactive_count);家族表与矩阵都已包含 IP-033。两条 P2 只针对 Validation 一节的准确性(未落地的测试文件与 inactive_count 覆盖声明),不阻塞合并,建议在 GH-C102 fixture 落地时一并收窄。
English verdict: APPROVE — at head fd2096a this adds the missing catalog pattern for a recorded refusal: the standing-decision contract keeps reject/cancel as receipts with inactive_count while activation stays bound to an explicit approve, and the entry is wired into the Human Decision family, the Pattern-To-Canary matrix and the pinned catalog smoke (interaction-pattern-catalog-smoke: ok). Two P2 accuracy findings do not block: the Validation bullet points at tests/control_plane_ts/production_scale_rejected_decision.test.ts, which does not exist at this head (and the sentence cites issue #4540 while the commit and PR body cite #4541, two open duplicate-titled GH-C102 issues), and it claims authority_store_conformance.ts asserts inactive_count, which is not true here (that file asserts active_count at :240; inactive_count lives only in loopx/control_plane/todos/standing_decision.ts:110). Minimum repair: cite one issue consistently, mark the deferred test as pending or point at the existing predicate owner, and correct the conformance claim — ideally with a catalog lint, since three other rows already cite missing paths.
Refs GH-C37 — contributor task board row: "Curate the interaction pattern catalog with one new public-safe good/bad case, including trigger signals, user channel, agent channel, state contract, bad smell, and validation reference."
Pattern
IP-033 Recorded Rejection Is Not Absent Authority (Human Decision, P1).
The standing-decision contract treats an explicit
rejectorcancelas arecorded receipt whose scope, owner, and chronology survive exactly like an
approval's, while activation stays bound to
decision_outcome === "approve".No pattern named that boundary, so the two failure modes had no catalog home:
undecided, and the agent re-asks a question the operator already answered;
structurally resembles.
The pattern records three rules (record it, do not activate it, read the
outcome instead of inferring it), a Mermaid visual, both bad smells, and its
validation links. It names IP-014 (how a decision is written) and IP-032
(what happens to a durable decision when its todo leaves the active window) as
the neighbouring patterns it does not duplicate.
What changed
docs/concepts/interaction-pattern-catalog.md#### IP-033detail sectionexamples/interaction-pattern-catalog-smoke.pyDocs-only change: no runtime, protocol, or fixture code is touched here.
Validation
python3 examples/interaction-pattern-catalog-smoke.py→ okpython3 -m loopx.entrypoint check --scan-path docs/concepts/interaction-pattern-catalog.md→
ok: True, errors=0 (the two warnings are a missing local.loopx/registry.json,unrelated to this change)
Relationship to the GH-C102 slice
The invariant this pattern describes is exercised by the shared production-scale
coordination fixture in #4549 (GH-C102), which is where
tests/control_plane_ts/production_scale_rejected_decision.test.tslands. Thisentry names that file with an "arrives with #4549" note so it reads correctly in
either merge order; the doc itself is valid on its own, and the catalog smoke
does not depend on the other PR.