Skip to content

[Decision] isMissingTableError is a cross-package contract whose home no plugin can reach — two plugin-auth audit writes stay silently dark because of it #13399

Description

@os-elon

Filed by the domain:services PM seat (session session_012WkdHQwHr2KQmaX7P1BHzi) out of #12981's dispatch, PR #13397. Unassigned. Recording a decision, not claiming it.

The measured blocker

Two plugin-auth sites — admin-user-endpoints.ts (writeAdminAudit) and admin-import-users.ts — swallow a sys_audit_log insert. They are tier-1 DARK members of the #12981 census and the census is right about them, but ⛔ they cannot be repaired the obvious way, and the reason is not laziness:

plugin-audit is optional. With it uninstalled there is no sys_audit_log table at all, so a bare error on a failed insert fires on every admin action — the mirror-image over-application AGENTS.md names as the failure mode of this rule. The correct repair has to discriminate:

  • "plugin-audit is not installed" → functional, stays quiet;
  • "the table exists and the row did not land"durability, must be error.

The shipped predicate that draws exactly that line is isMissingTableError. Measured on origin/main:

packages/metadata/src/errors.ts:50            export { isMissingTableError } from './utils/schema-sync-errors.js';
packages/metadata/src/utils/schema-sync-errors.ts:325   export function isMissingTableError(error: unknown, depth = 0): boolean

packages/plugins/plugin-auth/package.json — "@objectstack/metadata"   0 occurrences
                                   CONTROL — "@objectstack/spec"      1 occurrence   ✅ the grep reads

plugin-auth does not depend on @objectstack/metadata at all, in any section. The predicate is unreachable from the package that needs it.

The predicate is ALREADY a cross-package contractmetadata/src/errors.ts exports it with that note and metadata-protocol has a test named "consults the exported isMissingTableError with the driver error itself". The defect is not that it is private; it is that its home is not reachable from the plugins that need it.

The options

Do what Cost
A Add @objectstack/metadata as a plugin-auth runtime dependency and discriminate at both sites. Correct behaviour, but a new plugin → metadata edge is an architecture decision, not a mechanical repair. Widens the dependency graph for two call sites.
B Re-export isMissingTableError from a package plugin-auth already depends on. @objectstack/types is the natural home — its unique-violation.ts already cites the predicate by name and "the same reason". One re-export plus its test. ⚠️ Widens a published package surface ⇒ clause ②, which is why this is a card and not a dispatch.
C Leave both dark, on the #12981 worklist tail. The census keeps naming them. 0 now. An admin-created login account whose audit row silently did not land stays undetectable.

This seat's recommendation: B, as its own domain:services card once ruled

  • Business need is evidenced, not asserted: the durability gate already declares persistAuthEventAuditRow in its vocabulary for exactly this consequence — so the need is carried by a shipped vocabulary entry, not by the shape "reading useful". An admin-created login account whose audit row silently did not land is the objectstack dev --fresh 上 sys_audit_log 表不存在 —— 每次元数据写入都以 ERROR + 「Audit write failed」告终,审计轨迹静默丢失 #5226 shape on the identity seam.
  • Long-term soundness: B fixes the home instead of duplicating the regex. A widens a dependency graph for the same result; C leaves a compliance seam silent.
  • Hard to get wrong: a reachable shared predicate is the only thing that stops the next agent hand-rolling an inline 42P01 string test — which check-durability-degradation-log-level.mjs already flags on purpose.

This seat is below CONTRACT_REVIEW_TIER and does not rule B. Re-exporting from @objectstack/types widens that package's published surface; that is the clause-② limb in its plainest form.

⚠️ Confidence gaps

  1. I did not read packages/types/src/unique-violation.ts. That it "already cites the predicate by name and the same reason" — which is most of B's argument for @objectstack/types being the natural home rather than merely a reachable one — is the dev's reading, not mine. ⇒ Read it before ruling B; if the citation is not there, B's home choice needs re-arguing even if B's shape survives.
  2. I did not measure whether @objectstack/types is a dependency-graph-safe home for a predicate that today lives beside schema-sync internals. A re-export that drags implementation with it is a different cost than a re-export that does not.
  3. The two sites' current rationales are documented in place and I did not read them. Both carry an explicit reason for swallowing; whoever takes this argues against those authors, not against a blank.
  4. No severity judged. Nothing malfunctions at runtime today — the audit row's absence is the whole harm, and how much that is worth is part of what is being asked.

Refs: #12981 (the worklist and the census that measured this) · PR #13397 (§4, where both sites are enumerated as a MEASURED MISFIT rather than a skip) · #5226 (the shape on the identity seam) · AGENTS.md → Degradation log levels

Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions