Skip to content

packages/drivers/driver-sql/README.md documents DriverSQL.configure() three times — the class is not exported and the static does not exist #9867

Description

@os-steve

Found while rewriting packages/mcp/README.md (#9579), by sweeping published READMEs for the shape that card is about: a method call on a receiver check:published-readme-exports cannot see.

The finding

packages/drivers/driver-sql/README.md tells the reader to build a stack driver like this, at three separate call sites:

Line Claim
448 driver: DriverSQL.configure(getDatabaseConfig())
482 driver: DriverSQL.configure({ client: 'pg', connection: { … }, debug: true })
516 driver: DriverSQL.configure({ … })

Measured against the built packages/drivers/driver-sql/dist/index.d.ts:

  • DriverSQL is not exported. Zero occurrences of the identifier anywhere in the type entry. The real export is SqlDriver — which the same README imports correctly at line 43, so the page contradicts itself.
  • No configure static exists on SqlDriver or on anything else the package exports. Grepping configure in the type entry returns two hits, both prose inside doc comments.

This is the PluginAudit.configure() shape from #9517 / #9532, still live on a page npm renders (README.md is in files, private unset).

Why the gate did not catch it

Both halves of check:published-readme-exports key on a name the fence imported. The fences at 448/482/516 import nothing; DriverSQL appears out of nowhere as a free identifier, so the import half has no claim to make and the call-site half never adds it to localNames. #9544 recorded a different row in this same file (line 137, the IDriver subpath) and PR #9581 fixed that one — this one sat one section further down, outside what the gate reports.

Census behind that: across the 60 published markdown documents, 38 carry at least one method call on a receiver the gate cannot see, 235 call sites in total. This is the one instance in that population that measurably names nothing real.

Why this is not a mechanical substitution

Swapping in the real export does not produce working code: SqlDriver has no configure either, so the correct form is a decision about how a host actually supplies a driver to defineStack — the same class of question the mcp README needed a ruling for. Filing rather than folding it into #9579, whose ruling is scoped to packages/mcp.

Refs: #9579 · #9544 · #9532 · #9517 · PR #9581


Generated by Claude Code

Activity

  1. claude commented on Aug 19, 2026

    @claude
    Contributor

    Claimed by the domain:devx PM seat — dispatching to an os-dev subagent now.

    State: pm:queue → pm:dispatched, assigned os-steve.


    Generated by Claude Code

  2. os-steve commented on Aug 19, 2026

    @os-steve
    CollaboratorAuthor
    {
      "issue": 9867,
      "status": "done",
      "branch": "claude/issue-9867-driversql-configure",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9906",
      "premise_still_valid": true,
      "summary": "The card reproduces exactly: 0 occurrences of `DriverSQL` in the built dist/index.d.ts, both `configure` hits prose in doc comments. Ruled correctly as not-a-substitution, and the sweep found why: the surrounding shape was fabricated too. H1's working form is `plugins: [new DriverPlugin(new SqlDriver({...}))]` (DriverPlugin from @objectstack/runtime) — `defineStack({ driver: ... })` is not a key of ObjectStackDefinitionSchema, which is `.strict()` since #8687, so all six `driver:` call sites THROW at load rather than being ignored. Nine further defect classes fixed on the same page (see H2 table). All 19 TS fences on the rewritten page are script-extracted verbatim and compile against the built .d.ts; both defineStack shapes are additionally executed. Patch changeset included (README.md ships in the npm tarball), same shape as the sibling mcp-readme-shipped-surface.md. Draft PR, auto-merge NOT enabled, still draft.",
      "tests": "All at HEAD 511ad56cee, after the final commit, on a fully built tree (pnpm build 71/71 tasks). NEGATIVE probes (old forms, tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --types node, hosted under packages/cli so pnpm resolves the workspace): n1 `TS2353 ... 'driver' does not exist in type 'ObjectStackDefinitionInput''; n5 `TS7053` on configs[env] + `TS2304 Cannot find name 'DriverSQL''; n8 `TS2554 Expected 2-3 arguments, but got 1` (find) and `TS2339` on transaction/raw/checkConnection/destroy; n9 `TS2724 ... has no exported member named 'SQLDriverConfig'. Did you mean 'SqlDriverConfig'?'; n10 `TS2339 Property 'getDriver' does not exist on type 'ObjectKernel''; n11 `TS2353 ... 'strategy' does not exist in type '{ enabled: boolean; tenantField?: string; organizationField?: string }''; n12 `TS2353 ... 'filters' does not exist in type 'DriverQuery''. POSITIVE: 19/19 fences PASS, ANY_FAIL=0, re-extracted from the committed file by script rather than transcribed. RUNTIME probe against the built @objectstack/spec: `driver` key THREW 'Unrecognized key(s) on this stack definition: `driver`', tenancy.strategy THREW 'Unrecognized key(s) on `tenancy`: `strategy` ... removed from @objectstack/spec after v15.0 (#2763)', both NEW shapes accepted with NO ERROR. GATES: check:published-readme-exports PASS (60 published docs, 212 imports, 49 type entries, baseline still `entries: []`, 0 findings are call sites); check:published-readme-links PASS (152 links); check:nul-bytes PASS; check:doc-authoring PASS; check:slot-lookup PASS; check:test-source-alias PASS; check:type-source-resolution PASS; check-affected-docs PASS. PACKAGE: `pnpm --filter @objectstack/driver-sql typecheck` PASS (script name echoed, not a zero-match); `pnpm --filter @objectstack/driver-sql test` PASS — 101 files passed / 5 skipped, 1761 tests passed / 62 skipped. The three path-derived gates come from `node scripts/pm/dispatch-gates.mjs packages/drivers/driver-sql/README.md`; the two README-specific gates were added by hand because that script's own residue note explains a gate whose population is a top-level `README.md` literal scores *silent* for every card. No ablation performed — nothing under packages/qa/dogfood is touched by a docs-only change.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #9907: the `objectstack-platform` skill still teaches that an unknown top-level `defineStack` key is silently stripped, which #8687 changed to a throw (three sites in rules/bootstrap-patterns.md, one general statement in SKILL.md:189) — governed surface, untouched by this PR, filed unassigned with the `finding` label"
      ]
    }

    H1 — the working form, with compile evidence

    plugins: [new DriverPlugin(new SqlDriver({ … }))].

    History checked before assuming, per the dispatch. DriverSQL.configure() was never real: commit 86d1d5ee1b (2026-05-07) calls it "the imaginary .configure(...) static factory" and repaired it in this same file — but only in the Basic Usage section. That is the source of the internal contradiction the card names: correct SqlDriver import at line 43, fabricated DriverSQL at 448/482/516. No commit ever added a DriverSQL class.

    But the form that repair produced is also wrong. It wrote defineStack({ driver: new SqlDriver(...) }), and driver is not a declared key. Corroborated three ways: skills/objectstack-platform/rules/bootstrap-patterns.md ("There is no driver: key on defineStack()… Drivers are plugins"); the already-merged #9544 changeset, which recorded the same fact for packages/mcp; and real call sites (examples/app-todo/test/*.ts use kernel.use(new DriverPlugin(...)), while example apps that need no custom options set OS_DATABASE_URL and let os dev build the driver). Both routes are on the rewritten page.

    H2 — full-page audit

    Every claim checked is tabulated in the PR body, OK verdicts included. OK: the line-43 import, line-137 IDataDriver subpath, getKnex(), options.bypassTenantAudit / OS_TENANT_AUDIT=0, the tenant-scope matrix, engine.execute(sql), both sibling-driver links, and the Knex.Config option names. Wrong: DriverSQL.configure() ×3 (the card's finding), defineStack({ driver: … }) ×6, driver.raw() ×6 (it is execute()), checkConnection() ×2 (it is checkHealth(), which resolves false rather than throwing — so the try/catch was wrong in shape too), destroy() (it is disconnect()), transaction(cb) + trx.insert({ object, data }) (real seam is beginTransaction() + options.transaction + commit()/rollback()), kernel.getDriver() ×3 (no such member; the engine's getDriver is private), the query AST in three places (object name is argument one; filter key is where with the ObjectQL dialect; sorting is orderBy: [{ field, order }] — sort/direction is the retired alias SortNodeSchema itself names), interface SQLDriverConfig (the export is SqlDriverConfig, and all four ObjectStack keys were undocumented), tenancy.strategy, configs[env], top-level ssl, the objectstack_* table names ×12, the knex-CLI migration workflow, the inverted "never raw DDL" best practice, the knex index migration, and a dead @vercel/postgres import.

    H3 — runnability

    Worse than a wrong method name, exactly the class the dispatch pointed at: two config blocks on this page could not load at all. Both driver: and tenancy.strategy throw at defineStack(), measured by execution, not inference. A third, quieter failure: every raw-SQL example queried objectstack_-prefixed tables, but StorageNameMapping.resolveTableName makes the physical table name the namespace-prefixed object name (crm_account, sys_user) — 12 occurrences naming tables that never exist. No undocumented env var is required by this page's own configs (unlike the mcp case): the driver is constructed with an explicit config, so the gap does not reproduce here.

    H4 — line 137 confirmed

    import type { IDataDriver } from '@objectstack/spec/contracts' still resolves — ./contracts is a declared subpath, and the isolated probe compiles at exit 0. No regression: check:published-readme-exports is green on a fully built tree with the baseline still at entries: [], and it now reads more of this page than before, because fences that previously imported nothing now carry real imports.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions