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
Conversation
…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>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 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
|
Contract reviewServed-tier: Head confirmed unchanged at read time (PR #20895, branch ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each named right or wrong.
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 ② Semver level
③ Boundary flagsDev flags, each answered or escalated:
Report entries:
Implemented-by: VERDICT: PASS Generated by Claude Code |
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 touchmemory-driver.ts's four whole-day sites.What lands
MemoryAnalyticsService(cube face)lteUpperBound(the filter copy, 2 of the design's 5 call sites) is deleted. Thelterow on both exits (mingo$matchand SQL echo) now compiles the comparison it is handed. ThestorageFormhand-over that existed only for that derived bound is deleted too.normalizeFilters) callslowerFilterConditionwith noisDatetimeColumn, so rules 1 and 2 apply type-blind on every column. That is the reading the deleted copy gave, so no bound is lost.dateRangewindowwhere. 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.memory-matcher.tsis deleted (ruling D6).match()had no production caller and was never exported. The one production import from the module,getValueByPathinmemory-driver.ts, moves intomemory-driver.tsbyte for byte. The 21 importing test files keep every assertion; the table below lists where each one lives now.datetimecolumn to bound. The matcher's$ltecompared raw and applied no whole-day rule.The changeset is
.changeset/20822-driver-memory-face-copies.md,@objectstack/driver-memorypatch. Measured: retiringmatchremoves no export. The builtdist/index.d.tsexport list has the same 33 names before and after.function match(was never indist:index.tsdoes not reach it, so the bundler dropped it. The positive control isfunction 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 builtdist, throughexamples/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.countagrees:$lte '2026-07-28'before → after$lte '9999-12-31'before → afterdatetimedatetextholding ISO textThe seam's reader is
declaredDatetimeLoweringinengine.ts. It lowers only a declareddatetime. An object with no field map reads no column asdatetime, 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/metadataDatabaseLoader.queryHistory({ until }). It buildsrecorded_at: { $lte: until }and calls the driver directly whenever the loader holds a driver and no engine (setDatabaseDriver). Probe: two history rows recorded today, thenuntilset to today's bare day. Before: 2 rows. After: 0 rows.sinceanduntilboth set to today: 2 → 0.memory-temporal-conformance25,memory-driver-calendar-day-upper-bound5,memory-analytics-20661-lte-whole-day-first5,memory-datetime-storage4,memory-temporal-storage-form2 andmemory-analytics-shared-lowering-door2. So H2 ("dead on the seamed path, suites green") is falsified as stated.packages/cli(secret-reference-union,migrate/duplicates,secret/orphans,storage-driver),metadata-protocolmigrations,runtime,plugin-devandexamples/embed-objectqlhave 0.plugin-auth's adapter has$ltebut goes through the engine.metadata'squeryHistoryis 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
memory-matcher-or-semantics.test.ts(36)memory-driver-filter-logic-conformance.test.ts, theInMemoryDriver.finddescribe: oneitperFILTER_LOGIC_CASEScase, same names, sameexpected, plus a whole-table sweepmemory-matcher-not-null-safe.test.ts(17)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 $existsand$exists=$null: falsecells were added, and$not $notContainsnow runs on both readingsmemory-driver-filter-logic-conformance.test.tsexpected, in one assertionmemory-20041-like-nul-pattern.test.tsassertFilterConditionShapecalled directly (the gatematch()ran); the controls → live vsformula's rowsmemory-20143-like-code-point.test.tsmatchesLikePattern(whatmatch()evaluated)memory-20444-empty-operator.test.tsisEmptyFilterValue. The combinator by-value half →formula'smatches-filter-empty-operator.test.ts(same rows, same answers). Non-boolean refusal → the gate, plus a live$oridentity casememory-driver-document-not.test.tsmemory-empty-field-constraint.test.tsmemory-exists-has-value-faces.test.ts9905e61ca2; the analytics face is checked against bothmemory-filter-text-conformance.test.tsits → the query path's per-caseits (same names, sameexpected) plus a one-sweep assertion; refusals → the gate; the #14079 cell → the live pathmemory-filter-vocabulary-refusal.test.tsmemory-icontains.test.tsasciiCaseInsensitiveContains; the$containscase-exact pin → the live path; refusal → the gatememory-like-pattern.test.tsmatchesLikePatternfor$like/$ilike; the$containscontrol → the live pathmemory-matcher-array-and-date-comparand.test.tsdatetimecolumn (the case they were written for); the array refusals go through the live path's gatememory-matcher-no-value-negated-operators.test.tsspec'sfilter-logic-conformance.tspoints at it)memory-matcher-null-value-and-comparand.test.tsmemory-matcher-scalar-comparand-array-value.test.tsits → the live per-caseits 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 timememory-null-comparand-refusal.test.ts$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 reportedmemory-null-list-member-unreachable.test.tsmatchpipeline → compile-then-findmemory-null-ordering-comparand-unreachable.test.tsmemory-operator-key-clobber.test.tsCounts: the 21 files go from 560 to 483
its, and the package from 1490 to 1413. The 77 are 62 duplicate per-caseits (36 + 19 + 7), each still asserted by case name on the live path with the sameexpected, plus the 17 not-null-safeits folded intodocument-not(+2 there).Hypotheses (dispatch zone 2)
datetimecolumns and are equivalent ondate. 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.9905e61ca2: 1490 of 1490 green, twice. The window arm stays.getValueByPath), which moved. Every mention outsidedriver-memoryis a comment, not an import.driver-memoryis edited.Tests and gates (at
94616a955f, after mergingorigin/main4edb61449b)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.--listFilesshows all 65 test files in the program.node scripts/pm/dispatch-gates.mjs --commandsderived 60 families, all 60 run.--ranwith exit codes: "60 derived, 58 run, 2 NOT-MEASURED, 0 UNRUN". That includescheck: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-majorandcheck:empty-changeset, all exit 0.check:dual-build-cjs-loads(exit 3, PREREQUISITE NOT MET: it reads every workspace package'sdist). 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-memoryhas no ledger row, and its own typecheck is green.eslint --no-inline-config --format jsonover the 22 changeddriver-memoryfiles: 22 files, 0 errors, 0 warnings. All 22 are inside the config (--print-configreads each). The config enables no type-aware linting (eslint.config.mjsstates it has noparserOptions.project), so this diff cannot move a verdict on an untouched file. The fullpnpm lintis CI's.Ablations
Every leg below went through
scripts/ablation-replace.mjs(anchor hit on disk, restore proven by blob == HEAD).lterow replaced by a plain$lteat9905e61ca2: 1490/1490 green. The same for the F5 SQL echolterow: 1490/1490 green.lowerFilterCondition(admitted)removed → 14 red, including the 20661 whole-day cells, the echo's half-open$lte,9999-12-31and the shared-lowering door. The bound now lives in the door.$existsarm 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.$nullnon-boolean check disabled → 9 red (the gate-direct cells in null-comparand-refusal).driver-memoryrebuilt, then restored and rebuilt. Thedistpreflight shows the marker back and the tree clean, and the probe reads 2 again.Acceptance notes
driver-memorythat 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 indist/data/index.d.ts. It should readInMemoryDriver.find(mingo).packages/formula/src/matches-filter-not-null-safe.test.ts:17,service-analyticsfilter-normalizer-not-null-safe.test.ts:50andread-scope-not-null-safe.test.ts:43point at the deletedmemory-matcher-not-null-safe.test.ts. The cells now live inmemory-driver-document-not.test.ts.service-storageattachment-read-visibility.test.ts:13and:326citememory-matcher.tsandmemory-matcher-or-semantics.test.ts.plugin-securityclaim-seed-ownership.ts:91attributesInMemoryDriver'sid IN (…)scan tomemory-matcher.ts. It was always mingo's, and this was already wrong before this PR.service-analyticsobjectql-strategy.ts:1929andobjectql-contains-canonical-operator.test.tsdescribe the matcher's$regexarm in the present tense (that arm was retired by drivers:$regex响亮拒收 +$icontains各后端实现(#4706 裁决 B 案 · 驱动半边) #5702)..claude/skills/pm-dispatch/references/compile-surfaces.md:16namesmemory-matcher.ts:134as a live surface.docs/design/predicate-compilation-convergence.mdF4 row.specfilter-logic-conformance.ts:492still points at a live file, which kept its name for it.filter-refusal.ts(malformedBetweenError,nonBooleanNullComparandError,arrayComparandError) mention the reference matcher in the past tense. They stay true and are untouched.memory-matcher-names so that no pointer dangles. Each header says so.Generated by Claude Code