Uh oh!
There was an error while loading. Please reload this page.
docs(objectql): historicize the "delete only" example on HookEntry.dispatchUnscopedMultiWrite (#10172) - #10455
Conversation
…ispatchUnscopedMultiWrite` (#10172) The `HookEntry.dispatchUnscopedMultiWrite` doc block used `sys_attachment` as its worked example of how to express "delete only", asserting in the present tense that the guard "declares no update refusal" and says so "by declaring it on the `beforeDelete` registration and not on an update one". Both halves stopped being true when #10091 gave that guard's update verb a refusal of its own: on `main`, `attachment-access-hooks.ts` declares the flag on the `beforeUpdate` registration (:441) as well as the `beforeDelete` one (:473). The mechanism documentation is unchanged and was already correct — only the example's referent moved. The idiom is now stated without naming any live registration, and the `sys_attachment` reading is kept in the PAST tense against the two cards that bracket it (#9974 wrote it, #10091 falsified it). No live object replaces it: every registration that declares the flag today declares it on both verbs, so a fresh exemplar would only be the next referent free to move, and a past-tense claim about what two cards did cannot be falsified by the tree moving on. Comment only — no behaviour change, no test change. The `:2741` provenance passage ("#9719 commissions the `sys_attachment` restoration, #9974 the `sys_comment` update half") is deliberately left alone per triage: it is history, not a present-tense assertion about the live tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019yDEhPBC3tcGkW9bkce1HM
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not seeCoarse fallback — 14 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e91e44d960deec2ee888a1d31b81453e76602186 && git checkout e91e44d960deec2ee888a1d31b81453e76602186
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a03ac2808a9726f777daf813cbce006e96fbbc0e b54f1a44d3a8a6b24d6d2c9689ba1300eb1817d5 && git checkout -B drift-repro a03ac2808a9726f777daf813cbce006e96fbbc0e && git merge --no-ff b54f1a44d3a8a6b24d6d2c9689ba1300eb1817d5
node scripts/docs-audit/affected-docs.mjs --json a03ac2808a9726f777daf813cbce006e96fbbc0e |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10172
The
HookEntry.dispatchUnscopedMultiWritedoc block usedsys_attachmentas itsworked example of how to express "delete only". Both halves of that sentence stopped
being true when #10091 landed (PR #10169, squash
da891e0ef): onmain,packages/services/service-storage/src/attachment-access-hooks.tsdeclares the flag onthe
beforeUpdateregistration (:441, registration opens at:347) as well as on thebeforeDeleteone (:473). So the guard does declare an update refusal, and the flagis declared on an update registration.
The defect class is reference text pinned to a moving referent. This passage is not
mentioning
sys_attachmentin passing — it is the file's worked example for the idiom,so an author or agent reading it to learn how to say "delete only" is pointed at an
object that now declares both verbs.
Before —
packages/objectql/src/engine.ts:1358-1362After
Why the replacement cannot go stale the same way
Historicize (shape 1), with the idiom itself restated hypothetically (shape 2) so the
paragraph still teaches what it exists to teach. Deliberately not a swap to a
different live object, which would reproduce the defect with a fresh expiry date.
by two cards:
sys_attachmentsaid "delete only" this way, Decision: should an unscopedmulti: trueUPDATE get the whole-operation dispatch that #9719 gave DELETE? (the update half split out of #9798) #9974 wrote that down,sys_attachmenthas nobeforeUpdateauthorization guard at all — insert and delete are gated, update is not (the comment kit it was derived from gates all three) #10091 ended it. That is a claim about what two merged cards did. Nothing a future PRlands can make it false — the tree moving on is exactly the event it already describes.
beforeDeleteregistration and omit it fromthe update one" — names no object at all. It is a statement about the mechanism,
and it is true by construction of the dispatcher:
dispatchUnscopedMultiWriteHooksreads
this.hooks.get(event)and then filters that one event's entries on the flag(
engine.ts:2759-2763), so a registration declaring the flag on one verb gets thewhole-operation dispatch on that verb only. It goes stale only if the mechanism itself
changes, at which point the mechanism prose above it is being rewritten anyway.
object that declares the flag today declares it on both verbs" appears in the file. That
reads as supporting evidence but is itself a present-tense assertion about the live tree
— the very shape this card is about — and a third object declaring delete-only tomorrow
would falsify it.
Measurement on
origin/main(merge base46cfa5b4f)Every live declaration in the tree is on both verbs — there is no delete-only exemplar
left to point at, which is the second reason a live referent was not an option here.
Positive control on the grep: the flag appears 7 times in
attachment-access-hooks.ts,so it discriminates rather than matching everything.
Deliberately untouched
engine.ts:2741— "The #4757 unscoped multi-delete refusal on sys_attachment never fires through ObjectQL.delete — per-row hook dispatch bypasses it #9719 commissions thesys_attachmentrestoration, Decision: should an unscopedmulti: trueUPDATE get the whole-operation dispatch that #9719 gave DELETE? (the update half split out of #9798) #9974 thesys_commentupdate half". Per triage this stays: it is a provenance claim aboutwhat each card commissioned, i.e. history, not a present-tense assertion about the live
tree. Verified still accurate; not in this diff.
packages/services/**andattachment-access-hooks.ts— out of scope; nothing thereis wrong.
packages/objectql/src/search-companion.ts— a sibling dispatch is live in that file;this diff does not touch it.
packages/objectql/src/engine-unscoped-multi-write-dispatch.test.ts:20— reads "adelete-only guard still says 'delete only' by declaring it on
beforeDeletealone".Checked and left alone: it names no live object, so it is not the defect class.
Gates — all run at
b54f1a44d(the commit in this PR)Gate set re-derived from the change set with
node scripts/pm/dispatch-gates.mjs(no path arguments — the script takes the merge-base change set itself).
pnpm check:durability-log-level✓ durability-degradation log levels: 30 durability-critical catch seam(s), all loud, rethrowing or propagating to the caller (5 propagating, declared).pnpm check:slot-lookup✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new, and every file in the population parsed.pnpm check:stack-collection-maps✓ check-stack-collection-maps: 7 enumerations reconciled against 32 declared collections (16 waiver rows, each with a reason).node scripts/docs-audit/check-affected-docs.mjs✓ affected-docs self-test: 262 cases pass.pnpm check:nul-bytescheck-nul-bytes: OK (scanned 6136 text file(s) ...; no raw ASCII control bytes).pnpm --filter @objectstack/objectql typechecktsc --noEmit(script name echoed, so not a zero-match filter)eslint packages/objectql/src/engine.ts --no-inline-confignode scripts/check-engine-split-ratio.mjsis report-only (exit 0; the ADR-0076 OQ#5threshold is deliberately unset), and this commit moves its number, so both readings:
The drop is arithmetic, not a signal: this commit touches
engine.tsand nothing outsidepackages/objectql/, so it is the 30th engine-core commit in the 90-day window and the2nd that is not cross-package.
Tests: the change is comment-only, so the run was scoped (declared narrowing) to every
test whose input is
engine.tstext or the documented mechanism —engine-unscoped-multi-write-dispatch.test.ts,core-boundary.ratchet.test.ts,engine-middleware-operation-vocabulary.test.ts:3 passed (3)files,34 passed (34)tests. CI runs the full farm.
Changeset —
skip-changeset, on the repo's own ruleNo changeset. AGENTS.md Post-Task Checklist §3 asks for one "when the change is a feature
or functional improvement" and states that pure bug fixes do not need one; this is a TSDoc
comment correction with no behaviour change, no API change and no test change, so it
releases nothing of its own. That is route 2 of the changeset gate's own prescription
("it releases nothing → apply the
skip-changesetlabel"). Two precedents onmainforexactly this shape — a source-comment-only correction inside a published package landing
with zero changesets:
ce300c8d6(docs(spec), comment-only inobject.zod.ts) and97d2a03de(docs(plugin-auth), comment-only across 17 files).Generated by Claude Code