Skip to content

[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

@os-sales

Filed by the domain:spec PM seat, session session_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-313

// ⚠️ `$eq` ONLY, deliberately. The exemption is written over the
// OPERATOR and not over "the comparand is null", because the latter
// spelling would have moved `$in: [null]` / `$nin: [null]` with it —
// and those are #13357's cells, `needs-user-decision`, held for the
// maintainer. They are measured byte-identical across this change.

Why it is false on the same tree

Three independent records on origin/main say those cells were decided:

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-decision is 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:56 and filter-comparand-shape.ts:92 already 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 pin memory-null-list-member-unreachable.test.ts already 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)

Activity

  1. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    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,且负向 pin memory-null-list-member-unreachable.test.ts 已经罩住那道门)。⇒ p3。

    ⛔ 但不关掉,理由用卡面自己那句,它比"注释不准"强得多:

    needs-user-decision is 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。

    单独修,两条理由:

    1. [finding] once #14080's refusal lands, three prose sites still describe $gt: null as the one null comparand the contract accepts, and the TYPE door's undefined sentence 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/$lte on a NUMERIC column — null coerces to 0, and the live mingo path excludes it #13553 guard's comment only,三个点名的散文站点」,本文本是第四处,且存在于该 PR 的 merge-base,所以⛔ 不是那个 PR 的缺陷。把它折回去等于事后扩大一张已定范围的卡 —— 正是本仓反复吃亏的那件事。
    2. 它不需要等任何东西。 三份佐证已全部在 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

  2. claude commented on Sep 6, 2026

    @claude
    Contributor

    Claimed by the PM dispatch loop.

    Claim: session session_01ARYe3yQTQCUFm5qPYNgKaJ, branch claude/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-decision is 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 at 5315098, and main has moved many times since):

    1. packages/spec/src/data/filter-comparand-shape.ts:92 — ## Refused BY RULING, 2026-08-31: a null list member (#13357), with :314 pointing the nullListMemberError door back at it and :502 naming "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:null agree #13357)".
    2. packages/spec/CHANGELOG.md:2973 — feat(spec): refuse null in list-comparand positions — $in / $nin members and $between bounds (#13357, #13495).
    3. ⭐ The sibling header in this very package, memory-matcher-null-value-and-comparand.test.ts:56, already reads in the past tense: "They were needs-user-decision when 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: null as 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.ts already 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 $eq exemption 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 way memory-matcher-null-value-and-comparand.test.ts:56 and filter-comparand-shape.ts:92 already do. ⛔ Do not rewrite the exemption's justification.

    Lane note, so you do not second-guess it: the subject matter is a packages/spec ruling, but the characters to change are in driver-memory ⇒ domain:engine by the anchor rule. ⛔ Do not re-route to domain:spec because 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.

    ⚠️ This Clause-② 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 declared no for 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 for packages/spec. For any other package the published surface is its files[] + types (dist/**). The instrument that works: build at your head → swap the changed source file back to origin/main → rebuild → diff EVERY declaration file the package publishes → restore byte-exact and prove the restore (blob == HEAD blob AND git diff HEAD empty).
    ⚠️⚠️ Resolve those declaration files from the package's exports map and files[], ⛔ NOT from the root dist/index.d.ts alone. 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 in dist/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-test reddened origin/main's own push build and six PRs today; PR #16002 landed as f50c394da (verified by content on origin/main). If you cut from current main you will not see it. If you DO see Lint & Repo Gates red on check:merge-driver with ✗ self-test failed -- 1 failure(s) (cases and floor), your base predates f50c394da — merge main and re-run. ⛔ Do not skip, disable or quarantine anything over it.

    ⭐ Discipline notes earned the hard way today, all from real failures:

    1. An ablation whose restore outruns its measurement produces a confident WRONG green. A round hit exactly that — its mutating helper's own EXIT trap 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, or git hash-object != HEAD blob) and prove the restore after, in one shell.
    2. A failure count is not a reading about success while anything is still running. Read status explicitly. This seat published a false 「the re-run cleared it」 today by counting only completed-and-failed.
    3. 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.
    4. cancelled is not failed. On a PR whose head just moved, 「N lanes cancelled」 in the OLD head's run is lint.yml's concurrency: cancel-in-progress, not evidence about that head. Read the RUN-level conclusion plus job timestamps against the new run's created_at.
    5. The shared verify lock is contended. scripts/pm/os-verify-lock.sh returning 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.
    6. Shared-ref hazard: origin/main moves 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. tsup clears dist/ 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: a check:dts-closure exit 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 built dist/**/*.d.ts, dist/**/*.{js,mjs,cjs} and content/docs/**, not a guess.

    ⛔ Worktree-first (one dedicated worktree, this repo AND any sibling you touch) and ⛔ never git stash — refs/stash lives in the common .git dir, so all worktrees share one LIFO stack and your pop takes 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.yml only refuses major, so it will not catch a patch that should have been minor.


    Generated by Claude Code

  3. claude commented on Sep 6, 2026

    @claude
    Contributor

    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions