Skip to content

fix(plugin-security,driver-sql,driver-turso): lower type-blind at the RLS seam without a guard, then delete the F1/F2 whole-day and NOT-rewrite copies (#5930 step 4, group 2) - #20988

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-20822-g2-driver-sql-turso
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-20822-g2-driver-sql-turso

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Part of #20822
Clause-②: no (narrowing)

#5930 step 4, group 2: driver-sql F1, driver-turso remote F2, and the plugin-security rlsLowering absent-guard twin (the seat's answer 5918373748, A). Ruled by 5902355785 (D4 (b)) and ADR-0053 D-D1 items 5, 7 and 9 as amended. The order follows group 1b's pattern: make the seam type-blind first, then delete. Seven commits on main at 5f6b63a6fd:

# Commit What it does
1 e20e17e929 fix(plugin-security) rlsLowering hands the lowering no reader when the guard carries no datetime set (no guard, or a guard built without types). The RLS compile seam is now type-blind there (item 7). The typed branch is byte-identical. The pin "a guard with no types reads no column as datetime" is rewritten to the new reading. A new pin file covers the no-guard compile for both clauses, and a using policy through SecurityPlugin.getReadFilter handed to SqlDriver.find.
2 a9b1f79282 refactor(driver-sql, driver-turso) Deletes the whole-day copies. F1: calendarDayExclusiveUpperBound, calendarDayUpperBoundRewrite and calendarDayBetweenRewrite, plus their two call blocks in the emitter. F2: toRemoteFilter's whole-day arms (toRemoteUpperBound and the $lte / $between-max rewrites). The direct-call temporal suites are routed through lowerFilterCondition with each fixture's declared-datetime reader. New item-5 pins on both drivers.
3 d5277f98e6 refactor(driver-sql, driver-turso) Deletes the NULL-safe $not copies: F1 nullSafeNegationOperand and its three polarity tables, and F2 RemoteTransport's copy of the same. The $not direct-call suites are routed, including two service-analytics find() faces that stand for the engine. Refusal pins stay direct calls. New pins on both drivers.
4 4658e72939 docs(changeset) .changeset/20822-driver-sql-turso-copies.md, first draft.
5 6ec47a8770 docs(changeset, spec) Patch round (the seat's answers 5922273550 and 5922490848): driver-sql minor, Clause-②: no (narrowing), a BREAKING paragraph with a FROM → TO line, both step-2 sentences superseded, and @objectstack/spec patch for the ledger entry 18.driver-sql-calendar-day-methods-removed.ts with the regenerated registry.ts. The other three packages stay patch.
6 d8b8f9095a fix(spec) The ledger entry's printed reason states the decision in words instead of citing tracker ids. CI's migrate-meta-engine-guidance test was red on commit 5 for this.
7 0e8378cf54 docs(objectql) content/docs/protocol/objectql/query-syntax.mdx states the whole-day rule as the shared lowering's (the engine's where seam and the RLS compile seam), and a direct call to SqlDriver or a driver built on it as compared as written (contract review 5923464846, FAIL 1).

49 files, +1501 / -977 against 5f6b63a6fd at 0e8378cf54, under the 5000-line human-merge threshold. No governed path.

The answers that move, named

All rows were measured on SQLite (better-sqlite3 :memory:, and the libsql stub for Turso remote).

Path Filter Before After Ruled by
Any seamed read: engine where positions, aggregations[i].filter, RLS compile seam with a typed guard any — no move (ablations below) item 9
RLS using / check compiled with no guard, or with a guard without a datetime set record.signed_on before-or-on '2026-01-05' { signed_on: { $lte: '2026-01-05' } }, as written { signed_on: { $lt: '2026-01-06' } }; SQLite keeps every row of the day (pin §B); InMemoryDriver keeps the whole day too item 7 (seat answer A)
Direct SqlDriver / SqliteWasmDriver / TursoDriver call (both faces), datetime column $lte a bare day the whole day that day's midnight, as written item 5
same $between with a bare-day max the whole max day inclusive at both ends, as written item 5
same $lte '9999-12-31' every row with a value that midnight, as written item 5
Direct call, both faces $not over =, an ordering operator, $in, $contains, a $or a row with a NULL column was returned (#5146) SQL's three-valued NOT: that row is not returned item 5
same $not over $ne / $nin / $notContains, and $ne / $nin / $notContains without a $not NULL-safe unchanged: each emitter spells these NULL-safe itself (applyNullSafeNegative / nullSafeNegative, #5298); they are not copies of the $not rewrite —
Direct RemoteTransport call with an author-marked filter a refusal raised inside a $not operand (for example $or: [null]) withheld even for the author, because the rewrite's nodes carried no provenance mark resolves against the author's own mark and names the branch, as it does outside a $not #8220 contract (a refusal message, not an answer)

Nothing moved beyond item 5 or item 7, so no site stopped on that clause.

Two stops, measured (H3, and the $between split)

Hypotheses

  • H1 (sites): held, at 75519e1c0a, the first base. sql-driver.ts is unchanged by the rebase onto 5f6b63a6fd.
    • F1:
      • the quartet is lines 5054 to 5298: NullGuard 5064, nullValueSatisfiesOperator 5077, operatorIsNullTotal 5141, nullGuardForFieldSpec 5180, nullSafeNegationOperand 5265;
      • it is called from the $not branch at 16943;
      • assertDefinedComparands is at 4707, called at 4929 and 16588;
      • calendarDayExclusiveUpperBound is at 15476, calendarDayUpperBoundRewrite at 15498 and calendarDayBetweenRewrite at 15521, called at 17036 and 17053.
    • F2:
      • toRemoteFilter is at 2567 (the dispatch said about 2566);
      • the $between arm is at 2610 and the $lte arm at 2638;
      • toRemoteUpperBound is at 2685, and it calls calendarDayUpperBoundRewrite at 2691;
      • remote-transport.ts has its quartet at 379 to 614, called at 3116, and assertDefinedComparands at 4364, called at 2961.
  • H2 (deleting moves no seamed answer): held for every deleted copy. The ablations restored each copy on the committed head. Every routed suite stayed green with the copy back, and only the new direct-call pins turned red. The $between split is the exception above: it is structural, not a copy.
  • H3: held as a stop for both faces. See above.
  • H4 (direct callers): no caller to route. Grep over production src (driver.find / count / aggregate / updateMany / deleteMany / distinct, getDriverForObject, _find / _count):
    • metadata DatabaseLoader in driver mode builds equality filters only (baseFilter, nextEventSeq). queryHistory is already lowered (group 1b), and its §A pin stays green on SQLite with F1 gone.
    • metadata history-cleanup uses recorded_at $lt an instant, and type $nin.
    • objectql LifecycleService's archive pass uses hot.find / cold.deleteMany, through getDriverForObject / datasource. Its filters are $lt an ISO instant, an organization_id equality, and $or: [organization_id $nin, organization_id null].
    • The CLI's secret-reference-union / secret orphans use { type: 'datasource' } or no where.
    • metadata-protocol's migrations run raw SQL.
    • None of these builds a bare-day $lte, a $between, a $not or an undefined comparand. $lt an instant is never widened. $nin outside a $not keeps the emitter's own NULL-safe form.
  • H5 (the RLS twin): held. rlsLowering(undefined) and a guard without datetime both return {}. The existing pin flipped as predicted. The new pin goes through a using policy: SQLite keeps the whole day. The memory half is the filter shape (driver-agnostic) plus driver-memory's own §C pin of a type-blind lowered filter. A plugin-security test that imports @objectstack/driver-memory is an arrival outside the driver-memory census ledger.
  • H6: NOT MEASURED here. A live PostgreSQL / MySQL matrix and a live remote Turso server were not measured. The live matrix is Temporal Conformance (live PG + MySQL) in CI. Remote Turso was measured against the libsql stub only.

For the review

  • packages/spec is touched for the ledger only: one semantic entry in src/migrations/entries/semantic/ and the regenerated registry.ts. spec-changes and the upgrade guide stay byte-identical, because the generators stop at protocol 17. It is a declared cross-lane surface (the seat's answer 5922490848).

  • SqlDriver's published class surface shrinks. calendarDayExclusiveUpperBound, calendarDayUpperBoundRewrite and calendarDayBetweenRewrite were protected members of the exported class, so they are declared in dist/index.d.ts. Measured against the published .d.ts (tsc 6.0.3): a call gets TS2339, and an override re-declaration gets TS4113. A plain re-declaration still compiles, but the driver never calls it. The replacement is lowerFilterCondition from @objectstack/spec/data. The changeset carries it as BREAKING (minor), with the ADR-0087 ledger entry driver-sql-calendar-day-methods-removed.

    • In-repo, TursoDriver was the one caller, and it is updated. The test probe subclass is rewritten.
    • objectui / cloud at their pins: NOT MEASURED (no sibling checkout in this container).
    • The changeset states the removal.
  • One sentence of this release's step-2 changeset is superseded. .changeset/5930-shared-filter-lowering.md says "A guard without that set treats no column as datetime" and "Each driver keeps its own copy of these rules". The new changeset has a Supersedes paragraph naming both sentences. The step-2 file is not edited here, because the foreign-changeset rule in check-empty-changeset (finding: random changeset filenames collide silently across parallel agents — a round overwrote a sibling PR's minor changeset and every gate stayed green #17712) forbids it.

  • Expected values changed in three places; all other edits wrap arguments.

    1. The H5 flip in rls-shared-lowering-seam.test.ts.
    2. The $not refusal-provenance flip in remote-transport-not-operator.test.ts (e).
    3. sql-driver-calendar-day-upper-bound.test.ts's probe matrix. It called the deleted methods. It now pins the lowered bound's physical form per dialect, with the same values as before: 2026-07-29T00:00:00.000Z, and 2026-07-29 00:00:00.000 on MySQL. The calendar arithmetic and the scope rows are the lowering's (spec's filter-lowering.test.ts).

    Every other changed expect( line wraps its filter in seamed(...), and its expected value is unchanged.

Verification at head 4658e72939 (driver code; unchanged since. The patch round's gates at d8b8f9095a: 94 derived, 94 run, every one exit 0, under a stale-tree warning for 5 files main changed outside this diff)

Suites, each with vitest run --maxWorkers=2. Package baselines are from 75519e1c0a.

Package Files Tests Baseline
driver-sql 203 passed, 11 skipped 3281 passed, 188 skipped 3266 passed / 188 skipped
driver-turso 81 2210 passed, 33 skipped 2195 / 33
driver-sqlite-wasm 36 675 675
plugin-security 151 3272 passed, 23 skipped 3227 / 23
service-analytics 148 3425 —
metadata 56 836 —
objectql, --project local 348 6821 —
objectql, --project repo 1 5 —
rest 249 4964 passed, 114 skipped —

The typechecks for driver-sql, driver-turso, driver-sqlite-wasm, plugin-security (including check:test-typecheck) and service-analytics all exit 0. driver-sql's tsc program lists all 214 of its test files.

Ablations. Each restores the deleted copy on the committed head through scripts/ablation-replace.mjs. Each is restored with blob equal to HEAD and git diff HEAD empty.

# Restored Red (expected direction) Everything else
A1 F1 whole-day 8 of 3281: exactly the 8 §A cells (4 canonical, 4 legacy-normalised) green
A2 F1 $not rewrite 6: the 4 §B direct cells, plus 2 SQL-text pins that compile the lowered operand. The restored copy guards it a second time: diagnostics grow, same rows. green, including every row-result suite
A3 F1 + F2 whole-day 8: the turso §A cells, 4 per face green
A4 F2 transport $not rewrite 17: the 4 §B remote cells, 12 transport SQL-text pins (the double guard again), and the (e) provenance pin (the rewrite's new nodes withhold again) green, including filter-logic remote and local/remote NULL parity
A5 the RLS twin reverted 8: 4 §A compile cells, both §B cells (the SQLite rows fall to midnight-only), and the 2 flipped seam pins green
S1 the $between split removed the 6 cells named above green

A3 rebuilt driver-sql, and ablation-dist-preflight showed the marker present in dist/. The restore leg rebuilt it again, and --absent showed the marker gone from all 6 built files with the tree clean.

Gates.

  • node scripts/pm/dispatch-gates.mjs --commands derives 67 families: 46 paths against merge base 5f6b63a6f, with no stale-tree warning after the rebase.
  • All 67 were run, and every one exits 0. That includes check:driver-conformance (OK, 50 covered cells, 0 DEBT), check:driver-memory-census, check:engine-double-contract, check:query-options-erasure, check:nul-bytes, check:dual-build-cjs-loads and check:i18n. The last two first exited 3 (PREREQUISITE NOT MET) and were rerun after a full workspace build (72 tasks).
  • dispatch-gates --ran: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN.

Lint (narrowed).

  • eslint --no-inline-config --format json over the 45 changed .ts files: 45 files, 0 errors, 0 warnings.
  • Every file resolves under --print-config.
  • eslint.config.mjs never enables type-aware linting (no parserOptions.project), so no untouched file's verdict can move.
  • The full pnpm lint is CI's.

Acceptance notes

  • Stale pointers outside this file surface, for the last group PR, which per landing record 5920439482 corrects the stale pointers outside driver-memory:
    • objectql having-filter.ts around line 1365, and spec filter.zod.ts around lines 434, 734 and 750, still name calendarDayUpperBoundRewrite / calendarDayBetweenRewrite.
    • service-analytics read-scope-shared-lowering-seam.test.ts has a case titled "RLS using, as written (a guard without types)". Its rows still hold, but the RLS seam no longer emits that shape.
  • One copy is left in F1 on purpose: the undefined refusal. The seat answered A (5922273550): it stays in both faces as a door, not a copy of the lowering's meaning, because without it 5 of F1's 22 direct-call positions move from 400 to an answer.
  • applyNullSafeNegative (F1) and nullSafeNegative (F2) are not in the design's census quartet. They are the emitters' own NULL-safe spelling of $ne / $nin / $notContains, and they stay.

Generated by Claude Code

… guard carries no type set

ADR-0053 D-D1 item 7, as amended: a seam that cannot read the declared
field type applies the whole-day rewrite type-blind. rlsLowering read an
absent guard (the security plugin could not resolve the object's fields)
or a guard without a datetime set as "no column is datetime", which left
a bare-day upper bound to the drivers' own copies of the rule. Those
copies are deleted in the next commits, and a using filter is composed
into the query after the engine's where seam, so nothing else lowers it.

rlsLowering now hands the lowering no reader in that case. The typed
branch is unchanged. The existing pin "a guard with no types reads no
column as datetime" is rewritten to the new reading, and a new pin file
covers the no-guard compile for both clauses and a using policy through
SecurityPlugin.getReadFilter handed to SqlDriver.find.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
…seams' lowering answers the bound

ADR-0053 D-D1 items 5 and 9, as amended. The shared lowering
(lowerFilterCondition, @objectstack/spec/data) widens a bare-day upper
bound once, at the seams, so every seamed read hands these drivers $lt
the next day and no $between on a declared datetime. The faces' own
copies were idempotent on that input and are deleted:

- driver-sql F1: calendarDayExclusiveUpperBound,
  calendarDayUpperBoundRewrite and calendarDayBetweenRewrite (protected
  methods on the exported class) and their two call blocks in the
  emitter, on the plain and legacy-normalised column paths;
- driver-turso F2: toRemoteFilter's whole-day arms (toRemoteUpperBound
  and the $lte / $between-max rewrites). The two-bound $between split the
  remote transport needs is kept, structural only. The transport's
  $between refusal text no longer credits the split with the rule.

A caller that passes no seam gets the comparison it wrote (item 5). The
direct-call temporal suites (driver-sql, driver-sqlite-wasm, turso local
and remote) are routed through lowerFilterCondition with each fixture's
declared-datetime reader; the expected rows are unchanged. The probe
matrix that called the deleted methods now pins the lowered bound's
physical form per dialect. New pins cover one cell per deleted branch on
both drivers, with the lowered control.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
… the seams' lowering totalises the operand

ADR-0053 D-D1 items 5 and 9, as amended. The shared lowering's rule 3
makes every leaf of a $not operand total in the direction its operator
answers for a row with no value (#5146), once, at the seams. The faces'
own copies were idempotent on that input and are deleted:

- driver-sql F1: nullSafeNegationOperand and its polarity tables
  (nullValueSatisfiesOperator, operatorIsNullTotal,
  nullGuardForFieldSpec); the $not branch negates the operand it is
  handed;
- driver-turso F2: RemoteTransport's copy of the same (the third hand
  copy of the ruling).

applyNullSafeNegative / nullSafeNegative, the emitters' own NULL-safe
spelling of $ne, $nin and $notContains, are not copies of the $not
rewrite and stay. A caller that passes no seam gets SQL's three-valued
NOT (item 5); both Turso faces still agree. A refusal raised inside a
$not now resolves against the caller's own provenance marks, since the
operand is no longer a rewritten copy.

Not deleted (H3 stop): assertDefinedComparands, the undefined-comparand
refusal, in both faces. Deleting driver-sql's moves a direct caller from
INVALID_FILTER/400 to an answer on five positions; the transport's is
held to driver-sql's wording by the local/remote parity suite.

The $not direct-call suites are routed through lowerFilterCondition,
including two service-analytics find() faces that stand for the engine;
refusal pins stay direct calls. New pins cover the direct $not on both
drivers, with the lowered control.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
…gin-security patch for the deleted face copies

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

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/driver-sql, @objectstack/driver-turso, @objectstack/plugin-security, @objectstack/spec, touching 20 documentable anchor(s).

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

  • content/docs/data-modeling/drivers.mdx (via SqlDriver (symbol, a top-level class), TursoDriver (symbol, a top-level class))
  • content/docs/data-modeling/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/permissions/tenant-audit-census.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via SqlDriver (symbol, a top-level class), TursoDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/lifecycle.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/query-syntax.mdx (via SqlDriver (symbol, a top-level class), TursoDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/types.mdx (via SqlDriver (symbol, a top-level class))

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

  • content/docs/releases/v17/17-0.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/releases/v17/17-5.mdx (via SqlDriver (symbol, a top-level class), TursoDriver (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
  • 4 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 — 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 2f2fa11d756f665a4c06160480c1dce15b9d67a4 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 84631a24bdb4300f780eb9213369aa004f73fb58 — the merge of head 0e8378cf54e4a7809cd6388774a227e6b25a2a07 into base 2f2fa11d756f665a4c06160480c1dce15b9d67a4, 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 84631a24bdb4300f780eb9213369aa004f73fb58 && git checkout 84631a24bdb4300f780eb9213369aa004f73fb58
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2f2fa11d756f665a4c06160480c1dce15b9d67a4 0e8378cf54e4a7809cd6388774a227e6b25a2a07 && git checkout -B drift-repro 2f2fa11d756f665a4c06160480c1dce15b9d67a4 && git merge --no-ff 0e8378cf54e4a7809cd6388774a227e6b25a2a07

node scripts/docs-audit/affected-docs.mjs --json 2f2fa11d756f665a4c06160480c1dce15b9d67a4

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

…ed methods, registered in the ADR-0087 ledger

The changeset grades the removal of SqlDriver's calendarDayExclusiveUpperBound,
calendarDayUpperBoundRewrite and calendarDayBetweenRewrite as a narrowing:
driver-sql moves to minor under the launch-window convention, with a BREAKING
paragraph and its FROM -> TO line to lowerFilterCondition from
@objectstack/spec/data. It states plainly which two sentences of the
unreleased shared-lowering changeset it supersedes.

The protocol-18 step of the ledger gains the semantic entry
driver-sql-calendar-day-methods-removed, and the changeset carries the
matching registered disposition. registry.ts is regenerated by
gen:migration-registry. gen:spec-changes and gen:upgrade-guide were re-run
and left their outputs byte-identical: both stop at the current protocol, 17.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
…lowering in words, not by tracker number

The printed guidance of every semantic entry must carry no tracker id: an
author reads it in the terminal, and a number sends them to a page that can
be deleted. The entry's reason cited the lowering and the removal by tracker
number. It now names the shared filter lowering in @objectstack/spec/data
(lowerFilterCondition) and says the driver's copy was deleted, with no number.
registry.ts is regenerated by gen:migration-registry. gen:spec-changes and
gen:upgrade-guide were re-run and left their outputs byte-identical: both stop
at the current protocol, 17.

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

Copy link
Copy Markdown
Contributor Author

Contract review

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

Head confirmed on the PR at the moment of reading (open, draft, base main, head repo is the base repo, 6 commits). Merge base 5f6b63a6fd; six single-parent commits e20e17e929 → a9b1f79282 → d5277f98e6 → 4658e72939 → 6ec47a8770 → d8b8f9095a, one author and committer identity throughout, no merge commit. Net diff against the merge base: 48 files, +1490 / -973, the PR's file list exactly; no governed path. main is 14 commits past the merge base at reading (110 files), none of them among the PR's 48 paths; mergeable_state is clean.

Check-runs on this head at the moment of reading (not waited on): 42 runs, 37 success, 5 skipped (Console Pin Gate, Build Docs, Packed-tarball smoke, one Auto Label, one Check PR Size), 0 in_progress, 0 failed. Check Changeset (twice), Governed Surface Queue Guard, Lint & Repo Gates, Spec property liveness, Temporal Conformance (live PG + MySQL), Test Core 1 to 6 and the four Type Check jobs are all success. On commit 5 (6ec47a8770) Test Core and Test Core (6/6) were failure, as the PR body says; commit 6 cleared them.

① Derived judgments

Commit 1 (e20e17e929, plugin-security) — right. rlsLowering now returns {} when the guard carries no datetime set (no guard, or a guard built without types) and the typed reader otherwise; the typed branch answers exactly as before. The function is module-private and the only .d.ts-visible change is the RlsFieldGuard.datetime doc comment, which now states the type-blind reading (the stale sentence record 5919945193 noted is corrected here). Reach: judgeCompiledComparands, so using and check alike. Accept set unchanged: the lowering never refuses. The one answer move is a no-guard or typeless-guard policy's bare-day upper bound, now the whole named day — ADR-0053 D-D1 item 7's second half, the seat's answer A (5918373748). Pins: the flipped seam pin (place 1 of 3), the new §A (both clauses: bare day, last day, unresolved {today} left bare, instant never widened) and §B through SecurityPlugin.getReadFilter with an unresolvable schema handed to SqlDriver.find, three rows of the day kept. The memory half is by composition (driver-agnostic filter plus driver-memory's own §C pin), not through a door — the PR body's "InMemoryDriver keeps the whole day too" is an inference, a sound one.

Commit 2 (a9b1f79282) — right. F1: calendarDayExclusiveUpperBound, calendarDayUpperBoundRewrite and calendarDayBetweenRewrite (protected) are gone from sql-driver.ts (0 mentions at head), with both emitter call blocks: the $between day-range arm and the $lte rewrite, now op = rawOp and coerceFilterValue as written, on the plain and the legacy-normalised column paths. F2: toRemoteUpperBound (private) and the $lte arm are gone; the $between arm keeps only its structural split into $gte / $lte, both ends through temporalFilterValue, both inclusive. In-repo callers of the three methods on main, by grep: TursoDriver (updated here) and the test probe subclass (rewritten); every other mention is a comment (having-filter.ts:1365, filter.zod.ts:434/734/750, carried to group 3). Public surface: SqlDriver's declared class narrows by three protected members — the ② item. Accept set unchanged. Direct-call answers move as item 5 says.

Commit 3 (d5277f98e6) — right. F1 loses nullSafeNegationOperand, nullGuardForFieldSpec, operatorIsNullTotal, nullValueSatisfiesOperator and the NullGuard type (one 251-line block); the $not branch uses its operand as given. F2 loses the same quartet from remote-transport.ts (one 243-line block); const operand = value. applyNullSafeNegative (F1) and nullSafeNegative (F2) stay, and that reading is right: they are the emitters' SQL spelling of $ne / $nin / $notContains for the operator itself, not a rewrite of a $not operand; the card's census (its scope table, the authority for what this card deletes) does not list them, and sql-driver-not-null-safe.test.ts still pins the is null spelling inside a seamed $not { $ne }.

Routing, verified from the diff. Across the 38 modified suites every changed assertion wraps its filter in seamed(...) / lowerFilterCondition(...) with the fixture's own declared-datetime reader and keeps its expected value, except the three places the body names: (1) the seam pin flip to $lt the next day; (2) remote-transport-not-operator.test.ts (e), where an author-marked refusal inside a $not now names its branch instead of being withheld; (3) sql-driver-calendar-day-upper-bound.test.ts, whose probe subclass called the deleted methods and now asserts the lowered bound's per-dialect storage form with the same expected strings. Refusal pins stay direct calls (sql-driver-out-of-contract-filter-input.test.ts calls driver.find unlowered). The two service-analytics find() faces and the seven driver-sqlite-wasm suites are routed the same way. No it( or describe( removed; no assertion weakened. The ablations A1 to A5 and S1 are the dev's runs, not re-run; the new pins' cell layout (as-written versus lowered, four cells per face) agrees with their red counts.

Two measured stops — both right under D4 (b) and the card's acceptance rule.

H4, spot-checked from main's code. cli secret-reference-union.ts passes no where or { type: 'datasource' }; secret/orphans.ts reads by id; metadata history-cleanup.ts builds recorded_at $lt an instant and type $nin; database-loader.ts builds equality filters, and queryHistory lowers through HISTORY_FILTER_LOWERING since group 1b; objectql lifecycle-service.ts uses created_at $lt a cutoff for cold.deleteMany and an instant bound for hot.find; schema-migration-plugins.ts uses getDriverForObject for schema work. No production $not or $between is built outside objectql, the drivers, spec, formula and service-analytics. The claim holds on what was read.

Docs — the eight hand-written pages the drift check names, read on main. Seven name SqlDriver / TursoDriver only as a class, a constructor, or a door (findWithWindowFunctions, auditMissingTenant, createColumn, coerceFilterValue) and state none of the three rules. One does: content/docs/protocol/objectql/query-syntax.mdx, lines 568 to 572 — "as an upper bound ($lte, or the max of a $between) it covers the whole day — on a datetime column the driver compiles it half-open" (lt the next day). That sentence states the whole-day rule as the driver's own compile, which is the copy this PR deletes: at this head SqlDriver and TursoDriver compile a bare-day upper bound as written, the shared lowering at the engine and RLS seams rewrites it before a driver sees it, and a direct driver call — which the same page documents at lines 85 and 102 — no longer gets the whole day. FAIL item 1. The page's $not section (plain NOT (...) SQL) and its cross-field NULL table (the comparison emitter's stated semantics) are not falsified.

② Semver level

.changeset/20822-driver-sql-turso-copies.md: @objectstack/driver-sql minor; driver-sqlite-wasm, driver-turso, plugin-security, spec patch; summary line carries !; Clause-②: no (narrowing) in the changeset and on the PR body's line 2 (where the ADR-0087 gate reads the arm); one marker adr-0087: registered driver-sql-calendar-day-methods-removed; a BREAKING paragraph; a FROM → TO line (the three methods → lowerFilterCondition from @objectstack/spec/data). Check Changeset is green twice. Right, per package:

  • driver-sql minor, BREAKING, no (narrowing): three protected members of an exported concrete class leave the published declaration, so what a subclass author can call or override narrows — the grade of 17.5.0: every boot of a database created on 17.4 prints "Paged read of 'sys_migration' is NOT deterministic" for the platform's own primary-key lookup #20648 / PR fix(objectql,platform-objects,metadata-protocol): read sys_migration flag rows through findOne, ending the paging warning on every upgraded boot (#20648) #20766 (two published interfaces losing find): no (narrowing) is BREAKING, shipped minor under the launch-window convention that check-changeset-no-major enforces. TS2339 for a call and TS4113 for an override-modified re-declaration are the right codes, and a plain re-declaration compiling (no base member for noImplicitOverride to see) is the right reading; the root pins typescript ^6.0.3, as the measurement says. The .d.ts survey is the dev's build, not re-run; its findings match the source diff.
  • driver-sqlite-wasm patch: SqliteWasmDriver extends SqlDriver and declares none of the three; its own declaration is unchanged and the BREAKING paragraph names the inherited reach.
  • driver-turso patch: the only declaration change is the removed private toRemoteUpperBound, which no consumer or subclass can name; remote-face direct-call answers move as item 5 (the grade the group 1b driver-memory entry carried).
  • plugin-security patch: a module-private function and a doc comment; the no-guard widening is item 7 and the bullet states it.
  • spec patch: one semantic ledger entry plus the regenerated registry, the seat's A (5922490848); the gate's predicate 4 and the FROM → TO line close every not-required arm on the facts, and a code-only entry riding the removing PR has precedent (runtime-httpserver-wrapper-retired).

The ledger entry 18.driver-sql-calendar-day-methods-removed.ts is true and complete. surface names the three methods and the two inheriting classes (both extend SqlDriver); replacement is the lowering with isDatetimeColumn, and "leave it out and the rule applies to every column" matches the step-2 changeset's own words; reason states the rule, the deletion (items 5 and 9), the compiler half (TS2339 / TS4113) and the silent half (a bare re-declaration never called), and "the one caller was TursoDriver's remote face" is what the grep shows; acceptanceCriteria asks for a source search rather than tsc, for the stated reason, and names a row-level probe. Every field cites ADR-0053 / ADR-0087 only — no tracker number in any string the CLI prints (the migrate-meta-engine-guidance test that was red on commit 5 for #5930 is green at head). The // run above export const entry names the card and PR; that is the entries convention (entries/README.md: that comment run is the "why" and is carried into the registry; 71 of the 331 sibling entries carry a tracker number there) and is not a runtime string. registry.ts is generator output: the diff is one block inserted at the id-sorted position of step18's generated semantic list, byte-equal to the entry file including its comments, and check:migration-registry runs in lint.yml (Lint & Repo Gates green on this head).

Release text. The Supersedes paragraph quotes both now-false sentences of .changeset/5930-shared-filter-lowering.md verbatim (checked against main) and states each one's replacement with a pointer to the bullet that carries it. The step-2 file is untouched (the new changeset is the diff's only .changeset path, as #17712 requires). Enough for a release-notes reader: the compiled notes hold both entries, and spec and plugin-security CHANGELOGs receive both entries in the same release. One channel it cannot reach: objectql's CHANGELOG receives the step-2 sentence "Each driver keeps its own copy of these rules" with no superseding note, because this PR rightly does not bump objectql; the sentence there is about drivers, not objectql, and the dev's class (b) finding already names the seat-side wording fix. Escalated in ③, not a FAIL.

Review faces, sentence by sentence. Changeset: every sentence of the title, BREAKING, FROM → TO, Supersedes and the five bullets is true against the head's code (the plugin-security no-guard and typeless-guard cases; the deleted copies and kept emitters; the direct-call doors find, findOne, count, aggregate, distinct, updateMany, deleteMany, findWithWindowFunctions, all present on SqlDriver; the structural split; the kept undefined refusal worded alike; PROTOCOL_VERSION is 17 so the step-18 entry changes neither spec-changes.json nor the upgrade guide, and neither file is in the diff). PR body: line 2 true; "Six commits on main at 5f6b63a6fd" true; the six-row commits table true (row 6's CI red confirmed on commit 5's check-runs); the answers table true; the two stops true (the 13 / 14 / 20 counts are the dev's, corrected from the first report); H1 to H6 consistent with the diff; "TursoDriver was the one caller" true; "the test probe subclass is rewritten" true; "expected values changed in three places" true; the packages/spec surface named; the stale pointers in Acceptance notes verified. Two sentences false or ambiguous, both the seat's body edits, no verdict weight: line 15 "46 files, +1361 / -973 against 5f6b63a6fd" is commit 4's count — at this head it is 48 files, +1490 / -973 (the ledger entry, the registry block and the grown changeset); and the heading "One sentence of this release's step-2 changeset is superseded" introduces two sentences. The verification section's 67-family and 45-file counts are scoped to 4658e72939 by its heading and are the dev's runs.

③ Boundary flags

  • Behind main. At reading 14 commits and 110 files, none among this PR's paths; mergeable_state clean; CI ran on the merge ref (the drift check names the merge commit of this head into main). Not a defect; the queue leg re-runs.
  • Live PG / MySQL / remote Turso. The live PG + MySQL matrix is measured: Temporal Conformance (live PG + MySQL) is success on this head. A live remote Turso server is NOT MEASURED (libsql stub only) — ruling 5902355785 already records Turso remote as a confidence gap; carried, not a defect of this diff.
  • objectui / cloud subclasses of SqlDriver / TursoDriver. NOT MEASURED by the dev and not measurable here: this session is bound to this repository (org code search and the sibling repos answer 403), and Console Pin Gate was skipped on this head because the console path filter did not fire, so no CI leg built objectui against this removal. AGENTS.md Post-Task Checklist Add Changesets and GitHub Actions automation #4 makes the pinned-sibling grep a pre-merge check for the removal of an exported surface. Escalated to the landing seat: in the ACCEPT act, before pr_ready, git grep the three method names in ../objectui at .objectui-sha db11afd4967c (and in cloud at its pin); zero hits lands as reviewed, a hit makes it a sibling-fix-plus-pin-bump landing per the rule. Reach is unlikely (a console subclassing a SQL driver) but the rule asks for the grep, not the likelihood.
  • Triage's [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 pointer (5922592744). Its own condition — say so on [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 rather than drop it — is met by 5923177087: the Turso-remote $contains item stays on [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, serial after this PR, and goes to seat 2 with group 3. Right: it is a new behaviour fix with its own pin and answer move, outside a deletion card's claim.
  • Two --force-with-lease re-stacks before the PR opened. Six single-parent commits linear on 5f6b63a6fd, one author and committer identity with one trailer, no PR at the time; the reviewer and approval conditions are vacuous. Not a defect (the reading of record 5919688563).
  • Open questions. Report 5922245958 [0] (assertDefinedComparands) → seat A (5922273550), judged right above. Delta 5922471804 [0] (ADR-0087 disposition) → seat A register (5922490848), judged right above. None left open.
  • The dev's list of body sentences false after the patch round (5923254756): line 2, the commit count and rows, "For the review" bullets 1 and 2, the Acceptance note, the verification heading and the packages/spec surface are all corrected in the live body; the line-15 count is the one left (above), a seat edit.
  • Stale pointers outside this diff (having-filter.ts:1365, filter.zod.ts:434/734/750 — the latter ship in spec's declaration docblocks — and the read-scope-shared-lowering-seam.test.ts case title): carried to the last group PR per landing record 5920439482, the same carry the group-1 ACCEPT recorded. No change owed here.
  • objectql CHANGELOG residue of the step-2 sentence: a dedicated docs-only changeset-wording PR before the release compiles, or the release compile itself, closes it. Seat-side; not this PR's.
  • FAIL item 1's remedy. content/docs/protocol/objectql/query-syntax.mdx is a hand-written tree, not release-owned, so the clause may ride this PR: replace "on a datetime column the driver compiles it half-open" with the amended rule — the engine's and the RLS seams' shared lowering (lowerFilterCondition, ADR-0053 D-D1 as amended) rewrites a datetime column's bare-day upper bound half-open before any driver sees it, and a filter handed to a driver directly is compared as written. A docs-only PR landing with it is the other acceptable fix. Nothing else on this head needs to change; every other item above carries to the delta record.

Implemented-by: claude/issue-20822-g2-driver-sql-turso
Reviewed-by: session_01DEvba2nBuD4tWzfq8r8NFY

VERDICT: FAIL

FAIL items:

  1. content/docs/protocol/objectql/query-syntax.mdx lines 568 to 572 state the whole-day rule as the driver's own compile ("on a datetime column the driver compiles it half-open"), the copy this PR deletes from SqlDriver and TursoDriver; at this head a direct driver call, which the same page documents, no longer gets the whole day. One clause on that page (in this PR or a docs-only PR landing with it) clears it.

Generated by Claude Code

…lowering's, not the driver's

The page said that on a datetime column "the driver compiles" a bare-day upper
bound half-open. That was the copy this PR deletes from SqlDriver and
TursoDriver. The page now says that the shared filter lowering
(lowerFilterCondition from @objectstack/spec/data, ADR-0053 D-D1 as amended),
run by the engine's where seam and the RLS compile seam, rewrites the bound
half-open before any driver sees it. A seam that cannot read the declared types
applies the rewrite to every column. A filter handed directly to SqlDriver or a
driver built on it is compared as written. The page's other statements are
unchanged.

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0e8378cf54e4a7809cd6388774a227e6b25a2a07
Local-runs: none

Delta review on record 5923464846 (d8b8f9095a, FAIL, one item). Head confirmed on the PR at the moment of reading (open, draft, base main, head repo is the base repo, 7 commits, 49 files, +1501 / -977, mergeable true, mergeable_state blocked). The branch was fetched into a ref of this session's own. The delta d8b8f9095a..0e8378cf54 is one commit, 0e8378cf54 docs(objectql), single parent d8b8f9095a, one author and committer identity, no merge commit — a fast-forward on the reviewed head, as the delta report 5923644221 says. Its diff is exactly one path, content/docs/protocol/objectql/query-syntax.mdx, +11 / -4; .changeset/, packages/, docs/ and every other path are byte-identical to d8b8f9095a.

Check-runs on this head at the moment of reading (not waited on): 39 runs, 22 success, 4 skipped (Console Pin Gate, Packed-tarball smoke, one Auto Label, one Check PR Size), 13 in_progress, 0 failed. success: Build Core, Build Docs (skipped on d8b8f9095a; it ran on this head because the docs page moved, and it is green), Check Changeset (twice), Check Documentation Links, Governed Surface Queue Guard, Spec property liveness, Flag docs affected by code changes, Type Check · debt ledger, Type Check · source gates, Dogfood Verify CLI, the claim and single-writer guards. in_progress: Lint & Repo Gates, Temporal Conformance (live PG + MySQL), Test Core 1 to 6, Dogfood Regression Gate 1 to 3, Type Check · consumer gates, Type Check · workspace. in_progress is not a pass: of the seven required contexts, Build Core and Governed Surface Queue Guard are green and the other five are still running. Their conclusions are the gate verdicts the landing seat reads at landing; this record does not wait for them. Every one of them was success on d8b8f9095a, whose code this head does not change.

① Derived judgments

The new paragraph (page lines 569 to 581), sentence by sentence, against the code at this head.

  • "A bare YYYY-MM-DD bound is a calendar day. As a lower bound ($gte) it means the start of that day (midnight UTC); as an upper bound ($lte, or the max of a $between) it covers the whole day." — true, carried text. The lower bound is the comparand's form (SqlDriver.coerceFilterValue, datetime arm, ADR-0053 D-B1); the whole-day upper bound is the shared lowering's rules 1 and 2 (packages/spec/src/data/filter-lowering.ts, lowerBounds), applied at the seams the sentences below name. The direct-call exception is stated three sentences later, so the opening is the seamed reading, as the page's own where examples are.
  • "On a date column that is plain comparison, so the $between above includes Dec 31." — true. The $between above is close_date, declared date. A typed seam leaves it whole: lowerBounds returns the spec unchanged when the reader answers false (if (options.isDatetimeColumn and not isDatetimeColumn(field)) return none), so SqlDriver compiles whereBetween, inclusive, with toDateOnly form on both ends; the probe suite pins it ("$between on a Field.date column is unchanged", and the same for $lte). A direct call compiles the same. Under a type-blind seam the range is rewritten to $gte the min and $lt the next day, which on YYYY-MM-DD text orders exactly as the inclusive bound (ADR-0053 D-D1 item 7's soundness clause), so Dec 31 is included there too; the paragraph's own fourth sentence states that rewrite. One reading to note, no verdict weight: "plain comparison" names the typed seam's and the direct call's spelling; the type-blind seam reaches the same rows by the other spelling, and the paragraph says so.
  • "On a datetime column it is the shared filter lowering (lowerFilterCondition from @objectstack/spec/data, ADR-0053 D-D1 as amended): the engine's where seam and the RLS compile seam rewrite a bare-day upper bound on a datetime column half-open ($lt the next day) before any driver sees the filter" — true. lowerFilterCondition is exported from packages/spec/src/data/index.ts (export * from './filter-lowering'); ADR-0053 D-D1 carries the "amended 2026-09-30" heading and the numbered amendment. Engine seam: resolveThenLowerWhere is lowerFilterCondition(resolveWhereFilterTokens(where, context), lowering) with declaredDatetimeLowering(schema) as the reader, run on find, findOne, update, delete, aggregate's positions and the judge (packages/objectql/src/engine.ts), inside the engine, before the driver's door is called. RLS seam: judgeCompiledComparands returns lowerFilterCondition(normalizeFilterComparandTypes(filter), rlsLowering(fieldGuard)) (packages/plugins/plugin-security/src/rls-compiler.ts), for using and check alike. lowerBounds emits $lt of nextUtcCalendarDay. "Before any driver sees the filter" holds on the driver side too: at this head driver-sql, driver-turso and driver-sqlite-wasm src hold 0 mentions of lowerFilterCondition, calendarDay or toRemoteUpperBound outside tests — no driver lowers and none keeps a copy.
  • "and a seam that cannot read the declared types applies that rewrite to every column." — true. declaredDatetimeLowering returns {} when the schema carries no fields object; rlsLowering returns {} when fieldGuard?.datetime is undefined (no guard, or a guard built without types); and lowerBounds skips a column only when a reader exists and answers false, so with no reader rules 1 and 2 apply to every column. ADR-0053 D-D1 item 7, second half.
  • "A filter handed directly to SqlDriver or a driver built on it (SqliteWasmDriver, TursoDriver), past both seams, is compared as written: a bare-day $lte on a datetime column compares against that day's midnight." — true. SqlDriver's emitter now reads const op = rawOp; const coerced = this.coerceFilterValue(table, localField, opValue), case '$lte' compiles the less-than-or-equal operator against coerced, case '$between' compiles whereBetween, and the normalised-column path compiles between ? and ?; coerceFilterValue is form only (datetime to the canonical midnight instant, date to toDateOnly, time to wall-clock text). SqliteWasmDriver extends SqlDriver (sqlite-wasm-driver.ts) and overrides no filter arm. TursoDriver extends SqlDriver (turso-driver.ts): the local face inherits, and the remote face's toRemoteFilter keeps only the structural $between split into $gte / $lte, both ends through temporalFilterValue, which is inherited and is SqlDriver.temporalFilterValue, "operator-blind — it translates FORM, never bound semantics", returning coerceFilterValue. ADR-0053 D-D1 item 5: "once that face's copy is deleted, such a caller gets the comparison it wrote."
  • "A full ISO timestamp keeps exact-instant semantics on every operator." — true, carried text; upperBound lowers a bare day only and passes any other comparand through as written.

The scoping of the direct-call sentence — right, and necessary. The dispatch's unscoped clause ("a filter handed to a driver directly is compared as written") would be false at this head: packages/drivers/driver-mongodb/src/mongodb-filter.ts still computes nextUtcCalendarDay(value) in its $lte arm and nextUtcCalendarDay(value[1]) in its $between arm and emits $lt the next day — F6, the group 3 card, kept by ADR-0053 D-D1 item 9 until its deletion card lands. The sentence names SqlDriver and the two classes built on it, which are exactly the faces this PR deletes copies from, and is silent on driver-mongodb and driver-memory; silence is not a false statement, and writing Mongo's interim behaviour onto this page would author a sentence group 3 deletes. The 5923464846 remedy asked for "a filter handed to a driver directly is compared as written"; the dev narrowed it to what is true and said so in the report (5923644221, "One scoping choice"). Judged right.

Other sentences on the page, read at this head for drift from the edit. None became false. Three are near the paragraph and were read closely: the section opener (lines 531 to 534, "the driver puts the comparand into the same canonical form the column is stored in (SqlDriver.coerceFilterValue)") states form, not bound semantics, and is exactly what the driver still does; the code-block comment "a bare YYYY-MM-DD is completed to midnight UTC ... an exact-instant match" is about equality, which the lowering never touches, so it holds on every path; and the comment "A whole UTC day on a datetime needs a half-open range" ($gte / $lt) is the one spelling whose answer does not depend on the path — it was in the same relation to the previous paragraph (which also said $lte covers the whole day), so the edit moved nothing there; a pre-existing reading, no change owed. The page's $not section and its cross-field NULL table are untouched by the delta and stay as 5923464846 judged them: not falsified. Lines 85 to 86 and 102 (SqlDriver.find() members, findWithWindowFunctions) document the direct-call doors the new sentence now covers correctly. No sentence became ambiguous.

Everything 5923464846 judged on commits 1 to 6 carries over unchanged, because the code is byte-identical: commit 1 (rlsLowering type-blind, the pins), commit 2 (the F1 whole-day methods and F2 toRemoteUpperBound gone, the structural split kept), commit 3 (the $not quartets gone, applyNullSafeNegative / nullSafeNegative rightly kept), the routing verification, the two measured stops (assertDefinedComparands kept in both faces; the $between split), H4's spot-check, the ledger entry 18.driver-sql-calendar-day-methods-removed.ts and its generated registry.ts block, and the release text. The stale pointers 5923464846 named (having-filter.ts around line 1365, filter.zod.ts around 434 / 734 / 750, still naming the deleted methods at this head) are carried to the last group PR per landing record 5920439482, as before.

② Semver level

Unchanged from what 5923464846 judged right, and verified unchanged: git diff d8b8f9095a..0e8378cf54 -- .changeset is empty. At this head .changeset/20822-driver-sql-turso-copies.md still declares @objectstack/driver-sql minor; driver-sqlite-wasm, driver-turso, plugin-security, spec patch; the summary carries !; Clause-②: no (narrowing) in the changeset (line 11) and on the PR body's line 2; one marker adr-0087: registered driver-sql-calendar-day-methods-removed; the BREAKING paragraph with its FROM → TO line; the Supersedes paragraph quoting both step-2 sentences. The per-package grades stand as judged.

A docs-only commit needs no changeset change — right. A changeset describes what a released package publishes (Post-Task Checklist step 3). content/docs/** is consumed by apps/docs (@objectstack/docs, "private": true) and ships in no published tarball: the only package.json in the tree that names content/docs is plugin-webhooks, in a description pointer, not a files entry. The page is a hand-written tree, not release-owned, so the clause may ride this PR (the remedy 5923464846 named). Check Changeset is success twice on this head.

③ Boundary flags

  • The delta touches nothing else. One path, one commit, verified from the fetched ref. The PR's changed_files 49, additions 1501 and deletions 977 equal git diff --shortstat 5f6b63a6fd..0e8378cf54 exactly; the merge base is 5f6b63a6fd; git rev-list --count is 7; no path under docs/adr/, docs/NORTH-STAR.md, .claude/, skills/, AGENTS.md or CLAUDE.md.
  • The PR body's updated counts are true. "Seven commits on main at 5f6b63a6fd": 7, linear on that base. Row 7 of the commits table (0e8378cf54 docs(objectql), one page, the whole-day rule as the shared lowering's, a direct call compared as written, "contract review 5923464846, FAIL 1") matches the commit and its diff. "49 files, +1501 / -977 against 5f6b63a6fd at 0e8378cf54, under the 5000-line human-merge threshold. No governed path.": every number equal to the measurement, 2478 changed lines. The stale line-15 count 5923464846 flagged is corrected. The one body sentence still ambiguous is the heading "One sentence of this release's step-2 changeset is superseded", which introduces two sentences — a seat edit, no verdict weight, carried.
  • Check-runs in progress. Five of the seven required contexts are in_progress at reading (above). The Tier S landing condition is a PASS record for the current head AND every check green; the second half is the landing seat's read of the completed runs, not this record's. Nothing in the delta can move a code gate — the code is d8b8f9095a's, where all of them were success — and the one leg the delta can move, Build Docs, is success on this head; the dev's 40 derived docs gates (5923644221) are the dev's runs, not re-run here.
  • Behind main. At reading main (2f2fa11d75) is 17 commits and 131 files past the merge base, none of them among the PR's 49 paths; mergeable true, mergeable_state blocked (a draft with required checks running). Not a defect; the queue leg rebuilds on the merge ref.
  • The pinned-sibling grep, the seat's fact. The landing seat ran the grep 5923464846 escalated: objectui at .objectui-sha db11afd4967c (the pin at this head and on main, verified equal) has 0 hits for the three method names and no class extending SqlDriver or TursoDriver. Taken as the seat's fact, not re-judged here. The cloud repo is not reachable from this session and this repo pins no cloud sha: NOT MEASURED, carried exactly as 5923464846 carried it. Console Pin Gate is skipped on this head as on the last (the console path filter did not fire).
  • Every other item of 5923464846's ③ carries over unchanged: live PG + MySQL measured by Temporal Conformance (running on this head, success on the last); a live remote Turso server NOT MEASURED (ruling 5902355785's recorded gap); the [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 pointer settled by 5923177087; the two --force-with-lease re-stacks before the PR opened, not a defect; no open question left; the objectql CHANGELOG residue of the step-2 sentence, seat-side; the stale pointers outside this diff, carried to group 3.
  • Pre-existing reading, no change owed: the code-block comment "A whole UTC day on a datetime needs a half-open range" (above) is a recommendation that holds on every path; a later docs pass may say "on a direct driver call" beside it, but nothing on this head requires it.

FAIL item 1 of 5923464846 is cleared: the page no longer states the whole-day rule as the driver's own compile, and each sentence it states instead is true against the code at this head.

Implemented-by: claude/issue-20822-g2-driver-sql-turso
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