From 3acd66b3fc2f63c197f9587a16d4853ba1bd601c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 11:39:16 +0000 Subject: [PATCH 1/2] feat(spec): register the fourteen remaining door:'none' error codes that ship in dist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under the #16404 ruling (option D) the ledger is the published face: every code shipped in dist is registered, door or no door. #16449 took the nine measured on its tree; the fourteen boot-refusal rows that remained in dispatcher-error-vocabulary.ts — nine @objectstack/core refusals, MIXED_ARTIFACT_COLLECTION_SHAPE (runtime), DUPLICATE_ARTIFACT_OBJECT_NAME (objectql), the two drivers' *_MULTI_TENANT_UNSUPPORTED refusals and WALLED_MEMBERSHIP_POLICY_UNDECLARED (organizations) — gain ledger rows under their stamping packages, each measured present in that package's built dist/index.js. @objectstack/driver-mongodb returns as an owner key (the #8035 removal reversed on the record) and @objectstack/organizations is new. The fourteen vocabulary rows ratchet out as the gate's stale-row rule requires; the test that pinned MONGODB_MULTI_TENANT_UNSUPPORTED's absence now pins its presence, with OVERLAY_PERSISTENCE_FAILED as the retired-class witness and MULTI_TENANT_UNSUPPORTED as the still-refused control. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x --- .../register-remaining-boot-refusal-codes.md | 27 ++ .../src/dispatcher-error-vocabulary.ts | 309 ++---------------- .../spec/src/api/error-code-ledger.test.ts | 70 +++- .../spec/src/api/error-code-ledger.zod.ts | 181 +++++++++- 4 files changed, 279 insertions(+), 308 deletions(-) create mode 100644 .changeset/register-remaining-boot-refusal-codes.md diff --git a/.changeset/register-remaining-boot-refusal-codes.md b/.changeset/register-remaining-boot-refusal-codes.md new file mode 100644 index 0000000000..ac42228248 --- /dev/null +++ b/.changeset/register-remaining-boot-refusal-codes.md @@ -0,0 +1,27 @@ +--- +"@objectstack/spec": minor +--- + +feat(spec): register the fourteen remaining `door: 'none'` error codes that ship in `dist` — the rest of the #16404 class after #16449 enters `ERROR_CODE_LEDGER` (#16649) + +Under the #16404 ruling (director seat, decision batch #62, 2026-09-07, option D; maintainer 「同意」) **the published contract face for error codes is `ERROR_CODE_LEDGER` / `StandardErrorCode`**: every `code` that ships in a package's `dist` is registered there, door or no door, because a consumer's `catch (e) { switch (e.code) }` pins the spelling the moment it ships. #16449 registered the nine codes measured on its tree; fourteen more were still shipping unregistered — every `boot-refusal` row `dispatcher-error-vocabulary.ts` carried — and now have rows, each under the package that stamps it: + +| code | stamped by | `status` on the thrown value | reaches an HTTP door on this tree? | +|---|---|---|---| +| `INVALID_ARTIFACT_PACKAGES` · `INVALID_ARTIFACT_PACKAGE_ENTRY` · `DUPLICATE_ARTIFACT_PACKAGE` | `@objectstack/core` (`resolveArtifactPackageOrder`, ADR-0130 D4/D5) | 422 | no — boot-time `manifest.register()` aborts boot; the install route answers with its own `PLUGIN_REGISTER_FAILED` | +| `NO_SUCH_RUN` · `PLAN_CHANGED` · `PREFLIGHT_FAILED` · `NOT_COMPENSABLE` | `@objectstack/core` (`MigrationJournalRefusal`, the migration-journal runner) | none | no — caught by the CLI's `migrate` commands with `instanceof` and printed | +| `SERVICE_NOT_REGISTERED` | `@objectstack/core` (`PluginLoader.getService`'s "never registered" rejection) | none, by design | no — read in-process by the seam that catches the rejection | +| `PLUGIN_CONTRACT_VIOLATION` | `@objectstack/core` (`assertPluginContract`, raised at `kernel.use()`) | none | no — raised while the kernel is still registering plugins | +| `MIXED_ARTIFACT_COLLECTION_SHAPE` | `@objectstack/runtime` (`resolveArtifactCollections`, ADR-0130 D4) | 422 | no — every call site resolves before a transport exists | +| `DUPLICATE_ARTIFACT_OBJECT_NAME` | `@objectstack/objectql` (`SchemaRegistry.installPackage`, ADR-0130 D3) | 422 | no — the HTTP install sites pass no artifact scope, so they cannot raise it | +| `MEMORY_MULTI_TENANT_UNSUPPORTED` | `@objectstack/driver-memory` (the tenancy guard) | none | no — a boot refusal the CLI rethrows pre-HTTP | +| `MONGODB_MULTI_TENANT_UNSUPPORTED` | `@objectstack/driver-mongodb` (the tenancy guard) | none | no — a boot refusal the CLI rethrows pre-HTTP (registered by #3724, unregistered by #8035, re-registered here under the ruling) | +| `WALLED_MEMBERSHIP_POLICY_UNDECLARED` | `@objectstack/organizations` (the walled-posture membership-policy gate, `kernel:bootstrapped`) | none | no — fires before `kernel:listening` opens the socket | + +**Wire consequence, stated plainly.** Registration changes what a client reads only where a code reaches an HTTP door: `error.code` would carry the specific code instead of the standard member the status derives, with the producer's spelling no longer demoted into `declaredCode`. Re-measured on this tree at the sites each `boot-refusal` row named (the table's last column is that reading, one line per group), **none of the fourteen has such a door**, so **no HTTP body changes with this release**. What changes is the face: `ErrorCode` — the union `ApiErrorSchema.code` parses against — gains fourteen members, `REGISTERED_ERROR_CODES` lists them, the ledger gains two owner keys (`@objectstack/driver-mongodb` returns after #8035 removed it; `@objectstack/organizations` is new), and each refusal's `e.code` is now a member of the union a consumer's exhaustive `switch` is written over. Should a door ever answer with one of them, the wire carries the specific code from then on. + +**`MONGODB_MULTI_TENANT_UNSUPPORTED` is a deliberate reversal, not drift.** #8035 unregistered it on the ground that "host boot matching is not wire vocabulary"; the #16404 ruling supersedes exactly that ground (the ledger header's "Retiring a code" section records both halves), and the test that pinned its absence now pins its presence. What still retires a row is a code with no producer left in `packages/**` — `OVERLAY_PERSISTENCE_FAILED` (#5783) remains the pinned witness of that class. + +**Why `minor`, and no `BREAKING` banner.** Nothing is removed or renamed; every existing body parses exactly as before. The change is a purely additive widening of a published surface (fourteen new `ErrorCode` members, two new owner keys), which the 2026-09-04 ruling on #15294 requires to be at least `minor`. The one consumer-visible cost is type-level: an exhaustive `switch` over the `ErrorCode` TYPE gains fourteen cases to cover. + +The fourteen `boot-refusal` classification rows in `dispatcher-error-vocabulary.ts` ratchet out with the registrations (the gate reports a registered code's row as `stale-row`), their reachability reading now carried on the ledger rows; that module is not part of `@objectstack/runtime`'s published entry, so nothing in that package's `dist` moves. The `boot-refusal` verdict itself stays declared for a future pre-HTTP producer; retiring it and widening the gate's spec-face refusal to every published package is the card's second half and is not in this release. diff --git a/packages/runtime/src/dispatcher-error-vocabulary.ts b/packages/runtime/src/dispatcher-error-vocabulary.ts index c1c3390661..fb8d7696f3 100644 --- a/packages/runtime/src/dispatcher-error-vocabulary.ts +++ b/packages/runtime/src/dispatcher-error-vocabulary.ts @@ -221,8 +221,11 @@ export type CodeVerdict = * code, so registering it changes no HTTP body — and a row carrying it is * a registration OWED, exactly like `pending-registration`: the row * ratchets out when the ledger row lands (#16449 took the nine measured on - * that card's tree; the rows still here are the follow-up named in the - * running log below). ⛔ Under `packages/spec/src/**` the gate refuses + * that card's tree; #16649 took the fourteen that remained, so on this + * tree no row carries this verdict — it stays declared for the next + * pre-HTTP producer the scan finds, until the gate's spec-face refusal is + * widened to every published package and this verdict retires with it; + * see the running log below). ⛔ Under `packages/spec/src/**` the gate refuses * this verdict outright (`spec-face-unregistered`): a spec stamp site is a * ledger member, a foreign vocabulary or a runtime-pinned template — * nothing between. @@ -302,10 +305,25 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [ // `STACK_*` `defineStack` refusals and `PLUGIN_UI_REQUIRED_KEY_MISSING` // (`@objectstack/spec`) — were registered and ratcheted out; the // reachability each row recorded now lives on its ledger row. The - // `boot-refusal` rows still below are the same class and owe the same - // registration (#16649); `packages/spec/src/**` can no longer carry - // one at all — `check:dispatcher-error-vocabulary` refuses any verdict - // there but `foreign-vocabulary` / `runtime-pinned`. ── + // `packages/spec/src/**` can no longer carry one at all — + // `check:dispatcher-error-vocabulary` refuses any verdict there but + // `foreign-vocabulary` / `runtime-pinned`. ── + // + // ── [#16649] Fourth cycle, the rest of that class: the fourteen + // `boot-refusal` rows that remained after #16449 — the nine + // `@objectstack/core` refusals (the three ADR-0130 D4 artifact-package + // refusals, the four `MigrationJournalRefusal` codes, + // `SERVICE_NOT_REGISTERED`, `PLUGIN_CONTRACT_VIOLATION`), + // `MIXED_ARTIFACT_COLLECTION_SHAPE` (`@objectstack/runtime`), + // `DUPLICATE_ARTIFACT_OBJECT_NAME` (`@objectstack/objectql`), the two + // drivers' `*_MULTI_TENANT_UNSUPPORTED` refusals and + // `WALLED_MEMBERSHIP_POLICY_UNDECLARED` (`@objectstack/organizations`) — + // were registered under their stamping packages and ratcheted out + // (`stale-row`), the reachability each row recorded now carried on its + // ledger row. On this tree NO row carries the `boot-refusal` verdict; a + // future pre-HTTP producer the scan finds still lands here as an + // `unclassified-site` and takes one, then a registration, then comes out + // again. ── // ── runtime-pinned: an interpolated family, checked where it can be ───── { @@ -672,285 +690,6 @@ export const UNREGISTERED_CODE_SITES: readonly UnregisteredCodeSite[] = [ + 'that batch\'s input, and the registration is what ratchets it out again.', }, - // ── boot refusals: no HTTP boundary exists yet ───────────────────────── - // [#9460] The four `MigrationJournalRefusal` codes below arrive through the - // same code-carrying-helper shape as `owd_widening_forbidden` — a class - // constructor `(code, message)` whose `this.code = code` names the token but - // not the value — and land on the OTHER side of the reachability question. - { - code: 'MONGODB_MULTI_TENANT_UNSUPPORTED', - file: 'packages/drivers/driver-mongodb/src/mongodb-tenancy-guard.ts', - shape: 'classconst', - door: 'none', - verdict: 'boot-refusal', - why: - 'The ledger note names this exact code as the class precedent: registered by #3724, ' + - 'UNregistered by #8035 because the CLI rethrows it pre-HTTP and aborts. "Host boot matching ' + - 'is not wire vocabulary." Its throw site and constant deliberately live on.', - }, - // [#16130] The walled-posture membership-policy gate, which arrived in this - // repository with `@objectstack/organizations` when ADR-0132 moved the - // multi-org runtime to open core. Its row is written here rather than in the - // ledger for the same reason as the two rows below it. - { - code: 'WALLED_MEMBERSHIP_POLICY_UNDECLARED', - file: 'packages/plugins/organizations/src/membership-policy-gate.ts', - shape: 'classconst', - door: 'none', - verdict: 'boot-refusal', - why: - 'Thrown from the plugin\'s own `kernel:bootstrapped` hook, which fires BEFORE ' + - '`kernel:listening` opens the socket — so no request is ever served by a deployment this ' + - 'refuses, and no HTTP boundary exists on the path. `objectstack serve` prints the message ' + - 'verbatim and exits 1; a multi-kernel host catches it per kernel. The `code` field exists ' + - 'to let such a host discriminate this refusal from the two neighbouring boot refusals ' + - '(a licence failure and an absent package) STRUCTURALLY rather than by string match, ' + - 'across module instances — which is a host-boot concern, not wire vocabulary. Same class ' + - // The precedent is #8035's ruling on the MULTI_TENANT_UNSUPPORTED pair - // below — in a comment, not in the string: `pnpm check:doc-authoring` - // keeps tracker ids out of prose a reader cannot resolve them from. - 'and the same reasoning as the MULTI_TENANT_UNSUPPORTED pair below.', - }, - { - code: 'MEMORY_MULTI_TENANT_UNSUPPORTED', - file: 'packages/drivers/driver-memory/src/memory-tenancy-guard.ts', - shape: 'classconst', - door: 'none', - verdict: 'boot-refusal', - why: - 'The driver-memory twin of the row above — same guard shape, same MULTI_TENANT_UNSUPPORTED_CODE ' + - 'constant name, same pre-HTTP abort. Ruled by the same #8035 reasoning.', - }, - { - code: 'NO_SUCH_RUN', - file: 'packages/core/src/utils/migration-journal.ts', - shape: 'codehelper', - door: 'none', - verdict: 'boot-refusal', - why: - 'Raised by `MigrationJournalRefusal` when no journal rows exist for the requested run id. ' + - 'Its only consumers are `packages/cli/src/commands/migrate/resume.ts` and `recorded-by.ts`, ' + - 'which catch it with `instanceof` and print a message — no HTTP boundary exists on that path, ' + - 'and grep finds no other consumer in `packages/`. Same class as the two rows above and ruled ' + - 'by the same #8035 reasoning: a runner refusal the CLI rethrows is not wire vocabulary.', - }, - { - code: 'NOT_COMPENSABLE', - file: 'packages/core/src/utils/migration-journal.ts', - shape: 'codehelper', - door: 'none', - verdict: 'boot-refusal', - why: - 'Raised by `MigrationJournalRefusal` when a chunk cannot be compensated, so the runner refuses to resume. ' + - 'Its only consumers are `packages/cli/src/commands/migrate/resume.ts` and `recorded-by.ts`, ' + - 'which catch it with `instanceof` and print a message — no HTTP boundary exists on that path, ' + - 'and grep finds no other consumer in `packages/`. Same class as the two rows above and ruled ' + - 'by the same #8035 reasoning: a runner refusal the CLI rethrows is not wire vocabulary.', - }, - { - code: 'PLAN_CHANGED', - file: 'packages/core/src/utils/migration-journal.ts', - shape: 'codehelper', - door: 'none', - verdict: 'boot-refusal', - why: - 'Raised by `MigrationJournalRefusal` when the plan hash moved under a recorded run. ' + - 'Its only consumers are `packages/cli/src/commands/migrate/resume.ts` and `recorded-by.ts`, ' + - 'which catch it with `instanceof` and print a message — no HTTP boundary exists on that path, ' + - 'and grep finds no other consumer in `packages/`. Same class as the two rows above and ruled ' + - 'by the same #8035 reasoning: a runner refusal the CLI rethrows is not wire vocabulary.', - }, - { - code: 'PREFLIGHT_FAILED', - file: 'packages/core/src/utils/migration-journal.ts', - shape: 'codehelper', - door: 'none', - verdict: 'boot-refusal', - why: - 'Raised by `MigrationJournalRefusal` when the pre-resume checks refuse to start. ' + - 'Its only consumers are `packages/cli/src/commands/migrate/resume.ts` and `recorded-by.ts`, ' + - 'which catch it with `instanceof` and print a message — no HTTP boundary exists on that path, ' + - 'and grep finds no other consumer in `packages/`. Same class as the two rows above and ruled ' + - 'by the same #8035 reasoning: a runner refusal the CLI rethrows is not wire vocabulary.', - }, - // [#13905] The async service-resolution discriminator; the pre-HTTP reasoning - // is #8035's, the same one the migration-journal rows above cite. If a - // transport ever answers with this fact it becomes pending-registration and - // joins #8846's ledger batch. Tracker ids live in this comment, never in the - // `why` string below — that string is runtime prose and reaches readers who - // cannot resolve them. - { - code: 'SERVICE_NOT_REGISTERED', - file: 'packages/core/src/service-not-registered.ts', - shape: 'assignconst', - door: 'none', - verdict: 'boot-refusal', - why: - 'The discriminator that tells "nothing ever registered this service" from "the service IS ' + - 'registered and could not be built", stamped on the ONE rejection `PluginLoader.getService` ' + - 'raises for the first fact. Read in-process by the seam that catches the rejection and never ' + - 'serialized: measured on this tree, the only references to the code are its own module and the ' + - '`@objectstack/core` re-export — no door reads it, and both seams that catch `getServiceAsync` ' + - 'today (`seamOrUndefined` in packages/rest, `resolveService` in packages/runtime) use a bare ' + - '`catch` that inspects nothing. It carries no `status` on purpose: the whole point is that the ' + - 'CONSUMER decides whether an unwired service degrades or refuses, so binding an HTTP status here ' + - 'would presuppose that decision at the layer that must not make it. Same class as the ' + - 'migration-journal runner refusals above — a composition fact caught in-process is not wire ' + - 'vocabulary. If a transport ever ANSWERS with this fact, the verdict becomes ' + - 'pending-registration and the code belongs in the ledger batch.', - }, - // [#16049] The plugin-contract refusal `kernel.use()` now raises. Same - // pre-HTTP class as the rows above; the ruling that created it is the - // 2026-09-06 ADR-0049 enforce-or-remove call on `PluginSchema`. Since - // #16721 the stamp site is the module BOTH kernels call. - { - code: 'PLUGIN_CONTRACT_VIOLATION', - file: 'packages/core/src/plugin-contract.ts', - shape: 'assignconst', - door: 'none', - verdict: 'boot-refusal', - why: - 'Raised by `assertPluginContract` (`plugin-contract.ts`, the one statement `LiteKernel.use()` ' - + 'calls directly and `PluginLoader.validatePluginContract` runs for `ObjectKernel.use()`) ' - + 'when a plugin object does not satisfy the ' - + 'declared `PluginSchema` on any of the EIGHT keys that enforcement covers — `id`, `type`, ' - + '`staticPath`, `slug`, `default`, `description`, `author`, `homepage` — including an explicit ' - + '`null` on any of them, since all eight are `.optional()` and admit absence but not `null`. ' - + '`version` is excluded from the enforcement, and unknown keys are not refused at all (the ' - + 'schema carries no `.strict()`), so the narrowing stops at those eight. It is ' - + 'raised while the kernel is still registering plugins, before bootstrap and therefore before ' - + 'any HTTP boundary exists: `LiteKernel.use()` throws it as-is and `ObjectKernel.use()` re-wraps ' - + 'it into a fresh `Error` that the host rethrows and the process aborts on, so no door can answer ' - + 'with it and no door can demote it. ' - + 'Same class as the migration-journal runner refusals and the service-resolution discriminator ' - + 'above, ruled by the same reasoning those rows cite: a composition fact raised pre-HTTP is not wire ' - + 'vocabulary. The code is repeated at the head of the message because that re-wrap keeps only ' - + '`message`. If a transport ever ANSWERS with this fact, the verdict becomes ' - + 'pending-registration and the code belongs in the ledger batch.', - }, - // [ADR-0130 D4] The artifact load path's three wrapper refusals, added with the - // N-package load path itself. The pre-HTTP reasoning is the one the rows above - // cite; what is specific to these three is the second half recorded in each `why` - // — the one door that can reach them catches them and answers with its own - // registered code, so they are not demoted at a door, they never reach one. - { - code: 'INVALID_ARTIFACT_PACKAGES', - file: 'packages/core/src/artifact-packages.ts', - shape: 'codehelper', - door: 'none', - verdict: 'boot-refusal', - why: - 'The refusal for an artifact whose `packages` key is present but not an array. Raised by ' + - '`resolveArtifactPackageOrder`, which the `manifest` service calls on every `register()` ' + - '(ADR-0130 D4/D5). Measured on this tree, that service has three callers and none of them puts ' + - 'this code on a wire: `packages/runtime/src/app-plugin.ts` registers at boot inside plugin init, ' + - 'where a throw aborts boot before any HTTP boundary exists; the rehydrate loop in ' + - '`packages/cloud-connection/src/marketplace-install-local-plugin.ts` catches per entry and logs; ' + - 'and the HTTP install route in that same file catches and answers with its OWN registered ' + - '`PLUGIN_REGISTER_FAILED` at 422, interpolating this refusal\'s MESSAGE into that envelope. So the ' + - 'code reaches a reader only inside a message string, never as `error.code` — not demoted at a ' + - 'door, absent from the wire entirely. Its `status: 422` is the ADR-0112 envelope shape this ' + - 'repo\'s rejection tests assert on, not evidence of a door. If an install door ever answers with ' + - 'this code itself, the verdict becomes pending-registration and it belongs in the ledger batch.' - }, - { - code: 'INVALID_ARTIFACT_PACKAGE_ENTRY', - file: 'packages/core/src/artifact-packages.ts', - shape: 'codehelper', - door: 'none', - verdict: 'boot-refusal', - why: - 'The refusal for a `packages[]` element that is not a `{ manifest: … }` wrapper, or whose ' + - 'manifest carries no usable package id. Raised by `resolveArtifactPackageOrder`, which the ' + - '`manifest` service calls on every `register()` (ADR-0130 D4/D5). Measured on this tree, that ' + - 'service has three callers and none of them puts this code on a wire: ' + - '`packages/runtime/src/app-plugin.ts` registers at boot inside plugin init, where a throw aborts ' + - 'boot before any HTTP boundary exists; the rehydrate loop in ' + - '`packages/cloud-connection/src/marketplace-install-local-plugin.ts` catches per entry and logs; ' + - 'and the HTTP install route in that same file catches and answers with its OWN registered ' + - '`PLUGIN_REGISTER_FAILED` at 422, interpolating this refusal\'s MESSAGE into that envelope. So the ' + - 'code reaches a reader only inside a message string, never as `error.code` — not demoted at a ' + - 'door, absent from the wire entirely. Its `status: 422` is the ADR-0112 envelope shape this ' + - 'repo\'s rejection tests assert on, not evidence of a door. If an install door ever answers with ' + - 'this code itself, the verdict becomes pending-registration and it belongs in the ledger batch.' - }, - { - code: 'DUPLICATE_ARTIFACT_PACKAGE', - file: 'packages/core/src/artifact-packages.ts', - shape: 'codehelper', - door: 'none', - verdict: 'boot-refusal', - why: - 'The refusal for one artifact declaring the same package id twice — raised rather than ' + - 'deduplicated so a dropped body cannot go unnoticed. Raised by `resolveArtifactPackageOrder`, ' + - 'which the `manifest` service calls on every `register()` (ADR-0130 D4/D5). Measured on this ' + - 'tree, that service has three callers and none of them puts this code on a wire: ' + - '`packages/runtime/src/app-plugin.ts` registers at boot inside plugin init, where a throw aborts ' + - 'boot before any HTTP boundary exists; the rehydrate loop in ' + - '`packages/cloud-connection/src/marketplace-install-local-plugin.ts` catches per entry and logs; ' + - 'and the HTTP install route in that same file catches and answers with its OWN registered ' + - '`PLUGIN_REGISTER_FAILED` at 422, interpolating this refusal\'s MESSAGE into that envelope. So the ' + - 'code reaches a reader only inside a message string, never as `error.code` — not demoted at a ' + - 'door, absent from the wire entirely. Its `status: 422` is the ADR-0112 envelope shape this ' + - 'repo\'s rejection tests assert on, not evidence of a door. If an install door ever answers with ' + - 'this code itself, the verdict becomes pending-registration and it belongs in the ledger batch.' - }, - // [ADR-0130 D4 / option B] The reader half's own refusal, raised where the - // three above are merely propagated. Same pre-HTTP reasoning; what is - // specific to it is recorded in its `why`. - { - code: 'MIXED_ARTIFACT_COLLECTION_SHAPE', - file: 'packages/runtime/src/artifact-collections.ts', - shape: 'codehelper', - door: 'none', - verdict: 'boot-refusal', - why: - 'The refusal for one collection key declared in the ARRAY form by one source and in the ' + - 'RECORD form by another inside the same artifact — `functions` is ' + - '`z.union([z.record(…), z.array(…)])` and `datasources` is read in either shape, so both ' + - 'sides can pass `AssembledPackageBodySchema` and still disagree. Raised by ' + - '`resolveArtifactCollections`, whose every call site ships in this package and runs BEFORE ' + - 'any HTTP boundary exists: `app-plugin.ts` resolves inside plugin init (a throw aborts boot), ' + - '`load-artifact-bundle.ts` and `standalone-stack.ts` resolve while the artifact is being ' + - 'loaded into a stack that has no transport yet, and `resolve-project-database.ts` opens the ' + - 'artifact file to pick a database before a kernel exists at all. The function is not exported ' + - 'from `packages/runtime/src/index.ts`, so no package outside this one can reach it to put the ' + - 'code on a wire. Its `status: 422` is the ADR-0112 envelope shape this repo\'s rejection ' + - 'tests assert on, not evidence of a door. If an install or serve door ever answers with this ' + - 'code itself, the verdict becomes pending-registration and it belongs in the ledger batch.' - }, - { - code: 'DUPLICATE_ARTIFACT_OBJECT_NAME', - file: 'packages/objectql/src/registry.ts', - // [#16159] `classconst`, not `classfield`, since the literal became the - // exported `DUPLICATE_ARTIFACT_OBJECT_NAME_CODE` constant in the producer. The VALUE is - // byte-identical and the scanner resolves the constant back to it; only the - // spelling the scan matches on moved. The verdict below is untouched. - shape: 'classconst', - door: 'none', - verdict: 'boot-refusal', - why: - 'ADR-0130 D3 — the refusal for two packages delivered by ONE release artifact both claiming ' + - 'the same object name, raised by `SchemaRegistry.installPackage` ahead of every mutation it ' + - 'makes (and therefore ahead of all DDL). Its reachability is narrower than the method it lives ' + - 'in: it can only fire when `installPackage` is handed an artifact install scope naming a SECOND ' + - 'package, and measured on this tree exactly one caller builds one — the `manifest` service\'s ' + - '`register()` in `packages/objectql/src/plugin.ts`, the ADR-0130 D4/D5 load path. The two ' + - 'HTTP-facing install sites pass no scope at all and so cannot raise it: ' + - '`packages/metadata-protocol/src/protocol.ts` `installPackage(manifest, request.settings)` and ' + - '`packages/runtime/src/domains/packages.ts` `installPackage(manifest, body.settings)`. Through ' + - 'the load path the reading is the one the three sibling ADR-0130 codes above already record, ' + - 're-measured here: boot-time `manifest.register()` callers (`packages/runtime/src/app-plugin.ts`, ' + - 'the platform app plugins) register inside plugin init, where a throw aborts boot before any ' + - 'HTTP boundary exists; the rehydrate loop in ' + - '`packages/cloud-connection/src/marketplace-install-local-plugin.ts` catches per entry and logs; ' + - 'and the import route in that same file catches and answers with its OWN registered ' + - '`PLUGIN_REGISTER_FAILED` at 422, interpolating this refusal\'s MESSAGE into that envelope. So ' + - 'the code reaches a reader only inside a message string, never as `error.code`. Its ' + - '`status: 422` is the ADR-0112 envelope shape this repo\'s rejection tests assert on, not ' + - 'evidence of a door. If an install door ever answers with this code itself, the verdict becomes ' + - 'pending-registration and it belongs in the ledger batch.' - }, // ── [#13233] field-level catalogs, reached by the OBJECT-LITERAL helper ── // // The 29 rows below are the whole verdict cost of widening `codehelper` to diff --git a/packages/spec/src/api/error-code-ledger.test.ts b/packages/spec/src/api/error-code-ledger.test.ts index eccbdbd71d..d0c089e040 100644 --- a/packages/spec/src/api/error-code-ledger.test.ts +++ b/packages/spec/src/api/error-code-ledger.test.ts @@ -267,21 +267,59 @@ describe('ErrorCode (standard ∪ registered)', () => { expect(() => ErrorCode.parse('')).toThrow(); }); - it('rejects retired registered-but-unemittable codes (the ledger header "Retiring a code" class)', () => { - // MONGODB_MULTI_TENANT_UNSUPPORTED (#3724 → retired #8035): a BOOT - // refusal — the CLI rethrows it pre-HTTP and aborts; the one - // request-reachable trigger is swallowed by a documented best-effort - // catch. No response envelope can carry it, so keeping the row promised - // clients a code no response delivers (precedent: - // OVERLAY_PERSISTENCE_FAILED / #5783). The throw site and its constant - // (`MULTI_TENANT_UNSUPPORTED_CODE` in `@objectstack/driver-mongodb`) - // deliberately live on — host boot matching is not wire vocabulary — - // which is exactly why the WIRE vocabulary must refuse the string. - expect(() => ErrorCode.parse('MONGODB_MULTI_TENANT_UNSUPPORTED')).toThrow(); - expect(REGISTERED_ERROR_CODES).not.toContain('MONGODB_MULTI_TENANT_UNSUPPORTED'); - // The row was the package's only registration, so the owner key came out - // with it — a future driver-mongodb WIRE code re-adds the entry - // deliberately, with an emit path, not by reverting #8035. - expect(Object.keys(ERROR_CODE_LEDGER)).not.toContain('@objectstack/driver-mongodb'); + it('rejects a retired code with no producer left (the ledger header "Retiring a code" class)', () => { + // OVERLAY_PERSISTENCE_FAILED (#5264 deleted its only producer, #5783 + // unregistered it): the FIRST ground of the header's "Retiring a code" + // section — and since #16404 the only ground left, because the second + // one ("host boot matching is not wire vocabulary", the ground #8035 + // unregistered MONGODB_MULTI_TENANT_UNSUPPORTED on) is superseded by the + // "door or no door" rule. A code with no producer anywhere in + // `packages/**` source is a row promising a code no response can carry, + // and the wire vocabulary must refuse it. + expect(() => ErrorCode.parse('OVERLAY_PERSISTENCE_FAILED')).toThrow(); + expect(REGISTERED_ERROR_CODES).not.toContain('OVERLAY_PERSISTENCE_FAILED'); + }); + + it('accepts the #16649 batch — the fourteen remaining door:none codes, each under its stamping package (#16404)', () => { + // The rest of the #16404 class after #16449: every `boot-refusal` row + // `dispatcher-error-vocabulary.ts` still carried, each measured in its + // package's built `dist/index.js` and absent from the union before this + // batch. Registration widens the face only — none reaches an HTTP door on + // this tree, so no wire body changes; the rows ratcheted out with it. + const batch: Record = { + INVALID_ARTIFACT_PACKAGES: '@objectstack/core', + INVALID_ARTIFACT_PACKAGE_ENTRY: '@objectstack/core', + DUPLICATE_ARTIFACT_PACKAGE: '@objectstack/core', + NO_SUCH_RUN: '@objectstack/core', + PLAN_CHANGED: '@objectstack/core', + PREFLIGHT_FAILED: '@objectstack/core', + NOT_COMPENSABLE: '@objectstack/core', + SERVICE_NOT_REGISTERED: '@objectstack/core', + PLUGIN_CONTRACT_VIOLATION: '@objectstack/core', + MIXED_ARTIFACT_COLLECTION_SHAPE: '@objectstack/runtime', + DUPLICATE_ARTIFACT_OBJECT_NAME: '@objectstack/objectql', + MEMORY_MULTI_TENANT_UNSUPPORTED: '@objectstack/driver-memory', + MONGODB_MULTI_TENANT_UNSUPPORTED: '@objectstack/driver-mongodb', + WALLED_MEMBERSHIP_POLICY_UNDECLARED: '@objectstack/organizations', + }; + expect(Object.keys(batch)).toHaveLength(14); + for (const [code, owner] of Object.entries(batch)) { + expect(ErrorCode.parse(code)).toBe(code); + expect(ERROR_CODE_LEDGER[owner], `${code} registered under ${owner}`).toContain(code); + // None re-spells a standard member — registered plainly, no waiver. + expect(standardSynonymOf(code), `${code} needs no waiver`).toBeUndefined(); + } + // The reversal on the record: MONGODB_MULTI_TENANT_UNSUPPORTED was + // UNregistered by #8035 and its owner key came out with it (this suite + // pinned both absences); #16404 supersedes that ground, so the row and + // the key are back — pinned by presence now. `@objectstack/organizations` + // is a new owner key, the package's first registration. + expect(Object.keys(ERROR_CODE_LEDGER)).toContain('@objectstack/driver-mongodb'); + expect(Object.keys(ERROR_CODE_LEDGER)).toContain('@objectstack/organizations'); + // The control that lets this pin fail in the other direction: a code + // registered by nobody is still refused. `MULTI_TENANT_UNSUPPORTED` is the + // shared NAME of the two drivers' constants, never a code either stamps. + expect(() => ErrorCode.parse('MULTI_TENANT_UNSUPPORTED')).toThrow(); + expect(REGISTERED_ERROR_CODES).not.toContain('MULTI_TENANT_UNSUPPORTED'); }); }); diff --git a/packages/spec/src/api/error-code-ledger.zod.ts b/packages/spec/src/api/error-code-ledger.zod.ts index 05f48d47a0..a6912719a2 100644 --- a/packages/spec/src/api/error-code-ledger.zod.ts +++ b/packages/spec/src/api/error-code-ledger.zod.ts @@ -157,10 +157,14 @@ * host boot matching is not wire vocabulary. * ⚠️ That SECOND ground is superseded by #16404 ("Door or no door" above): a * boot refusal that ships in `dist` is owed a row, so the codes left out or - * retired on the "not wire vocabulary" reasoning — the remaining - * `boot-refusal` rows of `dispatcher-error-vocabulary.ts` — are registrations - * owed under the ruling, not re-argued per card. What still retires a row is - * the FIRST ground only: no producer left anywhere in `packages/**` source. + * retired on the "not wire vocabulary" reasoning were registrations owed + * under the ruling, not re-argued per card — #16449 registered the nine + * measured on its tree, and #16649 the fourteen `boot-refusal` rows + * `dispatcher-error-vocabulary.ts` still carried, among them + * `MONGODB_MULTI_TENANT_UNSUPPORTED` itself, back under + * `@objectstack/driver-mongodb` with the #8035 removal reversed on the + * record. What still retires a row is the FIRST ground only: no producer + * left anywhere in `packages/**` source. * Before deleting a row, check that no producer remains repo-wide AND * that no consumer — including `objectui` and `cloud` — reads the literal; * tests that merely CONSTRUCT the code are not producers, and a test pinned to @@ -478,6 +482,24 @@ export const ERROR_CODE_LEDGER = { // listed once per emitting package — provenance, not identity. 'WRITABLE_PACKAGE_REQUIRED', 'WRONG_PASSWORD', + // [#16649] ADR-0130 D4 — `resolveArtifactCollections` + // (`artifact-collections.ts`) refuses one collection key declared in the + // ARRAY form by one source and the RECORD form by another inside the same + // artifact; `refuse()` there stamps `code` + `status: 422`. Registered + // under the #16404 ruling (door or no door; see the header): the code + // ships in this package's `dist/index.js` (measured), so its spelling is + // the face a consumer's `catch (e) { switch (e.code) }` pins. + // `door: 'none'` on this tree — the reading its `boot-refusal` row in + // `dispatcher-error-vocabulary.ts` recorded and this row ratchets out, + // re-checked here: the function is not exported from `src/index.ts`, and + // its call sites all ship in this package on the artifact load path + // (`app-plugin.ts`, `load-artifact-bundle.ts`, `standalone-stack.ts`, + // `resolve-project-database.ts`), so no package outside this one can + // reach it. The `status: 422` is the ADR-0112 envelope shape this repo's + // rejection tests assert on, not evidence of a door. Not a synonym of any + // standard member (`ARTIFACT`, `COLLECTION`, `SHAPE` are tokens none + // carries). + 'MIXED_ARTIFACT_COLLECTION_SHAPE', ], '@objectstack/service-storage': [ 'ATTACHMENT_DELETE_DENIED', @@ -757,11 +779,95 @@ export const ERROR_CODE_LEDGER = { // QUERY_OBJECT_MISMATCH one layer up. 'UPDATE_ID_MISMATCH', 'VALIDATION_FAILED', + // [#16649] ADR-0130 D3 — two packages delivered by ONE release artifact + // both claiming the same object name, refused by + // `SchemaRegistry.installPackage` ahead of every mutation it makes and + // therefore ahead of all DDL (`registry.ts`; `status: 422`; the literal + // is the exported `DUPLICATE_ARTIFACT_OBJECT_NAME_CODE`). Registered + // under the #16404 ruling (door or no door; see the header): the code + // ships in this package's `dist` (measured), so its spelling is the face + // a consumer pins. `door: 'none'` on this tree — the reading its + // `boot-refusal` row in `dispatcher-error-vocabulary.ts` recorded and + // this row ratchets out, re-checked here: it can only fire when + // `installPackage` is handed an artifact install scope naming a SECOND + // package, and the two HTTP-facing install sites pass no scope at all — + // `packages/metadata-protocol/src/protocol.ts` + // `installPackage(manifest, request.settings)` and + // `packages/runtime/src/domains/packages.ts` + // `installPackage(manifest, body.settings)`; the one caller that builds a + // scope is the `manifest` service's `register()` in `plugin.ts`, the + // ADR-0130 D4/D5 load path. The `status: 422` is the ADR-0112 envelope + // shape this repo's rejection tests assert on, not evidence of a door. + // Not a synonym of any standard member (`ARTIFACT` is a token none + // carries; `DUPLICATE_RECORD` / `DUPLICATE_VALUE` name a different + // condition and share only the first token). + 'DUPLICATE_ARTIFACT_OBJECT_NAME', ], '@objectstack/core': [ 'ERR_BULK_RESULT_MISMATCH', 'FILTER_TOKEN_UNKNOWN', // filter references an unknown context token 'FILTER_TOKEN_UNRESOLVED', + // [#16649] The nine rows below are `door: 'none'` codes — raised while a + // process is still assembling itself, or by a runner the CLI drives — + // registered under the #16404 ruling (door or no door; see the header). + // Each ships in this package's `dist/index.js` (measured), so its + // spelling is the face a consumer's `catch (e) { switch (e.code) }` pins; + // none reaches an HTTP door on this tree, so registering them changes no + // HTTP body. The `boot-refusal` rows they carried in + // `dispatcher-error-vocabulary.ts` ratcheted out with this batch — the + // reachability reading each recorded now lives here, one comment per + // group. None re-spells a standard member (`ARTIFACT`, `RUN`, `PLAN`, + // `PREFLIGHT`, `COMPENSABLE`, `REGISTERED` and `CONTRACT` are tokens no + // member carries). + // + // ADR-0130 D4/D5 — `resolveArtifactPackageOrder` (`artifact-packages.ts`) + // refuses, in order: an artifact whose `packages` key is present but not + // an array; a `packages[]` element that is not a `{ manifest: … }` + // wrapper or whose manifest carries no usable package id; and one + // artifact declaring the same package id twice (raised rather than + // deduplicated, so a dropped body cannot go unnoticed). `refuse()` there + // stamps `code` + `status: 422`. Door reading carried from the vocabulary + // rows and re-checked at the sites they named: the `manifest` service's + // `register()` callers abort boot (`packages/runtime/src/app-plugin.ts`, + // inside plugin init) or catch — the marketplace rehydrate loop per + // entry, and the HTTP install route in + // `packages/cloud-connection/src/marketplace-install-local-plugin.ts` + // answers with its OWN registered `PLUGIN_REGISTER_FAILED` at 422, + // interpolating only the message. The `status: 422` is the ADR-0112 + // envelope shape this repo's rejection tests assert on, not a door. + 'INVALID_ARTIFACT_PACKAGES', + 'INVALID_ARTIFACT_PACKAGE_ENTRY', + 'DUPLICATE_ARTIFACT_PACKAGE', + // ADR-0119 D2 — the migration-journal runner's four refusals + // (`MigrationJournalRefusal`, `utils/migration-journal.ts`: a + // `(code, message)` constructor, `code` only, no `status`). Raised by + // `runMigrationJournal` when no journal rows exist for the requested run + // id, when the plan hash moved under a recorded run, when a step's + // preflight refuses to start, and when a plan declaring + // `onCrash: 'compensate'` has a step with no `compensate()`. Their only + // consumers on this tree are `packages/cli/src/commands/migrate/resume.ts` + // and `recorded-by.ts`, which catch with `instanceof` and print — no HTTP + // boundary exists on that path. + 'NO_SUCH_RUN', + 'PLAN_CHANGED', + 'PREFLIGHT_FAILED', + 'NOT_COMPENSABLE', + // The async service-resolution discriminator (#13905): the ONE rejection + // `PluginLoader.getService` raises for "nothing ever registered this + // service" (`serviceNotRegisteredError`, `service-not-registered.ts`), + // branded and `code`-bearing so the seam that catches it can tell an + // unwired embedder from a broken one. No `status`, by design: the + // consumer decides whether an unwired service degrades or refuses. Read + // in-process by that seam and never serialized on this tree. + 'SERVICE_NOT_REGISTERED', + // The plugin-contract refusal (#16049): `assertPluginContract` + // (`plugin-contract.ts`, called by `LiteKernel.use()` directly and by + // `PluginLoader.validatePluginContract` for `ObjectKernel.use()`) refuses + // a plugin object that does not satisfy the declared `PluginSchema`, with + // this code on the error's `code` and at the head of its message; no + // `status`. Raised while the kernel is still registering plugins, before + // bootstrap and therefore before any HTTP boundary exists. + 'PLUGIN_CONTRACT_VIOLATION', ], '@objectstack/hono': [ 'AUTH_CONFIG_ERROR', // auth service threw while the adapter mounted it @@ -934,6 +1040,26 @@ export const ERROR_CODE_LEDGER = { // was already registered by six other packages. 'INVALID_REQUEST', ], + '@objectstack/organizations': [ + // [#16649] The walled-posture membership-policy gate (#16130, ADR-0132): + // `assertWalledMembershipPolicyDeclared` (`membership-policy-gate.ts`) + // throws `WalledMembershipPolicyError` — this code as `code` via the + // exported `MEMBERSHIP_POLICY_ERROR_CODE`, no `status` — when a walled + // posture is requested and `auth.membership_policy` is undeclared (with + // no effective policy other than `auto`) or invalid. Registered under the + // #16404 ruling (door or no door; see the header): the code ships in this + // package's `dist/index.js` (measured), the package's first row. + // `door: 'none'` on this tree — the reading its `boot-refusal` row in + // `dispatcher-error-vocabulary.ts` recorded and this row ratchets out, + // re-checked here: the plugin runs the gate from its `kernel:bootstrapped` + // hook (`organizations-plugin.ts`), which fires before `kernel:listening` + // opens the socket, so no request is ever served by a deployment this + // refuses. The `code` exists so a multi-kernel host can discriminate this + // refusal from its neighbours structurally, across module instances. Not + // a synonym of any standard member (`WALLED`, `MEMBERSHIP`, `POLICY`, + // `UNDECLARED` are tokens none carries). + 'WALLED_MEMBERSHIP_POLICY_UNDECLARED', + ], '@objectstack/driver-memory': [ // [#13254] Provenance for the in-memory driver's uniqueness refusal, which // #13197 (field-level `unique`) and #13239 (declared `indexes[]` entries) @@ -959,6 +1085,44 @@ export const ERROR_CODE_LEDGER = { // admission rule checks WHO emits, so an unlisted emitter is invisible to // every gate the repo has. 'UNIQUE_VIOLATION', + // [#16649] The in-memory driver's tenancy refusal — + // `MemoryMultiTenantUnsupportedError` (`memory-tenancy-guard.ts`) carries + // this code as `code` via the exported `MULTI_TENANT_UNSUPPORTED_CODE`, + // no `status`; thrown by `assertSingleTenantPosture` and + // `assertObjectsNotTenantScoped`, which `memory-driver.ts` calls. + // Registered under the #16404 ruling (door or no door; see the header): + // the code ships in this package's `dist/index.js` (measured), so its + // spelling is the face a host's `code` match pins — "host boot matching + // is not wire vocabulary" (#8035) no longer exempts a shipped code. + // `door: 'none'` on this tree — the reading its `boot-refusal` row in + // `dispatcher-error-vocabulary.ts` recorded and this row ratchets out: + // the driver-memory twin of the `driver-mongodb` row below, same guard + // shape, same constant name, same pre-HTTP abort. Not a synonym of any + // standard member (`MEMORY`, `TENANT`, `UNSUPPORTED` are tokens none + // carries). + 'MEMORY_MULTI_TENANT_UNSUPPORTED', + ], + '@objectstack/driver-mongodb': [ + // [#16649] The MongoDB driver's tenancy refusal — + // `MongoDBMultiTenantUnsupportedError` (`mongodb-tenancy-guard.ts`) + // carries this code as `code` via the exported + // `MULTI_TENANT_UNSUPPORTED_CODE`, no `status`; thrown by + // `assertSingleTenantPosture` and `assertObjectsNotTenantScoped`, which + // `mongodb-driver.ts` calls. Registered by #3724, UNregistered by #8035 + // on "host boot matching is not wire vocabulary" (this owner key came + // out with it, the package's only row), and re-registered here under the + // #16404 ruling, which supersedes exactly that ground — a deliberate + // reversal on the record, not drift; the header's "Retiring a code" + // section carries both halves. The code ships in this package's + // `dist/index.js` (measured). `door: 'none'` on this tree — the reading + // its `boot-refusal` row in `dispatcher-error-vocabulary.ts` recorded and + // this row ratchets out, re-checked at the host: `packages/cli/src/commands/serve.ts` + // matches `e?.code === 'MONGODB_MULTI_TENANT_UNSUPPORTED'` and RETHROWS, + // so `os serve` aborts pre-HTTP rather than booting with no driver. + // #8035's reading of the one request-reachable trigger (a documented + // best-effort catch that logs and continues) is that ruling's, not + // re-measured here. Not a synonym of any standard member. + 'MONGODB_MULTI_TENANT_UNSUPPORTED', ], '@objectstack/driver-sql': [ // [#11991] The #11756 ruling's refusal (maintainer, 2026-08-25, verbatim @@ -969,11 +1133,14 @@ export const ERROR_CODE_LEDGER = { // `HttpStatusErrorCodeMap` already names for "this server does not do // that": the request is well-formed and nothing faulted. // - // Registered — not left driver-local like `MULTI_TENANT_UNSUPPORTED_CODE` - // — because it IS wire-reachable: publishing a drafted object calls + // Registered from the start — not left driver-local, as + // `MULTI_TENANT_UNSUPPORTED_CODE` was until #16649 — because it IS + // wire-reachable: publishing a drafted object calls // `engine.syncObjectSchema` → `SqlDriver.syncSchema` → the DDL gate, on a // server already serving HTTP. That is the exact test #8035 applied when - // it UNregistered `MONGODB_MULTI_TENANT_UNSUPPORTED` for failing it. + // it UNregistered `MONGODB_MULTI_TENANT_UNSUPPORTED` for failing it — a + // removal #16649 reversed under #16404; the test now decides only what a + // door answers with, never whether a shipped code is registered. // Producer: `packages/drivers/driver-sql/src/dialect-emission-refusal.ts`. 'SQL_DIALECT_EMISSION_UNSUPPORTED', ], From 6738c993e35cf03fcfe1f6b7f142ad379eef04d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 13:09:25 +0000 Subject: [PATCH 2/2] chore(spec): regenerate the error-code reference docs and shrink the prose-id baseline for the fourteen registrations check:docs proved content/docs/references/api/{contract,error-code-ledger}.mdx stale against the widened ledger (the ErrorCode union grows by fourteen, the ledger page lists the fourteen rows); check:generated --fix regenerated only that one artifact. check:doc-authoring's prose-id baseline over-pinned the vocabulary module for two ids whose only carriers were the fourteen boot-refusal rows that ratcheted out; the shrink-only census re-derives it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x --- content/docs/references/api/contract.mdx | 16 +++++++++++- .../docs/references/api/error-code-ledger.mdx | 26 ++++++++++++++++--- scripts/doc-authoring-prose-id.baseline.json | 2 -- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/content/docs/references/api/contract.mdx b/content/docs/references/api/contract.mdx index d8ce065810..9e24b29b2a 100644 --- a/content/docs/references/api/contract.mdx +++ b/content/docs/references/api/contract.mdx @@ -27,7 +27,7 @@ const result = ApiErrorSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +308 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | +| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +322 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | | **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | | **message** | `string` | ✅ | Readable error message | | **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | @@ -141,6 +141,8 @@ const result = ApiErrorSchema.parse(data); * `DOMAIN_VERIFICATION_DISABLED` * `DOMAIN_VERIFICATION_FAILED` * `DRIVER_UNAVAILABLE` +* `DUPLICATE_ARTIFACT_OBJECT_NAME` +* `DUPLICATE_ARTIFACT_PACKAGE` * `DUPLICATE_REQUEST` * `DUPLICATE_SOURCE_NOT_A_BASE` * `ELIGIBILITY_UNEVALUABLE` @@ -195,6 +197,8 @@ const result = ApiErrorSchema.parse(data); * `IMPORT_JOB_CREATE_FAILED` * `IMPORT_ROW_FAILED` * `INTERNAL` +* `INVALID_ARTIFACT_PACKAGES` +* `INVALID_ARTIFACT_PACKAGE_ENTRY` * `INVALID_EMAIL` * `INVALID_EXPIRY` * `INVALID_METADATA` @@ -221,10 +225,13 @@ const result = ApiErrorSchema.parse(data); * `MARKETPLACE_PROXY_FAILED` * `MARKETPLACE_STORAGE_FAILED` * `MARKETPLACE_UNAVAILABLE` +* `MEMORY_MULTI_TENANT_UNSUPPORTED` * `METADATA_BRANCH` * `METADATA_CONFLICT` * `METADATA_NOT_FOUND` * `METADATA_SCHEMA_INVALID` +* `MIXED_ARTIFACT_COLLECTION_SHAPE` +* `MONGODB_MULTI_TENANT_UNSUPPORTED` * `MULTI_UPDATE_HOOK_KEY_DIVERGENCE` * `NAMESPACE_CONFLICT` * `NAMESPACE_PREFIX` @@ -232,6 +239,7 @@ const result = ApiErrorSchema.parse(data); * `NODE_FAILURE` * `NOTHING_TO_PURGE` * `NOT_ATTEMPTED` +* `NOT_COMPENSABLE` * `NOT_CREATABLE` * `NOT_FOUND` * `NOT_OVERRIDABLE` @@ -241,6 +249,7 @@ const result = ApiErrorSchema.parse(data); * `NO_IDENTITY` * `NO_MATCH` * `NO_PENDING_VERIFICATION` +* `NO_SUCH_RUN` * `OAUTH_REGISTER_FAILED` * `OBJECT_API_DISABLED` * `OBJECT_API_METHOD_NOT_ALLOWED` @@ -260,10 +269,13 @@ const result = ApiErrorSchema.parse(data); * `PAYLOAD_TOO_LARGE` * `PERMISSION_NOT_ALLOWED` * `PHONE_NOT_ENABLED` +* `PLAN_CHANGED` +* `PLUGIN_CONTRACT_VIOLATION` * `PLUGIN_INSTALL_FAILED` * `PLUGIN_MANIFEST_INVALID` * `PLUGIN_REGISTER_FAILED` * `PLUGIN_UI_REQUIRED_KEY_MISSING` +* `PREFLIGHT_FAILED` * `PROJECT_MEMBERSHIP_REQUIRED` * `PROJECT_NOT_FOUND` * `PROJECT_PROVISIONING` @@ -302,6 +314,7 @@ const result = ApiErrorSchema.parse(data); * `SCHEDULES_LIST_FAILED` * `SCHEDULE_DELETE_FAILED` * `SELF_REGISTRATION_CLOSED` +* `SERVICE_NOT_REGISTERED` * `SETTINGS_ACTION_FAILED` * `SETTINGS_CRYPTO_UNAVAILABLE` * `SETTINGS_ENGINE_NOT_BOUND` @@ -351,6 +364,7 @@ const result = ApiErrorSchema.parse(data); * `VALIDATION_FAILED` * `VERSION_NOT_FOUND` * `VERSION_NOT_RESTORABLE` +* `WALLED_MEMBERSHIP_POLICY_UNDECLARED` * `WRITABLE_PACKAGE_REQUIRED` * `WRONG_PASSWORD` diff --git a/content/docs/references/api/error-code-ledger.mdx b/content/docs/references/api/error-code-ledger.mdx index 5a33e412ce..b72c1098eb 100644 --- a/content/docs/references/api/error-code-ledger.mdx +++ b/content/docs/references/api/error-code-ledger.mdx @@ -161,10 +161,14 @@ best-effort catch that logs and continues. Its throw site and constant host boot matching is not wire vocabulary. ⚠️ That SECOND ground is superseded by #16404 ("Door or no door" above): a boot refusal that ships in `dist` is owed a row, so the codes left out or -retired on the "not wire vocabulary" reasoning — the remaining -`boot-refusal` rows of `dispatcher-error-vocabulary.ts` — are registrations -owed under the ruling, not re-argued per card. What still retires a row is -the FIRST ground only: no producer left anywhere in `packages/**` source. +retired on the "not wire vocabulary" reasoning were registrations owed +under the ruling, not re-argued per card — #16449 registered the nine +measured on its tree, and #16649 the fourteen `boot-refusal` rows +`dispatcher-error-vocabulary.ts` still carried, among them +`MONGODB_MULTI_TENANT_UNSUPPORTED` itself, back under +`@objectstack/driver-mongodb` with the #8035 removal reversed on the +record. What still retires a row is the FIRST ground only: no producer +left anywhere in `packages/**` source. Before deleting a row, check that no producer remains repo-wide AND that no consumer — including `objectui` and `cloud` — reads the literal; tests that merely CONSTRUCT the code are not producers, and a test pinned to @@ -296,6 +300,8 @@ const result = ErrorCode.parse(data); * `DOMAIN_VERIFICATION_DISABLED` * `DOMAIN_VERIFICATION_FAILED` * `DRIVER_UNAVAILABLE` +* `DUPLICATE_ARTIFACT_OBJECT_NAME` +* `DUPLICATE_ARTIFACT_PACKAGE` * `DUPLICATE_REQUEST` * `DUPLICATE_SOURCE_NOT_A_BASE` * `ELIGIBILITY_UNEVALUABLE` @@ -350,6 +356,8 @@ const result = ErrorCode.parse(data); * `IMPORT_JOB_CREATE_FAILED` * `IMPORT_ROW_FAILED` * `INTERNAL` +* `INVALID_ARTIFACT_PACKAGES` +* `INVALID_ARTIFACT_PACKAGE_ENTRY` * `INVALID_EMAIL` * `INVALID_EXPIRY` * `INVALID_METADATA` @@ -376,10 +384,13 @@ const result = ErrorCode.parse(data); * `MARKETPLACE_PROXY_FAILED` * `MARKETPLACE_STORAGE_FAILED` * `MARKETPLACE_UNAVAILABLE` +* `MEMORY_MULTI_TENANT_UNSUPPORTED` * `METADATA_BRANCH` * `METADATA_CONFLICT` * `METADATA_NOT_FOUND` * `METADATA_SCHEMA_INVALID` +* `MIXED_ARTIFACT_COLLECTION_SHAPE` +* `MONGODB_MULTI_TENANT_UNSUPPORTED` * `MULTI_UPDATE_HOOK_KEY_DIVERGENCE` * `NAMESPACE_CONFLICT` * `NAMESPACE_PREFIX` @@ -387,6 +398,7 @@ const result = ErrorCode.parse(data); * `NODE_FAILURE` * `NOTHING_TO_PURGE` * `NOT_ATTEMPTED` +* `NOT_COMPENSABLE` * `NOT_CREATABLE` * `NOT_FOUND` * `NOT_OVERRIDABLE` @@ -396,6 +408,7 @@ const result = ErrorCode.parse(data); * `NO_IDENTITY` * `NO_MATCH` * `NO_PENDING_VERIFICATION` +* `NO_SUCH_RUN` * `OAUTH_REGISTER_FAILED` * `OBJECT_API_DISABLED` * `OBJECT_API_METHOD_NOT_ALLOWED` @@ -415,10 +428,13 @@ const result = ErrorCode.parse(data); * `PAYLOAD_TOO_LARGE` * `PERMISSION_NOT_ALLOWED` * `PHONE_NOT_ENABLED` +* `PLAN_CHANGED` +* `PLUGIN_CONTRACT_VIOLATION` * `PLUGIN_INSTALL_FAILED` * `PLUGIN_MANIFEST_INVALID` * `PLUGIN_REGISTER_FAILED` * `PLUGIN_UI_REQUIRED_KEY_MISSING` +* `PREFLIGHT_FAILED` * `PROJECT_MEMBERSHIP_REQUIRED` * `PROJECT_NOT_FOUND` * `PROJECT_PROVISIONING` @@ -457,6 +473,7 @@ const result = ErrorCode.parse(data); * `SCHEDULES_LIST_FAILED` * `SCHEDULE_DELETE_FAILED` * `SELF_REGISTRATION_CLOSED` +* `SERVICE_NOT_REGISTERED` * `SETTINGS_ACTION_FAILED` * `SETTINGS_CRYPTO_UNAVAILABLE` * `SETTINGS_ENGINE_NOT_BOUND` @@ -506,6 +523,7 @@ const result = ErrorCode.parse(data); * `VALIDATION_FAILED` * `VERSION_NOT_FOUND` * `VERSION_NOT_RESTORABLE` +* `WALLED_MEMBERSHIP_POLICY_UNDECLARED` * `WRITABLE_PACKAGE_REQUIRED` * `WRONG_PASSWORD` diff --git a/scripts/doc-authoring-prose-id.baseline.json b/scripts/doc-authoring-prose-id.baseline.json index d92ce4e629..55de44589b 100644 --- a/scripts/doc-authoring-prose-id.baseline.json +++ b/scripts/doc-authoring-prose-id.baseline.json @@ -858,9 +858,7 @@ }, "packages/runtime/src/dispatcher-error-vocabulary.ts": { "#12772": 1, - "#3724": 1, "#5085": 2, - "#8035": 6, "#8885": 1, "#9106": 1, "#9223": 1,