Skip to content

Commit b9f3cab

Browse files
docs(adr-0043,skill-automation): open-time notification is approval.requested; one-tap links stay remind()-only (#22631, Tier H half) (#22652)
Part of #22631 Clause-②: no Two governed texts said the opposite of what `openNodeRequest` does since PR #22625 (`e8c6666870`): opening an approval request publishes `approval.requested` to each concrete approver on the slate the request opens on, through the single `notify()` ingress, and one-tap links are still minted by `remind()` only. This PR carries items 1 and 2 of #22631 (Tier H: `docs/adr/**`, `skills/**`). Item 3 and the token-door item ride the sibling PR on `claude/issue-22631-qa-checklist-approval-requested`. #22631 remains open until both PRs land. Dispatched by the `domain:skills` seat PM, session `session_01RdnZdPZH9ByduzPRWuH9tN`. Claim `6095805515`, triage `6095381775`. ## Measured first - All premises held at `origin/main` `3d0eeefa` (`86da194919` plus five later commits; `git diff --stat 86da194 3d0eeef` over the three card paths is empty, so none of them moved). - ADR-0043 `:40`–`:42` read "(Open-time notification remains the flow author's `notify` node; templates there can adopt the same links later.)": 1 hit with a wrap-tolerant grep; positive control `sys_approval_token`: 2 hits. - The skill's item 5 read "rather than expecting the node to send mail itself": 1 hit; control `approve`: 48 hits. - `packages/plugins/plugin-approvals/src/approval-service.ts`: the literal is `'approval.requested'` (`:3349`, inside `openNodeRequest`, declared at `:2999`). The fan-out loops over `openedOn`, the slate after OOO delegation and after an `onEmptyApprovers: 'fallback'` replacement, minus `type:value` literals and OOO delegates, one `this.notify()` per approver. `issueActionTokens` has exactly one caller, `:4815` inside `remind()` (declared at `:4763`), so one-tap links are minted by `remind()` only. #22641 (`3d0eeefa`) shifted these lines by 15 and changed none of it. - The skill's eval set (`skills/objectstack-automation/evals/**`) asserts nothing about the dropped half: grep `send mail itself|node does not send|node to send|downstream nodes|notify node` gives 0 hits in both files; control `approval` gives 4 and 13 hits. Nothing moves there. ## What changed ### `docs/adr/0043-actionable-approval-links.md` (two one-line touches) - The `**Status**` line takes the corpus form of a dated status amendment, `· **Amended** (2026-10-10, #22631 — …)`, as ADR-0029, ADR-0030 and ADR-0044 carry theirs: open-time notification is the approvals service's own `approval.requested` topic, not a flow-authored `notify` node; one-tap links stay `remind()`-only; the token table and every decision below are unchanged. - The Issue bullet's parenthesis, the sentence the card quotes, becomes the dated correction: the topic as the code spells it, published by `openNodeRequest` to each concrete approver on the slate the request opens on (PR #22625), carrying no links, with one-tap links remaining `remind()`-only. - ⛔ No new ADR number. The token table and the decisions are untouched. ### `skills/objectstack-automation/references/state-machines-and-approvals.md` - Item 5 keeps the decision half (notify the submitter of the outcome from the `approve` / `reject` edges), drops "rather than expecting the node to send mail itself", and names the trap: the opening already tells each resolved approver (`approval.requested`), so no `notify` node for the opening, or they are told twice. - Ratchet currency: the file sat exactly at its token ceiling (5569 of 5569), and `check-skills-token-ratchet` accepts only deletion in the same file as payment. The deleted text is the two-line code comment at `:183`–`:184` ("No config and no `waitEventConfig`: the window ends on the submitter's explicit resubmit, not on a signal or a timer."), which restated the prose at `:167` ("The node takes **no config** — there is no signal to wait on."). No fact leaves the file. ## Readings (`skills/**`, per the line budget) | reading | before (`3d0eeefa`) | after (`af35cd5be`) | delta | |---|---|---|---| | `references/state-machines-and-approvals.md`, lines | 383 | 383 | 0 | | `references/state-machines-and-approvals.md`, tokens (`ceil(bytes/4)`, the ratchet's unit) | 5569 (ceiling 5569) | 5562 | −7 | | package lines (the five ratcheted files under `skills/objectstack-automation/`) | 1048 | 1048 | 0 | | package tokens | 14581 | 14574 | −7 | `node scripts/check-skills-token-ratchet.mjs` prints "state-machines-and-approvals.md is 5562 tokens (ceiling 5569; headroom 7)". No ceiling moved. ## Verification (all at `af35cd5be`) - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; the change set is taken from the merge base) derived 30 commands. All 30 ran in the foreground, each exit captured before any pipe; `--ran` reconciliation: "30 derived, 30 run, 0 NOT-MEASURED, 0 UNRUN". `check:doc-formula-expressions` first answered exit 3 (PREREQUISITE NOT MET: no `dist/` for `@objectstack/formula` and `@objectstack/lint`); after that build through the verify lock (4 of 4 tasks cached) it measured exit 0 ("22 record-scoped formula example(s) across 471 files / 1387 TS blocks judged clean"). - Among the 30: `check-adr-links`, `check-adr-symbol-anchors`, `check:adr-anchors`, `check-skills-token-ratchet` (each with its `--self-test`), `check:skill-refs`, `check:skill-compatibility`, `check:skill-frame-sync`, `check:skill-identifier-liveness`, `check:role-word`, `check:corpus-claim-drift`, `check:pm-prior-rulings`, `check:pm-governed-merges`, `check:doc-authoring`, `check:nul-bytes`: all exit 0. - Not run locally: the repo-wide `pnpm lint` (CI's; this diff touches no lintable source) and package test suites (no package is touched). ## Changeset Docs-only, nothing published: `docs/adr/**` and `skills/**` ship in no package's `files[]`. `skip-changeset` is applied through `label-write`. ## Landing Tier H (`docs/adr/**`, `skills/**`): draft until an authorized APPROVED review. No seat merges, queues or arms auto-merge on it. ## 维护者速读(草稿) **改了什么:** 两处文字。ADR-0043 的 Status 行加一条 2026-10-10 的修订记号;机制段里那句「开单通知仍由流程作者的 `notify` 节点负责」改为:开单通知现在是审批服务自己的 `approval.requested` 主题,发给这次开单落到的每个具体审批人(PR #22625);一键链接仍只在催办(`remind()`)时签发。自动化技能的最佳实践第 5 条保留「结果由 approve/reject 边上的下游节点通知提交人」,删掉「节点自己不发邮件」,并写明:开单已经通知了每个审批人,⛔ 不要再为开单加 `notify` 节点,否则重复通知。 **为什么改:** 自 PR #22625 起这两句是假的;技能那句正把 AI 作者引向变更集要求删掉的那个 `notify` 节点(重复通知陷阱)。 **风险与代价(含回滚):** 纯文档,无代码、无发布面;ADR-0043 的令牌表与各项决策不动,不新开 ADR。技能文件处在 token 上限,新增文字用同一文件里一段重复的代码注释抵付(该事实上一行散文已经写了),行数净增 0。回滚即 revert 本 PR。 **席位意见:** **你要做的:** 批准本 PR(Tier H,需你的 APPROVED review);落地由席位执行。 --- _Generated by [Claude Code](https://claude.ai/code/session_01RdnZdPZH9ByduzPRWuH9tN)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 65f4756 commit b9f3cab

2 files changed

Lines changed: 10 additions & 8 deletions

File tree

‎docs/adr/0043-actionable-approval-links.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0043: Actionable approval links — single-use tokens with a session-less confirm page
22

3-
**Status**: Accepted — implemented (proposed 2026-06-12 · calibrated 2026-06-12)
3+
**Status**: Accepted — implemented (proposed 2026-06-12 · calibrated 2026-06-12) · **Amended** (2026-10-10, #22631 — open-time notification is the approvals service's own `approval.requested` topic, not a flow-authored `notify` node; see the Issue bullet under Mechanics. One-tap links stay `remind()`-only; the token table and every decision below are unchanged)
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0042](./0042-approval-sla-escalation.md) (reserved system actors, audit-first discipline), thread interactions (#1740), [ADR-0012/0030](./0030-notification-platform-convergence.md) (messaging + outbox)
66
**Closes**: [#1743](https://github.com/objectstack-ai/objectstack/issues/1743)
@@ -37,9 +37,11 @@ GET-executes design gets requests approved by robots.
3737
- **Issue** (`issueActionTokens`): 256-bit random raw tokens, returned
3838
once, hashes stored. Wired into `remind()` — each pending approver with
3939
a concrete identity (not `role:*` literals) gets their **own**
40-
notification carrying their own approve/reject links. (Open-time
41-
notification remains the flow author's `notify` node; templates there
42-
can adopt the same links later.)
40+
notification carrying their own approve/reject links. (Amended
41+
2026-10-10, #22631: open-time notification is the approvals service's
42+
`approval.requested` topic, published by `openNodeRequest` to each
43+
concrete approver on the slate the request opens on — PR #22625; it
44+
carries no links, and one-tap links remain `remind()`-only.)
4345
- **Confirm page** (`GET /api/v1/approvals/act?token=…`): session-less
4446
minimal HTML rendered by the plugin on the host Hono app — request
4547
summary (flow label, record title, action) + a POST form. Invalid /

‎skills/objectstack-automation/references/state-machines-and-approvals.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,6 @@ Three pieces author it:
180180
id: 'manager_review', type: 'approval', label: 'Manager Review',
181181
config: { approvers: [{ type: 'position', value: 'manager' }], lockRecord: true, maxRevisions: 2 },
182182
},
183-
// No config and no `waitEventConfig`: the window ends on the submitter's
184-
// explicit resubmit, not on a signal or a timer.
185183
{ id: 'wait_revision', type: 'approval_revise', label: 'Awaiting Revision' },
186184
// …among the approval's edges…
187185
{ id: 'rev', source: 'manager_review', target: 'wait_revision', label: 'revise' },
@@ -379,5 +377,7 @@ These are wired on the **graph**, not in node config:
379377
edits while pending — otherwise approvers chase a moving target.
380378
4. **Model rejection as a visible branch** — a back-edge to revise, or an `end`
381379
node to terminate. The path is on the diagram, not hidden in config.
382-
5. **Notify from downstream nodes** wired to the `approve` / `reject` edges
383-
rather than expecting the node to send mail itself.
380+
5. **Notify the submitter from downstream nodes** wired to the `approve` /
381+
`reject` edges. Opening already tells each resolved approver
382+
(`approval.requested`); ⛔ no `notify` node for the opening: it tells
383+
them twice.

0 commit comments

Comments
 (0)