Skip to content

fix(driver-mongodb): MongoDBDriver compiles the whole-day comparison it is handed; formula's copy stops on a measured answer move - #21105

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20822-g3a-mongodb-formula
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20822-g3a-mongodb-formula

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20822
Clause-②: no

Group 3a of #20822 (#5930 step 4, the per-face deletions), under ruling 5902355785 (D4 (b): "drivers receive the seams' lowered input; no permanent local guards on faces that bypass a seam") and ADR-0053 D-D1 as amended. This PR does not close the card: group 3b (F8 having, serial on having-filter.ts) does.

What this PR does

  • F6 driver-mongodb: deleted. translateFieldOperators (mongodb-filter.ts) no longer applies the whole-day upper bound itself. MongoDBDriver compiles the comparison it is handed.
  • F7 formula lteBound: stopped and reported, not deleted. Deleting it moves an answer on a seam-fed path (the card's stop rule). The measurements are below, and the decision is asked in the report. matches-filter.ts has no net change in this PR.
  • [finding] $contains / $notContains on a declared multi-valued or JSON-stored field still answer SUBSTRING on five faces, the analytics RLS read scope among them (u1 admits a row storing u10) #20987's engine faces move to group 3b (triage pointer 5922592744). These are the mongodb $contains $regex, formula's case '$contains' and the engine.ts delete-probe docblock. The shared membership module (json-membership-sql.ts in @objectstack/core) is not on origin/main: at e07566b73, git ls-tree -r names 0 paths matching json-membership. Also, @objectstack/formula depends on @objectstack/spec alone, so an import from core would add a package edge.

H1: the sites, at origin/main 6f578888a

  • F6. nextUtcCalendarDay occurs 4 times in mongodb-filter.ts: the import (:27), the translateFieldOperators docblock (:1123), and 2 call sites. The call sites are the $lte arm (:1211) and the $between arm (:1305). The card's "4 sites" are these 4 occurrences. All 4 are gone. The @objectstack/core import is gone with them, and the clobber-class table in assembleLoweredWrites' docblock loses its $lt row.
  • F7. lteBound has 2 call sites, the $lte arm (:668) and the $between arm (:673), plus the function itself (:867). All 3 are kept.

H2: direct callers, with their answers before and after

  • F6. No in-repo production caller hands MongoDBDriver a bare-day $lte or $between outside a seam:

    • the CLI's secret-reference-union.ts (where: { type: 'datasource' }, an equality) and secret orphans;
    • objectql LifecycleService archive ($lt an ISO instant);
    • DatabaseLoader.queryHistory in driver mode, which has been its own seam since group 1b.

    Their answers do not move. The answer that moves belongs to a hypothetical direct call such as driver.find('task', { where: { created_at: { $lte: '2026-07-28' } } }) on a declared datetime column:

    • before: $lt 2026-07-29 midnight, so the whole day was kept;
    • after: $lte 2026-07-28 midnight, the comparison as written (item 5).

    Through the engine seam the answer is $lt the next day both before and after.

  • F7. Its callers were measured with the copy deleted (d045f1d6c), then the copy was restored (394df163a):

    • The RLS write check (security-plugin.ts, step 3.6). It is fed by the RLS compile seam, which lowers only the guard's datetime columns (item 7). Its answer moves, as the table below shows.
    • The explain engine (explain-engine.ts). Its filters come from computeLayeredRlsFilter, so they too are compiled through the RLS seam. It evaluates the STORED record, where a date column holds day text. No answer move was found.
    • The tenant check (security-plugin.ts, step 3.7). It evaluates the organization wall only, with no temporal bound, so its answer is unchanged.
    • F8's cross-field comparison (having-filter.ts, compareWithReference). It hands F7 { $field } comparands, which the lowering never rewrites. Its answer moves for a referent that resolves to a bare day, as the second finding below shows.
    • Any external caller of the published matchesFilterCondition. Under item 5 it would get the comparison it wrote.

F7: the measured answer move

The probe is temporary and not committed. It runs through the public insert door: ObjectQL, SecurityPlugin and SqlDriver on SQLite. The object has due_on: date and signed_on: datetime. One policy carries using and check, both record.due_on at most '2026-01-05'. The typed RLS seam leaves the date column as written, { due_on: { $lte: '2026-01-05' } }, as rls-shared-lowering-seam.test.ts pins. A member inserts each value. The same value is also written by the system and read back by the member.

post-image due_on stored the member's read of the stored row insert, F7 copy kept insert, F7 copy deleted
'2026-01-05' 2026-01-05 visible admitted admitted
'2026-01-05T10:00:00.000Z' 2026-01-05 visible admitted refused PERMISSION_DENIED / 403
new Date('2026-01-05T10:00:00Z') 2026-01-05 visible admitted refused PERMISSION_DENIED / 403
'2026-01-06' (control) 2026-01-06 not visible refused 403 refused 403

The record validator accepts both non-day spellings for a date field: a Date, and a string with a leading YYYY-MM-DD. The driver stores them as that day. The check judges the raw image before the driver converts it. lteBound is what reads the bound as the whole day there, on a column the typed seam does not rewrite. With it deleted, the write is refused although the read shows the stored row. ADR-0053 D-D1 item 9 says a copy is "idempotent on lowered input". That premise does not hold for this face.

A second answer moves too. A $lte against a { $field, addDays } reference whose referent resolves to a bare day gets the whole day only from lteBound. The lowering keeps any { $field } comparand as written, so no seam carries this rule. matches-filter-field-reference-offset.test.ts pins it ("a calendar day stays a calendar day, so a $lte still covers the whole shifted day"), and that pin goes red with the copy deleted.

Existing suites with the F7 copy deleted (d045f1d6c, formula rebuilt):

  • formula: 25 of 1241 red. Of these, 24 are direct-call cells that could be routed through the lowering: 20 in matches-filter-temporal-conformance.test.ts and 4 in matches-filter.test.ts. The 25th is the $field offset pin above, which cannot be routed.
  • plugin-security: 153 files, 3287 passed, 23 skipped. No suite there pins the date column case, so only the probe shows it.
  • objectql having / aggregate (src/having, src/engine-aggregate): 15 files, 573 passed.

F6: what moves, and what does not

  • Seam-fed reads do not move. The engine's where, aggregations[i].filter and the RLS compile seam hand this driver $lt the next day, a split $between, and $null: false on 9999-12-31. The deleted copy answered that input identically, as ablations A1 and A2 below show. A date column answers as before, because its stored day text orders the same either way.
  • Two answers converge on SqlDriver's (item 7's scope; seat answer 5915193659: "declare it in the changeset; it is not a decision"). On a registered object, a bare-day $lte or $between maximum on a column that is not declared datetime is now compared as written. That covers a text column holding ISO instant text and an undeclared column.
  • A direct call compares as written (item 5). The changeset gives the one-line way to keep the seam's reading: lowerFilterCondition(where, { isDatetimeColumn }).
  • Real mongod: NOT MEASURED. There is no mongod binary in this container, and OS_TEST_MONGODB_MEMORY_SERVER_ENABLED is off, as the card's recorded gaps state. The gated suites that call driver.find directly are routed through a typed seam helper, as the sibling drivers' suites are: mongodb-temporal-conformance.test.ts (both TEMPORAL_CASES and TEMPORAL_TIME_CASES, tokens resolved first) and mongodb-datetime-storage.test.ts (its 3 bare-day cells). Neither ran here.

Pins, routed and flipped

  • mongodb-filter.test.ts. The calendar-day block is split in two. The first half is the direct call, compiled as written: a bare-day $lte, a bare-day $between, the authored array form, and the last supported day for both. The second half is the seam-fed control, every former cell through lowerFilterCondition with created_at declared datetime, with the same documents as before.
  • mongodb-operator-key-clobber.test.ts. The enumeration rows change: a bare-day $lte now writes $lte, a bare-day $between now writes $gte and $lte, and the $lt contested row is removed. The "fourth member" cell is now two-sided:
    • a direct $lte beside $lt is uncontested;
    • seam-fed, the lowering's $lt arrives as its own conjunct, and both bounds survive in either key order.
  • mongodb-filter-shape-refusal.test.ts. A bare-day range is pinned as written, plus the seam-fed control.
  • mongodb-time-storage.test.ts. A comment only.

Ablations

Each ablation runs on the committed head through scripts/ablation-replace.mjs in WRAP mode, nested: the import anchor first, then the arm. The driver-mongodb suite imports ./mongodb-filter.js from src, so no dist is involved.

  • A1, the $lte arm restored: 5 red, exactly the direct-call $lte cells (3 in mongodb-filter.test.ts, 2 in the clobber file). Every seam-fed cell stayed green.
  • A2, the $between arm restored: 4 red, exactly the direct-call $between cells (2 in mongodb-filter.test.ts, 1 in the shape-refusal file, 1 clobber row). Every seam-fed cell stayed green.
  • Restore, both legs: the blob equals HEAD de35f1ee395c and git diff HEAD is empty.
  • The first attempt was a no-op. Its import replacement contained its own anchor, and the tool refused it: "the anchor count moved 1 -> 1". It was rerun with a corrected anchor.

Compile surfaces

  • F6 driver-mongodb translateFieldOperators: the whole-day copy is deleted. It compiles as handed, and seam-fed answers are unchanged (A1, A2).
  • F7 formula matchesFilterCondition: unchanged. The stop is reported, and lteBound stays until the decision.
  • engine.ts delete-probe docblock (referenceProbeFilter): unchanged. It moves to group 3b with the membership items.

Tests

The full figures are in the report comment on #20822, measured at d1137d7ea.

  • driver-mongodb: 30 files passed, 5 skipped (the gated real-mongod suites); 675 tests passed, 172 skipped. At the base: 671 passed, 172 skipped.
  • pnpm --filter @objectstack/driver-mongodb typecheck: exit 0, including check:test-typecheck.
  • check:driver-conformance, before and after: OK, 50 covered cells, 0 DEBT, 0 exempt.
  • Narrowed lint: eslint --no-inline-config --format json over the 8 changed .ts files gives 8 files, 0 errors and 0 warnings. eslint.config.mjs never enables type-aware linting, so no untouched file's verdict can move. The full pnpm lint is CI's.

Acceptance notes (noted, not filed)

  • @objectstack/core is now test-only in driver-mongodb. Its last src import was the deleted nextUtcCalendarDay one, and the remaining importer is mongodb-temporal-conformance.test.ts. The manifest still lists it under dependencies. Moving it to devDependencies is a manifest and lockfile edit outside this card. Carrier: none.
  • Stale pointers that name F1, not F6 / F7, are not carried here: filter.zod.ts around :434, :734 and :750 names SqlDriver.calendarDayUpperBoundRewrite / calendarDayBetweenRewrite, and the case title in read-scope-shared-lowering-seam.test.ts concerns the RLS change of group 2. Release 5924791165 places them with the last group PR, so the carrier is group 3b.
  • query-syntax.mdx's sentence on direct calls names only the SqlDriver family. It reads: "A filter handed directly to SqlDriver or a driver built on it … is compared as written". MongoDBDriver (this PR) and InMemoryDriver (group 1b) now answer the same way. The sentence is incomplete but not false. Carrier: group 3b.

Generated by Claude Code

claude added 4 commits October 1, 2026 05:54
…alendarDay, F7 lteBound)

Claude-Session: https://claude.ai/code/session_01Ujdtvqs7ree7WyQmEDwEnG
Co-authored-by: Claude <noreply@anthropic.com>
…lowering; F7 stops on a measured answer move (lteBound restored)

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

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/drivers/driver-mongodb/src/mongodb-temporal.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/drivers/driver-mongodb/src/mongodb-temporal.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 — 6 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 2742e53709b790ee76d2e8d1d07e23aa68de2110 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 2742e53709b790ee76d2e8d1d07e23aa68de2110

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d1137d7ea9c03481058e3a04d65cc13faf4873bc
Local-runs: none

PR #21105 (card #20822 group 3a, Part of #20822, claim 5925340434). Read in one read-only act at 2026-10-01T07:16Z: the card body and all 28 of its comments (ruling 5902355785 on #5930 included), the PR body and file list, the net diff against main at the merge-base e07566b73 (9 files, +195/-103, all under packages/drivers/driver-mongodb/src/ plus one changeset; nothing on origin/main since that base touches those paths), and the 31 check-runs on the head. Nothing was built, run or re-run.

① Derived judgments

F6, deleted — right. At the base, nextUtcCalendarDay occurred four times in mongodb-filter.ts (the @objectstack/core import, the translateFieldOperators docblock, the $lte arm, the $between arm); at the head it occurs zero times and no src file imports @objectstack/core. $lte joins the as-written group beside $gt / $gte / $lt, and $between writes $gte plus $lte. translateFilter is the one entry every verb uses (find, findOne, count, updateMany, deleteMany, explain in mongodb-driver.ts, and the $match stage through buildAggregationPipeline in mongodb-aggregation.ts), so the deletion reaches each verb once and the changeset's verb list is accurate.

Accept-set changes the diff implies, each judged:

  • A direct call (translateFilter, or a MongoDBDriver verb reached without a seam) with a bare-day $lte now compiles $lte that day's midnight, formerly $lt the next day; a bare-day $between maximum is now inclusive, formerly $lt the next day; $lte on the last supported day is now an ordinary bound, formerly $ne: null; a $between ending on that day keeps both bounds, formerly the minimum alone. This is ADR-0053 D-D1 item 5 as amended and ruling 5902355785 D4 (b). Right, and each is pinned in the new direct-call block of mongodb-filter.test.ts, the shape-refusal file and the clobber enumeration.
  • A seam-fed read is unchanged. The shared lowering (filter-lowering.ts) emits $lt the next day, a split $between, and { $null: false } on the last day; the head's translator compiles exactly those, as the deleted copy did on that input (item 9). Every former cell is kept as a seamed control with its expected value unchanged. Right.
  • Direct callers measured: the report's H2 names the in-repo callers (CLI secret-reference-union and secret orphans, equality; objectql LifecycleService archive, $lt an instant; DatabaseLoader.queryHistory, its own seam since group 1b). A read-only grep over non-test packages/*/src at origin/main finds no literal bare-day $lte / $between outside docblocks and the shared conformance fixture, which corroborates it. Right.
  • Ablation: A1 (the $lte arm restored) and A2 (the $between arm restored) are reported as 5 and 4 red with every seam-fed cell green. Not re-run here; the counts are checked against the diff's cells and match: 5 direct $lte cells (3 in mongodb-filter.test.ts, 2 in the clobber file) and 4 direct $between cells (2 there, 1 shape-refusal, 1 clobber row). Right.
  • A non-datetime column on a registered object (a text column holding ISO text, an undeclared column): through the typed engine seam it is not lowered, so a bare-day upper bound is now compared as written, which is what SqlDriver answers since group 2. Item 7's scope, as seat answer 5915193659 directs ("declare it in the changeset; it is not a decision"); the changeset declares it. Right.
  • A date column: unchanged on both paths. date stays day text on this driver (D-E2), and $lt next-day orders exactly as $lte day on that text. The storage suite keeps a direct created_on cell with the same rows. Right.
  • The A field operator whose lowering reuses another operator's key silently CLOBBERS it — $null, $between and $notContains on driver-memory and driver-mongodb, key-order dependent #13524 cell, seam-fed: $lte on the last day beside an author's own $ne now arrives as { $null: false } plus the lowering's NULL escape of the $ne; the expected document (value required, and null-or-not-equal) selects the same rows the former $and of two $ne did. Right in meaning; the exact shape is Test Core's verdict.
  • The clobber "fourth member" is retired on both sides: a direct $lte beside $lt is uncontested (each writes its own key); seam-fed, the lowering's $lt arrives as a conjunct of its own, so both bounds survive in either key order. The $lt row leaves the contested-keys map. Right.
  • Public surface: no exported name changes; translateFilter's signature is unchanged. @objectstack/core is now test-only in this package while package.json still lists it under dependencies; the report names it as a manifest-and-lockfile edit outside the card. Right to leave: debt, not a surface move.
  • matches-filter.ts has no net change: git diff from the merge-base lists nothing under packages/formula/, and at the head lteBound is declared and called at its two sites. Right, as the PR states it.

F7, measured and stopped — right as the half the PR says it is. The report names the two paths no seam lowers: the RLS write check, which judges the raw post-image while the typed RLS seam (rlsLowering with a guard) rewrites the guard's datetime columns only, so a date column reaches matchesFilterCondition as written; and a $lte against a { $field, addDays } referent, which the lowering never rewrites (a field reference is excluded in filter-lowering.ts). Both premises hold in the code at origin/main. The card's acceptance rule ("if routing it changes an answer, that face stops and is reported") is followed. The route is the seat's to answer, not this record's.

Carried, not this PR's — right: the three #20987 engine faces move to group 3b under the claim's H3 fallback (git ls-tree -r origin/main names no json-membership path, and @objectstack/formula depends on @objectstack/spec alone); the stale pointers filter.zod.ts :434 / :734 / :750 name SqlDriver.calendarDayUpperBoundRewrite / calendarDayBetweenRewrite (F1, verified at origin/main), so the claim's "where they name F6 / F7" condition leaves them to 3b with the read-scope-shared-lowering-seam.test.ts case title, as release 5924791165 places them.

Check-runs on the head (31). 16 completed / success: Auto Label, Dogfood Verify CLI, Dogfood Regression Gate (1/3), Dogfood Regression Gate (2/3), Build Core, No other open PR may claim the same single-writer path, Type Check · source gates, Check Documentation Links, filter, Check PR Size, Part-of PR must not also close its card, Check Changeset, The card this PR closes must claim this branch, Flag docs affected by code changes, Governed Surface Queue Guard, No other open PR may claim the same issue. 3 skipped: Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in); whether they are rostered is the seat's check-expected-skips reading. 12 in_progress, each NOT a verdict: Test Core (1/6), Test Core (2/6), Test Core (3/6), Test Core (4/6), Test Core (5/6), Test Core (6/6), Dogfood Regression Gate (3/3), Temporal Conformance (live PG + MySQL), Type Check · workspace, Type Check · consumer gates, Type Check · debt ledger, Lint & Repo Gates. Not waited on. The landing waits for them (Multi-agent discipline §7); this record does not.

② Semver level

  • The changeset .changeset/20822-driver-mongodb-face-copy.md is '@objectstack/driver-mongodb': patch, with Clause-②: no on its own line and the four moves stated: what is deleted, that a seam-fed read is unchanged, the item 7 convergence on a non-datetime column, and that a direct call gets the comparison it wrote, with the one-line way to keep the seam's reading (lowerFilterCondition(where, { isDatetimeColumn }) from @objectstack/spec/data). "No exported name changes." Right.
  • Clause-②: line: the PR body's second line is Clause-②: no, and the changeset carries the same. Right. The grade matches group 1b (driver-memory F3: patch, no, accepted) and differs from group 2's no (narrowing) regrade for the right reason: group 2 removed three protected members from an exported class; this diff removes no declared member. A direct-call answer moving is item 5's reading, not a published-surface narrowing.
  • The convergence on a non-datetime column of a registered object is item 7's scope as the changeset declares it, with Clause-②: no, as seat answer 5915193659 settled for group 1b. Right.
  • Check Changeset is success on this head. Not a skip-changeset diff: the package publishes and its behaviour on a direct call moves.
  • One sentence under-describes without being false: "the shared lowerFilterCondition has already rewritten on the declared datetime columns". Since group 2 the RLS seam with no guard rewrites every column; on that path the driver still receives a lowered filter and its answer is unchanged. Not a finding.

③ Boundary flags

Dev flags (the report's deviations), each answered:

  1. F7 not deleted — a measured stop, exactly as the card's acceptance text prescribes; the evidence pair stays in branch history (d045f1d6c deletes both copies, 394df163a restores lteBound), and the PR squashes. Complete as the half it declares.
  2. [finding] $contains / $notContains on a declared multi-valued or JSON-stored field still answer SUBSTRING on five faces, the analytics RLS read scope among them (u1 admits a row storing u10) #20987's three engine faces moved to 3b — the claim's H3 fallback, verified on origin/main (no json-membership path; formula's dependency edge).
  3. Stale pointers not carried — they name F1, verified; group 3b carries them with the last group PR.
  4. origin/main moved after the merge and no second merge was made — no commit on main since the merge-base e07566b73 touches packages/drivers/driver-mongodb/ or the changeset path (read at 8368f1c00); the queue rebuilds the merge ref.
  5. Real-mongod halves NOT MEASURED — the card's recorded gap (ruling 5902355785; ADR-0053 D-A3.1 coverage note): mongodb-temporal-conformance.test.ts and mongodb-datetime-storage.test.ts are opt-in behind OS_TEST_MONGODB_MEMORY_SERVER_ENABLED and skip in CI too (5 skipped files, 172 skipped tests). Their routing is by reading: the lowered / seamed helpers hand the driver what a typed seam hands it, the datetime columns of the synced field map. Escalated to the seat as a standing gap, not a new one, with one addition: the time-conformance cells now also pass through the lowering's NULL-polarity rules, equivalent under BSON $ne semantics but unexecuted anywhere until a binary is funded.

open_questions:

  • [0] F7's route (A / B / C / D) — escalated to the seat; ⛔ not this record's question. The PR's completeness does not depend on it.
  • [1] F7 membership versus substring with no field map (A / B / C) — group 3b's question; escalated with it.

Acceptance notes:

  • @objectstack/core test-only in driver-mongodb — noted; carrier none; a manifest-and-lockfile edit for whoever next touches that manifest.
  • query-syntax.mdx's direct-call sentence names the SqlDriver family alone; MongoDBDriver and InMemoryDriver now answer the same way. Incomplete, not false: the page states no capability this driver lacks. Carrier group 3b, under the card's "no open tail" rule.

Implemented-by: claude/issue-20822-g3a-mongodb-formula
Reviewed-by: session_01Ujdtvqs7ree7WyQmEDwEnG

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 1, 2026 07:21
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 1, 2026 07:21
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 53ed3d1 Oct 1, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20822-g3a-mongodb-formula branch October 1, 2026 07:44
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…rness text state each case in words instead of a tracker number (stage 5b) (objectstack-ai#21139)

Fixes objectstack-ai#20513
Clause-②: no

**Stage 5b, the last stage of this lane under the maintainer's A / A
ruling on the card (5902360492): the `src/`-shipped test strings.**
These are the 28 literals (32 occurrences in 6 files) that PR objectstack-ai#20924's
body inventoried as stage 5b and release 5922539397 restates: shared
case labels, a suite title, two contract `why` texts and the mongod
test-harness text. After this PR the `domain:engine` packages hold zero
rows in `scripts/doc-authoring-prose-id.baseline.json`. Text only: no
error `code`, export, HTTP status, case value or control flow moves.

## What this does

Each label or harness line sent the reader to a tracker number for the
reason behind it. In form D, as stages 1 to 5a applied it, the number
goes. Where the sentence already stated the case, only the citation goes
(17 literals). Where the sentence leaned on the number, it now says the
case in words (11 literals):

| Where | Cited | The text now says |
|---|---|---|
| `driver-mongodb` `test-mongod.ts` skip reason | 5517 | "retired from
default test runs because concurrent downloads made green runs exit 1" |
| `driver-mongodb` `test-mongod.ts` abandoned-download warning | 5517 |
"Ignoring the MongoDB binary download abandoned by a concurrent-download
race" |
| `ENGINE_DELETE_DISPATCH_CASES`: where.id plus a CAS operator, `multi:
false` | 11009 | "refused, the by-id path would drop the CAS guard" |
| `ENGINE_FINDONE_PREDICATE_CASES`: an empty `where` object | 3896 |
"read as match-every-row, so NOT a predicate" |
| `ENGINE_UPDATE_DISPATCH_CASES`: payload id beside a DIFFERENT scalar
where.id | 11142, 5748 | "refused; the payload id no longer silently
wins over a where.id naming another row" |
| same table: payload id beside an `$in` where.id, no multi | 11230 |
"refused; the by-id write would silently drop the declared row SET" |
| same table: payload id beside a NULL where.id | 11230 | "refused; a
declared where.id that is not one primary key is never silently dropped"
|
| same table: payload id beside a FALSY scalar where.id | 11142, 11230 |
"(it is a scalar, so neither the different-row nor the non-scalar
refusal applies)" |
| same table: where.id plus a CAS operator, `multi: false` | 11009 |
"the redeliver shape, refused rather than dropping its CAS guard" |
| same table: payload id plus an extra where predicate, no multi | 11009
| "refused; the by-id write would silently drop the predicate, through
the payload door" |
| `OBJECT_SCHEMA_MASK_CASES` `write-capable-caller/exempt` `why` | 7020,
6603 | "D4 is DERIVED from the schema write gate (`manage_metadata`)" |

Citation only: the contract suite's `serialized-form identity` group
title; 3 delete, 2 findOne and 9 update labels; the empty-readable-set
`why`; the mongod acquisition-timeout message. Each new clause was read
against today's code: `resolveEngineUpdateDispatch` (the different-row
refusal fires only on a truthy scalar where.id, the non-scalar refusal
only on a declared non-scalar), `ObjectQL.requireFindOnePredicate`
(absent, null and an empty object are read as match-every-row), and
`OBJECT_SCHEMA_WRITE_CAPABILITIES` (`manage_metadata`, the key the D4
exemption is derived from).

The `test-mongod.ts` docblock "Names the issue and the switch" now says
"Names the reason and the switch", so it stays true. No other comment or
docblock moves; code comments still cite ids, as in every earlier stage.

**Re-pins (H4).** The two `objectstack-ai#5517` number pins in
`mongodb-memory-server-gate.test.ts` now assert the text that replaced
the number: the skip line contains "retired from default test runs
because concurrent downloads made green runs exit 1", and the guard's
warning contains "download abandoned by a concurrent-download race".
Neither pin was deleted. Both were proven able to fail (below).

## Ledger (`scripts/doc-authoring-prose-id.baseline.json`)

Recomputed with `node scripts/check-doc-authoring.mjs --census-ledger`
(exit 0: no growth refusal) and copied into place. The diff is 25
deleted lines, nothing added.

| File | Before | After |
|---|---|---|
| `drivers/driver-mongodb/src/test-mongod.ts` | 3 | 0 |
| `metadata-core/src/contract-suite.ts` | 1 | 0 |
| `metadata-core/src/engine-delete-dispatch.ts` | 4 | 0 |
| `metadata-core/src/engine-findone-predicate.ts` | 3 | 0 |
| `metadata-core/src/engine-update-dispatch.ts` | 18 | 0 |
| `metadata-core/src/object-schema-fls-contract.ts` | 3 | 0 |
| **This stage** | **32 in 6 files** | **0** |

Whole ledger: files 175 → 169, pairs 441 → 428, occurrences 653 → 621.
Gate pinned sites 549 → 521. No other file's row moves. After each of
the four merges of `origin/main` (below) the recomputed ledger was
byte-identical to the committed one.

## Hypotheses, measured

- **H1, held.** At `origin/main` `9c8b65aa2` the six files held exactly
the inventory: `objectstack-ai#5517` ×3; `objectstack-ai#7856` ×1; `objectstack-ai#11009` ×4 (delete); `objectstack-ai#11767`,
`objectstack-ai#3896`, `objectstack-ai#4346` (findOne); `objectstack-ai#11009` ×6, `objectstack-ai#11142` ×4, `objectstack-ai#11230` ×4,
`objectstack-ai#5748` ×4 (update); `objectstack-ai#3545`, `objectstack-ai#6603`, `objectstack-ai#7020` (FLS). The gate's census
listed 28 literals carrying 32 occurrences.
- **H2, held.** The recomputed ledger has 0 rows under
`packages/objectql`, `core`, `formula`, `metadata*`, `platform-objects`,
`drivers/driver-*` and `plugins/plugin-pinyin-search` (the
`domain:engine` path set). The lane's ledger is empty. The whole ledger
is not, so the gate's stale-baseline arm keeps its blindness floor.
- **H3, nothing keys on the old labels.** Each of the 28 old literals
was searched byte for byte across the repo (`dist` excluded). Each was
found only in this stage's own source files (two delete labels have
update twins), with two exceptions. The tail `download — objectstack-ai#5517)` also
sits in a comment in `mongodb-pipeline-evaluator.testkit.ts`.
`serialized-form identity (objectstack-ai#7856)` also titles an independent `describe`
in `metadata-core/test/canonicalize.test.ts`, a test file outside the
ledger. Both are left alone. No CI workflow, `package.json` script,
vitest config, snapshot or skip list carries the labels or a `-t` filter
on them. The cited numbers appear elsewhere only in workflow comments,
an ADR anchor's invariant prose and a merge-parent sha. The labels reach
other code only as test names: objectql's `engine-*-dispatch` /
`engine-findone-predicate` parity loops, `service-queue`'s `it.each`,
and `assertObjectSchemaMaskCase`'s failure text. Each label stays unique
within its table (35 / 19 / 18).
- **H4, held.** See Re-pins.

## Changeset

`.changeset/20513-metadata-core-case-labels-state-the-case.md`: `patch`
for `@objectstack/metadata-core`. Its case tables ship in
`dist/index.*`, and the contract suite and FLS table ship in
`dist/testing.js` (measured after build: the new labels found in those
files).

There is **no `@objectstack/driver-mongodb` entry**, because
`test-mongod.ts` is not published. The package builds `src/index.ts`
only, and only test files import `test-mongod.ts`. Measured after build:
`mongodSkipReason`, "retired from default test runs",
"concurrent-download race" and "waiting for the MongoDB binary" are each
in 0 files under `packages/drivers/driver-mongodb/dist`. The positive
control `MongoDBDriver` is in 4.

## Text-only proof

A TypeScript-AST skeleton of each changed `.ts` file compares merge base
`670680e93` with head `24b72b809`. In the skeleton, every string literal
and template text is one placeholder, pure-literal `+` operands
collapse, and comments are never read. Result: 7 of 7 SAME, with token
and literal-slot counts identical per file. Control: the same tool
reports DIFF on `rest` `import-template.ts` across `6f1f1c103`, a real
code change.

## Tests (head `24b72b809` unless noted)

- Build: `turbo run build` over `./packages/*` and `./packages/*/*`
under the verify lock, 71/71, after the last merge.
- `@objectstack/metadata-core`: 16 files / 298 tests passed; `typecheck`
exit 0.
- `@objectstack/driver-mongodb`: 30 files passed and 5 skipped, 675
tests passed and 172 skipped (the skips need a MongoDB server);
`typecheck` exit 0, including `check:test-typecheck`.
- Consumers of the case tables, against the rebuilt `metadata-core`
`dist`:
- `objectql`, 7 files / 181 tests: `engine-delete-dispatch`,
`engine-update-dispatch`, `engine-findone-predicate`,
`engine-update-by-id-payload-id`, `engine-update-multi-payload-id`,
`engine-write-not-found-gate` and
`validation/operator-object-write-value`;
  - `service-queue` `db-queue-adapter`, 38;
  - `rest` `meta-object-fls`, 95;
  - `runtime` `domains/meta-object-fls`, 85;
  - `metadata-fs` `contract`, 31;
- `metadata-protocol` `sys-metadata-repository.contract` and
`protocol.update-path-id-wins`, 64.
- A verbose run before the merges showed the parity loops naming the new
labels. objectql's tests resolve `metadata-core` through `dist`, so this
shows they read the rebuilt build.
- Re-pin mutations, one-off, on the committed head (`2729901db`),
through `scripts/ablation-replace.mjs` in WRAP mode (restore armed on
EXIT/INT/TERM):
- Skip-reason anchor ×1 → ×0: `mongodb-memory-server-gate.test.ts` 1
failed / 7 passed.
  - Warning anchor ×1 → ×0: 1 failed / 7 passed.
- Each restore: blob equals HEAD's (`22a95643c8`), and `git diff HEAD`
is empty. After both, `git diff HEAD` was 0 bytes and `git status
--porcelain` 0 lines.

## Gates

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths) at `24b72b809` derived 69
commands. All 69 ran one at a time from the worktree, each exit 0.
`--ran` reports "69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN". Among
them:

- `check:doc-authoring`: 521 pinned sites across 169 files, no growth,
no burn-down unrecorded;
- `check:driver-conformance`: 50 covered cells, 0 DEBT, 0 exempt, the
same reading as before the change;
- `check:engine-double-contract`, `check:dual-build-cjs-loads` (105
require entry points across 66 packages load, read from the post-merge
build), `check:nul-bytes`, `check:type-check-coverage` and
`check:type-check-debt`.

After the first merge (`packages/spec` moved on main): `pnpm --filter
@objectstack/spec check:generated` reported all 15 artifacts up to date.

Narrowed lint: `eslint --no-inline-config --format json` over the 7
changed `.ts` files reported 7 files, 0 errors and 0 warnings, none
ignored (counts from eslint's JSON). The resolved `parserOptions` are
`ecmaVersion: latest, sourceType: module`, with no `project` or
`projectService`. So no type-aware rule runs, and this diff cannot move
an untouched file's verdict. Repo-wide `pnpm lint` is CI's.

NOT MEASURED locally (CI's): the live MongoDB cells of `driver-mongodb`,
the Test Core shards, Dogfood, and the workspace type-check lanes.

## Acceptance notes

- `origin/main` was merged four times while this was open (`1d55ee66e`,
`4f1c99822`, `4516a0643`, `24b72b809`; the third brings in objectstack-ai#21097). None
of the incoming commits touches a file this PR changes. They touch
`objectql`, `rest`, `runtime` and `metadata-protocol`, so those consumer
suites were re-run at the final head. `main` moved again after the gate
union; the queue rebuilds on it.
- PR objectstack-ai#21105, in `driver-mongodb`'s filter and temporal suites, landed
before the first merge and shares no file with this PR.
- `metadata-core/test/canonicalize.test.ts` titles its own `describe`
`serialized-form identity (objectstack-ai#7856)`, and
`mongodb-memory-server-gate.test.ts` titles two `describe` blocks
`[objectstack-ai#5517] …`. Both are `*.test.ts` files, which the ruling's third
category (`src/`-shipped test strings) and the ledger do not cover. They
are left as they are: noted, not filed.
- The lane children objectstack-ai#20749, objectstack-ai#20751 and objectstack-ai#20753 carry `Blocked-by:` lines
naming this card. They unlock when it leaves the open state on merge.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Ujdtvqs7ree7WyQmEDwEnG)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants