docs(driver-memory): the $eq exemption's note says the null-list cells were RULED, not held - #16103
Conversation
…lls were RULED, not held (#15480) The `$eq`-only exemption above the no-value guard justified its operator-shaped spelling by naming the cells it declined to move — `$in: [null]` / `$nin: [null]` — as "#13357's cells, `needs-user-decision`, held for the maintainer". That sentence stopped being true on 2026-08-31, when the maintainer ruled them (option C) and the shapes became REFUSED at the contract's validation entrance (`assertListComparandShapes`), with the negative pin `memory-null-list-member-unreachable.test.ts`. This is not wording polish. `needs-user-decision` is a live protocol state with a named reader — the maintainer's inbox. A source comment asserting a cell sits in that state, when the door two packages over refuses the shape by ruling, invites the next author to re-escalate a settled question into the most expensive queue in this repo. The same package already stated it correctly, twice: the `$gte: null` note 34 lines below in this file ("the 2026-08-31 ruling had refused the three siblings"), and the sibling header in `memory-matcher-null-value-and-comparand.test.ts` ("They were `needs-user-decision` when #13494/#13495/#13549 landed"). So the package stated the decision state two ways, and the stale one is the one an author reading this guard arrives at first. The exemption's REASONING is deliberately unchanged: writing it over the OPERATOR rather than over "the comparand is null" is still right, because a rule spelled over the VALUE would still reach arms whose no-value answer is ruled elsewhere. Only the cells' state moved — refused at the door, rather than held. The note now says so, and says explicitly that the ruling is not a licence to re-spell the exemption. Comment-only. No arm, no guard condition, no behaviour: every added and removed line in the diff is a `//` comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
…ent-ruling-state Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 8 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 62c8f8509b4302d8b7bb1f33a9ddbd4291ff1703 && git checkout 62c8f8509b4302d8b7bb1f33a9ddbd4291ff1703
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b3820c3e20c2d57987e78ea6ab7f39b4780278d6 1ea6196138154c29f9c0ff2ea081ffa601abc12c && git checkout -B drift-repro b3820c3e20c2d57987e78ea6ab7f39b4780278d6 && git merge --no-ff 1ea6196138154c29f9c0ff2ea081ffa601abc12c
node scripts/docs-audit/affected-docs.mjs --json b3820c3e20c2d57987e78ea6ab7f39b4780278d6 |
Fixes #15480
What was false
The
$eq-only exemption abovecheckCondition's no-value guard justified its operator-shaped spelling by naming the cells it declined to move —$in: [null]/$nin: [null]— as "#13357's cells,needs-user-decision, held for the maintainer".That stopped being true on 2026-08-31. The maintainer ruled those cells (option C, #13357) and the shapes are now REFUSED at the contract's validation entrance (
assertListComparandShapes), with the negative pinmemory-null-list-member-unreachable.test.ts.This is not wording polish.
needs-user-decisionis a live protocol state with a named reader — the maintainer's inbox. A source comment asserting a cell sits in that state, when the door two packages over refuses the shape by ruling, invites the next author (human or AI) to re-escalate a settled question into the most expensive queue in this repo.The four sites, re-derived by text on this branch's base (not by line number)
The card measured at
e8c7956c4and triage re-took the readings at5315098;mainhas moved many times since, so all four were re-derived by symbol/text at1157e7b72:packages/drivers/driver-memory/src/memory-matcher.ts:312and those are #13357's cells,needs-user-decision, held for thepackages/spec/src/data/filter-comparand-shape.ts:92## Refused BY RULING, 2026-08-31: anulllist member (#13357)(with:502naming "the null-member carve-out (2026-08-31 ruling, #13357)" andnullListMemberErrorthrown at:509)packages/spec/CHANGELOG.md:2973e398863: feat(spec): refusenullin list-comparand positions —$in/$ninmembers and$betweenbounds (#13357, #13495)memory-matcher-null-value-and-comparand.test.ts:56They wereneeds-user-decisionwhen #13494/#13495/#13549 landed;The adjacency is the card's sharpest point and it holds, measured: the stale note sits at
memory-matcher.ts:309-313, and the same file states it correctly at:343— the$gte: nullnote's "the 2026-08-31 ruling had refused the three siblings". Thirty-four lines apart, in one file, and the stale one is the one an author reading the guard arrives at first.What changed, and what deliberately did not
The parenthetical is re-pointed the way
memory-matcher-null-value-and-comparand.test.ts:56andfilter-comparand-shape.ts:92already do: past tense for the held state, then the ruling, the door, and the negative pin.The exemption's REASONING is unchanged, deliberately. Writing the exemption over the operator rather than over "the comparand is null" is still right — a rule spelled over the VALUE would still reach arms whose no-value answer is ruled elsewhere. Only the cells' state moved: refused at the door, rather than held. The note now says that, and adds one sentence saying explicitly that the ruling is not a licence to re-spell the exemption — the opposite error, which a reader who now knows the cells are decided could otherwise make.
⛔ Nothing behavioural moves. No arm, no guard condition, no behaviour. No second pin was added:
memory-null-list-member-unreachable.test.tsalready covers that door.Comment-only, proven
Every added and removed line in the diff is a
//comment. Mechanically:prints nothing — no executable line moved. Diff is
1 file changed, 18 insertions(+), 2 deletions(-).Clause-② —
no, both limbs, instrument runBoth limbs of the test — does any exported symbol or signature move, and is any request newly accepted or rejected — are
no. The diff is prose, and this section says so rather than skipping the instrument.The instrument was run with the corrected resolution: every declaration file the package publishes, resolved from its
exportsmap andfiles[], not the rootdist/index.d.tsalone.@objectstack/driver-memorydeclaresfiles: ["dist", "README.md", "CHANGELOG.md"]and a single.export (types./dist/index.d.ts,import./dist/index.mjs,require./dist/index.js);dist/holds exactly six files, all six were hashed.Procedure: build at head → swap
memory-matcher.tsback to1157e7b72→ prove the mutation on disk (anchored grep counts flipped 1/0,git hash-object2644c3bbdvs HEAD blob4abcecf2d) → rebuild → hash → restoregit checkout HEAD -- ...→ prove the restore (blob back to4abcecf2d,git diff HEADempty,git status --porcelainempty) → rebuild → confirm the head snapshot reproduced byte-for-byte (a determinism control, so the one difference below is signal and not build noise).dist/index.d.tsdist/index.d.mtsdist/index.jsdist/index.mjsdist/index.js.mapdist/index.mjs.mapThe only movement is the two sourcemaps, whose embedded
sourcesContentcarries the comment text. No executable byte and no declaration byte moves.Why
skip-changesetand no changeset fileskip-changesetmeans "this PR declares no release of its own" (pr-automation.yml'schangeset-check), and here that is a measurement, not a judgement: against builtdist/**/*.d.tsanddist/**/*.{js,mjs,cjs}the published surface is byte-identical (table above), andcontent/docs/**is untouched. Nothing ships. The label is applied on this PR.Verification
Everything below was run at
1ea619613, this PR's head (the merge oforigin/main33e939ff3), after the final commit.pnpm --filter '@objectstack/driver-memory^...' build— exit 0 (dependency closure built first, so the test run is not reading staledist).pnpm --filter @objectstack/driver-memory build— exit 0.pnpm --filter @objectstack/driver-memory test— exit 0.Test Files 43 passed (43),Tests 1096 passed (1096). Identical to the pre-merge run at97923fdec(43/1096), i.e. the package's suite is unchanged before and after.pnpm --filter @objectstack/driver-memory typecheck— exit 0 (tsc --noEmitplustsconfig.typecheck.json, so the test files are compiled too).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack. Its provenance line readsderived from the tree of 'objectstack-ai/objectstack' at commit 1ea619613, change set1 path(s), andnone of the commit(s) this tree can SEE touched what this answer derives from. 38 runnable families; 37 pass.pnpm check:dual-build-cjs-loads— NOT MEASURED, not red. It exits 3 withPREREQUISITE NOT MET — this gate reads built output, and some package has no dist/(80 packages listed, none of themdriver-memory), i.e. it wants a whole-repopnpm build. That is CI's run. Declared narrowing, with the reason it cannot move: this gate reads packages' built CJS/ESM output, and this diff's own package emits byte-identicaldist/index.js,dist/index.mjs,dist/index.d.tsanddist/index.d.mts(measured above), while no other package's source is touched at all.pnpm lint— a declared narrowing, measuredThe repo-wide
eslint . --no-inline-configsweep is CI's run; what is delivered here is a narrowed one plus the proof the narrowing excluded nothing:eslint.config.mjsstates, with its own measurement and positive control, that this repo "runs oneeslint.config.mjs, which never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file" — confirmed by grep: noparserOptions.projectand noprojectServiceanywhere in it.--format json:npx eslint --no-inline-config --format json packages/drivers/driver-memory/src/memory-matcher.tslinted 1 file, 0 errors, 0 warnings, exit 0.Bytes
pnpm check:nul-bytespasses, and the changed file was additionally self-scanned for control characters (grep -naPover the C0 set plus DEL) with no hit.Not a duplicate
#13357, #13494, #13495 and #13549 are all in a closed state — they are the rulings and the behaviour repairs. None of those cards is "the comment recording the decision state is now false". This is also not folded into #14426: that card's fence is explicit ("the #13553 guard's comment only", three named prose sites), and this text is a fourth site outside it, present at PR #15464's merge-base.
Generated by Claude Code