Skip to content

refactor(driver-memory): retire the reference matcher and the cube face's own whole-day bound (#5930 step 4, group 1: F4 + F5; F3 stopped) - #20895

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-20822-g1-driver-memory
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-20822-g1-driver-memory

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20822
Clause-②: no

#5930 step 4, group 1 of 3 (driver-memory), under ruling 5902355785 (D4 (b), D6) and ADR-0053 D-D1 as amended. Two of the three faces land here: F5 and F4. F3 is stopped and reported. Its deletion moves answers on a production direct caller and on engine-seamed reads, measured below. The card says "If routing it changes an answer, that face stops and is reported". This PR does not touch memory-driver.ts's four whole-day sites.

What lands

Face Change Typed column reader
F5 MemoryAnalyticsService (cube face) lteUpperBound (the filter copy, 2 of the design's 5 call sites) is deleted. The lte row on both exits (mingo $match and SQL echo) now compiles the comparison it is handed. The storageForm hand-over that existed only for that derived bound is deleted too. None, by design. The step-3 door (normalizeFilters) calls lowerFilterCondition with no isDatetimeColumn, so rules 1 and 2 apply type-blind on every column. That is the reading the deleted copy gave, so no bound is lost.
F5, dateRange window Kept (the other 3 call sites). It is not a where. The lowering never sees it (ADR-0053 D-D1 item 8, review 5903065983 item 8), and removing it would drop the explicit-end whole day. n/a (a window, not a filter)
F4 reference matcher memory-matcher.ts is deleted (ruling D6). match() had no production caller and was never exported. The one production import from the module, getValueByPath in memory-driver.ts, moves into memory-driver.ts byte for byte. The 21 importing test files keep every assertion; the table below lists where each one lives now. No datetime column to bound. The matcher's $lte compared raw and applied no whole-day rule.

The changeset is .changeset/20822-driver-memory-face-copies.md, @objectstack/driver-memory patch. Measured: retiring match removes no export. The built dist/index.d.ts export list has the same 33 names before and after. function match( was never in dist: index.ts does not reach it, so the bundler dropped it. The positive control is function getValueByPath, which is present in both builds.

Why F3 stops — measured before and after its deletion

The four sites were measured with the deletion applied (commit 6df6dcfab1, never pushed; patch kept), against built dist, through examples/embed-objectql's dependency graph. Rows: at = 2026-07-27T10Z / 07-28T00Z / 07-28T10Z / 07-29T10Z / null; note (text) = 2026-07-27 / 2026-07-28 / 2026-07-28 late / zzz / null.

Engine (the seamed path), engine.find; engine.count agrees:

Column the filter names $lte '2026-07-28' before → after $lte '9999-12-31' before → after
declared datetime r1,r2,r3 → r1,r2,r3 (unchanged) r1..r4 → r1..r4 (unchanged)
declared date r1,r2,r3 → r1,r2,r3 (unchanged) unchanged
declared text holding ISO text r1,r2,r3 → r1,r2 r1..r4 → r1,r2,r3
column not declared on a registered object r1,r2,r3 → r1 —
object not in the registry r1,r2,r3 → r1 —

The seam's reader is declaredDatetimeLowering in engine.ts. It lowers only a declared datetime. An object with no field map reads no column as datetime, and its comment says the rule is then "left to the faces, as it was". ADR-0053 D-D1 item 7 says a seam that cannot read the declared type "applies the rewrite type-blind". Deleting F3 exposes that gap.

Direct callers (no seam):

  • @objectstack/metadata DatabaseLoader.queryHistory({ until }). It builds recorded_at: { $lte: until } and calls the driver directly whenever the loader holds a driver and no engine (setDatabaseDriver). Probe: two history rows recorded today, then until set to today's bare day. Before: 2 rows. After: 0 rows. since and until both set to today: 2 → 0.
  • The face's own suites call the driver directly. With the four sites deleted, 43 tests go red: memory-temporal-conformance 25, memory-driver-calendar-day-upper-bound 5, memory-analytics-20661-lte-whole-day-first 5, memory-datetime-storage 4, memory-temporal-storage-form 2 and memory-analytics-shared-lowering-door 2. So H2 ("dead on the seamed path, suites green") is falsified as stated.
  • In-repo production direct callers grepped for an upper-bound filter: packages/cli (secret-reference-union, migrate/duplicates, secret/orphans, storage-driver), metadata-protocol migrations, runtime, plugin-dev and examples/embed-objectql have 0. plugin-auth's adapter has $lte but goes through the engine. metadata's queryHistory is the one hit, measured above.

Options and a recommendation are in the report on #20822 (open_questions).

F4 — the 21 importing test files, and where each assertion lives

File Disposition The matcher's assertions now live in
memory-matcher-or-semantics.test.ts (36) deleted memory-driver-filter-logic-conformance.test.ts, the InMemoryDriver.find describe: one it per FILTER_LOGIC_CASES case, same names, same expected, plus a whole-table sweep
memory-matcher-not-null-safe.test.ts (17) deleted memory-driver-document-not.test.ts. 15 of the cells were already asserted there, live, same fixture, both readings. The $not $nin (both readings), $not $exists and $exists = $null: false cells were added, and $not $notContains now runs on both readings
memory-driver-filter-logic-conformance.test.ts edited the live-vs-matcher sweep became live vs the table's expected, in one assertion
memory-20041-like-nul-pattern.test.ts edited the matcher's refusals → assertFilterConditionShape called directly (the gate match() ran); the controls → live vs formula's rows
memory-20143-like-code-point.test.ts edited the matcher door → spec matchesLikePattern (what match() evaluated)
memory-20444-empty-operator.test.ts edited the leaf by-value cells → spec isEmptyFilterValue. The combinator by-value half → formula's matches-filter-empty-operator.test.ts (same rows, same answers). Non-boolean refusal → the gate, plus a live $or identity case
memory-driver-document-not.test.ts edited the live-vs-reference columns → literal expectations on the live path
memory-empty-field-constraint.test.ts edited refusals → the gate; record-independence → the live path over one-row and empty tables; row answers and the nested-object comparison → the live path
memory-exists-has-value-faces.test.ts edited the matcher exit → its literal answers (the ruling's), held on the live path; the composed cells' oracle → the literals measured on the matcher at 9905e61ca2; the analytics face is checked against both
memory-filter-text-conformance.test.ts edited the 19 per-case matcher its → the query path's per-case its (same names, same expected) plus a one-sweep assertion; refusals → the gate; the #14079 cell → the live path
memory-filter-vocabulary-refusal.test.ts edited refusals → the gate (same message as the live path); record-independence → the live path over one-row and empty tables
memory-icontains.test.ts edited → spec asciiCaseInsensitiveContains; the $contains case-exact pin → the live path; refusal → the gate
memory-like-pattern.test.ts edited the agreement check → spec matchesLikePattern for $like / $ilike; the $contains control → the live path
memory-matcher-array-and-date-comparand.test.ts edited, name kept every cell → the live path. The Date cells use a declared datetime column (the case they were written for); the array refusals go through the live path's gate
memory-matcher-no-value-negated-operators.test.ts edited, name kept (spec's filter-logic-conformance.ts points at it) every cell → the live path, both readings
memory-matcher-null-value-and-comparand.test.ts edited, name kept the matcher half of each cell dropped (the live half was already literal); the matcher-only comparisons → the live path
memory-matcher-scalar-comparand-array-value.test.ts edited, name kept 7 per-case matcher its → the live per-case its plus a whole-matrix sweep; the card's three rows, OR-over-elements and $ne's per-row complement → the live path, a row at a time
memory-null-comparand-refusal.test.ts edited refusals → the gate; answers → the live path. $exists: 'yes' is the one divergence found: the matcher answered ['1'] (it read the flag by truthiness), the live path answers ['2'] (val === true → $ne: null, anything else → $eq: null). The live answer is pinned and the divergence reported
memory-null-list-member-unreachable.test.ts edited the compile-then-match pipeline → compile-then-find
memory-null-ordering-comparand-unreachable.test.ts edited the same
memory-operator-key-clobber.test.ts edited the matcher lines dropped; each cell's literal was already asserted on the live path

Counts: the 21 files go from 560 to 483 its, and the package from 1490 to 1413. The 77 are 62 duplicate per-case its (36 + 19 + 7), each still asserted by case name on the live path with the same expected, plus the 17 not-null-safe its folded into document-not (+2 there).

Hypotheses (dispatch zone 2)

  • H1 held for F5 (reached only through step 3's door) and was falsified for F3 (see above).
  • H2 was falsified. F3's sites are dead only for declared datetime columns and are equivalent on date. They are alive for text, undeclared and unregistered columns on the engine path, for every direct caller, and for 43 of the face's own tests.
  • H3 held. Each F5 filter site was ablated on its own at 9905e61ca2: 1490 of 1490 green, twice. The window arm stays.
  • H4 held, with two corrections: 21 importing test files, not 20; and the module had one production import (getValueByPath), which moved. Every mention outside driver-memory is a comment, not an import.
  • H5: stale comments are listed under Acceptance notes. Nothing outside driver-memory is edited.

Tests and gates (at 94616a955f, after merging origin/main 4edb61449b)

  • pnpm --filter @objectstack/driver-memory exec vitest run --maxWorkers=2: 1413 passed / 0 failed, 65 files.
  • pnpm --filter @objectstack/driver-memory typecheck (tsc --noEmit && tsc --noEmit -p tsconfig.typecheck.json): exit 0. --listFiles shows all 65 test files in the program.
  • node scripts/pm/dispatch-gates.mjs --commands derived 60 families, all 60 run. --ran with exit codes: "60 derived, 58 run, 2 NOT-MEASURED, 0 UNRUN". That includes check:driver-conformance (OK, 50 covered cells, 0 DEBT), check:driver-memory-census (OK), check:engine-double-contract, check:nul-bytes, check:published-files, check:adr-0087-registration, check:changeset-no-major and check:empty-changeset, all exit 0.
  • NOT MEASURED: check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET: it reads every workspace package's dist). Narrowed probe: driver-memory's CJS entry loads, with 23 runtime exports. NOT MEASURED: check:type-check-debt (exit 3, PREREQUISITE NOT MET: 16 unbuilt dependencies of ledgered packages). driver-memory has no ledger row, and its own typecheck is green.
  • Lint, narrowed: eslint --no-inline-config --format json over the 22 changed driver-memory files: 22 files, 0 errors, 0 warnings. All 22 are inside the config (--print-config reads each). The config enables no type-aware linting (eslint.config.mjs states it has no parserOptions.project), so this diff cannot move a verdict on an untouched file. The full pnpm lint is CI's.
  • Consumers: the public surface is byte-identical (33 exports), so no downstream suites are owed.

Ablations

Every leg below went through scripts/ablation-replace.mjs (anchor hit on disk, restore proven by blob == HEAD).

  • F5 mongo lte row replaced by a plain $lte at 9905e61ca2: 1490/1490 green. The same for the F5 SQL echo lte row: 1490/1490 green.
  • Control, after the deletion: the door's lowerFilterCondition(admitted) removed → 14 red, including the 20661 whole-day cells, the echo's half-open $lte, 9999-12-31 and the shared-lowering door. The bound now lives in the door.
  • F4, the moved row assertions bind the live path: the live $exists arm reverted to mingo key presence → 19 red across document-not, exists-has-value, null-value-and-comparand, null-comparand-refusal, operator-key-clobber and filter-logic.
  • F4, the moved refusals bind the gate: the gate's $null non-boolean check disabled → 9 red (the gate-direct cells in null-comparand-refusal).
  • F3 metadata-history leg: patch applied, driver-memory rebuilt, then restored and rebuilt. The dist preflight shows the marker back and the tree clean, and the probe reads 2 again.

Acceptance notes

  • Stale comments outside driver-memory that name the retired matcher (not edited: the spec lane's or another package's; .claude/** is governed):
    • packages/spec/src/data/filter-logic-conformance.ts:15: the backend table's "In-memory matcher / memory-matcher" row. It ships in dist/data/index.d.ts. It should read InMemoryDriver.find (mingo).
    • packages/formula/src/matches-filter-not-null-safe.test.ts:17, service-analytics filter-normalizer-not-null-safe.test.ts:50 and read-scope-not-null-safe.test.ts:43 point at the deleted memory-matcher-not-null-safe.test.ts. The cells now live in memory-driver-document-not.test.ts.
    • service-storage attachment-read-visibility.test.ts:13 and :326 cite memory-matcher.ts and memory-matcher-or-semantics.test.ts.
    • plugin-security claim-seed-ownership.ts:91 attributes InMemoryDriver's id IN (…) scan to memory-matcher.ts. It was always mingo's, and this was already wrong before this PR.
    • service-analytics objectql-strategy.ts:1929 and objectql-contains-canonical-operator.test.ts describe the matcher's $regex arm in the present tense (that arm was retired by drivers: $regex 响亮拒收 + $icontains 各后端实现(#4706 裁决 B 案 · 驱动半边) #5702).
    • .claude/skills/pm-dispatch/references/compile-surfaces.md:16 names memory-matcher.ts:134 as a live surface.
    • docs/design/predicate-compilation-convergence.md F4 row.
    • spec filter-logic-conformance.ts:492 still points at a live file, which kept its name for it.
  • Runtime refusal texts in filter-refusal.ts (malformedBetweenError, nonBooleanNullComparandError, arrayComparandError) mention the reference matcher in the past tense. They stay true and are untouched.
  • Four migrated files keep their memory-matcher- names so that no pointer dangles. Each header says so.

Generated by Claude Code

…son they are handed (#5930 step 4, F5)

The whole-day upper bound, its last supported day and D-E3's widen-then-convert
order are applied once, by the shared lowering at the face's door
(normalizeFilters); lteUpperBound and the storageForm hand-over it needed are
deleted. The dateRange window's explicit-end arm (ADR-0053 D-D1 item 8) stays.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
… live path and the shared gate, batch 1 (#5930 step 4, F4)

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
… live path and the shared gate, batch 2 (#5930 step 4, F4)

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
…assertions move onto the live path (#5930 step 4, F4, ruling D6)

memory-matcher.ts had no production caller: the driver imported only
getValueByPath from it, which moves into memory-driver.ts unchanged. Every
test that imported match() now asserts the live query path, the shared
shape gate it ran, or the spec predicate it evaluated.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
…nd copy and the retired reference matcher (#5930 step 4)

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-memory, touching 22 documentable anchor(s).

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

  • content/docs/kernel/contracts/auth-service.mdx (via user.name (literal, a string literal in a comment on a changed line))
  • content/docs/kernel/contracts/cache-service.mdx (via user.name (literal, a string literal in a comment on a changed line))
What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 9 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 00a92e18da8cd1106bcaf3318dad1fd7563a41c9 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from b8c9e5af74dda4c6a52e3d74e0c2c238b504dd1d — the merge of head 94616a955f0dccedcd37779ac8360573afcd8d03 into base 00a92e18da8cd1106bcaf3318dad1fd7563a41c9, 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 b8c9e5af74dda4c6a52e3d74e0c2c238b504dd1d && git checkout b8c9e5af74dda4c6a52e3d74e0c2c238b504dd1d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 00a92e18da8cd1106bcaf3318dad1fd7563a41c9 94616a955f0dccedcd37779ac8360573afcd8d03 && git checkout -B drift-repro 00a92e18da8cd1106bcaf3318dad1fd7563a41c9 && git merge --no-ff 94616a955f0dccedcd37779ac8360573afcd8d03

node scripts/docs-audit/affected-docs.mjs --json 00a92e18da8cd1106bcaf3318dad1fd7563a41c9

⚠️ 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 00a92e18da8cd1106bcaf3318dad1fd7563a41c9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 94616a955f0dccedcd37779ac8360573afcd8d03
Local-runs: none

Head confirmed unchanged at read time (PR #20895, branch claude/issue-20822-g1-driver-memory, draft, base main at 00a92e18da, merge-base 4edb61449b). Inputs read: card #20822's body and its five comments (5908945858, 5913721009, 5913798258, 5915148713, 5915193659); ruling 5902355785 on #5930; ADR-0053 D-D1 as amended; predicate-compilation-convergence.md census and §S5; PR #20857 (793fb839), the step-3 seam; the PR body, its 26-file list and the net diff origin/main...94616a955f; the head's check-runs. Nothing built, run or re-run; the diff was read through git on the fetched ref.

① Derived judgments

Every accept-set and public-surface change the diff implies, each named right or wrong.

  1. F5: the cube face's lte rows compile the comparison as handed — no accept-set change (right). Read off memory-analytics.ts at the head: the only callers of the two predicate-builder tables are mongoConjunction and sqlConjunction, reached from query() and generateSql(), and both are fed by normalizeFilters(query). That method reads query.where alone (the face has no cube-style filters spelling), then runs assertListComparandShapes, normalizeFilterComparandTypes, lowerFilterCondition(admitted) with no options, the vocabulary gate, and only then flattenFilterCondition. So a bare-day $lte reaches either table as lt a calendar string (storage-form converted like any comparand) or as isNull: false on the last supported day, and the isNull rows exist on both exits since step 3. An instant or Date comparand is never widened by the lowering and lands on the new plain lte rows, which is exactly the deleted through arm. The deleted lteUpperBound read raw[0] type-blind and the door reads type-blind too, so the two readings coincide on every column: no row set and no echoed SQL moves. The storageForm hand-over existed only for the derived bound; its removal from both builder inputs leaves no reader behind.
  2. F5 typed reader, judged against ADR-0053 D-D1 item 7 (right). The door passes no isDatetimeColumn, so rules 1 and 2 apply on every column, a superset of datetime. Item 7 says a seam that cannot read the declared type applies the rewrite type-blind; this face reaches declared types only as a storage-form conversion, so type-blind is item 7's own reading for it. On a date or ISO-text column the half-open rewrite is order-equivalent, and it was already this face's reading through the deleted copy, so no bound is gained or lost.
  3. F5 dateRange window arm untouched (right). The three window sites (nextUtcCalendarDay(end) and the two isUnboundedAbove reads) sit in query()'s time-dimension stage, which no hunk enters; both helpers stay imported and used there. ADR item 8 respected; the design's "5 sites" minus these 3 is the 2 deleted.
  4. F3 untouched (right). memory-driver.ts changes by one removed import, one added function (getValueByPath) and comment text only. Its four nextUtcCalendarDay sites shift by fourteen lines with identical bodies. No F3 site is deleted or altered in behaviour.
  5. F4 retired with no public-surface change (right). match was exported from memory-matcher.ts but never re-exported: index.ts is byte-identical across the diff, package.json publishes the . entry alone, and no file in the repository imported the module by a deep path at main. The export list read off index.ts is 33 names (23 runtime values including default, 10 types), matching the dev's dist count. No script, census ledger, ADR anchor or debt file names memory-matcher or lteUpperBound. getValueByPath, the one production import, moves with identical logic but not literally byte for byte: it is re-indented from four to two spaces and drops export. A wording nit in the PR body and the moved function's comment, not a behaviour.
  6. F4 test migration, assertion by assertion (right; one measured divergence, declared). Static it( count moves 711 to 693; the dev's runtime 1490 to 1413 is consistent once the loop-generated describes are counted (36 + 19 + 7 duplicate per-case cases, plus 17 folded into document-not, minus 2 added there).
    • memory-matcher-or-semantics.test.ts (36 loop cases): memory-driver-filter-logic-conformance.test.ts already loops FILTER_LOGIC_CASES on InMemoryDriver.find with it(c.name) against c.expected; the live-vs-matcher sweep became live-vs-expected over the whole table in one assertion. Nothing lost.
    • memory-matcher-not-null-safe.test.ts (17 its): all 17 cells are in memory-driver-document-not.test.ts at the head, on the live path through matched() (both readings of no-value must agree, then the literal). The 14 $not cells are at lines 115 to 194 with the same fixture and ids; the three settled cells ($not $nin on both readings, $not $notContains now on both readings, $exists equal to $null: false) are at lines 290 to 308.
    • memory-filter-text-conformance.test.ts: the 19 matcher per-case its had the same names and expected as the query-path loop that remains; refusals move onto assertFilterConditionShape called directly, which was the first statement match() executed; the driver-memory's reference matcher answers $notContains NO for every valued NON-STRING row — the live mingo path answers YES #14079 cell moves onto a live one-table probe with the same four polarities.
    • memory-matcher-scalar-comparand-array-value.test.ts: the 7 matcher per-case its duplicated the live per-case loop; the matrix sweep is now live-vs-written; the card's three rows, the OR-over-elements property and the $ne complement are re-asked of find() a row at a time (the complement is asserted as a set difference, equivalent per row).
    • Refusal files (20041, empty-field-constraint, filter-vocabulary-refusal, null-comparand-refusal, 20444's non-boolean flag, icontains' empty needle): every matcher refusal becomes the shared gate called directly, same envelope, message equal to the live path's; record-independence is re-stated as one-row and empty tables refusing alike.
    • Predicate files: 20143 and like-pattern call the spec's matchesLikePattern, which is what the matcher's $like / $ilike arm evaluated; icontains calls asciiCaseInsensitiveContains, the matcher's $icontains arm; 20444 judges a single leaf through isEmptyFilterValue, the matcher's $empty arm. The combinator by-value half of 20444 is pointed at formula's matches-filter-empty-operator.test.ts, whose rows r1 to r5 and its $not / $or / $and cells and the $empty: false, $ne: 'x' cell carry the same answers (its $and cell reads tags where this package's reads owners; the answer coincides). One composed cell, tags: { $empty: true, $ne: null } (r2), has no by-value twin in formula's file, so its by-value half is asserted only on the live path now. A nit: the retired reading served no caller.
    • array-and-date-comparand, no-value-negated-operators, null-value-and-comparand, exists-has-value-faces, operator-key-clobber, the two unreachable files: every cell moves to the live path with its literal unchanged; the composed-cell oracle in exists-has-value-faces is written out as the five row sets the fixture implies, and each is the correct set for that fixture.
    • The one divergence, $exists: 'yes' (memory-null-comparand-refusal.test.ts): the matcher pinned ['1'] (truthiness); the live path pins ['2'] (val === true lowers to $ne: null, anything else to $eq: null). The pin's text calls the answer measured and divergent, states that the refusal the cell lacks is reported rather than decided there, and adds the boolean control. No pin asserts a wrong live answer as correct. This one records a wrong-per-spec live answer (FieldOperatorsSchema declares $exists a boolean) as a measurement, and the finding is filed as [finding] $exists with a non-boolean comparand ("yes", 1) is accepted at every door and inverted on driver-memory: it returns the rows with NO value; the spec declares $exists: z.boolean(), and its $null twin is refused #20897; when a refusal lands there, this pin moves.
  7. Stale prose. Inside the package two historical mentions of memory-matcher.ts remain (the measured-answers table in filter-refusal.ts, the $contains history in memory-driver.ts); both read as history and are accurate as such. Outside the package the PR lists eight pointers that still name the module or the two deleted files; a ninth, packages/spec/src/data/filter.zod.ts line 899 (the $contains case-folding table), is missing from that list. None is an import, so nothing dangles at build time.

Check-runs on the head at the moment of reading: 31 runs, 19 completed (16 success, 3 skipped: Console Pin Gate, Build Docs, Packed-tarball smoke), 12 in progress (Test Core 1 to 6 of 6, Dogfood Regression Gate 2 and 3 of 3, Temporal Conformance live PG and MySQL, Type Check workspace, Type Check consumer gates, Lint and Repo Gates), no failure. Completed successes include Build Core (hosts check:dual-build-cjs-loads), Type Check debt ledger (hosts check:type-check-debt), Type Check source gates (hosts check:driver-conformance), Check Changeset, Check PR Size, Governed Surface Queue Guard, the four claim guards, Flag docs affected, Check Documentation Links, Dogfood Verify CLI and Dogfood Regression Gate 1 of 3. The Vercel status is success. No governed surface is in the 26-file list. Not waited on.

② Semver level

.changeset/20822-driver-memory-face-copies.md: @objectstack/driver-memory patch, Clause-②: no. Graded right. The published surface is unchanged (33 names, index.ts untouched) and no answer moves on any path: F5's rows are reached only through a door that lowers first, and F4 had no production caller and no export. The dist does change (a module and a code path are deleted), so skip-changeset would be wrong; nothing widens or narrows, so minor is not owed and no ADR-0087 disposition is required. The changeset body matches what the diff publishes: the lte rows compile as handed on both exits with rows and echo unchanged; the dateRange end keeps its own arm (item 8); the matcher retired under D6 with getValueByPath kept and the tests moved onto the live path, the shared gate or the spec predicate. Clause-②: no is also the PR body's line, and both review faces (the changeset that ships as CHANGELOG, the PR body) tell the same story. The PR body's "byte for byte" is the one overstatement (① item 5); the changeset itself does not make it.

③ Boundary flags

Dev flags, each answered or escalated:

  • Four migrated files keep their memory-matcher- names. Answered: packages/spec/src/data/filter-logic-conformance.ts line 492 points at memory-matcher-no-value-negated-operators.test.ts, which still exists at the head, and each of the four headers states it now holds the live path. Accepted as a pointer-stability choice.
  • No comment corrections outside driver-memory. Answered: outside the claim's file surface; the spec text ships in dist/data/index.d.ts and would owe a spec changeset; .claude/** is governed. Escalated: the stale pointers have no carrier (the report says 承接者:无). A follow-up card should be filed by the seat for the nine sites (spec filter-logic-conformance.ts line 15 and filter.zod.ts line 899; formula matches-filter-not-null-safe.test.ts; service-analytics filter-normalizer-not-null-safe.test.ts, read-scope-not-null-safe.test.ts, objectql-strategy.ts, objectql-contains-canonical-operator.test.ts; service-storage attachment-read-visibility.test.ts; plugin-security claim-seed-ownership.ts; the design doc's F4 row; the governed compile-surfaces.md row by its own tier) so a retired module name stops being cited as a live surface. Not a blocker for this PR.
  • check:dual-build-cjs-loads and check:type-check-debt not measured locally. Answered by the head's check-runs: Build Core success, Type Check debt ledger success.
  • One merge of main. Answered: the head is that merge commit; this review is of the net diff against origin/main, so the merge contributes nothing of its own.
  • One vitest batch ran without the verify lock. A process flag. The authority on the tests is Test Core, in progress at read time with no shard failed; nothing in the diff depends on that local run.

Report entries:

Implemented-by: claude/issue-20822-g1-driver-memory
Reviewed-by: session_01DEvba2nBuD4tWzfq8r8NFY

VERDICT: PASS


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants