Skip to content

refactor(driver-sql): move applyIndexDriftOp and restoreBareIndexAfterFailedTighten into sql-driver-schema-sync.ts (#25521 cut 8) - #26169

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-wbvc9p
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-wbvc9p

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Part of #25521
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840
Ruling-ref: 6348891160

Cut 8 of the sql-driver.ts split (#24586 ruling A, recorded in 6270097840). The claim is 6417688620; this is the slice cuts 6 and 7 both proposed (Release 6417590425). The bodies of the protected SqlDriver#applyIndexDriftOp (one index drift op, every dialect) and SqlDriver#restoreBareIndexAfterFailedTighten (the ADR-0120 restore it runs when a NULL-safe tightening cannot be created after the old index is gone) move unedited into packages/drivers/driver-sql/src/sql-driver-schema-sync.ts, beside cut 6's applyDriftOpInPlace and rebuildSqliteTablePatched. They reach the class through that module's existing host, SchemaSyncHost, widened by exactly the seven members these two bodies read and nothing else. Both methods keep their name, docblock, signature (less async) and position.

The ADR-0120 D4 paragraph of the sql-driver.ts anchor entry follows the apply-side body into a new anchor entry for sql-driver-schema-sync.ts (below). No docs/adr/** edit, no packages/spec/** edit, no new gate.

It is move-only, and the card stays open for the later cuts. Branch claude/project-thread-wbvc9p (platform-assigned to this card thread, as the claim records). One commit, 68fc9470, on origin/main a335f1def.

Net line count (the seat's rule from ACCEPT 6385306916). sql-driver.ts goes from 21,431 lines to 21,334: -97. The two bodies are 122 lines out (105 + 17); the two stubs are 24 lines in (15 + 9); the schema-sync import comment gains one line (+1). The module grows 227 to 372 lines.

Serial with the file's in-flight PRs. Re-measured at my start: 32 open PRs, each file list read in full (the two over 100 files paged to the end: #26138, 343 files, none under packages/drivers/**; #25640, the Version Packages PR, only CHANGELOG.md and package.json for the driver packages). None touches sql-driver.ts, sql-driver-schema-sync.ts or the driver-sql anchor entries.

The area, measured on a335f1def

  • applyIndexDriftOp is lines 14776-14882 of sql-driver.ts (105 body lines); restoreBareIndexAfterFailedTighten is lines 14893-14914 (17 body lines). Callers: applyMigrationEntries (class) and cut 6's applyDriftOpInPlace body (module, through its host closure) call applyIndexDriftOp; only applyIndexDriftOp's body calls the restore.
  • Both are protected with annotated return types (a promise of boolean, a promise of void), so the inferred-return limit does not bite.
  • Neither builds through getBuilder (the only knex use is this.knex(op.table).columnInfo()), so check:tenant-chokepoint rule 1 is untouched, and neither has a .raw( / .where( site, so rule 2 visits exactly what it visited.
  • No static read and no cast-wrapped this read. The this reads are knex, logger, syncDeclaredIndexes, getExistingIndexNames, dropIndexIfExists, dropOrphanedHashShadowColumn, probeNullSafeUniqueDuplicates, logDurabilityFailure and restoreBareIndexAfterFailedTighten (the restore reads four of those). No this.FIELD.delete/insert/update( site, so UNTYPED_RECEIVERS does not move.
  • TursoDriver and SqliteWasmDriver override neither (turso-driver.ts names applyIndexDriftOp once, in a comment). One test subclass calls applyIndexDriftOp through this (sql-driver-13056-orphan-shadow-column-cleanup.test.ts), which still reaches the class method. No script, doc or ADR names either method by path.

The shape (#23050 shape B, as cuts 1-7 apply it)

  • Each class method keeps its docblock and signature, byte-identical less async. Its body is one host literal plus NAME.call(host, ...), marked [#25521 cut 8], and it returns the moved function's own promise.
  • knex and logger go as GETTERS that read the driver at call time; the seven methods go as closures that call the method on the driver at call time. So a subclass override, an instance spy, or a knex/logger replaced after construction is still the one the body reaches (probe below).
  • Each moved function types its this as the Pick of SchemaSyncHost over the members it reads, and each stub annotates its literal as the ThisParameterType of the moved function. No cast, and no member becomes public.
  • SchemaSyncHost gains dropIndexIfExists, dropOrphanedHashShadowColumn, getExistingIndexNames, logDurabilityFailure, probeNullSafeUniqueDuplicates, restoreBareIndexAfterFailedTighten and syncDeclaredIndexes, each typed as the class member's indexed access, in the interface's alphabetical order. knex, logger and applyIndexDriftOp were already there.
  • Module-level declarations. The bodies read only the DriftOp type, which the module already imports from schema-drift.ts. No declaration gains an export; index.ts re-exports nothing from the module, and sql-driver.ts drops no import.
  • The module header gains a [#25521 cut 8] paragraph; the sql-driver.ts import comment names cut 8.
  • The move was produced by a script from the source text, not retyped: each body line re-indented two columns.

The ADR anchor follows the body

check:adr-anchors counts decision-letter citations on the anchored surface (each anchor's invariant plus the full text of each anchored file). The moved bodies carry three ADR-0120 D4 citations (the ceremony comment and the refusal text in applyIndexDriftOp, the restore's error text); the restore's docblock citation stays on the class.

  • scripts/adr-anchors/packages__drivers__driver-sql__src__sql-driver.ts.json: the ADR-0120 D4: … sentences leave its invariant and ADR-0120 leaves its adrs. The ADR-0053 paragraph is untouched.
  • New scripts/adr-anchors/packages__drivers__driver-sql__src__sql-driver-schema-sync.ts.json: adrs: ["ADR-0120"], and its invariant is those D4 sentences, byte-identical.
  • Count: 1879 decision-letter citations at base a335f1def and at head 68fc9470; anchored files 88 to 89; both runs exit 0.
  • What stays in sql-driver.ts: the plan-side duplicate pre-flight (applyNullSafeUniquePreflight, probeNullSafeUniqueDuplicates) and the GLOBAL_TENANT key fold, which that paragraph also describes. The file still cites ADR-0120 on 29 lines (10 of them ADR-0120 D4), but no anchor entry names it for ADR-0120 any more. Raised to the seat in the report as an open question; this PR follows the claim's wording.

The published .d.ts does not change

Under the series bar (ruling 6348891160, letter A). Each end built with pnpm turbo run build --filter=@objectstack/driver-sql against the same dependency tree; the base leg restores the base text of the two source files in this worktree after the commit, under an EXIT/INT/TERM trap that restores HEAD (proved by HEAD blob hashes, an empty git diff HEAD and 0 status entries).

  • This package emits dist/index.d.ts and dist/index.d.mts; the two are identical at both ends.
  • Raw sha256 of both files at base and at head: 5028e040e1501b0bd72b91fafcf4e1cb372393c59baa5bab8b936b240dd2d3f5 (cut 7's hash), cmp identical.
  • Positive control: the head index.d.ts with protected dropped from applyIndexDriftOp (line 5606) hashes to e5e7a31c2fd4231d94f0a95af3c6e7e7b49225b6c1538a86528e7c9bc20fefa7, a 4-line diff, cmp exit 1.
  • Export lists (CJS require and ESM import of the built dist): 56 / 56 names at both ends, identical.
  • The head dist carries the move: .call(host sites in index.mjs 19 at base, 21 at head.

Move-only proof

TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base a335f1def against head 68fc9470, scratch script, not committed:

  • applyIndexDriftOp: body 502 tokens, 0 differ; 46 comments identical; parameters less this 3 tokens, 0 differ; return type 4 tokens, 0 differ; async on both; 107 body lines, each the base line less two leading spaces.
  • restoreBareIndexAfterFailedTighten: body 132 tokens, 0 differ; 1 comment identical; parameters less this 15 tokens, 0 differ; return type 4 tokens, 0 differ; async on both; 19 body lines, each the base line less two leading spaces.
  • Both stubs: leading trivia (docblock) and signature identical to base less async.
  • Class: 348 to 348 members in the same order; the 346 unmoved members byte-identical (full text with trivia).
  • sql-driver.ts top level: 207 to 207 statements, 205 byte-identical; the two that differ are the schema-sync import (with its comment) and the class.
  • sql-driver-schema-sync.ts: the 5 base statements become 7; 3 byte-identical, the 2 that differ are the import carrying the module header and SchemaSyncHost; the 2 new ones are the moved functions.
  • Result PROOF OK, exit 0. Negative control (REFUSING to rebuild to REFUSING to rebui1d in a module copy): 1 token and 1 line differ, PROOF FAILED, exit 1.

Tests

Each suite under its own os-verify-lock hold, vitest JSON compared per test name and status, base a335f1def against head 68fc9470:

Suite Base Head Lost / changed / added
@objectstack/driver-sql 4010 (3796 passed, 214 skipped; 248 files) 4010 (3796 passed, 214 skipped; 248 files) 0 / 0 / 0
@objectstack/driver-turso (against that end's built driver-sql dist) 2526 (2492 passed, 34 skipped; 95 files) same 0 / 0 / 0
@objectstack/driver-sqlite-wasm 704 (704 passed; 39 files) same 0 / 0 / 0
  • pnpm exec tsc --noEmit in driver-sql: exit 0. pnpm exec eslint --no-inline-config packages/drivers/driver-sql/src: exit 0, 268 files, 0 errors, 0 warnings.
  • Late-binding probe (scratch, node against each end's built dist on in-memory SQLite, not committed): 13 readings, base and head output identical (diff empty). It covers the unbuildable_index early return with no knex read; create_index through a knex replaced on the instance after construction; replace_unique_index both ways, the skipped create warning on a logger replaced after construction; recreate_index refused over duplicates through an instance spy on logDurabilityFailure, applied on clean data, its create throwing (an instance spy on the restore reached with the cause, the restore's error logged, the rejection rethrown) and its create silently absent (a subclass override of the restore reached with an undefined cause); the restore whose own re-create fails (the NOT enforced message, the cause passed as meta); drop_index running the index half before the shadow half; a null op answering a TypeError rejection, never a synchronous throw, from both methods; and the default arm.
  • No ablation: no tested behaviour changed. test:repo not run locally (the diff touches no repo-project test; CI's Test Core runs it).

Gates

Local, head 68fc9470.

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (change set vs merge base a335f1def, 4 paths): 65 commands, each exit captured before any pipe. 62 exit 0 on the first run. check-plugin-teardown-shape.mjs --self-test exit 3 (shallow clone) then exit 0 after git fetch origin 621a4876 (its positive control). check:lean-entry-closure exit 3 (no objectql dist) then exit 0 after pnpm turbo run build --filter=@objectstack/objectql under the lock. check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET (needs every package's dist): NOT MEASURED locally, CI runs it.
  • --ran: "65 derived famil(ies) accounted for — 64 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", exit 0.
  • Card-named, base read in a clean detached worktree at a335f1def and head at 68fc9470, exit 0 at both ends:
    • check:adr-anchors: 88 to 89 anchored files; 1879 decision-letter citations at base and at head.
    • check:adr-symbol-anchors: 2658 anchors resolve at both.
    • check:tenant-chokepoint: 24 getBuilder() bindings across 3 files at both; rule 2 clean at both.
    • check:logger-receiver-detach: 0 detaches at both (3731 non-test TS files walked at base, 3732 at head, where the base worktree lacks the gitignored generated spec registry; the logger getter keeps the receiver).
    • check:driver-conformance: 55 covered, 0 DEBT, 0 exempt at both.
    • node scripts/check-tenant-audit-census.mjs: 243 write call sites certified, every UNTYPED_RECEIVERS row matched, at both.
  • Also exit 0: check:nul-bytes, check:commit-card-trailers. check-platform-checklist --changed-from not owed (no exported symbol renamed or deleted).
  • origin/main moved 9 commits (to d00215f68) while this ran; none touches packages/drivers/**, scripts/adr-anchors/** or check-adr-anchors.mjs, git merge-tree against it is clean, and dispatch-gates --commands derives the same 65-command list there. Not merged: CI tests the merge ref.

Acceptance notes

  • The ADR-0120 anchor for sql-driver.ts is gone with this cut (see above); the residual plan-side mechanism there is named in the report's open_questions for the seat.
  • Proposed next slice: none measured in this cut. The card's remaining SqlDriver areas are the seat's to pick.

skip-changeset: move-only, nothing a published package ships changes (the .d.ts is byte-identical; the emitted JS runs the same bodies through the host).


Generated by Claude Code

…estore into sql-driver-schema-sync.ts

Move-only. The bodies of the protected SqlDriver#applyIndexDriftOp and
SqlDriver#restoreBareIndexAfterFailedTighten move unedited (re-indented one
level) into sql-driver-schema-sync.ts beside cut 6's pair. The class keeps
both methods as stubs that hand the moved function the SchemaSyncHost members
it reads; the host grows by exactly those seven members.

The ADR-0120 D4 paragraph of the sql-driver.ts anchor entry follows the
apply-side body into a new anchor entry for sql-driver-schema-sync.ts.

Claude-Session: https://claude.ai/code/session_0155UnTppT9uLobphwpxhiZu
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/data-modeling/indexing.mdx (via recreate_index (literal, a string literal in applyIndexDriftOp; a string literal in restoreBareIndexAfterFailedTighten))
  • content/docs/deployment/cli.mdx (via create_index (literal, a string literal in applyIndexDriftOp), drop_index (literal, a string literal in applyIndexDriftOp), recreate_index (literal, a string literal in applyIndexDriftOp; a string literal in restoreBareIndexAfterFailedTighten), replace_unique_index (literal, a string literal in applyIndexDriftOp), unbuildable_index (literal, a string literal in applyIndexDriftOp))
  • content/docs/deployment/seed-tenancy-repair.mdx (via replace_unique_index (literal, a string literal in applyIndexDriftOp))

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

  • content/docs/releases/v17/17-5.mdx (via unbuildable_index (literal, a string literal in applyIndexDriftOp))

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
  • 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 — 13 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 365fe5ecc4a1f7157186d8053cd721d2384e3f56 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 5150b9b2e0a632e0743f8ce4c607e5291603e101 — the merge of head 68fc94704d0833d5b3a6f1395969c2bfd22a0584 into base 365fe5ecc4a1f7157186d8053cd721d2384e3f56, 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 5150b9b2e0a632e0743f8ce4c607e5291603e101 && git checkout 5150b9b2e0a632e0743f8ce4c607e5291603e101
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 365fe5ecc4a1f7157186d8053cd721d2384e3f56 68fc94704d0833d5b3a6f1395969c2bfd22a0584 && git checkout -B drift-repro 365fe5ecc4a1f7157186d8053cd721d2384e3f56 && git merge --no-ff 68fc94704d0833d5b3a6f1395969c2bfd22a0584

node scripts/docs-audit/affected-docs.mjs --json 365fe5ecc4a1f7157186d8053cd721d2384e3f56

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-26T15:09Z · head 68fc94704d0833d5b3a6f1395969c2bfd22a0584 · #25521 cut 8

The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.

  1. Shape: a draft against main. The body opens with Part of #25521 (⛔ not Fixes; the card stays open for later cuts). Clause-②: no and Metadata-protocol: none are line-initial. skip-changeset applies: the change is move-only. check-governed-merges --pr 26169: NOT governed, 312 changed lines.
  2. Premise measured: the claim 6417688620 named the slice cuts 6 and 7 proposed. applyIndexDriftOp and restoreBareIndexAfterFailedTighten move into sql-driver-schema-sync.ts beside cut 6's bodies, through the existing SchemaSyncHost widened by the seven members they read; each function's this is the Pick of exactly those members. The seat compared every line removed from sql-driver.ts with the module after stripping indentation: each one is present there, except the import line (now naming the two moved functions) and the two signatures, which drop async only. Each stub delegates every method through this, so a subclass override or an instance spy is still the one the moved body reaches (restoreBareIndexAfterFailedTighten included). sql-driver.ts goes 21,431 → 21,334. The dev reports the three driver suites (driver-sql, driver-turso, driver-sqlite-wasm) at 0 lost, changed or added per test, the AST proof at 0 differing tokens, and .d.ts byte-identical.
  3. Gates: at this stamp the head's 52 check-runs read 36 success and 16 skipped (path-filtered), with none failing or in progress; mergeable, clean. The dev reports 64 of 65 derived gate families run green locally at 68fc94704d, reconciled with --ran; the one not measured (dual-build-cjs-loads) is CI's. check:adr-anchors, check:driver-conformance and check:tenant-chokepoint read the same at base and head.
  4. Accepted deviations: the ADR-0120 D4 paragraph of the sql-driver.ts anchor entry moves to a new entry for sql-driver-schema-sync.ts, so sql-driver.ts's entry no longer names ADR-0120 (88 → 89 anchored files, 1,879 = 1,879 decision letters). The seat takes the report's option A: one home per paragraph, as cut 7 of [maintainer] refactor(objectql): split src/engine.ts (23,903 lines, +7,501 in 30 days) along its ObjectQL seams, one move-only slice per PR #25522 did; the plan-side half that stays in sql-driver.ts still carries its own ADR-0120 D4 comment citations.
  5. Out of scope: none filed. No next slice was measured; the seat picks cut 9 from a fresh seam table.
  6. Landing: this seat runs pr_ready and then automerge_enable through the relay, confirms MERGED on both readings, and records the landing on [maintainer] refactor(driver-sql): split src/sql-driver.ts (22,096 lines, +2,533 in 30 days) along its SqlDriver seams, one move-only slice per PR #25521 with a Release: back to pm:queue for cut 9.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 15:11
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 15:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 51cc7f9 Oct 26, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-wbvc9p branch October 26, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants