Skip to content

docs(catalog): add IP-033 recorded rejection is not absent authority - #4550

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
superwesleyhys-ux:docs-ip-033-recorded-rejection
Sep 16, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
superwesleyhys-ux:docs-ip-033-recorded-rejection

Conversation

@superwesleyhys-ux

Copy link
Copy Markdown
Contributor

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 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 pattern named that boundary, so the two failure modes had no catalog home:

  • a surface that only renders active authority shows a refused scope as
    undecided, and the agent re-asks a question the operator already answered;
  • a consumer over-reads a retained, done, global todo as the approval it
    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

File Change
docs/concepts/interaction-pattern-catalog.md catalog row, Pattern-To-Canary matrix cell, and the #### IP-033 detail section
examples/interaction-pattern-catalog-smoke.py three pinned strings so the entry cannot silently drift

Docs-only change: no runtime, protocol, or fixture code is touched here.

Validation

  • python3 examples/interaction-pattern-catalog-smoke.py → ok
  • python3 -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.ts lands. This
entry 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.

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>
@superwesleyhys-ux
superwesleyhys-ux force-pushed the docs-ip-033-recorded-rejection branch from 19d67f9 to fd2096a Compare September 16, 2026 13:06

@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.

评审提交: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.

@steven-kid

Copy link
Copy Markdown
Collaborator

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 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.

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. 三条规则的分工:第 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 记录。
  2. Visual Model 明确三条分支(approve → active、reject/cancel → inactive、missing/mixed → standing_decision_order_unresolved),与代码里的 active_count/inactive_count 投影一一对应,读目录的人不需要再去猜 conflict 分支的落点。
  3. 两份 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.

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