Skip to content

refactor(driver-sql): move introspectIndexes's body out of sql-driver.ts into sql-driver-introspection.ts (#25521 cut 5) - #25931

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

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

Cut 5 of the sql-driver.ts split (#24586 ruling A, recorded in 6270097840). The claim is 6408067063. The body of the protected SqlDriver#introspectIndexes moves unedited into cut 1's module, packages/drivers/driver-sql/src/sql-driver-introspection.ts, and reaches the class through cut 1's host, IntrospectionHost, which gains one member. The method keeps its name, docblock, signature (less async) and position. resolveHashShadowKeys, the one class method the body calls, stays in the class with the rest of the hash-shadow family.

It is move-only, and the card stays open for the later cuts. Branch claude/project-thread-8aooxu (platform-assigned to this card thread, as the claim records), cut from origin/main at f00e8d5c8 (cut 4 merged as b1790841b before it).

Net line count (the seat's rule from ACCEPT 6385306916). sql-driver.ts goes from 21,595 lines at base to 21,512 at head: -83 (cut 4 priced it at about -82). sql-driver-introspection.ts goes from 800 to 927 lines.

Serial with the file's in-flight PRs. At claim the seat read no open PR touching sql-driver.ts (hot-file queue on seat post #20966: this is the one slice in flight). Re-measured just before this PR was opened: 8 open PRs, each file list read in full. None touches packages/drivers/driver-sql/**, driver-turso/** or driver-sqlite-wasm/**, except the Version Packages PR #25640, which touches only those packages' CHANGELOG.md and package.json. origin/main moved one commit past the base meanwhile (#25904, now 2a728ba2a). It touches no driver package and no gate script this diff derives, and it merges clean, so main is not merged in.

The area, re-measured at f00e8d5c8

  • introspectIndexes with its docblock is lines 15096-15250 of sql-driver.ts (155 lines; the method itself 15143-15250). git blame with --since 30 days before the base attributes 0 of those lines to the last 30 days; the file has 63 commits in that window (the clone's history was deepened to 2026-09-20, before the window opens, for the reading).
  • It is protected, and its return type is annotated (a promise of PhysicalIndex[]), so the inferred-return limit does not bite.
  • It does not build through getBuilder (check:tenant-chokepoint rule 1: 24 bindings across 3 files at both ends).
  • It reads no static: the fields and getters it reads are knex, isMysql, isPostgres and isSqlite, all already IntrospectionHost members, plus the method resolveHashShadowKeys.
  • It has no this.FIELD.delete/insert/update( site: its .set( / .get( / .push( calls are on a local Map and local arrays.
  • insertOnlyUpsertColumns is not touched (drivers(sql): upsert's empty-merge-set fallback is merge-ALL, which re-admits every insert-only column — currently unreachable, but it is the wrong shape for "nothing to merge" #8740).

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

  • The class keeps introspectIndexes, docblock and parameter JSDoc included, byte-identical except that async is dropped. Its body is now one host literal plus introspectIndexes.call(host, tableName, opts), marked [#25521 cut 5]. It returns the moved function's own promise, so a caller awaits the same settlement.
  • IntrospectionHost gains one member, resolveHashShadowKeys, typed as the class member (an indexed access on SqlDriver). Its ten existing member lines are unchanged. The moved function types its this as the Pick of the host over exactly the five members its body reads, and the stub annotates its literal as the ThisParameterType of the moved function. There is no cast, and no member becomes public.
  • knex and the three dialect getters go as GETTERS that read the driver at call time. resolveHashShadowKeys goes as a late-bound closure that calls it on the driver, so a subclass override or an instance spy is still the one the body reaches.
  • Why resolveHashShadowKeys stays. The claim's surface allows the helpers only introspectIndexes calls, and it is one of them (its only caller). But the series carries "protected statics keep the hash-shadow family in the class" (cut 4's release 6388161035), and cut 4's proposal for this cut kept it too, so it is left for the seat to price as its own slice (see the acceptance notes).
  • Module-level declarations. The body reads parseIndexDdl, applyIndexKeyParts and the type PhysicalIndex. All three live in schema-drift.ts, which exports them, so the module imports them from there (not a copy). After the move sql-driver.ts no longer reads the two functions, so they leave its ./schema-drift.js import list; PhysicalIndex stays there, other members still read it. No declaration gains an export. index.ts re-exports nothing from this module.
  • The move was produced by a script from the source text, not retyped: the parameter list, return type and body lines, re-indented two columns, except the 9 lines that continue one of the body's two multi-line SQL template literals. Those keep their bytes (cut 1's rule: there the indentation is statement text).
  • Cut 1's module header gains a [#25521 cut 5] paragraph, and the import comment in sql-driver.ts is re-worded to name introspectIndexes. Both are comment-only.

Overrides and spies, measured

  • TursoDriver overrides introspectIndexes and calls super.introspectIndexes(tableName, opts) on its local arm. The class method is still there and hands the body its host.
  • SqliteWasmDriver overrides the getter isSqlite. The host reads it through a getter that reads the driver.
  • sql-driver-12732-varchar-emitter-parity-wiring.test.ts spies on introspectIndexes on the instance; that is still the class method.
  • A scratch probe against the built dist (not committed) gave 18 readings, identical at base and at head (diff empty). It covered: the SQLite answer for a table with a primary key, a composite UNIQUE constraint, a plain index, a partial unique index and an expression index (columns, partial, expressions, nullSafeColumns); getExistingIndexNames; a missing table; a failing PRAGMA index_info under the default (rejects) and under onFailure: 'partial' (the short list); a knex.raw that throws synchronously (the method still returns a promise that rejects, at both ends); a subclass introspectIndexes that calls super; the MySQL arm through a subclass overriding the three dialect getters, with the EXPRESSION fallback statement; a subclass override of resolveHashShadowKeys and then an instance assignment of it (both reached); a rejecting resolveHashShadowKeys (surfaces by default, swallowed under partial); and a subclass isSqlite getter flipped between two calls (read at call time).

The published .d.ts does not change

Under the series bar (ruling 6348891160, letter A), measured on base f00e8d5c8 and head bd1670fe0, each built with pnpm --filter @objectstack/driver-sql build against the same dependency tree (the base leg restores the two base files into this worktree after the cut commit, rebuilds, reads, then restores HEAD, proved by blob hash and an empty git diff HEAD).

  • This package emits dist/index.d.ts and dist/index.d.mts (no .d.cts); the two are identical at both ends.
  • Raw sha256 of both files, base and head, all four the same: 93da45168e82136b7613944525e47402d5dc6f9f8daa9efa4bdceeaa11b52a89 (cuts 1-4's hash, unchanged). cmp base vs head: identical, raw diff 0 lines, so the proof rests on raw identity and no normaliser is relied on.
  • Positive control on a copy of the head dist/index.d.ts: line 5714, protected introspectIndexes(tableName: string, opts?: {, with protected removed, gives cbaacf3c70282bd8dfc867c2f97d0f218b114c06b7fc11fbde5b03f5c05f9da0 and a 4-line diff.
  • Export tables: the ESM and CJS bundles each export the same 56 names at base and head. The sorted list hashes to d61a9dd3a02661602b1bb2dbe18152ac43986148f36b2746f40c0884bd8ffdec on all four (cut 4's value).
  • The head build carries the move: dist/index.mjs 29 to 30 host .call( sites, dist/index.js 30 to 31.

No changeset: skip-changeset, per ruling 6348891160 item 4.

Proof of move-only

  • Body, by AST token (recursive getChildren leaves, template pieces as their exact source text, comments and JSDoc compared line by line less leading whitespace). The moved function's parameters (less the added this), return type and body have the same token sequence as the base method, and async matches. 673 body tokens compared, 0 differ; 2 parameters identical; return type identical.
  • Lines: 98 body lines compared. 88 are the base line with two leading spaces removed, 9 are byte-identical template-literal continuation lines, 0 other.
  • Negative control: in a copy of the module, type = 'index' becomes type = 'indx' inside the SQLite arm's sqlite_master template literal. The check then reports 1 token and 1 line different and exits PROOF FAILED, exit 1.
  • Class side: 347 members at base and head. The 346 that did not move are byte-identical to base, leading trivia included.
  • Top level of sql-driver.ts: 202 non-class statements at base and head; 200 byte-identical. The two others are the ./schema-drift.js import (loses exactly applyIndexKeyParts and parseIndexDdl) and the ./sql-driver-introspection.js import (gains exactly introspectIndexes; its leading comment re-worded).
  • The module: 9 statements at base, 11 at head. The 8 base statements other than IntrospectionHost are byte-identical at head (the first carries the re-worded header as leading trivia). IntrospectionHost keeps its ten member lines and gains one. The two new statements are the ./schema-drift.js import and the moved function.
  • Size: +143 / -99 over 2 files, far under the 3,000-line manual-merge line.

Readers of sql-driver.ts by path, checked against the moved text

None needed an edit:

  • ADR anchors. scripts/adr-anchors/packages__drivers__driver-sql__src__sql-driver.ts.json carries ADR-0053 (formatOutput, presentReadValue) and ADR-0120 D4 (the recreate_index ceremony). Neither paragraph describes the moved body, so per card ask 3 no entry is owed. The moved body cites no ADR; the docblock's ADR-0048 mention stays on the class. check:adr-anchors: 85 anchored files at both ends; decision-letter citations on the anchored surface 1,847 at base and 1,847 at head.
  • Symbol anchors. No ADR, checklist or doc anchors sql-driver.ts#introspectIndexes (git grep over docs/, content/, scripts/, .github/: 0 hits). check:adr-symbol-anchors: 2,474 anchors resolve at both ends.
  • check:tenant-chokepoint. Rule 1 verdict and population unchanged (above). Rule 2 visits raw-family calls in SCAN_FILES; the moved body's five knex.raw( call sites (sqlite_master, PRAGMA index_list, PRAGMA index_info, pg_index, MySQL STATISTICS) are catalog reads, none says IS NULL against a tenant column (the pg_index read says ix.indpred IS NOT NULL, not a tenant column). Verdict unchanged; .raw( call sites in sql-driver.ts go 75 to 70 (see the acceptance notes).
  • Logger receivers. The moved text reads no logger. check:logger-receiver-detach: 0 detaches at both ends.
  • Doc comments that name SqlDriver.introspectIndexes (schema-drift.ts, turso-driver.ts, packages/spec/src/contracts/schema-diff-service.ts) and the {@link introspectIndexes} references in the class still point at the method, which is still there.
  • check:driver-conformance at base and head: 55 covered, 0 in the DEBT ledger, 0 exempt, both times.

Verification

  • tsc --noEmit (driver-sql) exit 0. eslint --no-inline-config over packages/drivers/driver-sql/src (265 files, the module included): 0 errors, 0 warnings. Repo-wide pnpm lint (the seat asked for it; driver-sql declares no lint script): exit 0 at head bd1670fe0.
  • packages/drivers/driver-sql suite: base 3,999 tests (3,787 passed, 0 failed, 212 skipped; 247 files), head the same. Compared per test name and status from vitest's JSON reporter: 0 lost, 0 changed, 0 added.
  • packages/drivers/driver-turso suite, against the rebuilt driver-sql dist at each end: base 2,526 tests (2,492 passed, 0 failed, 34 skipped; 95 files), head the same; per name 0 / 0 / 0 (2,513 distinct names, 13 repeat).
  • packages/drivers/driver-sqlite-wasm suite: base 704 tests (704 passed; 39 files), head the same; per name 0 / 0 / 0.
  • Each suite ran under its own os-verify-lock call, VERDICT command-exit 0 at both ends.
  • dispatch-gates --commands on this diff, at head bd1670fe0: 55 derived commands, each run with its exit code captured before any pipe. --ran reconciles 55 derived, 54 run, 1 NOT MEASURED, 0 unrun. The NOT MEASURED one is check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET: it needs every package's dist), which CI runs. check:lean-entry-closure first exited 3 (no objectql dist) and exits 0 after turbo run build --filter=@objectstack/objectql; check-plugin-teardown-shape --self-test first exited 3 on the shallow clone and exits 0 after git fetch --unshallow.
  • check:commit-card-trailers --range origin/main..HEAD: 1 commit, clean.
  • No reverse verification or ablation: the diff changes no tested behaviour.

Acceptance notes

  • check:tenant-chokepoint rule 2 reads 5 fewer raw call sites in sql-driver.ts. SCAN_FILES names sql-driver.ts and not the seam modules, so rule 2 no longer visits the five catalog knex.raw( calls that moved; the module now holds 24 .raw( sites. None is an arm on a tenant column today, but a future edit that added one in the module would not be caught by rule 2. Widening SCAN_FILES is a gate edit and the open precondition the series carries for the getBuilder areas; it is not made here.
  • resolveHashShadowKeys could follow. Its only caller is now the moved body, its body reads only knex and two schema-drift.ts functions (no static), and its body (about 29 lines) is longer than the stub it would need (about 7), so it would pass the net-line rule at roughly -22. It stays because the series keeps the hash-shadow family in the class; whether it moves is the seat's call.
  • Proposed cut 6 (cut 4's next candidate, re-read here): the SQLite rebuild pair applyDriftOpInPlace (14860) and rebuildSqliteTablePatched (14954), Schema sync. Neither builds through getBuilder and neither has a this.FIELD.delete/insert/update( site. Between them they read knex (14 times), logger (twice, so check:logger-receiver-detach is the gate to watch on a host getter), dialectName, isMysql, isPostgres, and the methods introspectColumns, syncTableIndexes, resolveTenantField, nowColumnDefault, buildRebuiltColumn, applyIndexDriftOp, applyDeclaredColumnDefault, plus the fields managedObjectIndexes and managedObjectFields. That is a wide host; it should be priced against the net-line rule at its dispatch.
  • TursoDriver's introspectIndexes override still calls super.introspectIndexes(tableName, opts). That now returns the moved function's promise instead of an async method's promise. A caller awaits the same settlement, and a synchronous throw inside the body still arrives as a rejection (probe reading 6).
  • Lines that continue a SQL template literal keep their base indentation inside the module, so they sit two columns deeper than the code around them (cut 1's rule).

Generated by Claude Code

…ction.ts

Move-only: the body of SqlDriver#introspectIndexes moves unedited into the
module the other catalog reads live in, and the class method hands it its
host (the dialect getters and knex as getters, resolveHashShadowKeys as a
late-bound closure so a subclass override or an instance spy still
dispatches). The method keeps its docblock and signature, less async, so
the published declarations do not move. sql-driver.ts no longer reads
parseIndexDdl or applyIndexKeyParts and drops them from its import; the
module imports them from schema-drift.ts.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Wyp3j7s8XDQmCUaKPbmXH
@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

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

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 2a728ba2acf52c13d1918c2afca7b79bf391e1f5 → packageMentionDocs.

Which tree this was computed on

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

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:server seat 2 (#20966) · seat session session_01TFnDdepdwyDJeM2kJ4GtMy · 2026-10-26T05:13Z · head bd1670fe011f23dd40219d0d59b1a65e6e2f4991 · card #25521 cut 5 (claim 6408067063, report 6408436124)

  1. Shape: move-only, 2 files (sql-driver.ts +16/−99, sql-driver-introspection.ts +127/−0). skip-changeset is correct: the method stays protected with the same signature, and nothing published changes. There is no published schema, no governed path, and no narrowing or Clause-② declaration, so no contract-tier review is owed. This is the seat's own TIER_DEFAULT review of record.
  2. Premise, measured: on base f00e8d5c against this head, all 95 lines removed from sql-driver.ts (the introspectIndexes body, the two imports it alone read, and the import comment) appear in the new module in the same order, matching after indentation is stripped (difflib: 95 of 95). The only added lines that do not come from the base are the module docblock, the schema-drift.js import, the one IntrospectionHost member (resolveHashShadowKeys), and the function head with its this Pick over exactly the five members the body reads. The class stub hands over getters that read the driver at call time, and a late-bound closure for resolveHashShadowKeys, so a subclass override (TursoDriver calls super) or an instance spy is still the one it reaches. Net sql-driver.ts is −83 lines. That is within the release's net-line rule (6388161035). The body does not build through getBuilder, no protected static moved, and insertOnlyUpsertColumns is untouched (drivers(sql): upsert's empty-merge-set fallback is merge-ALL, which re-admits every insert-only column — currently unreachable, but it is the wrong shape for "nothing to merge" #8740).
  3. Gates: check-runs on this head: 51 total, 36 success, 15 skipped, 0 failed, 0 in progress; mergeable clean.
  4. Accepted deviations: main moved one commit past the base (test(ui-merge): the repo-gates wiring pins retire with their port; "no workflow runs this" pins read the root workflows #25904). It touches no driver package or gate this diff derives, and it merges clean, so main was not merged in. Accepted.
  5. Out-of-scope findings: none.
  6. Landing: Tier S (no governed path in the file list), not over 3000 lines. It lands once the maintainer's landing word for this seat thread is on record. Until then it stays draft and out of the queue. The card stays open for the later cuts (Part of).

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 05:56
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 05:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 9407318 Oct 26, 2026
52 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-8aooxu branch October 26, 2026 06:23
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