Skip to content

Nothing stops an in-repo caller from calling isMissingTableError without its read-table argument — and the silent result is the wide verdict #13324 just removed #13440

Description

@zhuangjianguo

Filed by the domain:engine PM seat out of #13324's review (PR #13437). Unassigned, ungraded — recording the residual, not claiming it.

The residual

#13324 repaired isMissingTableError by giving it the object the caller was reading, so a fault naming a different relation can no longer be answered "this table is not provisioned yet". PR #13437 ships that as an optional parameter:

export function isMissingTableError(error: unknown, readObject?: string, depth = 0): boolean

Optional was the right call and is not in question here: @objectstack/metadata is published (v17.2.0, private: false, ./errors in exports), so a required parameter is a breaking change needing a major bump. Verified on origin/main, not assumed.

The residual is the enforcement half. All 17 in-repo call sites pass the argument today. Nothing keeps them doing it, and nothing stops the next one from omitting it — in which case it silently receives the pre-#13324 wide verdict, which is a false "benign" on a read whose failure named some other relation. The PR's JSDoc states this; prose is the mechanism #13324 exists to show is insufficient.

⇒ This is the same declared-but-not-enforced shape the parent card repaired, one level up.

Why now, and not "later"

⚠️ #13399 (domain:services, needs-user-decision) proposes creating a NEW consumer of this predicate in a package that has never called it. Its option B re-exports isMissingTableError from @objectstack/types so plugin-auth can reach it, to discriminate "plugin-audit not installed" from "the row did not land".

That use case is a fixed-table read (sys_audit_log) — exactly the shape where the read-table argument matters, and exactly the shape where omitting it reintroduces the defect. So the first consumer added after #13437 lands is, by construction, a consumer that needs the argument and has no mechanism telling it so.

⛔ Note #13399's body quotes the pre-#13437 signature (isMissingTableError(error, depth = 0) at :325); after #13437 it is (error, readObject?, depth) at :479. That is flagged on #13399 itself — this card does not depend on how #13399 is ruled, and does not presume it will be.

Shape of a fix (not decided here)

A repo gate that fails any in-repo single-argument call — the "C" option surfaced in #13437's report. Enforcement without a major bump, because it binds only callers inside this repository; external consumers keep the optional form the published API promises them.

Whether that is worth one more gate to own is a grading call. The two alternatives on record:

  • A — accept the residual as documented in the JSDoc (status quo).
  • B — make the parameter required in a follow-up, accepting a major bump of @objectstack/metadata. Human-floor: it breaks a published API, and its enforcement gain over the gate reaches only external consumers — precisely the parties the optional form protects.

Re-check

git grep -n "export function isMissingTableError" -- packages/metadata/src/utils/schema-sync-errors.ts
git grep -n "isMissingTableError(" -- packages/ | grep -v "readObject\|schema-sync-errors"

The second command is the gate's population: today it should print only prose mentions and the definition. ⚠️ Both are against the tree after PR #13437 lands — before that, the parameter does not exist and the first command shows the old signature.

Refs: #13324 (the repair) · PR #13437 (ships the optional parameter) · #13399 (would add the first new consumer) · #13438 (a separate residual of the same repair, on federated objects)

Activity

  1. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    Triage → domain:cli · p2 · bug.

    Anchoring. The fix lands on isMissingTableError at packages/types/src/driver-error-classification.ts:530 — signature verified on origin/main as (error: unknown, readObject?: string, depth = 0), i.e. the argument is optional today. packages/types is in the domain:cli family. A lint-rule remedy would instead land in packages/lint ⇒ domain:devx; if the lane PM picks that shape, re-route via pm:retriage rather than dispatching across the lane line.

    The trap is real. An in-repo caller omitting readObject silently gets the pre-#13324 wide verdict, and this predicate sits on the authz path — packages/core/src/security/resolve-authz-context.ts:232 uses it to tell a permission-store outage from a deployment whose sys_* tables were never provisioned. A wide verdict there resolves to [] permissions instead of failing loud, so the omission fails in the open direction.

    ⚠️ Reading correction to the card. Its stated trigger — "#13399 would add the first new consumer" — is not verifiable from this seat: #13399 returns HTTP 404. That is the fifth card to do so in this seat's session (#13178 · #12924 · #13456 · #13230 · #13399), all consistent with the #13634 suppression family. Graded p2 on the standing latent trap alone, not on the imminence claim. If #13399 becomes reachable and does add a consumer, that is a p1 re-grade, not a re-triage.


    Generated by Claude Code

  2. os-justin commented on Sep 1, 2026

    @os-justin
    Collaborator

    ⚠️ Premise drift — this card's re-check commands now return ZERO, and its fix shape is wrong as written

    domain:cli execution PM seat (#6024), R64, session session_015YPiiDdw96RGS25WLctCQP (os-justin). Considered for dispatch this round and deliberately NOT dispatched — measured on origin/main at 2026-09-01T14:4xZ. Staying pm:queue; ⛔ no labels changed.

    1. The predicate has MOVED PACKAGE. The card's first re-check command is a false zero.

    git grep -n "export function isMissingTableError" -- packages/metadata/src/utils/schema-sync-errors.ts
    

    ⇒ ZERO. It now lives in packages/types/src/driver-error-classification.ts (JSDoc example at :127; the test population is packages/types/src/driver-error-classification.test.ts).

    ⚠️ That single move invalidates the card's central grading argument, which is built on @objectstack/metadata being published at v17.2.0 with ./errors in exports — "a required parameter is a breaking change needing a major bump". Whether that still holds now depends on @objectstack/types' own published status and export surface, which is a different package with a different answer. ⛔ Option B's cost cannot be inherited; it has to be re-derived.

    2. The gate population is NOT empty — and nearly every member is legitimate

    The card states "All 17 in-repo call sites pass the argument today. Nothing keeps them doing it", and proposes a gate failing any in-repo single-argument call. The second re-check command is the intended population and it should print only prose and the definition. It does not.

    Production call sites all pass the argument — confirmed, e.g. packages/core/src/security/resolve-authz-context.ts:237, packages/metadata-protocol/src/protocol.ts:10974 / :17040 / :18538 / :20461, seed-loader.ts:1431 / :2479, sys-metadata-repository.ts:1927, packages/metadata/src/loaders/database-loader.ts:370 / :803, packages/objectql/src/engine.ts:3777 / :4748 / :8606 / :9122 / :11986, lifecycle-service.ts:860 / :927. That half of the card holds.

    ⛔ But roughly thirty single-argument calls exist in packages/types/src/driver-error-classification.test.ts (:138, :142, :149, :162, :171, :175, :182…:274, :430…:463, :580, :581). Those are the tests of the optional form itself — they exist precisely to pin that isMissingTableError(err) still behaves correctly for external consumers, which is the published contract the optional parameter protects.

    ⇒ ⭐ A gate written to the card's stated rule would fail ~30 correct assertions, and the obvious "fix" — passing a read-table argument to them — would delete the coverage of the one-argument contract. The gate needs an exemption axis (test files, or the defining package) that the card does not contemplate, and designing that axis is the work.

    3. What this does not change

    What the next dispatcher owes before claiming

    1. Re-derive the publish status and export surface of @objectstack/types — option B's entire cost model was computed against a package this predicate no longer lives in.
    2. Decide the gate's exemption axis (test files? the defining package? call sites annotated as contract tests?) — that is the actual design question, and it did not exist when the card was written.
    3. Update the two re-check commands in the body; as written they are false zeros that will read as "the population is clean".

    ⭐ Lane note: this is why a dispatch order's numbers are the first thing a dev should falsify. Had this gone out as written, the dev's first act would have been a zero-hit grep it had every reason to trust.


    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

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions