Skip to content

docs: comments outside driver-memory name what replaced the retired reference matcher (#20822 group 4) - #21395

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-20822-retired-matcher-pointers
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-20822-retired-matcher-pointers

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #20822
Clause-②: no

#20822 group 4, the card's last group: the comments and docblocks outside driver-memory that still named its retired reference matcher (memory-matcher.ts, retired by commit 8fec76a2b) as a live surface. This is the carry group 1's ACCEPT put on the card's last group PR. Claim: the PM's Claim: comment 5948997842 (branch claude/issue-20822-retired-matcher-pointers). Cross-lane declarations: spec seat post (5949027331) and services seat post (5949038856).

The seat confirms Fixes at ACCEPT. Hypothesis H1 (the site list is complete outside driver-memory) is falsified: 28 more sites outside the claim's file surface still name the matcher as live (25 comments and docblocks, one JSON ledger note, and 2 string literals in code). They are not edited here. They are listed under "Sites outside the claim's surface" below, and the route is the open question in the os-dev-report on #20822.

Base 11905a4f8b; origin/main db0cf2231b merged once (merge ff241ad71a, no conflict, no file in this diff). Head ff241ad71a. Net diff against main: 10 files, +68 / -35. Not governed.

What changes

Comment and docblock prose only. A sentence that named the matcher as a live surface now names what carries the semantics today, measured per site, or says the matcher is retired. Historical sentences stay.

Site Reading at base Action
spec filter-logic-conformance.ts:15 live: the backend table's "In-memory matcher / memory-matcher" row now "In-memory query path / driver-memory normalizeFilterCondition, then mingo", with the retirement in the same row (H3)
spec filter-comparand-shape.ts:127 live: "the matcher's own answers ... are sealed behind this refusal" past tense, plus the retirement
spec filter-comparand-shape.ts:142 and :144 live: "compares through JS coercion", "the matcher is not repaired" past tense, plus the retirement
service-analytics objectql-strategy.ts:1687 live: "driver-memory's matcher ... pin" {$not: {}} now driver-memory's query path (memory-driver-document-not.test.ts pins it)
service-analytics objectql-strategy.ts:2055 (the unlock read it at :2014) live: memory-matcher.ts "does" read $regex past tense, until $regex and then the matcher were retired
service-analytics filter-normalizer-not-null-safe.test.ts:50 live: points at the deleted memory-matcher-not-null-safe.test.ts now memory-driver-document-not.test.ts, which holds its cells
service-analytics objectql-contains-canonical-operator.test.ts:31, :103-:110, :118, :305 live: the mirror evaluates "the way memory-matcher.ts does", and "driver-memory's $regex arm is deliberate and serves a real producer" past tense; the producer's move to $contains is pointed at in filter-refusal.ts. :118 and :305 are in the same file but not in the unlock's list
service-storage attachment-read-visibility.test.ts:13 live: "Mirrors memory-matcher.ts and formula's matches-filter.ts" now mirrors formula's matches-filter.ts; the matcher is past tense
service-storage attachment-read-visibility.test.ts:326 live: points at the deleted memory-matcher-or-semantics.test.ts now memory-driver-filter-logic-conformance.test.ts, which holds its cases
plugin-security claim-seed-ownership.ts:91 live, and wrong before the retirement: the id IN (...) scan attributed to memory-matcher.ts now mingo's $in, which InMemoryDriver hands the list to (measured below)
formula matches-filter-not-null-safe.test.ts:17 live: points at the deleted memory-matcher-not-null-safe.test.ts now memory-driver-document-not.test.ts
formula matches-filter-not-null-safe.test.ts:120 live: the matcher "answers the opposite" the query path answers the same as this face (memory-driver-document-not.test.ts pins ['1']); the matcher answered the opposite until PR #13356 and is retired
docs/design/predicate-compilation-convergence.md:44, :56, :358 census rows anchored at 3711e0b763 past tense plus the retirement, as PR #21336 did for the F7 row

Read and left as they are, because each is already historical or not a claim about a live matcher: spec filter-logic-conformance.ts:184, :211 (a measurement table dated by its commits), :244, :480; :492 names memory-matcher-no-value-negated-operators.test.ts, which still exists under that name and holds the live path's cells; :503 and :509 are string literals in the past tense; filter-comparand-shape.ts:122-:124 (the reason for the 2026-08-31 ruling); formula matches-filter-icontains.test.ts:91 ("what the reference matcher was moved onto"); the design doc's :510 (the D6 decision row) and :570 (a commit-table row).

Measurements

H3, what driver-memory evaluates a filter with today. InMemoryDriver.find, count, updateMany, deleteMany and the others call convertToMongoQuery (memory-driver.ts:1421). It runs assertFilterConditionShape (filter-refusal.ts), then normalizeFilterCondition (memory-driver.ts:1600), and hands the result to mingo's Query. memory-driver-filter-logic-conformance.test.ts runs FILTER_LOGIC_CASES through InMemoryDriver.find, and check:driver-conformance holds it. So the spec table's in-memory row names the query path.

claim-seed-ownership.ts's id IN (...) sentence. normalizeFieldOperators' $in arm (memory-driver.ts:1862) passes $in through. mingo 7.2.4's $in predicate (operators/_predicates.js) is built once per query and called once per document; each call runs intersection([values, list]) (util/_internal.js), which fills a hash map from the whole list. So the sentence's "linear scan of the id list PER ROW" holds, through mingo, and the attribution to the matcher was wrong.

Code-token guard (PR #21357's two readings), base 11905a4f8b against the working tree at head, TypeScript 6.0.3. Reading 1 is the parser's leaf nodes from a forEachChild walk, so comments are trivia and JSDoc is never visited; a leaf that is not a token is re-scanned with trivia skipped. Reading 2 is the token stream from a getChildren walk, with JSDoc nodes skipped. Identifiers and string, template and numeric literals are compared in full.

  • Real run over all 8 touched .ts files: 26,645 base tokens (reading 2), 0 files with a token change (exit 0).
  • Comment control ("invents no second one" to "invents NO second one", objectql-strategy.ts): 0 files changed (exit 0).
  • Positive control, an identifier (filterNodeToCondition to filterNodeToConditionX, objectql-strategy.ts): DIFFER on both readings (exit 1).
  • Positive control, a string literal (a FILTER_LOGIC_CASES name gains an X, filter-logic-conformance.ts): DIFFER on both readings (exit 1).
  • Positive control, a numeric literal (MAX_BULK_PER_ROW_HOOK_ROWS / 2 to / 3, claim-seed-ownership.ts): DIFFER on both readings (exit 1).

Each mutation went through scripts/ablation-replace.mjs in wrap mode (anchor 1 to 0). Each restore was proven: blob equal to HEAD and git diff HEAD empty.

dist reach (H4), three legs plus a determinism leg. The five packages' dist files were hashed after each build. Every build exited 0 and ran under the shared verify lock.

All four legs ran at 3ba971f1b6; the later commits change no file in the five packages.

  • Leg 1: a turbo build of the five packages and their closure (20 tasks, all five cache misses).
  • Leg 2: the 8 changed files of the five packages back at their base blobs (8 of 8 proven equal), then each package's own build.
  • Leg 3: the 8 files restored (8 of 8 equal to their HEAD blob, git diff HEAD empty), then the same five builds.
  • Leg 4: @objectstack/spec's own build again. It equals leg 3 in all 230 files, so that build path is deterministic.
Package Changed Added non-test lines found verbatim in dist Leg 2 against leg 3 Changeset
@objectstack/spec 2 src files 1 of 8: the table row, in data/index.d.ts and data/index.d.mts 34 files differ: data/index.d.ts / .d.mts, 30 source maps (line offsets), 2 build-input hashes patch
@objectstack/service-analytics 1 src + 2 test files 5 of 5, in index.js / index.cjs (one also in index.d.ts / index.d.cts) 6 of 6 differ patch
@objectstack/plugin-security 1 src file 0 of 4 only index.js.map and index.mjs.map differ: line offsets, because the docblock grew by two lines; the maps carry no sourcesContent none
@objectstack/formula 1 test file none 0 differ none
@objectstack/service-storage 1 test file none 0 differ none

.changeset/20822-retired-matcher-pointers.md therefore declares patch for @objectstack/spec and @objectstack/service-analytics, comment text only, with Clause-②: no. Each changeset sentence maps to a diff line: the spec table row at filter-logic-conformance.ts:15, and the two ObjectQLStrategy comments at objectql-strategy.ts:1687 and :2055.

Gates and tests (head ff241ad71a)

  • Derived gates: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at ff241ad71a (10 paths against merge base db0cf2231) derived 89 commands. All 89 ran, each exit code captured before any pipe. 87 exited 0 on the first run. check:dual-build-cjs-loads and check:i18n exited 3 (PREREQUISITE NOT MET: unbuilt workspace packages), not a measurement. Both exited 0 after a whole-workspace build (turbo run build --filter=!@objectstack/docs, 72 tasks, VERDICT command-exit 0). --ran reports "89 derived, 89 run, 0 NOT-MEASURED, 0 UNRUN" and exits 0.
  • Tests, under the verify lock, vitest run --maxWorkers=2:
    • spec --project local: 598 files, 17,529 passed, 1 todo;
    • spec --project repo: 48 files, 849 passed;
    • formula: 43 files, 1,257 passed;
    • service-analytics: 167 files, 3,786 passed, 83 skipped;
    • service-storage: 41 files, 629 passed;
    • plugin-security: 159 files, 3,479 passed, 23 skipped.
  • Typecheck: pnpm --filter ... typecheck exits 0 for spec (with check:scripts-typecheck and check:test-typecheck), formula, service-analytics, service-storage and plugin-security. Formula, service-storage and plugin-security also run check:test-typecheck, and service-analytics' tsc --listFiles program holds 164 of its __tests__ files, both touched ones included.
  • Lint, as a proven narrowing: eslint --no-inline-config --format json over the 8 touched .ts files plus service-analytics/dist/index.js as a control gives 9 results, 0 errors and 1 warning: the control's ignore notice. None of the 8 is reported ignored, and each resolves under --print-config. eslint.config.mjs never enables type-aware linting (its lines 327-328 say so), so a comment edit cannot move the verdict on an untouched file. The repo-wide pnpm lint is CI's run.
  • Bytes: pnpm check:nul-bytes exits 0. A control-byte scan over the 10 changed files finds none.

Sites outside the claim's surface (round 0; superseded by patch round 1 below)

Read at base 11905a4f8b with every spelling: memory-matcher, reference matcher, in-memory matcher, memory matcher, match(), the memory-matcher-* test-file names, and "driver-memory's matcher". driver-memory, CHANGELOG.md and content/docs/releases/** are excluded. Each of these still names the matcher as a live surface:

  • service-analytics read-scope-not-null-safe.test.ts:43: points at the deleted memory-matcher-not-null-safe.test.ts. It is on group 1's list and inside the ACCEPT's "service-analytics test docblocks", but not in the unlock's list or the claim.
  • driver-mongodb mongodb-filter.ts:1151 ("it is the oracle both drivers agree with").
  • driver-turso remote-transport-boolean-identity.test.ts:43 and remote-transport-not-operator.test.ts:40.
  • formula matches-filter.ts:729 (asciiCaseInsensitiveContains is "the same one driver-memory's matcher ... call[s]").
  • objectql:
    • having-filter.ts:23, :26 and :2064;
    • having-filter.test.ts:8 and :60;
    • number-comparand-declared-type-door.ts:46 ("the memory matcher compares"; which face it means is ambiguous);
    • validation/record-validator.ts:533 ("five hand-rolled shape tests", one of them the matcher);
    • tsconfig.test.json:22 and test-typecheck-debt.json:3 (a JSON string).
  • plugin-security:
    • bootstrap-declared-capabilities.test.ts:39 and bootstrap-system-capabilities.test.ts:29;
    • rls-check-stored-form.ts:40, written after the retirement, so it probably means the query path.
  • service-analytics strategies/filter-normalizer.ts:450 ("the in-memory matcher ... already held to" the table).
  • spec:
    • filter.zod.ts:411, :940 and :3111 (live implementation-status tables), :1198, :1242 (the exported asciiCaseInsensitiveContains docblock) and :3142 (the $empty table);
    • filter-text-conformance.ts:342 ("both then and now");
    • ui/view.zod.ts:605 ("match() runs assertFilterConditionShape").
  • String literals, outside this group's form:
    • spec filter.zod.ts:470, the author-facing refusal for a null ordering comparand. Measured on the published schema door: FieldOperatorsSchema.safeParse({ $gt: null }) answers "... its reference matcher compares through JS coercion ...", in the present tense.
    • spec filter-operator-vocabulary.test.ts:86, an assertion message that prescribes editing the reference matcher.

The governed .claude/skills/pm-dispatch/references/compile-surfaces.md:16 stays on the seat post's protocol observation, as the ACCEPT placed it. 34 more hits outside the surface are historical (past tense, dated measurements, or string literals in the past tense). The os-dev-report on #20822 lists them.

Patch round 1 (head 54c8e70e88)

Section added by the domain:engine#1 seat, from the dev's patch-round report (5952834398 on #20822).

  • The seat's answer to round 0's open question: A, minus the two string literals (claim amendment 5950842658). The round corrects the 26 comment, docblock and ledger-note sites listed there, under the same rule.

    • read-scope-not-null-safe.test.ts:43 comes first, in its own commit 68ca26224f. It is the site inside group 1's ACCEPT carry that the seat's unlock had dropped, so every site that ACCEPT carried is now corrected, and Fixes #20822 stands.
    • The added surfaces are declared on the spec (5950854566) and services (5950863357) seat posts.
  • Not edited:

  • Commits:

    The net diff against main is 28 files, +152/−81, not governed.

  • Measured per site: each rewritten sentence names what carries the semantics today, or says commit 8fec76a2b retired the matcher. The probes behind the sentences that state a behaviour:

    • mingo's string ordering for filter.zod.ts:411;
    • mingo over the declared-number table for number-comparand-declared-type-door.ts:46;
    • InMemoryDriver's null-or-missing match for the two plugin-security bootstrap tests;
    • the callers of asciiCaseInsensitiveContains.

    Historical sentences are untouched.

  • Code-token guard (both readings, base = merge base 56238d890, so it covers the whole PR):

    • all 24 touched .ts files: 0 files changed;
    • the two JSON files through ts.parseJsonText, with test-typecheck-debt.json's _note masked: 0 changed. That _note is the text this round edits by design; an entries value control still DIFFERS under the mask;
    • controls (identifier, string, numeric, and JSON value) each DIFFER.
  • dist reach (three legs; the legs agree byte for byte in 276 of 276 files):

    package rewritten lines in dist entry
    @objectstack/spec 12 of 21: 9 in the filter declaration chunk and 3 in the data bundles; filter.zod.ts and view.zod.ts also ship as source patch, extended
    @objectstack/formula 3 of 3 patch, added
    @objectstack/objectql 3 of 19 patch, added
    @objectstack/service-analytics its round-0 entry stands; this round's line reaches only the source maps unchanged
    driver-mongodb, plugin-security maps only, or nothing none
    driver-turso test files only none
  • Tests and typecheck at 54c8e70e88:

    • the suites of spec, formula, objectql, driver-turso, driver-mongodb, plugin-security and service-analytics are green. driver-mongodb's real-mongod suites are not measured here: there is no mongod;
    • every touched package's typecheck exits 0;
    • gates: 95 derived, 95 run, 0 not measured.

Acceptance notes

  • Build path, not this diff. Leg 1 (turbo) and leg 3 (each package's own build) are both at the same text. They differ in 14 @objectstack/spec declaration files (api, automation, contracts, marketplace, system and two node-executor.zod chunks, .d.ts / .d.mts). Leg 3 equals leg 4 byte for byte, so each path is deterministic and the rewrite's effect was read on legs 2 and 3, which share a path. The difference between the paths is not explained here.
  • main moved after the merge. Seven more commits landed after db0cf2231b (to 69a12a0952 when this was written); none touches a file here. The derivation's only stale input among them is scripts/sdui-manifest.record.json. CI judges the merge ref.
  • Contract review is owed at tier: the path limb is packages/spec/src/**, non-test. The seat runs it.

Generated by Claude Code

claude added 3 commits October 2, 2026 09:31
…eference matcher

driver-memory's reference matcher (memory-matcher.ts) was retired by
commit 8fec76a. Sentences outside that package that still named it as a
live surface now name what carries the semantics today, or say it is
retired: driver-memory's query path (normalizeFilterCondition, then
mingo), formula's matches-filter.ts, or the driver-memory suites that
hold the deleted test files' cells (memory-driver-document-not.test.ts,
memory-driver-filter-logic-conformance.test.ts). Historical sentences are
unchanged. Comment and docblock prose only.

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
The table row in spec's filter-logic conformance docblock reaches
data/index.d.ts and data/index.d.mts, and both ObjectQLStrategy comments
reach service-analytics' JavaScript output (one also its declaration
file), so each package takes a patch entry. formula and service-storage
changed test files only, and plugin-security's rewritten docblock is not
in its dist.

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tests tooling labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 6 package(s): @objectstack/driver-mongodb, @objectstack/formula, @objectstack/objectql, @objectstack/plugin-security, @objectstack/service-analytics, @objectstack/spec, touching 5 documentable anchor(s). ⚠️ 11 changed file(s) yielded no anchor (packages/drivers/driver-mongodb/src/mongodb-filter.ts, packages/objectql/src/number-comparand-declared-type-door.ts, packages/objectql/src/validation/record-validator.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/objectql/query-syntax.mdx (via FILTER_TEXT_CASES (symbol, a top-level const object))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via ObjectQLStrategy (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 11 changed file(s) yielded no anchor (packages/drivers/driver-mongodb/src/mongodb-filter.ts, packages/objectql/src/number-comparand-declared-type-door.ts, packages/objectql/src/validation/record-validator.ts, …) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ecb6ca0258176466767588a6805363387c5777a6 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 06c1a6e1e17aed8c72dd76246262bd8d27296378 — the merge of head 54c8e70e88fb043f30ee71ab376726a3a7829528 into base ecb6ca0258176466767588a6805363387c5777a6, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 06c1a6e1e17aed8c72dd76246262bd8d27296378 && git checkout 06c1a6e1e17aed8c72dd76246262bd8d27296378
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ecb6ca0258176466767588a6805363387c5777a6 54c8e70e88fb043f30ee71ab376726a3a7829528 && git checkout -B drift-repro ecb6ca0258176466767588a6805363387c5777a6 && git merge --no-ff 54c8e70e88fb043f30ee71ab376726a3a7829528

node scripts/docs-audit/affected-docs.mjs --json ecb6ca0258176466767588a6805363387c5777a6

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs ecb6ca0258176466767588a6805363387c5777a6 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

claude added 4 commits October 2, 2026 10:57
…r-memory suite that holds the deleted matcher file's cells

The docblock pointed at memory-matcher-not-null-safe.test.ts, deleted with
driver-memory's reference matcher in commit 8fec76a. It now names
memory-driver-document-not.test.ts, which holds those cells on the query
path, and keeps the historical note that the suites were run while these
cases were written. Comment only.

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
…; spec's entry names the new docblocks

Measured with three legs at 57d675d: the round's rewritten lines
reach spec's filter declaration chunk, data/index.js and the shipped
src/**/*.zod.ts, formula's index.js/.mjs and objectql's index and core
bundles. driver-mongodb and service-analytics changed source maps only,
and driver-turso and plugin-security not at all, so they take no new
entry.

Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 54c8e70e88fb043f30ee71ab376726a3a7829528
Local-runs: none

Inputs: card #20822, its body and all 44 comments; PR #21395's body (round 0 plus the seat's "Patch round 1" section), its 28-file list, and the net diff against main at the merge base 56238d890d (28 files, +152/−81, read in full); the 42 check-runs on the head; for reference commit 8fec76a2b and #21397 (with its draft PR #21408); and, to settle the tail, #21109, #21238 (PR #21253) and #21242 (PR #21336). Everything below was read on GitHub and from git show / git grep at the head. Nothing was built, run or re-run; the dev's probes are named as probes where a sentence rests on one.

① Derived judgments

No accept set and no public surface moves — right. All 197 changed lines (old and new sides counted) across the 24 touched .ts files are comment text: 195 begin with *, // or /**, and the remaining two are the old and new spelling of one line, having-filter.test.ts:61 (base :60), where the code .toEqual(['c1', 'c2', 'c3']); is byte-identical and only its trailing // comment changed. The non-.ts changes are the changeset, three rows of the design doc, a // line in tsconfig.test.json's JSONC header, and test-typecheck-debt.json's _note.

packages/spec/src, every hunk read in its file context at the head:

  • filter.zod.ts, six hunks (new-side :407, :939, :1196, :1242, :3112, :3143): each sits inside a /** … */ docblock (the string-ordering note, the $icontains face table, the foldAsciiCase docblock, the asciiCaseInsensitiveContains docblock, and the $like / $ilike and $empty tables of the FILTER_OPERATORS docblock, whose closing */ is followed by export const FILTER_OPERATORS). No .describe(...) string, message, or name / note string is touched; nullOrderingComparandMessage at :466–:472 is not in any hunk.
  • view.zod.ts:602–:611: inside the scalar-arm docblock.
  • filter-logic-conformance.ts:15: the backend table row of the header docblock; no case name or note.
  • filter-comparand-shape.ts:124–:132 and :140–:150: both in the header docblock; nullOrderingComparandError's string is untouched.
  • filter-text-conformance.ts:339–:344: confirmed a // comment block between two elements of the FILTER_TEXT_CASES array literal (the \' inside it is the block's pre-existing habit, a backslash inside a comment, not a string delimiter); the neighbouring name:, filter:, expected: and note: strings are unchanged.

Sampled and read in the other packages (more than 12): driver-mongodb mongodb-filter.ts:1148; driver-turso remote-transport-boolean-identity.test.ts:40 and remote-transport-not-operator.test.ts:37; formula matches-filter.ts:726, matches-filter-not-null-safe.test.ts:14 and :118; objectql having-filter.ts:20 and :2061, having-filter.test.ts:5 and :61, number-comparand-declared-type-door.ts:43, record-validator.ts:529, tsconfig.test.json:18; plugin-security claim-seed-ownership.ts:87, rls-check-stored-form.ts:37, both bootstrap tests; service-analytics objectql-strategy.ts:1684 and :2052, filter-normalizer.ts:447, the three tests; service-storage attachment-read-visibility.test.ts:10 and :323. Comment prose only, every one.

test-typecheck-debt.json: the diff is exactly one line, line 3, the authored _note; entries is untouched — right.

The rewritten sentences, judged against the code at the head:

  • driver-memory's query path is convertToMongoQuery (memory-driver.ts:1421) running assertFilterConditionShape (:1452) then normalizeFilterCondition (:1454, defined :1600), and the result is handed to mingo's Query at every verb (:726, :960, :1032, :1077, :1324, :1387) — so the spec table row, the view.zod.ts sentence, the two turso tests, rls-check-stored-form.ts, the bootstrap tests, filter-normalizer.ts and having-filter.test.ts:61 credit the right face. Right.
  • asciiCaseInsensitiveRegexSource is what the query path (memory-driver.ts:1789) and the analytics face (memory-analytics.ts:1673) fold through; asciiCaseInsensitiveContains is called outside spec only by formula/matches-filter.ts:742 and objectql/having-filter.ts:2101. So the $icontains table row, the asciiCaseInsensitiveContains docblock and matches-filter.ts:729 are right, and "five JS evaluation faces" (query path, analytics face, driver-mongodb, having, formula) is the right count with the sixth named as retired.
  • The record-at-a-time faces at having-filter.ts:2064: the arm is $notContains (the [#5905] mirror), and the sentence names formula and the walker as answering it, with the matcher in the past tense. Right.
  • memory-matcher.ts, memory-matcher-not-null-safe.test.ts and memory-matcher-or-semantics.test.ts are absent at the head; memory-driver-document-not.test.ts (its header names the not-null-safe fixture it holds), memory-driver-filter-logic-conformance.test.ts (runs every FILTER_LOGIC_CASES case, including 'empty $or is FALSE — the OR identity', through InMemoryDriver.find; its header names the deleted or-semantics copy), memory-operator-key-clobber.test.ts (its header: scored against the matcher's literal answer "and still is") and memory-matcher-no-value-negated-operators.test.ts (still named by filter-logic-conformance.ts:492) all exist. The pointers to them are right.
  • $null: true lowers to $eq: null (memory-driver.ts:1851) and value comparisons take the storage form (:1856–:1862, put(op, store(val))); implicit equality goes through toStorageForm. So "absent folds into null, as driver-memory's query path reads $null" and "pair the two" are right. memory-matcher-null-value-and-comparand.test.ts:291–:293 pins $null: true on both the missing and the nulled fixture.
  • Historical attributions: PR fix(driver-memory): a no-value row satisfies $nin / $notContains in the reference matcher (#13166) #13356 (178325bcbe) is the commit that changed the matcher's $nin / $notContains answer; $regex on driver-sql is not a regex — it compiles to a substring LIKE, so it both over-matches and silently matches nothing #4706 retired $regex (filter-refusal.ts:314, :705); the [#5702] note over SUPPORTED_FIELD_OPERATORS is at filter-refusal.ts:303. Right.
  • Nothing rewritten keeps the matcher live in the present tense: every "matcher" sentence in the diff is past tense, a retirement note, or names the query path.
  • Wrong: record-validator.ts:534–:540, the count "four since commit 8fec76a2b retired the matcher". At the head the retirement did not leave four: memory-driver.ts, the query path itself, carries two non-comment startsWith('$') shape tests (:1637, if (key.startsWith('$')), and :1644, Object.keys(value).some(…) over k.startsWith('$'), the exact shape the docblock describes), and the same idiom lives in filter-normalizer.ts, native-sql-strategy.ts and analytics-carrier-filter.ts. The enumeration was already incomplete when it was written (the memory-driver test dates from 06ba036270, 2026-08-06; the "five hand-rolled" sentence from a682670b1e, 2026-08-07), so this PR inherited an undercount rather than creating one — but the rewritten sentence states "four" as the present count, and the dev's own measurement checked only that the four named files still carry the test, not that no other does. The kept "Writing a sixth startsWith('$') here" is stale by the same token. This line does not reach dist by the body's probe (objectql: 3 of 19 lines ship, all from having-filter.ts), so it is not a contract face; it is escalated in ③.
  • Inexact, not wrong: the design doc's F4 rows (:44, :358) now say "retired" but keep the census count "20 test files", where the retirement's own record (8fec76a2b, H4) measured 21; the untouched D6 row at :510 says 20 as well. Unpublished; noted in ③.

Sentences that rest on the PR body's stated probes, not re-measured here: the dist reach (the .d.ts table row; the 9 declaration-chunk and 3 data-bundle lines in spec; the service-analytics, formula and objectql JavaScript lines), mingo's $in building its hash from the whole list per document (claim-seed-ownership.ts), mingo ordering strings (filter.zod.ts:411) and comparing 12 against "12" without coercion (number-comparand-declared-type-door.ts). Each is consistent with the code read: the query path passes $in through as put('$in', store(val)) and hands the document to mingo, which is the evaluator each sentence names. mingo is not installed in this checkout, so its source was not read.

② Semver level

  • .changeset/20822-retired-matcher-pointers.md: @objectstack/spec, @objectstack/service-analytics, @objectstack/formula, @objectstack/objectql, each patch, Clause-②: no — right. No export, type, message, status or runtime answer moves (①), so no entry may be above patch, and Clause-②: no holds. Each package named ships a rewritten line by the body's three-leg table: spec in data/index.d.ts / .d.mts, the filter declaration chunk and the data bundles, and filter.zod.ts / view.zod.ts also ship verbatim (package.json files carries src/**/*.zod.ts, read at the head); service-analytics in index.js / index.cjs; formula 3 of 3 lines in index.js / index.mjs; objectql 3 of 19 in index.js / .mjs / core.js / .mjs.
  • Owed and missing: none. driver-mongodb (0 of 4 lines; two source maps' offsets), plugin-security (0 of 1; nothing), driver-turso and service-storage (test files only) and docs/design (unpublished) publish no rewritten text; a map offset is not text an upgrader reads.
  • Every changeset sentence maps to a diff line: the backend table row → filter-logic-conformance.ts:15; the $icontains table → filter.zod.ts:942; the $like / $ilike and $empty retirement notes → :3115 and :3146; "five JS evaluation faces where it counted six" → :1199–:1202 (and :1209); the asciiCaseInsensitiveContains callers → :1245–:1246; the string-ordering note → :410–:413; convertToMongoQuery runs assertFilterConditionShape → view.zod.ts:605–:608; the FILTER_TEXT_CASES comment → filter-text-conformance.ts:342–:344; the two ObjectQLStrategy comments → objectql-strategy.ts:1687 and :2055–:2058; the formula $icontains arm → matches-filter.ts:729–:731; the having $notContains arm → having-filter.ts:2064–:2066; "Comment only: no export, type, error code, status, message text or runtime behaviour changes" → ①. The one clause not checkable read-only is which dist file each line lands in; it is the body's probe and is internally consistent with the three-leg table.
  • PR body: line 1 Fixes #20822, line 2 Clause-②: no — right. "Patch round 1" states the net diff as 28 files, +152/−81, which matches git diff --stat at the merge base.

③ Boundary flags

Does Fixes #20822 leave an open tail? No. Every carry the thread put on "the last group" or "the card's close", read in the card's own words:

Carry, where it was placed Where it is done
ACCEPT 5915623622: spec filter-logic-conformance.ts:15, "with a spec patch entry for the shipped line" this PR, :15; changeset @objectstack/spec: patch
ACCEPT 5915623622: formula, service-analytics and service-storage test docblocks this PR: formula matches-filter-not-null-safe.test.ts:17 / :120; service-analytics filter-normalizer-not-null-safe.test.ts:50, read-scope-not-null-safe.test.ts:43 (dropped by unlock 5947980479, restored by amendment 5950842658, commit 68ca26224f), objectql-contains-canonical-operator.test.ts:31 / :103–:110 / :118 / :305; service-storage attachment-read-visibility.test.ts:13 / :326
ACCEPT 5915623622: plugin-security claim-seed-ownership.ts:91 this PR
ACCEPT 5915623622: service-analytics objectql-strategy.ts:1929 (:2055 at base) this PR, plus :1687 in the same file
ACCEPT 5915623622: the design doc's F4 row this PR, :44, :56, :358
ACCEPT 5915623622: governed .claude/skills/pm-dispatch/references/compile-surfaces.md:16 by the ACCEPT's own sentence it "stays on the seat post's protocol observation", not on this card's PR; the claim and unlock repeat the exclusion. Not a tail of the card. It still names the matcher as live and can land only through a Tier S record — named here so the seat post's observation is not lost
Patch decision 5935317796 (12): the group-1 carry "must be named on the claim of the PR that deletes F7 … because that PR is this card's last" PR #21336 did not carry it; this PR's claim 5948997842 and amendment 5950842658 name it, and this PR is now the card's last
Landing 5936605972: F7's deletion #21242, PR #21336 → 7aab75920 (landing 5947944849; lteBound 0 hits at the squash)
Landing 5936605972: "the scalar-wrap fold, if #21109's claim did not take it (5935014513)" #21109's claim 5938708535 did not take it: PR #21235's own table reads tags: 'x' under contains('x') as "403 (not folded)". It was filed as #21238 (route A, triage 5940327789), landed as PR #21253 → d2bc644f20 (an ancestor of origin/main; contract review PASS 5942463292), and #21238 is closed completed. Done, by a named carrier
ACCEPT 5923842206 (group 2): the group-3 stale pointers (having-filter.ts:1365, filter.zod.ts:434/734/750, the seam-test title) group 3b, PR #21196 → e18fea6dc
ACCEPT 5923842206: #20987's Turso-remote $contains item stays on #20987 (5923177087), then #21178 (5935317796 (11)); not this card's
ACCEPT 5923842206: the objectql CHANGELOG residue of the step-2 sentence placed on "a docs-only wording PR or the release compile"; CHANGELOG.md is release-owned (AGENTS.md), and the group-2 changeset supersedes both sentences; not placed on this card's close
ACCEPT 5926676602 (group 3a): #20987's engine faces, query-syntax.mdx's sentence, the F1 pointers group 3b, PR #21196
5935317796 (4), (9); ACCEPT 5935777268's acceptance-note items acceptance notes with no carrier, or a lane carrier (domain:services at its next RLS-seam claim for (9)); none placed on this card's close
Group 1 report: $exists non-boolean #20897
#21242's residual rows #21299 (5947595627)

Nothing placed on the card's close is open, so Fixes #20822 is right. Two adjacent items are not tails of this card and are named so the seat does not mistake them for one: #21397 (the two string literals, a separately filed domain:spec defect, claimed 5951484481, draft PR #21408 open) — the ACCEPT's carry says "comments", and string literals were never in it; and the governed compile-surfaces.md above.

The round-1 deviations (1)–(9), from 5952834398: (1) the container restart killed leg B after three builds; its restore trap never ran, but the tree was found clean (18 of 18 round files equal to HEAD) and legs B and C were re-run, with A equal to C in 276 of 276 files — a measurement fact, no effect on the diff. (2) a lock queue timeout, re-queued — none. (3) a mistyped base sha failed loudly before anything was measured — none. (4) filter.zod.ts:1208 "six faces" to "these faces" is in the diff and is the companion of the five-count at :1198; the changeset covers both under the foldAsciiCase docblock — right. (5) record-validator.ts keeps "Writing a sixth" — wrong together with the count, see ① and the escalation below. (6) test-typecheck-debt.json's _note is a JSON string, so the unmasked JSON guard differs by construction and the masked guard is the one that counts; the diff is that one line — right. (7) filter.zod.ts:3106 not edited — judged next. (8) main was merged once; the merge base is still 56238d890d, origin/main has since moved to 6d67ad5eca, and the PR reads mergeable_state: clean; CI judges the merge ref. (9) two wip(…) commit subjects — the PR squash-lands and the seat sets the squash message; the branch history is not a contract face.

The excluded filter.zod.ts:3106. Read in context: "Membership here is what makes driver-memory's SUPPORTED_FIELD_OPERATORS ACCEPT a name; adding $like before that driver's matcher has an arm would turn its loud refusal into a dropped predicate". This is staging reasoning about the mechanism (an accepted name with no evaluator arm drops the predicate), written when driver-memory had two evaluators; "matcher" is generic there and the query path has the arm, as the table under it says. It is not a present-tense claim that memory-matcher.ts is live, so leaving it is defensible. A one-word change ("evaluator" or "query path") would remove the ambiguity; an acceptance note, not a block.

The two string literals moved to #21397. filter.zod.ts:466–:472 (nullOrderingComparandMessage) is in no hunk, and filter-operator-vocabulary.test.ts is not in the file list, so this PR stays comment-only and the token guard's "0 files changed" claim is consistent with the diff I read. #21397's claim measured its lines disjoint from this PR's hunks in both shared files; whichever lands later merges main.

Check-runs on 54c8e70e88, read at 2026-10-02 after 13:00Z: 42 runs, all completed: 37 success, 5 skipped, 0 failure, 0 pending. The skips are Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in), and the 12:58Z reruns of Auto Label and Check PR Size (a PR-edit event re-ran the seven PR-shape checks at 12:58Z; the 11:44Z runs of both are success). Check Changeset, Governed Surface Queue Guard, Lint & Repo Gates, Spec property liveness, Temporal Conformance (live PG + MySQL), all six Test Core shards and the four Type Check jobs are success; Part-of PR must not also close its card and The card this PR closes must claim this branch are success, consistent with Fixes. Combined status success (Vercel: canceled by ignored build step).

Escalated:

  1. record-validator.ts:534–:540: the present-tense count "four since commit 8fec76a2b retired the matcher" and the kept "Writing a sixth" are wrong at the head (①). One comment line; not a contract face and not in dist. Carrier: the seat — either a patch commit on this branch (which moves the head, so a delta record would then be owed on the new head) or the next objectql claim that touches record-validator.ts. The seat decides which; this record does not block on it.
  2. The design doc's F4 rows keep "20 test files" where the retirement record counted 21. Acceptance note; the same carrier if the seat takes item 1 now.
  3. filter.zod.ts:3106's generic "that driver's matcher". Acceptance note.

Implemented-by: claude/issue-20822-retired-matcher-pointers
Reviewed-by: session_017xfMoEjKUuSh2xYB8sCozp

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 13:16
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 13:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants