You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Filing gate: ① — reproducible product defect (authorization; reproduction withheld under RUNNER rule 2 — the "possible data exposure" exception: reachability was measured first)
Path: approvals.act-door-and-admin-rescue-slate
type: Bug (contradicts the code's own declared storage/identity contract and ADR-0043; the relay channel sets no issue type, so it is pre-filled here for triage)
Checklist item priority: P1 — found adjacent to the item's acceptance[1] (not an authored clause, so the item itself does not fail); assessed severity high, candidate 18.0 release blocker
reach: measured through public HTTP doors on a live stock showcase boot at 5b14b7f, twice — the exact calls are withheld pending maintainer
Acting reader: the maintainer first (disclosure and release call), then the domain:services lane seat at dispatch — the fix lands in packages/plugins/plugin-approvals
Dedupe: one search_issues query on approval action-link tokens (open + closed; its exact wording is withheld with the reproduction, since it names the mechanism) → 4 hits, all closed, none the same defect: nearest #22616 (the token table's own data-door narrowing — fixed, different surface)
QA-source: #24558 · approvals.act-door-and-admin-rescue-slate · X1 (finding adjacent to acceptance[1], not an authored clause)
Found by the 18.0 release QA sweep (subject 5b14b7f3). This is an authorization finding: under RUNNER rule 2's carve-out its reproduction and mechanism are not published anywhere on GitHub. Detail withheld pending maintainer — the reproduction is held by the QA runner session and has been handed to the wave's dispatch session for the maintainer.
Area: approval action links (the one-tap act door), packages/plugins/plugin-approvals.
Status of the finding: reproduced twice by the runner on a stock showcase boot (single-organization posture); independently re-derived from the captured evidence and the source by a second agent (RUNNER rule 7), which confirmed every link of the chain and found it contradicts the code's own declared contract and ADR-0043.
Assessed severity: high; the verifier rates it a candidate 18.0 release blocker (impact class: the audit trail can attribute a decision to the wrong person). Please treat as must-triage before the 18.0 cut.
Ask the maintainer for the reproduction; do not add one to this card or its comments.
Triage: p1 + security + target:v18 · domain:services · area:workflow · pm:queue · put to the maintainer as a candidate 18.0 blocker · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T13:59Z
Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. ⛔ This card carries no reproduction, and nothing added to it may (RUNNER rule 2, #9387).
Grade. It is an authorization defect, reproduced twice and independently re-derived (RUNNER rule 7). Its impact class is that the audit trail attributes a decision to the wrong person. Hence p1 + security.
Lane domain:services:packages/plugins/plugin-approvals.
The QA verifier rates it a candidate release blocker. The seat put to the maintainer, in chat, whether the 18.0 cut waits on this fix.
Until that answer, this card rides 18.0 if ready.
For the dispatching seat: the reproduction is with the QA runner and the wave's dispatch session, for the maintainer. Get it from the maintainer off GitHub. The PR's tests are the pins, and its description carries no recipe.
Claim: maintainer-directed dispatch (18.0 QA wave)
Session: session_01CfRDy7SuTfUqaTu2AEH2Hi
Account: os-warren (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/funny-albattani-eue8pk
Worktree: objectstack-issue-24564
Domain: domain:services
Seat: none — the 18.0 QA wave's dispatch session, acting on the maintainer's direct instruction quoted below; not a lane seat
File surface: packages/plugins/plugin-approvals/src/**; packages/services/service-messaging/src/** only if the fix belongs at that seam; .changeset/ (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (TIER_DEFAULT — dispatch-gates.mjs --tier on this surface reads "no path-derived mandate · floor sonnet · default opus · ceiling fable")
Clause-②: no — the change narrows what the platform persists; it is a runtime security-boundary change, so it sits on the human floor and its PR stays draft until the maintainer approves
Responsibility: plugin-approvals (action-link issuance) | none known | an authenticated role below platform admin, through a public door — detail withheld
Thread-read: 6269014281
Serial constraints cleared: #24527 edits approval-service.ts in disjoint hunks (resume / decide paths); this card's region is not among them — no serialization, whichever lands second merges main
Directed-by: the maintainer, verbatim 「你测试出的安全问题如果不方便写issue 的,你可以自己派发」, in Claude Code session session_01CfRDy7SuTfUqaTu2AEH2Hi, 2026-10-19. The reproduction is handed to the dev inside that session; ⛔ nothing on this card or its PR carries it.
os-dev-report
{
"issue": 24564,
"status": "done",
"branch": "claude/funny-albattani-eue8pk",
"pr": "#24620",
"session": "session_01CfRDy7SuTfUqaTu2AEH2Hi",
"premise_still_valid": true,
"summary": "remind() no longer mints ADR-0043 action tokens and no longer puts approve/reject links in the payload it hands to messaging, which messaging stores on sys_notification and sys_notification_delivery; each concrete approver still gets their own approval.reminder with the same title, body, deep link and dedup key. ApprovalNotificationPayload no longer accepts links (a call site handing one over does not compile), and issueActionTokens now states that a raw token travels only in a message that is sent and not kept. Delivery path, read at merge base de43524: the reminder names no channel, messaging falls back to inbox, the inbox row never carried actions and no channel in this repo reads payload.actions, so removing the links changes nothing a recipient sees and no send-time seam was needed. Pre-fix stored tokens die at their 72h TTL or earlier (used, request no longer pending, slot reassigned); no revocation migration ships because 18.0 is fresh-install only.",
"tests": "At head 63262a2 (origin/main 5a871e4 merged in): pnpm --filter @objectstack/plugin-approvals test under os-verify-lock, VERDICT command-exit 0, 82 files / 1116 tests passed; pnpm --filter @objectstack/plugin-approvals typecheck exit 0 (check:test-typecheck ledger unchanged). New pin src/reminder-action-link-at-rest.integration.test.ts (real ObjectQL + SqlDriver better-sqlite3, real plugin, real sys_notification object), 4/4 passed: 'no emit input and no stored row holds an action-page URL or the preimage of a live token', 'the data door serves the notification log with the reminder in it, and nothing redeemable', 'the reminder mints no token, and reaches its approver with the deep link to the request', 'the action page approves as the bound approver, without an override, and refuses a replay'. Updated: approval-service.test.ts 'remind: each concrete approver gets their own reminder, and none carries an action link (#24564)'; approval-notification-body.integration.test.ts (declared payload fields drop actions); sys-approval-token-generic-door.integration.test.ts (mints via issueActionTokens). Ablation at 63262a2 via scripts/ablation-replace.mjs (old mint-and-attach restored in remind(); anchor 1 to 0, blob a4f87cc6fbf6 to 0d12afd9c3e1, restored: blob == HEAD a4f87cc6fbf6, git diff HEAD empty): 7 failed / 377 passed across the 4 files, red in 3 (the 3 at-rest pins, the remind unit pin, 3 body-battery pins); positive control and token-door file stayed green as designed; the at-rest failure listed the action URL in both the emit input and the stored sys_notification row. Expected direction: turns red; observed: turns red. Subject resolves through relative src imports, so no dist rebuild was involved. Live check at 466a4fd on a fresh showcase boot (own port, torn down): after a remind, the stored sys_notification, sys_notification_delivery and sys_inbox_message rows each held 1 reminder row and 0 action-link hits; no unfixed live control leg (the ablation is the control).",
"gates": "Local derived set: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 63262a2 = 69 commands, each run with exit captured before any pipe: 69 x exit 0. dispatch-gates.mjs --ran at 63262a2: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN, exit 0 (includes check:type-check-debt re-measure OK, check:query-options-erasure 'ratchet holds', check:test-source-alias, check:nul-bytes, check:changeset-gate-self-tests, check-adr-0087-registration, check-changeset-no-major). Earlier run at 466a4fd needed one deepen (git fetch --unshallow, for check-plugin-teardown-shape --self-test's pinned fixture) and fuller builds for check:dts-closure / check:dual-build-cjs-loads / check:i18n; the 63262a2 run is the cited one. CI on PR 24620 head 63262a2 at report time: every check-run queued (Lint & Repo Gates, TypeScript checks: Type Check workspace/source gates/debt ledger/consumer gates, Test Core 1-6/6, Build Core, Governed Surface Queue Guard, Check Changeset, The card this PR closes must claim this branch, and the rest): in_progress, not awaited.",
"mcp_calls": "1 - mcp__claude-code-remote__add_repo (objectstack-ai/cloud, access read; refused: no access). 0 MCP GitHub tools.",
"api_writes": "2 - (1) POST /repos/objectstack-ai/objectstack/dispatches, fleet-write stroke fw-20261019T161830Z-9cd0b1: pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/24620/assignees [os-warren], landed as objectstack-fleet[bot]; dispatch.mjs answered UNCONFIRMED (exit 6, run still listed queued at 300 s) but read-back shows PR 24620 by objectstack-fleet[bot], draft, assignee os-warren, body byte-identical; not re-sent. (2) POST /repos/objectstack-ai/objectstack/dispatches, the stroke carrying this os-dev-report comment = POST /repos//issues/24564/comments. Zero label writes (none named; a changeset is present).",
"deviations": [
"test:repo (vitest repo project) not run locally: the diff touches none of its tests and the derived list does not name it; declared to CI (Test Core).",
"Cross-package consumers of @objectstack/plugin-approvals not re-tested: its public surface (exports, spec contract) is unchanged; ApprovalNotificationPayload is not exported.",
"The local branch claude/funny-albattani-eue8pk already existed at 5b14b7f (an ancestor of origin/main, no unique commits, no remote); it was reset onto origin/main with git worktree add -B.",
"Commit trailers use AGENTS.md's model-free pair, and the PR body uses its session-URL footer, not the harness reminder's model-named Co-Authored-By trailer or its footer text.",
"Not measured: whether any channel outside this repository reads payload.actions (objectstack-ai/cloud not reachable from this session)."
],
"open_questions": [
{
"question": "ADR-0043 now says more than the code: its Mechanics bullet says remind() gives each concrete approver their own approve/reject links, and its Non-goals say the links ride the messaging payloads. Prime Directive 13 wants the reversal recorded. How is it recorded?",
"options": [
"A: amend ADR-0043 with a status line (reminders carry no links; a raw token travels only in a message that is sent and not kept; one-tap links come back only with send-time minting for a channel that renders them). Cost: one status line and one bullet, a Tier H docs edit by the maintainer, minutes.",
"B: commission send-time minting now: messaging gains a send-time resolver approvals registers, the stored payload carries a token-free reference, and a chosen channel (email or IM) renders the links. Cost: a new cross-package contract in service-messaging plus a product decision on which channel gets reminder links; days, and it delivers nothing until that channel exists."
],
"recommendation": "A, because this PR restores the ADR's storage axis and removes nothing a recipient ever received; B is a feature with its own product decision and can supersede A later."
},
{
"question": "Released surface: npm @objectstack/plugin-approvals latest 17.7.0 and next 18.0.0-next.5 (npm view dist-tags) carry the pre-fix remind() (package CHANGELOG, 18.0.0-next.0 entry: 'Those links are still minted only by a reminder'). Is the producer fix backported to 17.x?",
"options": [
"A: backport the remind() change to a 17.x patch release. Cost: one small cherry-pick plus a changeset on the 17.x line, if that line is maintained.",
"B: ship in 18.0 only. Cost: nothing now; 17.x deployments keep the pre-fix reminder until they move to 18.0."
],
"recommendation": "A if a 17.x patch line is maintained, since the pre-fix behaviour recurs on every reminder; otherwise B. This is the release lane's call."
}
],
"out_of_scope_findings": [
"carrier: the PM seat that dispatched 24564 (session_01CfRDy7SuTfUqaTu2AEH2Hi) · docs/qa/platform-checklist/areas/approvals.json item approvals.email-action-token-door takes its token from the stored reminder payload; after this PR that capture is gone and no REST door mints a token, so the item needs a revision (its live mint leg becomes blocked(fixture) or moves to the pinned tests) · noted, not filed"
]
}
Activity
objectstack-fleet commentedon Oct 19, 2026
Triage: p1 +
security+target:v18·domain:services·area:workflow·pm:queue· put to the maintainer as a candidate 18.0 blocker · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-19T13:59ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. ⛔ This card carries no reproduction, and nothing added to it may (RUNNER rule 2, #9387).
security.domain:services:packages/plugins/plugin-approvals.6261853191on [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194). This card is not on it.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: maintainer-directed dispatch (18.0 QA wave)
Session:
session_01CfRDy7SuTfUqaTu2AEH2HiAccount:
os-warren(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/funny-albattani-eue8pkWorktree:
objectstack-issue-24564Domain:
domain:servicesSeat: none — the 18.0 QA wave's dispatch session, acting on the maintainer's direct instruction quoted below; not a lane seat
File surface:
packages/plugins/plugin-approvals/src/**;packages/services/service-messaging/src/**only if the fix belongs at that seam;.changeset/(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(TIER_DEFAULT —dispatch-gates.mjs --tieron this surface reads "no path-derived mandate · floor sonnet · default opus · ceiling fable")Clause-②: no — the change narrows what the platform persists; it is a runtime security-boundary change, so it sits on the human floor and its PR stays draft until the maintainer approves
Responsibility:
plugin-approvals (action-link issuance) | none known | an authenticated role below platform admin, through a public door — detail withheldThread-read: 6269014281
Serial constraints cleared: #24527 edits
approval-service.tsin disjoint hunks (resume / decide paths); this card's region is not among them — no serialization, whichever lands second merges mainDirected-by: the maintainer, verbatim 「你测试出的安全问题如果不方便写issue 的,你可以自己派发」, in Claude Code session
session_01CfRDy7SuTfUqaTu2AEH2Hi, 2026-10-19. The reproduction is handed to the dev inside that session; ⛔ nothing on this card or its PR carries it.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{
"issue": 24564,
"status": "done",
"branch": "claude/funny-albattani-eue8pk",
"pr": "#24620",
"session": "session_01CfRDy7SuTfUqaTu2AEH2Hi",
"premise_still_valid": true,
"summary": "remind() no longer mints ADR-0043 action tokens and no longer puts approve/reject links in the payload it hands to messaging, which messaging stores on sys_notification and sys_notification_delivery; each concrete approver still gets their own approval.reminder with the same title, body, deep link and dedup key. ApprovalNotificationPayload no longer accepts links (a call site handing one over does not compile), and issueActionTokens now states that a raw token travels only in a message that is sent and not kept. Delivery path, read at merge base de43524: the reminder names no channel, messaging falls back to inbox, the inbox row never carried actions and no channel in this repo reads payload.actions, so removing the links changes nothing a recipient sees and no send-time seam was needed. Pre-fix stored tokens die at their 72h TTL or earlier (used, request no longer pending, slot reassigned); no revocation migration ships because 18.0 is fresh-install only.",
"tests": "At head 63262a2 (origin/main 5a871e4 merged in):
pnpm --filter @objectstack/plugin-approvals testunder os-verify-lock, VERDICT command-exit 0, 82 files / 1116 tests passed;pnpm --filter @objectstack/plugin-approvals typecheckexit 0 (check:test-typecheck ledger unchanged). New pin src/reminder-action-link-at-rest.integration.test.ts (real ObjectQL + SqlDriver better-sqlite3, real plugin, real sys_notification object), 4/4 passed: 'no emit input and no stored row holds an action-page URL or the preimage of a live token', 'the data door serves the notification log with the reminder in it, and nothing redeemable', 'the reminder mints no token, and reaches its approver with the deep link to the request', 'the action page approves as the bound approver, without an override, and refuses a replay'. Updated: approval-service.test.ts 'remind: each concrete approver gets their own reminder, and none carries an action link (#24564)'; approval-notification-body.integration.test.ts (declared payload fields drop actions); sys-approval-token-generic-door.integration.test.ts (mints via issueActionTokens). Ablation at 63262a2 via scripts/ablation-replace.mjs (old mint-and-attach restored in remind(); anchor 1 to 0, blob a4f87cc6fbf6 to 0d12afd9c3e1, restored: blob == HEAD a4f87cc6fbf6, git diff HEAD empty): 7 failed / 377 passed across the 4 files, red in 3 (the 3 at-rest pins, the remind unit pin, 3 body-battery pins); positive control and token-door file stayed green as designed; the at-rest failure listed the action URL in both the emit input and the stored sys_notification row. Expected direction: turns red; observed: turns red. Subject resolves through relative src imports, so no dist rebuild was involved. Live check at 466a4fd on a fresh showcase boot (own port, torn down): after a remind, the stored sys_notification, sys_notification_delivery and sys_inbox_message rows each held 1 reminder row and 0 action-link hits; no unfixed live control leg (the ablation is the control).","gates": "Local derived set:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat 63262a2 = 69 commands, each run with exit captured before any pipe: 69 x exit 0.dispatch-gates.mjs --ranat 63262a2: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN, exit 0 (includes check:type-check-debt re-measure OK, check:query-options-erasure 'ratchet holds', check:test-source-alias, check:nul-bytes, check:changeset-gate-self-tests, check-adr-0087-registration, check-changeset-no-major). Earlier run at 466a4fd needed one deepen (git fetch --unshallow, for check-plugin-teardown-shape --self-test's pinned fixture) and fuller builds for check:dts-closure / check:dual-build-cjs-loads / check:i18n; the 63262a2 run is the cited one. CI on PR 24620 head 63262a2 at report time: every check-run queued (Lint & Repo Gates, TypeScript checks: Type Check workspace/source gates/debt ledger/consumer gates, Test Core 1-6/6, Build Core, Governed Surface Queue Guard, Check Changeset, The card this PR closes must claim this branch, and the rest): in_progress, not awaited.","mcp_calls": "1 - mcp__claude-code-remote__add_repo (objectstack-ai/cloud, access read; refused: no access). 0 MCP GitHub tools.",
"api_writes": "2 - (1) POST /repos/objectstack-ai/objectstack/dispatches, fleet-write stroke fw-20261019T161830Z-9cd0b1: pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/24620/assignees [os-warren], landed as objectstack-fleet[bot]; dispatch.mjs answered UNCONFIRMED (exit 6, run still listed queued at 300 s) but read-back shows PR 24620 by objectstack-fleet[bot], draft, assignee os-warren, body byte-identical; not re-sent. (2) POST /repos/objectstack-ai/objectstack/dispatches, the stroke carrying this os-dev-report comment = POST /repos//issues/24564/comments. Zero label writes (none named; a changeset is present).",
"deviations": [
"test:repo (vitest repo project) not run locally: the diff touches none of its tests and the derived list does not name it; declared to CI (Test Core).",
"Cross-package consumers of @objectstack/plugin-approvals not re-tested: its public surface (exports, spec contract) is unchanged; ApprovalNotificationPayload is not exported.",
"The local branch claude/funny-albattani-eue8pk already existed at 5b14b7f (an ancestor of origin/main, no unique commits, no remote); it was reset onto origin/main with git worktree add -B.",
"Commit trailers use AGENTS.md's model-free pair, and the PR body uses its session-URL footer, not the harness reminder's model-named Co-Authored-By trailer or its footer text.",
"Not measured: whether any channel outside this repository reads payload.actions (objectstack-ai/cloud not reachable from this session)."
],
"open_questions": [
{
"question": "ADR-0043 now says more than the code: its Mechanics bullet says remind() gives each concrete approver their own approve/reject links, and its Non-goals say the links ride the messaging payloads. Prime Directive 13 wants the reversal recorded. How is it recorded?",
"options": [
"A: amend ADR-0043 with a status line (reminders carry no links; a raw token travels only in a message that is sent and not kept; one-tap links come back only with send-time minting for a channel that renders them). Cost: one status line and one bullet, a Tier H docs edit by the maintainer, minutes.",
"B: commission send-time minting now: messaging gains a send-time resolver approvals registers, the stored payload carries a token-free reference, and a chosen channel (email or IM) renders the links. Cost: a new cross-package contract in service-messaging plus a product decision on which channel gets reminder links; days, and it delivers nothing until that channel exists."
],
"recommendation": "A, because this PR restores the ADR's storage axis and removes nothing a recipient ever received; B is a feature with its own product decision and can supersede A later."
},
{
"question": "Released surface: npm @objectstack/plugin-approvals latest 17.7.0 and next 18.0.0-next.5 (npm view dist-tags) carry the pre-fix remind() (package CHANGELOG, 18.0.0-next.0 entry: 'Those links are still minted only by a reminder'). Is the producer fix backported to 17.x?",
"options": [
"A: backport the remind() change to a 17.x patch release. Cost: one small cherry-pick plus a changeset on the 17.x line, if that line is maintained.",
"B: ship in 18.0 only. Cost: nothing now; 17.x deployments keep the pre-fix reminder until they move to 18.0."
],
"recommendation": "A if a 17.x patch line is maintained, since the pre-fix behaviour recurs on every reminder; otherwise B. This is the release lane's call."
}
],
"out_of_scope_findings": [
"carrier: the PM seat that dispatched 24564 (session_01CfRDy7SuTfUqaTu2AEH2Hi) · docs/qa/platform-checklist/areas/approvals.json item approvals.email-action-token-door takes its token from the stored reminder payload; after this PR that capture is gone and no REST door mints a token, so the item needs a revision (its live mint leg becomes blocked(fixture) or moves to the pinned tests) · noted, not filed"
]
}
fix(plugin-approvals): a reminder's notification payload carries no o…