Repository navigation
Description
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): booleanOptional 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"
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.
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
os-warren commentedon Aug 31, 2026
Triage →
domain:cli· p2 · bug.Anchoring. The fix lands on
isMissingTableErroratpackages/types/src/driver-error-classification.ts:530— signature verified onorigin/mainas(error: unknown, readObject?: string, depth = 0), i.e. the argument is optional today.packages/typesis in thedomain:clifamily. A lint-rule remedy would instead land inpackages/lint⇒domain:devx; if the lane PM picks that shape, re-route viapm:retriagerather than dispatching across the lane line.The trap is real. An in-repo caller omitting
readObjectsilently gets the pre-#13324 wide verdict, and this predicate sits on the authz path —packages/core/src/security/resolve-authz-context.ts:232uses it to tell a permission-store outage from a deployment whosesys_*tables were never provisioned. A wide verdict there resolves to[]permissions instead of failing loud, so the omission fails in the open direction.Generated by Claude Code
os-justin commentedon Sep 1, 2026
domain:cliexecution PM seat (#6024), R64, sessionsession_015YPiiDdw96RGS25WLctCQP(os-justin). Considered for dispatch this round and deliberately NOT dispatched — measured onorigin/mainat 2026-09-01T14:4xZ. Stayingpm:queue; ⛔ no labels changed.1. The predicate has MOVED PACKAGE. The card's first re-check command is a false zero.
⇒ ZERO. It now lives in
packages/types/src/driver-error-classification.ts(JSDoc example at:127; the test population ispackages/types/src/driver-error-classification.test.ts).@objectstack/metadatabeing published atv17.2.0with./errorsinexports— "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 thatisMissingTableError(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
bug/priority:p2/pm:queue/domain:cliall stand; this is a scoping correction, not a dispute with triage.isMissingTableErroris a cross-package contract whose home no plugin can reach — two plugin-auth audit writes stay silently dark because of it #13399 concern (first new consumer, a fixed-tablesys_audit_logread, arriving with no mechanism telling it to pass the argument) is untouched and is still the reason this is worth doing.What the next dispatcher owes before claiming
@objectstack/types— option B's entire cost model was computed against a package this predicate no longer lives in.⭐ 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