Skip to content

fix(metadata,objectql,metadata-protocol): require a missing-table error to name the table that was read - #13437

Merged
zhuangjianguo merged 4 commits into
mainfrom
claude/issue-13324-missing-table-must-name-the-read-table
Aug 30, 2026
Merged

fix(metadata,objectql,metadata-protocol): require a missing-table error to name the table that was read#13437
zhuangjianguo merged 4 commits into
mainfrom
claude/issue-13324-missing-table-must-name-the-read-table

Conversation

@claude

@claudeclaudeBot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Fixes#13324

isMissingTableError answers the one question that licenses a fail-soft caller to
treat an empty result as the truth: "did this READ fail because the table has not
been provisioned yet?". It matched the shape of the dialect phrase and never
asked which table the phrase names.

The defect, measured

Driven against a real libsql file database on this branch:

CREATE VIEW sys_metadata AS SELECT id, type, state FROM table_that_does_not_exist;
SELECT * FROM sys_metadata;
-> SQLITE_ERROR: no such table: main.table_that_does_not_exist

sys_metadata exists and may be backed by rows. The phrase matches the regex, so
the read was answered benign — a false "benign", the direction the module's own
docblock calls far more expensive than a false "real".

Why the phrase alone cannot decide it

The suggested cheaper route was to exclude the measured "names something else"
shapes, as excludes already does for the Postgres column phrasings. Measured, that
route cannot work: a view over a missing base table and a genuine missing table the
caller happened to qualify produce byte-identical messages.

caselibsql messagecorrect verdict
view over a dropped base tableno such table: main.absent_baseloud
genuine missing table, qualifiedno such table: main.ordersbenign

Nothing in the string separates them. The only discriminator is whether the name in
the phrase is the name that was asked for, so the read's name is a parameter
rather than another regex. A shape-only exclusion would have had to give up one of
those two verdicts, and giving up the second is a lost true positive.

The change

A third front-exclusion channel on MISSING_TABLE, alongside the #6347
sub-object phrase it is the sibling of. That one recognises a failure about
something inside a relation; this one recognises a failure about a different
relation. Both end the question with false, and both can only ever subtract
benign verdicts, never add one:

  • no name extractable, or no name supplied — unchanged verdict
  • any extracted name matches the read — benign (a true positive)
  • names found, none of them the read's — loud

The comparison folds away schema/database qualifiers, the legacy
namespace__short prefix resolveTableName strips, and case, so a tolerant match
never manufactures a loud verdict for a genuine missing table.

The parameter is optional.@objectstack/metadata is a published package and
./errors a published subpath, so a required parameter would be a breaking API
change and a major bump. Omitting it reproduces the previous behaviour exactly; all
17 in-repo call sites pass it. The cost of that choice is that the narrowing is
opt-in per call site, and it is stated in the JSDoc.

Call sites

Seventeen, across six files in three packages — packages/metadata,
packages/objectql, packages/metadata-protocol. Five of the thirteen files in
the dispatched surface turned out to be prose only (packages/rest x2,
packages/runtime, packages/types, and packages/drivers/driver-sql), so this
lane's hot serial file is not written by this PR at all.

One shared helper needed more than a name. rethrowUnlessMetadataStoreUnprovisioned
serves callers reading four different tables (sys_metadata,
sys_metadata_audit, sys_metadata_commit, sys_metadata_history); the first
draft hardcoded sys_metadata and turned diffMetaItem's genuinely unprovisioned
sys_metadata_history into a loud failure. Its parameter is required — it is
private, so the compiler can enforce it — and each caller now names what it read.

Two test fixtures were unfaithful, and are corrected rather than re-baselined

Neither expectation moved. Both fixtures threw one fixed error for every read
regardless of object, so a sys_organization probe received a fault phrased for
my_app_widget, and a test titled "an unprovisioned sys_metadata_commit" injected
a fault phrased for sys_metadata. No driver does that. The injectors now phrase
the fault for the table actually read; the assertions are untouched.

Evidence

  • Ablation — removing the new channel turns exactly the 3 defect pins red and
    leaves all 72 other cases green, so the true-positive pins do not ride on it.
    Mutation and restore were each confirmed on disk by blob hash against HEAD.

  • The card's own repro, A/B on one tree (ablated dist vs fixed dist,
    os migrate plan, NODE_ENV=production):

    pre-fixfixed
    sys_metadata view reads reported at ERROR03
    sys_metadata_activation genuine-miss WARN (true positive)11
    plan still produced, exit 0yesyes

    Three — the count the card measured, and the phrase named main exactly as it
    recorded.

  • Fence: no true positive regressed. Every shape is pinned with the read named
    — sqlite no such table: X and its schema-qualified form, Postgres
    relation "x" does not exist, MySQL table "x" doesn't exist, unknown table,
    the SQLSTATE, symbolic-code and errno limbs, and the driver wrapper that carries
    the original as cause.

Tests and gates, all at 6316ca068f: @objectstack/metadata 698, @objectstack/objectql
4287, @objectstack/metadata-protocol 2017 (10 skipped), plus 51 runtime real-driver
fail-soft probes. Full-repo pnpm lint and pnpm build (71/71) green. 38 of the 40
derived gates green; check-test-completeness and pm/check-half-states are exit 3
NOT MEASURED (they need a CI turbo log and a GitHub credential respectively), which
both gates declare is not a finding.

#13273 is not addressed here and remains open — it only adds one more reader of this
predicate, a consequence of the gap rather than its cause.

Generated by Claude Code


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/metadata, @objectstack/objectql, touching 56 documentable anchor(s).

32 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 3d10755f02070fa693bd7004c645255ad1fef544.

6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 24 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 3d10755f02070fa693bd7004c645255ad1fef544packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0c1e5a2a184b3e48c81ea522bb59e242c30cb430 — the merge of head 6316ca068f28f377be1c9e0af38409af2df07331 into base 3d10755f02070fa693bd7004c645255ad1fef544, 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 0c1e5a2a184b3e48c81ea522bb59e242c30cb430 && git checkout 0c1e5a2a184b3e48c81ea522bb59e242c30cb430
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3d10755f02070fa693bd7004c645255ad1fef544 6316ca068f28f377be1c9e0af38409af2df07331 && git checkout -B drift-repro 3d10755f02070fa693bd7004c645255ad1fef544 && git merge --no-ff 6316ca068f28f377be1c9e0af38409af2df07331
node scripts/docs-audit/affected-docs.mjs --json 3d10755f02070fa693bd7004c645255ad1fef544

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

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 30, 2026
@zhuangjianguo
zhuangjianguo marked this pull request as ready for review August 30, 2026 12:14
@zhuangjianguo
zhuangjianguo added this pull request to the merge queueAug 30, 2026
Merged via the queue into main with commit 4cda78cAug 30, 2026
34 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-13324-missing-table-must-name-the-read-table branch August 30, 2026 12:31
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@zhuangjianguo@claude