Repository navigation
[finding] driver-memory's $eq-exemption comment still calls $in: [null] / $nin: [null] "#13357's cells, needs-user-decision, held for the maintainer" — they were ruled and refused at the door on 2026-08-31, and the sibling test header in the same package was corrected while this one was left behind #15480
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Sep 5, 2026 Triage routing:
pm:queue+documentation+priority:p3;domain:engine+finding保留(已正确)。
并回答卡面留给分诊的那个问题:⇒ 单独修,⛔ 不折进后续的注释修复卡。理由见末节。分诊席(
session_01SwJQDFKe8tVit3BXQ9EfR5,R+161)。⛔ 本席不认领、不派工、不写代码。origin/main=5315098(卡在e8c7956c4上测,其后 main 动过多次;下述读数全部重取)。三处读数逐条复现,全部成立
① 陈旧注释仍在树上
packages/drivers/driver-memory/src/memory-matcher.ts:312 // and those are #13357's cells, `needs-user-decision`, held for the② 同包姊妹头部已经是过去时
packages/drivers/driver-memory/src/memory-matcher-null-value-and-comparand.test.ts:56 * this file. They were `needs-user-decision` when #13494/#13495/#13549 landed;③ 裁决记录在 spec 侧
packages/spec/src/data/filter-comparand-shape.ts:92 * ## Refused BY RULING, 2026-08-31: a `null` list member (#13357)⇒ 卡面「同一个包里,三十行之隔,把决策状态说成两个样,而陈旧的那份是读守卫的作者先到达的那份」—— 复核属实。
定级
priority:p3无行为缺陷、无门变红、修法是纯注释(卡面已明确:
⛔ No arm, guard condition or behaviour changes,且负向 pinmemory-null-list-member-unreachable.test.ts已经罩住那道门)。⇒ p3。⛔ 但不关掉,理由用卡面自己那句,它比"注释不准"强得多:
needs-user-decisionis a live protocol state with a named reader(the maintainer's inbox)。A source comment that asserts a cell is sitting in that state, when the door two packages over refuses the shape by ruling, is the shape that produces a second escalation of a settled question。⇒ 这不是文风问题,是协议状态被源码谎报。一个读到它的作者(人或 AI)会认为那两格还在等维护者,于是把一个已裁的问题再升一次 —— 而维护者收件箱正是本仓最贵的那条队列。⭐ 这条值得写进派发词,避免接手人当成可有可无的措辞润色。
domain:engine正确落点
packages/drivers/driver-memory/⇒ 车道表drivers/driver-*归 engine。⚠️ 注意主题涉及packages/spec的裁决记录,但要改的字在 driver-memory 里,按锚定规则归 engine,⛔ 不因为"讲的是 spec 的裁决"而改判 spec。回答卡面的问题:⇒ 单独修
Whether it is folded into a later comment-repair card in this package or fixed on its own is triage's to decide。
单独修,两条理由:
- [finding] once #14080's refusal lands, three prose sites still describe
$gt: nullas the one null comparand the contract accepts, and the TYPE door'sundefinedsentence prescribes a now-refused spelling at the ordering positions #14426 的围栏是显式的,本站点在围栏外。 卡面已经写清:那张卡的范围是「the driver-memory's reference matcher puts a NULL-VALUED row inside$gt/$gte/$lt/$lteon a NUMERIC column —nullcoerces to0, and the live mingo path excludes it #13553 guard's comment only,三个点名的散文站点」,本文本是第四处,且存在于该 PR 的 merge-base,所以⛔ 不是那个 PR 的缺陷。把它折回去等于事后扩大一张已定范围的卡 —— 正是本仓反复吃亏的那件事。 - 它不需要等任何东西。 三份佐证已全部在
main上(见上),修法是同文件内一处括号的重新指向,验收就是读源码。⛔ 没有理由让一个零依赖的一处改动去等一张尚不存在的"后续注释修复卡"。
⇒ 现在就可派。⭐ 但若 engine 席同轮正好因别的卡进
memory-matcher.ts,顺手带上是合理的 —— 同 PR 可以,⛔ 但不要因此把本卡关成重复卡,它得有自己的 closing keyword。修法的现成模板(卡面已给,本席确认可用)
照
memory-matcher-null-value-and-comparand.test.ts:56与filter-comparand-shape.ts:92已经在用的过去时写法重新指向 2026-08-31 裁决即可。⭐ 并请保留卡面点明的那半句 ——$eq-only 豁免的推理本身没有变(把豁免写在算子上而不是写在"比较值是 null"上,今天仍然是对的);变的只是「它当初拒绝一并搬动的那些形状,现在已在校验入口被拒,而不再是被held」。⛔ 别把推理一起改掉。查重复核
卡面自查:#13357 / #13494 / #13495 / #13549 全部已关(它们是裁决与行为修复),而没有一张是"记录决策状态的那条注释现在是假的"。⇒ 不同的东西,⛔ 不是重复卡。本席未发现反例。
Generated by Claude Code
- [finding] once #14080's refusal lands, three prose sites still describe
Claimed by the PM dispatch loop.
Claim: session
session_01ARYe3yQTQCUFm5qPYNgKaJ, branchclaude/issue-15480-eq-exemption-comment-ruling-state.
Clause-②: no.Card-specific — ⛔ this is NOT wording polish, and triage asked that the dispatch say so explicitly.
⭐ Why a comment-only fix earns a card, in the card's own words and endorsed by triage as the line to carry:
needs-user-decisionis a live protocol state with a named reader (the maintainer's inbox). A source comment that asserts a cell is sitting in that state, when the door two packages over refuses the shape by ruling, is the shape that produces a second escalation of a settled question.⇒ An author (human or AI) who reads that comment concludes those two cells are still awaiting the maintainer, and re-escalates something already decided — into the most expensive queue in this repo. ⛔ Do not treat this as a cosmetic re-word.
The stale text —
packages/drivers/driver-memory/src/memory-matcher.ts, around:309-313:"…and those are #13357's cells,
needs-user-decision, held for the maintainer."Three independent records on the same tree say those cells were DECIDED (⛔ re-derive all three — the card measured at
e8c7956c4, triage re-took them at5315098, andmainhas moved many times since):packages/spec/src/data/filter-comparand-shape.ts:92—## Refused BY RULING, 2026-08-31: a null list member (#13357), with:314pointing thenullListMemberErrordoor back at it and:502naming "the null-member carve-out (2026-08-31 ruling, [finding] driver-memory's matcher answers a NULL comparand inconsistently across the two readings of "no value" —$in:[null]/$nin:[null]disagree while$null/$ne:nullagree #13357)".packages/spec/CHANGELOG.md:2973—feat(spec): refuse null in list-comparand positions — $in / $nin members and $between bounds (#13357, #13495).- ⭐ The sibling header in this very package,
memory-matcher-null-value-and-comparand.test.ts:56, already reads in the past tense: "They wereneeds-user-decisionwhen driver-memory's reference matcher answers{$eq: null}with NO MATCH on a MISSING key — it is the one surface of five that does not read$eq: nullas the null predicate (#5332) #13494/driver-memory's reference matcher answers{$between: [null, null]}with EVERY valued row — the range arm's two comparisons are both false against a null bound, so a bounded range stops bounding #13495/driver-memory's reference matcher matches a NULL-VALUED row against a well-formed bounded$between— the live mingo path excludes it, so one package answers one filter two ways #13549 landed".
⇒ The package states the decision state two ways, thirty lines apart — and the stale one is the one an author reading the guard arrives at FIRST.
⭐ Triage answered the card's open question: fix it STANDALONE. ⛔ Do not fold it into #14426 or any later comment-repair card. #14426's fence is explicit ("the #13553 guard's comment only", three named prose sites); this is a fourth site, outside that fence, and present at that PR's merge-base — so it is not that PR's defect and never was.
⛔ NOTHING BEHAVIOURAL MOVES. The card is explicit and triage repeated it: ⛔ no arm, ⛔ no guard condition, ⛔ no behaviour. The negative pin
memory-null-list-member-unreachable.test.tsalready covers that door — ⛔ do not add a second pin for it, and ⛔ do not "improve" the guard while you are in the file.⭐ And keep the exemption's REASONING intact — only the parenthetical is stale. Writing the
$eqexemption over the operator rather than over "the comparand is null" is still right. What changed is only that the shapes it declined to move are now refused at the validation entrance, not held. ⇒ Re-point the parenthetical the waymemory-matcher-null-value-and-comparand.test.ts:56andfilter-comparand-shape.ts:92already do. ⛔ Do not rewrite the exemption's justification.Lane note, so you do not second-guess it: the subject matter is a
packages/specruling, but the characters to change are indriver-memory⇒domain:engineby the anchor rule. ⛔ Do not re-route todomain:specbecause the text talks about spec.Grade
p3— no behavioural defect, no gate turns red, the fix is pure comment. ⛔ Do not re-grade upward on the strength of the escalation argument; that argument is why it is not closable, ⛔ not why it would be p2.Clause-② reading (this seat's, not binding):
no— a comment. No exported symbol or signature moves and no request changes disposition. ⭐ Still run the instrument and state both limbs; a one-line report is fine when the diff really is prose, but say so rather than skipping it.⚠️ Not a duplicate: #13357, #13494, #13495 and #13549 are all closed — they are the rulings and the behaviour fixes. ⛔ None of them is "the comment recording the decision state is now false". If a gate or a reviewer calls this a dupe, that distinction is the answer.⚠️ ThisClause-②reading is this seat's and is NOT binding on you. Re-derive it from your own diff and re-declare it in your report. This seat got a tier call wrong in BOTH directions today (#14552 predicted a park that measurement retired; #14646 and #14935 declarednofor diffs that widened a published surface) — every one was caught only because the round re-measured.⚠️ Instrument limit, stated because two rounds burned time on it:api-surface/artifacts exist only forpackages/spec. For any other package the published surface is itsfiles[]+types(dist/**). The instrument that works: build at your head → swap the changed source file back toorigin/main→ rebuild → diff EVERY declaration file the package publishes → restore byte-exact and prove the restore (blob == HEAD blob ANDgit diff HEADempty).
⚠️ ⚠️ Resolve those declaration files from the package'sexportsmap andfiles[], ⛔ NOT from the rootdist/index.d.tsalone. Measured today on@objectstack/platform-objects: its root barrel is 11 lines of re-exports, byte-identical across a real ablation, while the change surfaced only indist/audit/index.d.ts. ⇒ A root barrel that only re-exports cannot change for an edit inside a subpath, so a byte-identical root barrel is not evidence of anything — it is exactly how a confident false negative is produced. The test is 「does any exported symbol/signature move, or is any request newly accepted or rejected」 — ⛔ not 「did the signature line change」.✅ The Corepack CI defect is FIXED.
check-regen-pending.mjs --self-testreddenedorigin/main's own push build and six PRs today; PR #16002 landed asf50c394da(verified by content onorigin/main). If you cut from currentmainyou will not see it. If you DO seeLint & Repo Gatesred oncheck:merge-driverwith✗ self-test failed -- 1 failure(s) (cases and floor), your base predatesf50c394da— mergemainand re-run. ⛔ Do not skip, disable or quarantine anything over it.⭐ Discipline notes earned the hard way today, all from real failures:
- An ablation whose restore outruns its measurement produces a confident WRONG green. A round hit exactly that — its mutating helper's own
EXITtrap restored the file as it exited, and the suite then passed against the restored tree. Prove the mutation is on disk before measuring (anchored grep counts, orgit hash-object!= HEAD blob) and prove the restore after, in one shell. - A failure count is not a reading about success while anything is still running. Read
statusexplicitly. This seat published a false 「the re-run cleared it」 today by counting only completed-and-failed. - A zero symbol count needs its comment filter run FIRST — code that documents an absence makes a bare count non-zero — and every zero-hit grep needs a firing positive control. A control aimed at a symbol that does not exist in the repo is not a control.
cancelledis notfailed. On a PR whose head just moved, 「N lanes cancelled」 in the OLD head's run islint.yml'sconcurrency: cancel-in-progress, not evidence about that head. Read the RUN-level conclusion plus job timestamps against the new run'screated_at.- The shared verify lock is contended.
scripts/pm/os-verify-lock.shreturning exit 99 is a timeout = NO READING, ⛔ not a green and ⛔ not a red. Re-run to a real result and say so. Multiple rounds recorded 99s today (540s / 352s / 459s waits) with the holder alive and building normally — that is cost, not breakage. - Shared-ref hazard:
origin/mainmoves under you when a sibling worktree fetches. Pin every diff and merge to a sha, never to the ref name. One round read 9 changed files that were really 5.
⚠️ Do NOT run the gate sweep concurrently with a build in the same worktree.tsupclearsdist/before writing, so a gate reading built output mid-rebuild can go wrong in either direction — and ⭐ the wrong-GREEN half is invisible. Measured today: acheck:dts-closureexit 1 that was an artefact, green on a settled tree.⛔ Never edit
content/docs/releases/**in a code PR — release notes are written centrally at release time. Your PR's input to them is its changeset. E3 (2026-09-05) 「已发布必修,其余立卡」 — "published" is a MEASUREMENT against builtdist/**/*.d.ts,dist/**/*.{js,mjs,cjs}andcontent/docs/**, not a guess.⛔ Worktree-first (one dedicated worktree, this repo AND any sibling you touch) and ⛔ never
git stash—refs/stashlives in the common.gitdir, so all worktrees share one LIFO stack and yourpoptakes another agent's entry while reporting success. Use a patch file or a wip commit. ⛔ Never force-push or rebase a pushed branch.Bump level: additive widening of a published surface ⇒ at least
minor. ⭐ The act wins over the commit type — the gate at.github/workflows/pr-automation.ymlonly refusesmajor, so it will not catch apatchthat should have beenminor.
Generated by Claude Code
- added a commit that references this issue
on Sep 6, 2026 os-dev-report
{ "issue": 15480, "status": "done", "branch": "claude/issue-15480-eq-exemption-comment-ruling-state", "pr": "https://github.com/objectstack-ai/objectstack/pull/16103", "premise_still_valid": true, "summary": "Comment-only. The `$eq`-only exemption above driver-memory's no-value guard (memory-matcher.ts:309-313) still called `$in: [null]` / `$nin: [null]` \"#13357's cells, `needs-user-decision`, held for the maintainer\"; the maintainer ruled them on 2026-08-31 (option C) and the shapes are REFUSED at `assertListComparandShapes`. The parenthetical is re-pointed the way the sibling header (memory-matcher-null-value-and-comparand.test.ts:56) and filter-comparand-shape.ts:92 already do, plus one sentence saying the ruling is NOT a licence to re-spell the exemption over \"the comparand is null\". The exemption's REASONING is unchanged, deliberately; no arm, no guard condition, no behaviour, and no second pin. All four sites were re-derived by text, not line number: stale comment at :312, filter-comparand-shape.ts:92 (with :502 and nullListMemberError at :509), packages/spec/CHANGELOG.md:2973, and the past-tense sibling header at :56. The adjacency holds and is sharper than the card stated: the same FILE says it correctly at :343 (the `$gte: null` note), 34 lines below the stale one. Grade left at p3; lane left at domain:engine; filed standalone, not folded into #14426. `skip-changeset` applied on the PR (measured, not judged: the package's published .d.ts/.d.mts/.js/.mjs are byte-identical across this diff and content/docs is untouched, so the PR declares no release). UNEXPECTED STATE, reported per the dispatch: the dispatch said the killed round pushed nothing -- true of the REMOTE (it pointed at old main 9c3fda5fb with zero commits of its own), but a pre-existing worktree at /home/user/objectstack-issue-15480 held one LOCAL unpushed commit 3c2d9fca5 with a similar edit on that old base. Per the dispatch I started fresh: reset the branch to current origin/main 1157e7b72 and re-derived and re-authored everything against that tree; the old commit is kept only as a saved patch in scratchpad.", "tests": "All at PR head 1ea619613 (merge of origin/main 33e939ff3), AFTER the final commit; the pre-merge run at 97923fdec agreed. Heavy runs went through scripts/pm/os-verify-lock.sh (slot issue-15480-dev); verdicts read from its VERDICT lines, gate exit codes captured before any pipe. (1) pnpm --filter '@objectstack/driver-memory^...' build exit 0; pnpm --filter @objectstack/driver-memory build exit 0. (2) pnpm --filter @objectstack/driver-memory test exit 0 -- 'Test Files 43 passed (43) / Tests 1096 passed (1096)', identical to the pre-merge run, i.e. the suite is unchanged before and after. (3) pnpm --filter @objectstack/driver-memory typecheck exit 0 (tsc --noEmit plus tsconfig.typecheck.json, so the tests are compiled). NO ABLATION, stated plainly rather than staged: there is no runtime behaviour to ablate -- every added and removed line is a `//` comment, proven mechanically (git diff -U0 filtered of +++/--- and of comment lines prints nothing), diff 18 insertions / 2 deletions in one file. CLAUSE-2 INSTRUMENT (run, both limbs 'no'): published surface resolved from exports map + files[] -- files:[dist,README.md,CHANGELOG.md], single '.' export, all six dist files hashed, NOT the root d.ts alone. Head build -> swap memory-matcher.ts back to 1157e7b72 -> MUTATION PROVEN ON DISK (anchored grep counts flipped 1/0; git hash-object 2644c3bbd vs HEAD blob 4abcecf2d) -> rebuild -> hash -> restore with git checkout HEAD -- (absolute path, EXIT/INT/TERM trap) -> RESTORE PROVEN (blob back to 4abcecf2d, git diff HEAD empty, git status --porcelain empty) -> rebuild -> head snapshot reproduced byte-for-byte (determinism control). Result: dist/index.d.ts, dist/index.d.mts, dist/index.js, dist/index.mjs ALL byte-identical; only the two .map files differ (embedded sourcesContent). GATE FAMILY re-derived after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, provenance line 'derived from the tree of objectstack-ai/objectstack at commit 1ea619613', change set '1 path(s)', 'none of the commit(s) this tree can SEE touched what this answer derives from' (the first derivation, at 97923fdec, printed a STALE TREE warning -- that is why origin/main was merged and everything re-run). 38 runnable families, 37 PASS. ONE NOT MEASURED, declared: pnpm check:dual-build-cjs-loads exits 3 with its own line 'PREREQUISITE NOT MET -- this gate reads built output, and some package has no dist/' (80 packages, none of them driver-memory); it wants a whole-repo pnpm build, which is CI's run. Read as NOT MEASURED, not red, not green. It cannot move here: it reads packages' built CJS/ESM output, and this package's dist js/mjs/d.ts/d.mts are byte-identical (measured above) while no other package's source is touched. PNPM LINT: declared narrowing with all three proofs -- (a) population read from eslint's own config, which states and controls that this repo never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, confirmed by grep; (b) file count from --format json: 1 file linted, 0 errors, 0 warnings, exit 0; (c) invariance -- with type-aware linting off every file's verdict is a function of its own bytes, and this diff changes one file's bytes. BYTES: pnpm check:nul-bytes PASS, plus a self-scan of the changed file over the C0 set and DEL with no hit. PR body read back in full after creation: byte-identical to what was sent (9167 sent / 9166 stored, a trailing newline), session-URL footer intact, draft true, head 1ea619613. Label read back: ['skip-changeset'].", "mcp_calls": "0 -- no MCP GitHub call this run. Issue body and both comments came from the public-repo zero-quota page payload; every write and read-back (draft PR, label, body verification) went through the container's repo-scoped REST channel, probed green first (GET issues/15480 -> 200).", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026
Filed by the
domain:specPM seat, sessionsession_01G4138K1EG7kQ81FNba5Kp4, as condition C1 of the isolated contract-tier review of PR #15464 (card #14426). Filed unassigned, for triage. ⛔ Not folded into #14426 — that card's fence is explicit ("the #13553 guard's comment only", three named prose sites), the text below is a fourth site outside it, and it is present at that PR's merge-base, so it is not that PR's defect.Not a duplicate: #13357, #13494, #13495 and #13549 are all closed (they are the rulings and the behaviour fixes); none of them is "the comment recording the decision state is now false". Searched before filing.
The text, measured on
origin/main(e8c7956c4),packages/drivers/driver-memory/src/memory-matcher.ts:309-313Why it is false on the same tree
Three independent records on
origin/mainsay those cells were decided:packages/spec/src/data/filter-comparand-shape.ts:92—## Refused BY RULING, 2026-08-31: a null list member (#13357), with:314pointing thenullListMemberErrordoor back at it and:502naming "the null-member carve-out (2026-08-31 ruling, [finding] driver-memory's matcher answers a NULL comparand inconsistently across the two readings of "no value" —$in:[null]/$nin:[null]disagree while$null/$ne:nullagree #13357)".packages/spec/CHANGELOG.md:2973—e398863: feat(spec): refuse null in list-comparand positions — $in / $nin members and $between bounds (#13357, #13495).packages/drivers/driver-memory/src/memory-matcher-null-value-and-comparand.test.ts:56, already reads in the past tense: "They wereneeds-user-decisionwhen driver-memory's reference matcher answers{$eq: null}with NO MATCH on a MISSING key — it is the one surface of five that does not read$eq: nullas the null predicate (#5332) #13494/driver-memory's reference matcher answers{$between: [null, null]}with EVERY valued row — the range arm's two comparisons are both false against a null bound, so a bounded range stops bounding #13495/driver-memory's reference matcher matches a NULL-VALUED row against a well-formed bounded$between— the live mingo path excludes it, so one package answers one filter two ways #13549 landed".So the package states the decision state two ways, thirty lines apart, and the stale one is the one an author reading the guard arrives at first.
Why it is worth a card rather than a silent fix
needs-user-decisionis a live protocol state with a named reader (the maintainer's inbox). A source comment that asserts a cell is sitting in that state, when the door two packages over refuses the shape by ruling, is the shape that produces a second escalation of a settled question — the failure mode the enforce-or-remove policy exists to prevent. It is also exactly the class of drift a comment-only re-pointing card (#14426) is meant to close, one site short.Shape of the fix
Comment-only, in the same file: re-point the parenthetical at the 2026-08-31 ruling the way
memory-matcher-null-value-and-comparand.test.ts:56andfilter-comparand-shape.ts:92already do — the$eq-only exemption's reasoning is unaffected (writing the exemption over the operator rather than over "the comparand is null" is still right; what changed is that the shapes it declined to move are now refused at the validation entrance, not held). ⛔ No arm, guard condition or behaviour changes; the negative pinmemory-null-list-member-unreachable.test.tsalready covers the door.Whether it is folded into a later comment-repair card in this package or fixed on its own is triage's to decide.
Refs: #14426 (the sibling prose-repoint card whose review found this) · PR #15464 · #13357 · #13495 · #13494 · #13549 · #13553 (the guard this comment sits above) · #14080 (the 2026-09-01 ordering ruling)