Repository navigation
docs(adr): ADR-0094 D2 under-describes its own shipped code — the recovery doors project too, and D3 is no longer the only healing path #15244
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationpriority:p2Medium: important, M3Medium: important, M3
on Sep 4, 2026 Claim: PM loop — maintainer direct-dispatch channel (2026-08-10 standing authorization)
Provenance: maintainer, 2026-09-04, live chat, verbatim 「这几个问题你都帮我派发处理」 — dispatching the loose ends this session surfaced on the 17.3 review.
Session:6679d191-11f4-465b-b322-0e0409d76793
Branch:claude/issue-15244-adr-0094-recovery-doors-amendment
Worktree:objectstack-issue-15244
Domain: domain:engine (the record governspackages/metadata-protocol); ⛔docs/adr/**is a GOVERNED surface — draft PR, human merge, no auto-merge
File surface:docs/adr/0094-sys-permission-set-pure-projection.mdonly
Container & model: S,mode:subagent,model: opus— an amendment to an authorization-adjacent record; judgement default
Clause-②: no — documentation of shipped behaviour, no accept-set change
Serial constraints cleared: no open PR touchesdocs/adr/0094-*(REST search 2026-09-04); PR #14980, the voided predecessor, is closed and its branch is untouched — ⛔ do not reopen or reuse itos-dev-report
{ "issue": 15244, "status": "done", "branch": "claude/issue-15244-adr-0094-recovery-doors-amendment", "pr": "https://github.com/objectstack-ai/objectstack/pull/15249", "premise_still_valid": true, "summary": "Amended docs/adr/0094-sys-permission-set-pure-projection.md only (105 insertions, 2 deletions, one file). Added a dated `## Amendment (2026-09-04, #15244)` section in the record's own established style (the same shape its 2026-08-14 amendment already uses), plus two inline markers: an annotation on the D2 heading and a blockquote after D2's best-effort paragraph. Original sentences are left verbatim and marked, never restated; no decision changed, D1 untouched. The amendment enumerates all seven projecting doors in a table with state/body/response-surface per door, corrects the healing-path claim, and carries the ADR-0131 forward pointer. I re-verified every attribution against the seven call sites by computing each one's enclosing method rather than trusting the card, which turned up three refinements the card did not carry (below). PM half-state note: issue 15244 has no assignee (per contract I never set one); the PM claim comment 5536922803 is present and names this branch.", "tests": "No ablation and no test run: the diff is one Markdown file with zero code, so there is no behaviour to mutate or rebuild — saying otherwise would be fabricated evidence. Gate derivation instead: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` with no paths passed, letting it compute its own change set — it printed `change set derived from git — 1 path(s) vs merge base 1bc3c092a` and named 16 runnable families. All 16 run at final commit f2acdc322, each exit code captured by redirect-then-capture before any pipe, never through a pipe. 15 green on the first run: check-adr-links (+ --self-test), check-adr-symbol-anchors (+ --self-test), check:adr-anchors, check:doc-authoring, check:nul-bytes, check:pm-governed-merges, check:cross-package-test-inputs, check-ci-filter-parity, check-closing-keyword-parity (+ --self-test), check-comment-mask-corpus, check:refd-timer-probe, check:watch-hint-literal. NOT MEASURED then resolved: `pnpm --filter @objectstack/lint run check:doc-formula-expressions` answered exit 3 twice, the gate's own distinct code printing `PREREQUISITE NOT MET ... Nothing was measured ... It is NOT a finding` — first for an unbuilt @objectstack/formula, then for an unbuilt @objectstack/lint. Built both (turbo, through scripts/pm/os-verify-lock.sh) and re-ran to a real exit 0. The three gates the card called out are green; no `file.ts:NNN` anchor exists in the diff (verified by grepping the added lines for that shape: none). Control-byte self-scan clean. `pnpm lint` is a DECLARED NARROWING with all three pieces of evidence, not a skip: (1) population read from eslint's own config, every `files:` glob in eslint.config.mjs names only {ts,tsx,mts,cts,js,jsx,mjs,cjs} and no Markdown block exists; (2) count from --format json, running eslint on the changed file returns one entry, 0 errors, message `File ignored because no matching configuration was supplied`, i.e. zero files linted; (3) invariance, the diff touches no eslint config / TS source / tsconfig and no type-aware path reaches a .md file. VERIFY-LOCK DECLARATION, quoted verbatim from the entry point: `UNLOCKED (declared) - no usable flock on this host, so the shared verify lock was NEVER taken and NOTHING was serialized`. Both builds still exited 0; this is declared in the PR body as that script instructs. Re-check command in the PR body: `git grep -c \"this.runMutationProjector(\" -- packages/metadata-protocol/src/protocol.ts` answers 7.", "mcp_calls": "0 — the REST channel probe passed at minute one, so every GitHub read and write went through gh REST (`gh api`); no MCP GitHub tool was called. One channel switch to declare: `gh pr create` was refused with `GraphQL: API rate limit already exceeded for user ID 50353452` even though `gh api rate_limit` reported graphql 5000/5000 remaining and core 4611 remaining, so the PR was created with `POST /repos/.../pulls` and `draft:true` over REST core instead. The skip-changeset label went on through the additive endpoint `POST /issues/15249/labels`, then a comparison read-back returned [size/m, skip-changeset] — the size-labeler's own label plus mine, nothing stripped.", "open_questions": [], "out_of_scope_findings": [ "filed as #15247: two comments introduced by PR #14982 cite `ADR-0094 D3` for boot reconciliation, which is that record's D4 (D3 is the data-door write-through) — one in protocol.ts's rollbackMetaItem projector comment, one in the recovery-doors test file header; every other citation in the tree spells it D4. Deduped first with a targeted REST /search/issues query before filing." ], "measured_refinements_to_the_card": [ "The publish door is `runPublishSideEffects`, not `publishMetaItem`. The projecting call sits at that private helper's own body level (indent 8 in a method declared at indent 4, so unconditional), and the helper has TWO callers: publishMetaItem and publishPackageDrafts. The package-draft publish path has therefore been projecting undocumented. The card and the ADR both named only publishMetaItem.", "`projectionApplied` is surfaced at only four of the seven sites. saveMetaItem, the publish helper and both deleteMetaItem exits put an outcome on the response; rollbackMetaItem and both revertCommit limbs call and await the projector while declaring no such key on their response types (#14982 deliberately added none). D2's failure-surfacing sentence therefore holds at four sites, not seven — a second incompleteness the card did not name, now recorded in the amendment.", "The 'only healing path' defect is narrower than the card's phrasing. The ADR contains NO sentence attaching the word 'only' to boot reconciliation — I grepped every 'only'/'sole' occurrence. The actual claim is D2's best-effort clause ending 'boot reconciliation heals on next start'. And it was never strictly the only heal even before #14982: any later projecting door re-derives, because the projector re-reads the fresh layered effective body. The amendment says exactly that and keeps D4's boot pass as the guaranteed floor, rather than asserting the recovery doors falsified a claim the record does not make.", "The card's warning about the six pre-existing rollback/revert mentions checked out and cost nothing: `git grep -ci` returns 6, and none is about the rollbackMetaItem / revertCommit protocol methods — all six are the ADR-0005 rollback (#6483 / PR #6608) plus one unrelated 'reverting an admin's reclassification'. No stale sentence there, so none was touched.", "Forward-pointer attribution refined: ADR-0131's D2 is what names 'the sys_permission_set projector and reconciler of ADR-0094 D2/D4' as retired; its D3 retires the object itself (with D13); D14 puts it on the v18 line; C3 is the executing card (#15204). The card attributed all of it to D3." ] }
Generated by Claude Code
Landed. PR #15249 merged 2026-09-04T14:48:20Z by the maintainer's own hand-merge; merge commit
bcb6a17cf, confirmed onorigin/main— "docs(adr): ADR-0094 D2 names three projecting doors; the shipped code has seven". Verified on the merged tree: the amendment section is present and the seven-door table with it.docs/adr/**is a governed surface, so the human merge is this PR's review record (#9495 regime). It was opened draft with auto-merge off, exactly as that regime requires, and the maintainer merged it.What the record now says that it did not:
- The enumeration. D2 named
saveMetaItem/publishMetaItem/deleteMetaItem. The shipped code projects from seven call sites, and the amendment tables all of them with thestatepassed, the body handed over, and whether an outcome reaches the response. The count is enforced by a structural pin, and the amendment gives readers the one-line command to re-derive it. - Three things the three-name sentence hid, each measured on the call sites rather than inferred from method names: the publish door is the shared helper
runPublishSideEffectswith two callers, so the package-draft publish path had been projecting undocumented;deleteMetaItemprojects from two exits; andprojectionAppliedreaches the response at only four of the seven. - The healing-path correction, stated more precisely than the card asked for: the ADR contains no sentence calling boot reconciliation the only heal — the dev checked every
only/solebefore writing. The real claim is D2's best-effort clause, and any later projecting door re-derives, because the projector re-reads the fresh layered effective body. D4'skernel:readypass stays the guaranteed floor. - A forward pointer with correct attribution. ADR-0131 D2 names this projector and reconciler among the machinery that retires; D3+D13 retire the object; D14 puts it on the v18 line; C3 (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under
singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204) executes it. ⭐ The card body attributed all of that to D3 — the dev corrected it against the record.
One correction this card inherited and passed on: its own body said "D3 boot reconciliation", taken from the closure note on the voided PR #14980. In ADR-0094, D3 is the data-door write-through and D4 is boot reconciliation. The dev caught it and filed the two stale
ADR-0094 D3citations that PR #14982 introduced as #15247, left naked for triage.Why this card existed at all: #14982 shipped the code and its documentation half, PR #14980, was closed void on the maintainer's instruction — so the record under-described its own behaviour for a day. Not wrong, incomplete, which is the harder kind to notice.
pm:dispatchedstripped in the same stroke as the close;documentation,domain:engine,priority:p2,target:v17retained.- The enumeration. D2 named
docs/adr/**is a governed surface: draft PR, human merge. ⛔ The dev does not flip ready, does not enable auto-merge, and does not merge. The maintainer hand-merges.Why this card exists
PR #14982 merged as
95464ed65and put the ADR-0094 mutation projector behind the recovery doors. Its documentation half, PR #14980, was closed as void on the maintainer's instruction (「14980 作废关闭」, recorded on that PR by theos-muskseat). The code shipped; the record did not follow it. The maintainer, 2026-09-04, on being shown that consequence: 「这几个问题你都帮我派发处理」.So
docs/adr/0094-sys-permission-set-pure-projection.mdnow under-describes its own shipped code. Not wrong — incomplete, which is the harder kind to notice.Measured on
origin/mainbefore filingthis.runMutationProjector(has seven call sites inpackages/metadata-protocol/src/protocol.ts—:15504,:16737,:19516,:19621,:20015,:20571,:20739(declaration at:4981). Three are the original doors; four are recovery-door sites.protocol.recovery-doors-mutation-projector.test.ts:488pins the count structurally, so the number is enforced, not incidental.The ADR's D2 text still names three: "awaited inside
saveMetaItem/publishMetaItem/deleteMetaItem" (around:29-30and:97-98; ⛔ locate by sentence, line numbers rot). The shipped code also projects fromrollbackMetaItem, both limbs ofrevertCommit, anddeleteMetaItem's legacy raw-engine exit.git grep -ci "rollback\|revert"on that file returns 6, so the ADR does discuss rollback and revert elsewhere. Measure which sentences are actually stale before editing any of them — the defect is the projecting-door enumeration and the healing-path claim, not every mention.Scope
sys_permission_setand this projector in v18 via refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 (C3) — so this amendment describes a mechanism with one major to live. That sentence exists to stop the next reader from building on it.content/docs/releases/**; any code change; any change to D1 (which stands); reopening PR docs(adr): ADR-0094 D2 amendment — the recovery doors project too #14980 or its branch.Acceptance
The ADR's projecting-door enumeration matches the seven call sites on the tree, with a re-check command in the PR body that a reviewer can run (
git grep -c "this.runMutationProjector(" -- packages/metadata-protocol/src/protocol.ts, plus the structural pin's own name). The amendment carries its date and names ADR-0131's retirement.check-adr-links,check-adr-anchorsandcheck-adr-symbol-anchorsgreen — the last one refusesfile.ts:NNNanchors, so cite line numbers as bare prose numbers or not at all.Changeset: none — docs-only. Apply the
skip-changesetlabel; ⛔ an empty-frontmatter changeset is rejected by the gate (#4898).Refs: ADR-0094 · PR #14982 (
95464ed65) · PR #14980 (closed void) · #14415 · ADR-0131 D3 · #15204 (C3).