Skip to content

refactor(driver-sql): move resolveHashShadowKeys' body out of sql-driver.ts into sql-driver-introspection.ts (#25521 cut 7) - #26114

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-3jc3ll
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-3jc3ll

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 7 of the sql-driver.ts split (#24586 ruling A, recorded in 6270097840). The claim is 6415078284. The body of the protected SqlDriver#resolveHashShadowKeys, the MySQL hash-shadow enrichment of the index read, moves unedited into packages/drivers/driver-sql/src/sql-driver-introspection.ts, beside introspectIndexes, its only caller (cut 5). It reaches the class through that module's existing host, IntrospectionHost, which already lists knex and resolveHashShadowKeys, so the host is not widened. The method keeps its name, docblock, signature (less async) and position.

It is move-only, and the card stays open for the later cuts. Branch claude/project-thread-3jc3ll (platform-assigned to this card thread, as the claim records). The cut commit 0b01e18ba sits on origin/main b4199c968; seat 1's #26015 (SqlDriver.upsert) then landed, and origin/main was merged in (156e3538f, merge base 0a6948210, clean, no conflict). Every reading below was taken on both pairs: base b4199c968 vs head 0b01e18ba, and base 0a6948210 vs merged head 156e3538f.

Net line count (the seat's rule from ACCEPT 6385306916). sql-driver.ts goes from 21,449 lines to 21,431 on the merged pair (21,398 to 21,380 on the first pair): -18. The body is 25 lines out; the stub is 6 lines in; the introspection import gains one name (+1) and its comment one line (+1); isHashShadowCarrier leaves the schema-drift.js import (-1), since the moved body was its last value read in the file. The module grows 927 to 975 lines. The slice pays alone, so no hash-shadow neighbour was paired with it.

Serial with the file's in-flight PRs. Re-measured at my start: 40 open PRs, each file list read. The only one on packages/drivers/driver-sql/**, driver-turso/** or driver-sqlite-wasm/** was #26015 (upsert region at lines ~2256 and ~9495-10078, plus one import line after unencoded-json-text.js); this cut's region is the introspection stub (~15134) and the import lines 126 and 163-173. git merge-tree of the cut against #26015's head b4fc36f35e was clean; #26015 then landed first and this branch merged main after it, as card ask 4 says. The Version Packages PR #25640's file list could not be paged through the proxy (HTTP 403 on the redirected page); it has carried only CHANGELOG.md and package.json changes for these packages in every earlier cut.

The area, measured on 0a6948210

  • resolveHashShadowKeys is lines 15134-15163 of sql-driver.ts (a 28-line docblock above it, 25 body lines). Its only caller is cut 5's introspectIndexes body in sql-driver-introspection.ts, through the host closure.
  • Protected, with an annotated return type (a promise of void), so the inferred-return limit does not bite.
  • It does not build through getBuilder (it reads the catalog through this.knex.select(...).from('information_schema.COLUMNS')), so check:tenant-chokepoint rule 1 is untouched.
  • No static read and no cast-wrapped this read (AST screen): the only member it reads is knex (twice). It has no this.FIELD.delete/insert/update( site.
  • TursoDriver and SqliteWasmDriver do not override it (git grep over both packages: 0 hits), and no test, script, doc or ADR names it (git grep over docs/, content/, scripts/, .github/: 0 hits).

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

  • The class method keeps its docblock and signature, byte-identical less async. Its body is one host literal plus resolveHashShadowKeys.call(host, tableName, indexes), marked [#25521 cut 7], and it returns the moved function's own promise.
  • knex goes as a GETTER that reads the driver at call time (the shape cut 5's stub uses), so a knex replaced on the instance after construction is the one the body reads.
  • The moved function types its this as the Pick of IntrospectionHost over knex alone, and the stub annotates its literal as the ThisParameterType of the moved function. No cast, and no member becomes public.
  • introspectIndexes' body still calls this.resolveHashShadowKeys(...) through its host closure, which calls the class method on the driver. A subclass override or an instance spy is still the one reached (probe below).
  • Module-level declarations. The body reads isHashShadowCarrier and parseHashShadowKeyParts, both exported by schema-drift.ts, so the module imports them from there (not a copy). sql-driver.ts still reads parseHashShadowKeyParts (two other call sites) and keeps it; it no longer reads isHashShadowCarrier as a value (two prose mentions in comments remain) and drops it from its import. No declaration gains an export; index.ts re-exports nothing from the module.
  • The module header gains a [#25521 cut 7] paragraph; the sql-driver.ts import comment names cut 7.
  • The move was produced by a script from the source text, not retyped: each body line re-indented two columns. The body holds no template literal.

The published .d.ts does not change

Under the series bar (ruling 6348891160, letter A). Each end built with pnpm --filter @objectstack/driver-sql build against the same dependency tree; the base legs restore the base text of the two files in this worktree after the commits, 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 (no .d.cts); the two are identical at every end.
  • First pair: raw sha256 of both files at base b4199c968 and head 0b01e18ba is 93da45168e82136b7613944525e47402d5dc6f9f8daa9efa4bdceeaa11b52a89 (cuts 1-6's hash); cmp identical.
  • Merged pair: fix(driver-sql): upsert with nothing left to merge is a plain INSERT, not merge-ALL #26015 changed the published surface, so the hash moved for both ends together: base 0a6948210 and merged head 156e3538f are both 5028e040e1501b0bd72b91fafcf4e1cb372393c59baa5bab8b936b240dd2d3f5; cmp identical.
  • Positive control on a copy of the first-pair head dist/index.d.ts: line 5750, the protected resolveHashShadowKeys(tableName: string, indexes: PhysicalIndex[]) declaration, with protected removed, gives 766c5f27828eae19be82213dcfedb1fd5f4e52239fb3d6435fc9e5fa0c2f121f and a 4-line diff; cmp exits 1.
  • Export tables: the ESM and CJS bundles each export the same 56 names at every end (sorted lists diff-identical).
  • The head build carries the move: .call(host sites in dist/index.mjs and dist/index.js go 18 to 19.

No changeset: skip-changeset, per ruling 6348891160 item 4 (move-only, no product behaviour change).

Proof of move-only

TypeScript AST leaves (recursive getChildren, JSDoc skipped), base 0a6948210 vs merged head 156e3538f (and the same on the first pair):

  • Body: 220 tokens, 0 differ; 1 comment, identical; parameters (less the added this) 8 tokens, 0 differ; return type 4 tokens, 0 differ; async matches.
  • Lines: 27 body lines (braces included), each the base line with two leading spaces removed.
  • Negative control: in a copy of the module, 'GENERATION_EXPRESSION' in the .select(...) becomes 'GENERATION_EXPRESSI0N'. The check reports 1 token and 1 line different and exits PROOF FAILED, exit 1.
  • Class side: 348 members at both ends, same order; the 347 that did not move are byte-identical, leading trivia included; the stub's leading trivia and signature equal the base's, less async.
  • Top level of sql-driver.ts: 207 statements at both ends; 204 byte-identical. The three that differ are the schema-drift.js import (isHashShadowCarrier out), the sql-driver-introspection.js import (resolveHashShadowKeys in, its comment rewrapped) and the class (the one member above).
  • Top level of the module: 11 to 12 statements; the first import differs only by the header paragraph in its leading trivia, the schema-drift.js import gains two names, and the new statement is the moved function.

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 and ADR-0120 D4; neither paragraph describes this body, and the body cites no ADR, so no entry is owed. check:adr-anchors: 88 anchored files at both ends; decision-letter citations on the anchored surface 1,878 at base (0a6948210, clean detached worktree) and 1,878 at head (156e3538f).
  • Symbol anchors. No anchor names resolveHashShadowKeys. check:adr-symbol-anchors: 2,658 anchors resolve at both ends.
  • check:tenant-chokepoint. 24 getBuilder bindings across 3 files at both ends, rule 2 clean at both. Rule 2's SCAN_FILES names sql-driver.ts and not the seam modules, so it no longer visits the body's one .where({ TABLE_SCHEMA, TABLE_NAME }) and one .whereIn('COLUMN_NAME', ...) (catalog filters, no null comparand, no tenant column); where/whereIn/whereRaw/raw call lines in sql-driver.ts go 100 to 98. The verdict and the population the gate prints are unchanged, so it does not go empty.
  • Logger receivers. The body does not log. check:logger-receiver-detach exits 0 at both ends.
  • Text readers. The CLI *.pin.test.ts files, logger-receiver-detach.test.ts, sql-driver-compile-refusal-seam.test.ts, the spec liveness test and the scripts that read sql-driver.ts as text match none of isHashShadowCarrier, parseHashShadowKeyParts, HashShadow or shadowKey (git grep: 0 hits).
  • check:driver-conformance at both ends: 55 covered, 0 in the DEBT ledger, 0 exempt.

Verification

  • tsc --noEmit (driver-sql) exit 0. eslint --no-inline-config packages/drivers/driver-sql/src exit 0 (driver-sql declares no lint script; the repo-wide pnpm lint is CI's).
  • Each suite under its own os-verify-lock call, exit 0 at every end, compared per test name and status from vitest's JSON reporter:
    • driver-sql: first pair 3,999 tests (3,787 passed, 212 skipped; 247 files); merged pair 4,010 (3,796 passed, 214 skipped; 248 files, fix(driver-sql): upsert with nothing left to merge is a plain INSERT, not merge-ALL #26015's new file). 0 lost, 0 changed, 0 added on each pair.
    • driver-turso (against that end's built driver-sql dist): 2,526 tests (2,492 passed, 34 skipped; 95 files) at every end; 0 / 0 / 0 (2,513 distinct names).
    • driver-sqlite-wasm: 704 tests (704 passed; 39 files) at every end; 0 / 0 / 0.
  • Late-binding probe (scratch, node against the built dist, not committed): 17 readings, identical at base and head on both pairs (diff empty). It covered: a direct call with a fake knex set on the instance after construction (the same builder chain, information_schema.COLUMNS, the database() read, the carrier columns in whereIn, and the parsed shadowKey per carrier, NULL-safe and raw, plus an unresolved carrier and a plain index left alone); no carrier (returns without touching knex); a synchronous throw in the builder and an async rejection (both swallowed, resolves undefined); a non-array argument (a rejection with a TypeError, never a synchronous throw, at both ends); a subclass override of resolveHashShadowKeys reached from introspectIndexes' MySQL arm (via subclass dialect getters and a knex.raw stub); the real method through that arm; and an instance spy on it, reached once.
  • Live MySQL-dialect cell: the four hash-shadow live files (11627, 12998, 13015, 15479) ran against a local MariaDB 10.11 at base 0a6948210 and merged head 156e3538f: 31 tests at both ends (12 passed, 18 failed, 1 skipped), 0 status differences. MariaDB does not take the shadow path these files assert on (the failures name a missing shadow column or a missing shadow-path log line, identically at both ends), so this cell does not reach the moved body; it is MariaDB, not MySQL 8, and only shows the diff changes nothing there. CI's live MySQL lane, where it runs, is the authority.
  • dispatch-gates --commands on this diff at the merged head 156e3538f (merge base 0a6948210, no STALE TREE): 55 derived commands, each run with its exit captured before any pipe. --ran: 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-plugin-teardown-shape --self-test first exited 3 (shallow clone) and exits 0 after fetching its positive-control commit; check:lean-entry-closure first exited 3 (no objectql dist) and exits 0 after building it.
  • Also run, outside the derivation: check:adr-anchors, check:adr-symbol-anchors, check:commit-card-trailers and its --range origin/main..HEAD (2 commits, clean): all exit 0.
  • No reverse verification or ablation: the diff changes no tested behaviour.

Acceptance notes

  • check:tenant-chokepoint rule 2 visits two fewer catalog filters in sql-driver.ts (above). Widening SCAN_FILES to the seam modules is a gate edit and the open precondition the series carries for the getBuilder areas; it is not made here.
  • The class method now returns the moved function's promise instead of being an async method; a caller awaits the same settlement, and a synchronous throw inside the body still arrives as a rejection (probe reading above).
  • Proposed next slice (unchanged from cut 6's report): applyIndexDriftOp and restoreBareIndexAfterFailedTighten into sql-driver-schema-sync.ts beside cut 6's pair. Neither builds through getBuilder; screen statics and price at dispatch; applyIndexDriftOp carries the ADR-0120 D4 ceremony, so the anchor entry's D4 paragraph would need a new entry for that module.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AuyWbsvLe5peToiMx3LiTd

…ver.ts into sql-driver-introspection.ts (#25521 cut 7)

The MySQL hash-shadow enrichment moves unedited beside its only caller,
introspectIndexes, and reaches the class through the existing
IntrospectionHost (knex only). The class method keeps its name, docblock
and signature (less async) and hands the body its host. sql-driver.ts
21,398 -> 21,380 lines; isHashShadowCarrier leaves its import.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AuyWbsvLe5peToiMx3LiTd
@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

4 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 679fc7ce87c0f9d5d439a65f1a7a24d056a37a74 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6e1e41e037f83c3ea8ddad044e8dbe75336c78bb — the merge of head 156e3538f0db8a601c7ef39b26ece9c567c0c567 into base 679fc7ce87c0f9d5d439a65f1a7a24d056a37a74, 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 6e1e41e037f83c3ea8ddad044e8dbe75336c78bb && git checkout 6e1e41e037f83c3ea8ddad044e8dbe75336c78bb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 679fc7ce87c0f9d5d439a65f1a7a24d056a37a74 156e3538f0db8a601c7ef39b26ece9c567c0c567 && git checkout -B drift-repro 679fc7ce87c0f9d5d439a65f1a7a24d056a37a74 && git merge --no-ff 156e3538f0db8a601c7ef39b26ece9c567c0c567

node scripts/docs-audit/affected-docs.mjs --json 679fc7ce87c0f9d5d439a65f1a7a24d056a37a74

⚠️ 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 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-26T13:15Z · head 156e3538f0db8a601c7ef39b26ece9c567c0c567 · #25521 cut 7

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 26114: NOT governed, 90 changed lines.
  2. Premise measured: the claim 6415078284 named resolveHashShadowKeys, and the slice pays alone (sql-driver.ts net −18 lines). The seat extracted the method body at the merge base and the moved function at this head and compared them after the one-level re-indent: the 25 body lines are byte-identical. The function takes this: Pick<IntrospectionHost, 'knex'>, so IntrospectionHost is not widened. The class method keeps its name, docblock, protected signature and position; it drops async and returns the moved body's promise through a host with a knex getter, so a subclass override or an instance spy is still the one introspectIndexes reaches. isHashShadowCarrier leaves sql-driver.ts's import; parseHashShadowKeyParts stays, since the class still reads it. The dev re-took every reading after merging main once (seat 1's fix(driver-sql): upsert with nothing left to merge is a plain INSERT, not merge-ALL #26015 landed in between): .d.ts byte-identical at both ends of both pairs, three driver suites 0 lost, changed or added, the AST proof OK with a negative control, and the probe 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 54 of 55 derived gates run green locally, the one not measured (dual-build-cjs-loads) being CI's.
  4. Accepted deviations: none beyond the series shape.
  5. Out of scope: none filed.
  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 8.

Generated by Claude Code

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

Labels

size/s 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