Uh oh!
There was an error while loading. Please reload this page.
fix(federation): distinct unreachable SchemaDiffEntry kind — an unreachable remote is no longer reported as missing_table or aborted on at boot - #11358
Conversation
…le' diff kind, not missing_table (#11166) - spec: SchemaDiffEntryKind gains 'unreachable' — the one kind that states validation was indeterminate rather than a measured schema fact - service-datasource: validateEach's per-object catch produces the new kind (rows stay ok:false, severity error, error text in 'actual') - runtime: the boot gate no longer feeds unreachable rows to onMismatch — loud warn + continue under every policy value; drift tick summary says 'could not read', never 'drift detected', for unreachable rows Maintainer ruling 2026-08-23 (issue comment, verbatim quote recorded there). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RadETjNRLALFLhFA3xehZP
📓 Docs Drift Check7 anchor(s) derived from 3 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 132 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d911fb24f1a16ea9a7b8d9d7b7c09db984ae8747 && git checkout d911fb24f1a16ea9a7b8d9d7b7c09db984ae8747
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 09db64a486a84456d5b753cdccaec995b28fbc9c 84854c5fbdca41e1cdfa7e7130441d0dad2e9ab8 && git checkout -B drift-repro 09db64a486a84456d5b753cdccaec995b28fbc9c && git merge --no-ff 84854c5fbdca41e1cdfa7e7130441d0dad2e9ab8
node scripts/docs-audit/affected-docs.mjs --json 09db64a486a84456d5b753cdccaec995b28fbc9c |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11166
Implements the maintainer ruling recorded on the card (comment 5383569537, 2026-08-23, quote kept verbatim there: 「其他同意你的意见」 — adopting recommendation A):
SchemaDiffEntrygains a distinct "could not be read" kind (unreachable); the boot gate stops treating an unreachable remote as a schema mismatch — noonMismatch: 'fail'abort for unreachable, loud logging instead; drift ticks report it under the new kind, not asmissing_table.Anchors re-verified on the merged ref (
d39569239)The dispatch named
packages/spec/src/contracts/schema-diff-service.tsas the contract surface. Re-verified on the merged ref:SchemaDiffEntry/SchemaDiffEntryKindlive inpackages/spec/src/shared/external-errors.ts(the ADR-0015 federation error contract);schema-diff-service.ts(the file #11270 edited) declares the introspection types and carries neither symbol. The spec edit therefore lands inshared/external-errors.ts;schema-diff-service.tsis untouched. The defect site (validateEach,packages/services/service-datasource/src/external-datasource-service.ts) and both runtime consumer sites were re-verified as the card describes.Reproduction
Pinned as a unit test with a throwing introspector — the card's measured shape verbatim (
connect ECONNREFUSED 10.0.0.5:5432): before this change the row came back{ kind: 'missing_table', severity: 'error' }; the new pin (classifies a per-object throw as 'unreachable', never 'missing_table' (#11166)) asserts the full row shape including the error text inactual.What changed
packages/spec/src/shared/external-errors.ts—SchemaDiffEntryKindadds'unreachable', appended to the union, with a docblock carrying the transient-vs-fact distinction: it is the one kind that asserts nothing about the remote schema — validation was indeterminate; every other kind is a measured fact about a schema that was successfully read.packages/services/service-datasource—validateEach's per-object catch produceskind: 'unreachable'(rows stayok: false,severity: 'error', error text inactual).missing_tableis still produced — only from its measured branch (!tableafter a successful introspection).packages/runtime—runValidationpartitions each failed row:unreachableentries are logged loudly (datasource + object + underlying error + "unverified for this boot") and never feedonMismatch; only measured diffs reach the policy, so a mismatch row still aborts underfaileven beside an unreachable row.runDriftCheckstill emitsexternal.schema.driftfor unreachable rows (consumers discriminate onkindin the payload'sdiffs), but the operator-facing summary now says "could not read the remote", never "drift detected", for them.Design decisions (measured, then decided within the ruling)
unreachable— the ruling's example fits the existing vocabulary (snake_case single-fact names) and the cloud-side seam measurement was taken against this spelling family.READ_FAILURE_DISCRIMINATORSprecedent (packages/metadata/src/utils/schema-sync-errors.ts), not a hand-rollederr.codetest. That precedent's binding principle is a fact verdict must be positively earned, never defaulted to. Here the discrimination "connection failure or schema fact?" is answered by structure: every schema fact this service reports is derived from an introspection that returned; a throw means the comparison never ran, so no signature test on the thrown value can earn a claim about a schema nobody read. A hand-rolledECONNREFUSED-style allowlist (the prohibited shape) would misclassify every unrecognised connection error back into a fact; amissing-table-shaped rescue from the throw would mislabel in a second direction — on this path a "no such table" throw is about the metadata store or the introspection machinery, not the remote table the row names. So every catch-path throw isunreachable, and the precedent module is cited at the classification site. (This is stated in the code docblock; if the PM intended a literal shared-predicate call here, say so and it forks back — no such call can produce a sound fact from this catch.)'error'— decided from consumer measurement:okmust stayfalse(silently-okwould be the invent-an-answer failure), and the interactive consumers (CLIos datasource validateexits 1 onseverity: 'error'; Studio renders a red badge) should treat "could not validate" as attention-level, not success. The transient-vs-fact distinction consumers act on is the kind axis; severity is the attention axis. The ruling fixed the kind, not the severity.warn, outside theonMismatchresolution — per AGENTS.md's degradation table this is a functional degradation (a check did not run, and says so; nothing claims to have persisted). Logged under everyonMismatchvalue: that policy governs measured mismatches, and an outage is a different condition — even anignoredatasource's operator is told their boot ran unverified. Message names the consequence and the fix per the discipline.onUnreachable-style knob) proved unnecessary: unconditional loud-log-and-continue implements the ruling with no new authorable surface.Consumer posture toward the new kind (measured; controls make the zeros valid)
Enumeration:
grep -rn "SchemaDiffEntry|missing_table|schema\.drift|SchemaValidationResult"overpackages/atd39569239(control: 2305from '@objectstack/specimport sites in-repo — the scan surface is live). All non-test hits:unreachableruntimerunValidation(boot gate)runtimerunDriftCheck(drift tick)diffs; loud pass-through + summary-log splitspecrenderDiffEntry/ExternalSchemaMismatchErroractualtext — loud pass-through (pinned)clios datasource validated.kind;severity: 'error'→ exit 1 — loud, correct (an unvalidatable object is not a passing validation)restexternal-datasource-routesdriver-sqlschema-drift.tsSchemaDiffEntryvalues (managed drift); consumes no service rows;d.kindappears only in its own dedup key — unaffectedValidationPanel(app-shell, measured @ad404e0)DIFF_LABEL[diff.kind] ?? diff.kind— renders the raw kind, loud; compile:DIFF_LABELisRecord<SchemaDiffEntry['kind'], string>total on purpose — its own docblock declares that a kind added upstream "fails THIS map to compile instead of rendering a blank cell" (their designed arrival mechanism; the label lands objectui-side at their next spec sync). Not a silent break. Heads-up filed: objectstack-ai/objectui#5831 (unassigned, recording only).No consumer breaks silently on the new kind → nothing forks back to the PM.
Console Pin Gate safety (additions can break the pinned sibling too): verified not here — the pinned console build (
apps/consolebuild=tsc && vite build) type-checks against@objectstack/specfrom objectui's own lockfile (the published spec); theOBJECTSTACK_SPEC_DISTinjection is honored only invite.config.ts(bundle resolution), which does not type-check. So the totalDIFF_LABELrecord at the pinned SHA (190fbd01d) stays consistent with the spectscsees, and the gate is unaffected by this addition.Verification
All commands at final commit
84854c5fb(clean tree), throughscripts/pm/os-verify-lock.sh; exit codes captured before any pipe.@objectstack/spec419 files / 11136 passed (run on the byte-identical tree the commit was made from);@objectstack/service-datasource26 files / 572 passed and@objectstack/runtime184 files / 2715 passed — both re-run as the union at84854c5fb(Test Files 26 passed/Tests 572 passed;Test Files 184 passed/Tests 2715 passed,VERDICT command-exit 0).typecheckfor spec, service-datasource, runtime + the measured consumers (cli, rest, driver-sql):Tasks: 61 successful, 61 total.pnpm --filter @objectstack/spec check:generated→✓ All 14 generated artifacts are up to date(the api-surface snapshot records export existence, not union membership — no regen owed; verified by the gate, not assumed).node scripts/pm/dispatch-gates.mjs, no hand-fed paths; derivation header named this repo @84854c5fb): all 22 derived commands +check:where-matcher+check:nul-bytesgreen (OVERALL_FAIL=0after a fullturbo run buildclearedcheck-dev-prereqs's workspace-not-built precondition — a worktree fact, not a diff fact).kind: 'missing_table'(mutation confirmed on disk by anchored grep at the catch site, line 732) turned exactly the two predicted pins red —classifies a per-object throw as 'unreachable' … (#11166)andvalidateDatasource › keeps the per-object failure row—2 failed | 570 passed; restore viagit checkout HEADconfirmed by grep (the test imports the service by in-package relative path, so vitest ran the mutated source directly; no dist leg involved).pnpm lint/fullpnpm testsweep left to CI (this PR's local runs are the affected packages + measured consumers + the derived gate families); CI runs the full farm regardless.Process notes
packages/spec/src/**touched) — expected; content-limb self-grade: yes (contract vocabulary addition, per the recorded ruling).@objectstack/specminor (additive public union member),@objectstack/service-datasource/@objectstack/runtimepatch (behavior fix). Not declared-breaking → no ADR-0087 disposition marker owed (check-adr-0087-registrationgreen;check-changeset-no-majorgreen).content/docs/releases/untouched; no gate weakened; no ledger raised.Generated by Claude Code
Generated by Claude Code