Skip to content

Commit 613bfbd

Browse files
claude[bot]claude
andauthored
feat(spec): register the fourteen remaining door:'none' error codes that ship in dist (#16879)
* feat(spec): register the fourteen remaining door:'none' error codes that ship in dist 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x * 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c1d8f98 commit 613bfbd

7 files changed

Lines changed: 316 additions & 315 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
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)
6+
7+
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:
8+
9+
| code | stamped by | `status` on the thrown value | reaches an HTTP door on this tree? |
10+
|---|---|---|---|
11+
| `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` |
12+
| `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 |
13+
| `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 |
14+
| `PLUGIN_CONTRACT_VIOLATION` | `@objectstack/core` (`assertPluginContract`, raised at `kernel.use()`) | none | no — raised while the kernel is still registering plugins |
15+
| `MIXED_ARTIFACT_COLLECTION_SHAPE` | `@objectstack/runtime` (`resolveArtifactCollections`, ADR-0130 D4) | 422 | no — every call site resolves before a transport exists |
16+
| `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 |
17+
| `MEMORY_MULTI_TENANT_UNSUPPORTED` | `@objectstack/driver-memory` (the tenancy guard) | none | no — a boot refusal the CLI rethrows pre-HTTP |
18+
| `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) |
19+
| `WALLED_MEMBERSHIP_POLICY_UNDECLARED` | `@objectstack/organizations` (the walled-posture membership-policy gate, `kernel:bootstrapped`) | none | no — fires before `kernel:listening` opens the socket |
20+
21+
**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.
22+
23+
**`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.
24+
25+
**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.
26+
27+
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.

content/docs/references/api/contract.mdx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const result = ApiErrorSchema.parse(data);
2727

2828
| Property | Type | Required | Description |
2929
| :--- | :--- | :--- | :--- |
30-
| **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) |
30+
| **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) |
3131
| **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) |
3232
| **message** | `string` || Readable error message |
3333
| **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);
141141
* `DOMAIN_VERIFICATION_DISABLED`
142142
* `DOMAIN_VERIFICATION_FAILED`
143143
* `DRIVER_UNAVAILABLE`
144+
* `DUPLICATE_ARTIFACT_OBJECT_NAME`
145+
* `DUPLICATE_ARTIFACT_PACKAGE`
144146
* `DUPLICATE_REQUEST`
145147
* `DUPLICATE_SOURCE_NOT_A_BASE`
146148
* `ELIGIBILITY_UNEVALUABLE`
@@ -195,6 +197,8 @@ const result = ApiErrorSchema.parse(data);
195197
* `IMPORT_JOB_CREATE_FAILED`
196198
* `IMPORT_ROW_FAILED`
197199
* `INTERNAL`
200+
* `INVALID_ARTIFACT_PACKAGES`
201+
* `INVALID_ARTIFACT_PACKAGE_ENTRY`
198202
* `INVALID_EMAIL`
199203
* `INVALID_EXPIRY`
200204
* `INVALID_METADATA`
@@ -221,17 +225,21 @@ const result = ApiErrorSchema.parse(data);
221225
* `MARKETPLACE_PROXY_FAILED`
222226
* `MARKETPLACE_STORAGE_FAILED`
223227
* `MARKETPLACE_UNAVAILABLE`
228+
* `MEMORY_MULTI_TENANT_UNSUPPORTED`
224229
* `METADATA_BRANCH`
225230
* `METADATA_CONFLICT`
226231
* `METADATA_NOT_FOUND`
227232
* `METADATA_SCHEMA_INVALID`
233+
* `MIXED_ARTIFACT_COLLECTION_SHAPE`
234+
* `MONGODB_MULTI_TENANT_UNSUPPORTED`
228235
* `MULTI_UPDATE_HOOK_KEY_DIVERGENCE`
229236
* `NAMESPACE_CONFLICT`
230237
* `NAMESPACE_PREFIX`
231238
* `NEEDS_PASSWORD`
232239
* `NODE_FAILURE`
233240
* `NOTHING_TO_PURGE`
234241
* `NOT_ATTEMPTED`
242+
* `NOT_COMPENSABLE`
235243
* `NOT_CREATABLE`
236244
* `NOT_FOUND`
237245
* `NOT_OVERRIDABLE`
@@ -241,6 +249,7 @@ const result = ApiErrorSchema.parse(data);
241249
* `NO_IDENTITY`
242250
* `NO_MATCH`
243251
* `NO_PENDING_VERIFICATION`
252+
* `NO_SUCH_RUN`
244253
* `OAUTH_REGISTER_FAILED`
245254
* `OBJECT_API_DISABLED`
246255
* `OBJECT_API_METHOD_NOT_ALLOWED`
@@ -260,10 +269,13 @@ const result = ApiErrorSchema.parse(data);
260269
* `PAYLOAD_TOO_LARGE`
261270
* `PERMISSION_NOT_ALLOWED`
262271
* `PHONE_NOT_ENABLED`
272+
* `PLAN_CHANGED`
273+
* `PLUGIN_CONTRACT_VIOLATION`
263274
* `PLUGIN_INSTALL_FAILED`
264275
* `PLUGIN_MANIFEST_INVALID`
265276
* `PLUGIN_REGISTER_FAILED`
266277
* `PLUGIN_UI_REQUIRED_KEY_MISSING`
278+
* `PREFLIGHT_FAILED`
267279
* `PROJECT_MEMBERSHIP_REQUIRED`
268280
* `PROJECT_NOT_FOUND`
269281
* `PROJECT_PROVISIONING`
@@ -302,6 +314,7 @@ const result = ApiErrorSchema.parse(data);
302314
* `SCHEDULES_LIST_FAILED`
303315
* `SCHEDULE_DELETE_FAILED`
304316
* `SELF_REGISTRATION_CLOSED`
317+
* `SERVICE_NOT_REGISTERED`
305318
* `SETTINGS_ACTION_FAILED`
306319
* `SETTINGS_CRYPTO_UNAVAILABLE`
307320
* `SETTINGS_ENGINE_NOT_BOUND`
@@ -351,6 +364,7 @@ const result = ApiErrorSchema.parse(data);
351364
* `VALIDATION_FAILED`
352365
* `VERSION_NOT_FOUND`
353366
* `VERSION_NOT_RESTORABLE`
367+
* `WALLED_MEMBERSHIP_POLICY_UNDECLARED`
354368
* `WRITABLE_PACKAGE_REQUIRED`
355369
* `WRONG_PASSWORD`
356370

content/docs/references/api/error-code-ledger.mdx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,14 @@ best-effort catch that logs and continues. Its throw site and constant
161161
host boot matching is not wire vocabulary.
162162
⚠️ That SECOND ground is superseded by #16404 ("Door or no door" above): a
163163
boot refusal that ships in `dist` is owed a row, so the codes left out or
164-
retired on the "not wire vocabulary" reasoning — the remaining
165-
`boot-refusal` rows of `dispatcher-error-vocabulary.ts` — are registrations
166-
owed under the ruling, not re-argued per card. What still retires a row is
167-
the FIRST ground only: no producer left anywhere in `packages/**` source.
164+
retired on the "not wire vocabulary" reasoning were registrations owed
165+
under the ruling, not re-argued per card — #16449 registered the nine
166+
measured on its tree, and #16649 the fourteen `boot-refusal` rows
167+
`dispatcher-error-vocabulary.ts` still carried, among them
168+
`MONGODB_MULTI_TENANT_UNSUPPORTED` itself, back under
169+
`@objectstack/driver-mongodb` with the #8035 removal reversed on the
170+
record. What still retires a row is the FIRST ground only: no producer
171+
left anywhere in `packages/**` source.
168172
Before deleting a row, check that no producer remains repo-wide AND
169173
that no consumer — including `objectui` and `cloud` — reads the literal;
170174
tests that merely CONSTRUCT the code are not producers, and a test pinned to
@@ -296,6 +300,8 @@ const result = ErrorCode.parse(data);
296300
* `DOMAIN_VERIFICATION_DISABLED`
297301
* `DOMAIN_VERIFICATION_FAILED`
298302
* `DRIVER_UNAVAILABLE`
303+
* `DUPLICATE_ARTIFACT_OBJECT_NAME`
304+
* `DUPLICATE_ARTIFACT_PACKAGE`
299305
* `DUPLICATE_REQUEST`
300306
* `DUPLICATE_SOURCE_NOT_A_BASE`
301307
* `ELIGIBILITY_UNEVALUABLE`
@@ -350,6 +356,8 @@ const result = ErrorCode.parse(data);
350356
* `IMPORT_JOB_CREATE_FAILED`
351357
* `IMPORT_ROW_FAILED`
352358
* `INTERNAL`
359+
* `INVALID_ARTIFACT_PACKAGES`
360+
* `INVALID_ARTIFACT_PACKAGE_ENTRY`
353361
* `INVALID_EMAIL`
354362
* `INVALID_EXPIRY`
355363
* `INVALID_METADATA`
@@ -376,17 +384,21 @@ const result = ErrorCode.parse(data);
376384
* `MARKETPLACE_PROXY_FAILED`
377385
* `MARKETPLACE_STORAGE_FAILED`
378386
* `MARKETPLACE_UNAVAILABLE`
387+
* `MEMORY_MULTI_TENANT_UNSUPPORTED`
379388
* `METADATA_BRANCH`
380389
* `METADATA_CONFLICT`
381390
* `METADATA_NOT_FOUND`
382391
* `METADATA_SCHEMA_INVALID`
392+
* `MIXED_ARTIFACT_COLLECTION_SHAPE`
393+
* `MONGODB_MULTI_TENANT_UNSUPPORTED`
383394
* `MULTI_UPDATE_HOOK_KEY_DIVERGENCE`
384395
* `NAMESPACE_CONFLICT`
385396
* `NAMESPACE_PREFIX`
386397
* `NEEDS_PASSWORD`
387398
* `NODE_FAILURE`
388399
* `NOTHING_TO_PURGE`
389400
* `NOT_ATTEMPTED`
401+
* `NOT_COMPENSABLE`
390402
* `NOT_CREATABLE`
391403
* `NOT_FOUND`
392404
* `NOT_OVERRIDABLE`
@@ -396,6 +408,7 @@ const result = ErrorCode.parse(data);
396408
* `NO_IDENTITY`
397409
* `NO_MATCH`
398410
* `NO_PENDING_VERIFICATION`
411+
* `NO_SUCH_RUN`
399412
* `OAUTH_REGISTER_FAILED`
400413
* `OBJECT_API_DISABLED`
401414
* `OBJECT_API_METHOD_NOT_ALLOWED`
@@ -415,10 +428,13 @@ const result = ErrorCode.parse(data);
415428
* `PAYLOAD_TOO_LARGE`
416429
* `PERMISSION_NOT_ALLOWED`
417430
* `PHONE_NOT_ENABLED`
431+
* `PLAN_CHANGED`
432+
* `PLUGIN_CONTRACT_VIOLATION`
418433
* `PLUGIN_INSTALL_FAILED`
419434
* `PLUGIN_MANIFEST_INVALID`
420435
* `PLUGIN_REGISTER_FAILED`
421436
* `PLUGIN_UI_REQUIRED_KEY_MISSING`
437+
* `PREFLIGHT_FAILED`
422438
* `PROJECT_MEMBERSHIP_REQUIRED`
423439
* `PROJECT_NOT_FOUND`
424440
* `PROJECT_PROVISIONING`
@@ -457,6 +473,7 @@ const result = ErrorCode.parse(data);
457473
* `SCHEDULES_LIST_FAILED`
458474
* `SCHEDULE_DELETE_FAILED`
459475
* `SELF_REGISTRATION_CLOSED`
476+
* `SERVICE_NOT_REGISTERED`
460477
* `SETTINGS_ACTION_FAILED`
461478
* `SETTINGS_CRYPTO_UNAVAILABLE`
462479
* `SETTINGS_ENGINE_NOT_BOUND`
@@ -506,6 +523,7 @@ const result = ErrorCode.parse(data);
506523
* `VALIDATION_FAILED`
507524
* `VERSION_NOT_FOUND`
508525
* `VERSION_NOT_RESTORABLE`
526+
* `WALLED_MEMBERSHIP_POLICY_UNDECLARED`
509527
* `WRITABLE_PACKAGE_REQUIRED`
510528
* `WRONG_PASSWORD`
511529

0 commit comments

Comments
 (0)