Skip to content

Commit 36e4647

Browse files
fix(spec): the error-code waiver reasons and the auth-feature registry notes state each decision in words instead of a tracker number (stage 7) (#21636)
Part of #20749 Clause-②: no Stage 7 of the `domain:spec` lane's share of the runtime-string burn-down (ruling `5902360492`, A / A; form D per `5749154545`): class (f), the internal registry rationales in `packages/spec/src`. Every rewritten reason or note now states in words what the cited decision was, or drops a citation its sentence already explained. Text only. The card stays open for its later stages (`migrations/registry.ts`, then the test strings). ## What changed - **17 messages / 33 tracker ids / 22 distinct cited records** (21 cards and one PR) in two files: - `api/error-code-ledger.zod.ts`: the `reason` of all five `STANDARD_SYNONYM_WAIVERS` entries and of nine `PROVENANCE_WAIVERS` entries (14 messages / 29 ids). - `kernel/public-auth-features.ts`: `PUBLIC_AUTH_FEATURES.deviceAuthorization.exempt.reason`, `.phoneNumber.notes` and `.phoneNumberOtp.exempt.reason` (3 / 4). - One `@objectstack/spec` **patch** changeset, `Clause-②: no`. These are exported constants, and the new text ships in the package's `dist`: measured in 14 `.js` and 14 `.mjs` chunks, and in no `.d.ts` (the reasons are `+` chains, so their type is `string`). - No generated artifact moves (`check:generated`: all 15 up to date). No reader matches these strings by their text (see "Readers" below), so no test or gate moves. - Landing site: `packages/spec`, as the claim predicted. Each producer is the constant itself, so no other package is touched. ## Census at the base (A1) The earlier stages' `census.cjs` is not on disk in this container. I re-implemented it from stage 3's stated semantics, then held it to two references: - **The gate's own per-literal leg.** `check-doc-authoring.mjs --census` was run from a scratch copy whose `PACKAGES_PROSE_ROOT` / `PACKAGES_PROSE_EXCLUDED` were pointed at `packages/spec/src` and at nothing (two lines differ, nothing else). Result: 3 files, 231 = 231 ids, 0 per-file differences. - **Stage 6's numbers** at `2f30de851c`. Test strings 1804 / 1920 in 425 files, identical. Class (f) 17 / 33, identical, line for line against stage 3's table. At base `045b946256`, 1849 files were scanned and 1346 parsed after the byte prefilter, with 0 parse diagnostics: | file (under `packages/spec/src`) | messages / ids | message by message (`line:id`) | |:--|--:|:--| | `api/error-code-ledger.zod.ts` | 14 / 29 | 1618:8111 1619:8211 · 1625:13353 1627:8211 · 1633:8211 · 1639:19441 1640:8211 · 1646:8211 1647:8211 · 1752:11142,11230 · 1760:9415,9446 · 1766:9415,9446 · 1773:10025,11504,10413 1774:10576 · 1782:13353 1786:8016 · 1793:7870 1796:13353 · 1812:8016,3918 · 1821:8016,16322 · 1838:7780 1839:20492 | | `kernel/public-auth-features.ts` | 3 / 4 | 197:2513,2874 (the `2513` is the cross-repo `objectui#2513`) · 220:2871 · 230:2780 | The claim's raw-grep figures (192 and 14 lines) include comments. Comments are out of this stage. **Controls.** - Lit, multi-line: `error-code-ledger.zod.ts:1773-1776` reads as ONE message, with 4 ids on lines 1773-1774. - Lit, single: `public-auth-features.ts:220` reads as one message, `#2871`. - Dark: `error-code-ledger.zod.ts:1589` (docblock, `#8211`), `:1687` (a `//` heading, `#13353`), `public-auth-features.ts:106` (`// [#11544]`) and `:209` (a `//` inside the array, `#9968`) each read 0. - Whole tree: 2151 hit lines, none on a comment line. The rest of the non-test population at this base is `migrations/registry.ts`: 45 messages / 198 ids. Stage 6 read 43 / 196 at `2f30de851c`, so main has since added two messages there. That file is the next stage's. ## Delivered (A2): each site, the decision as read, the new words I read each cited card in full through REST, body and every comment. The decision column names the record. Line is the base line of the message. | file:line | ids | decision as read | old → new | |:--|:--|:--|:--| | `error-code-ledger.zod.ts:1618` (`CONFLICT`) | 8111, 8211 | 8111: dev report `5272495731` (census-first route `5271501976`). The record-sharing errors moved onto the ADR-0112 envelope, and the unregistered 409 `CONFLICT` was registered as it stood under `@objectstack/rest`, so the wire stayed byte-identical. 8211: triage adjudication `5273232415`, option C. The admission gate refuses a semantic synonym unless waived. The existing synonyms are grandfathered by waivers, and consolidation (B) is deferred until a specific code has a measured victim | `(respondSharingError 409 arm; registered by #8111). Wire value kept; consolidation deferred per #8211.` → `(respondSharingError 409 arm), registered as it stood when the record-sharing errors moved onto the ADR-0112 envelope, so the wire stayed byte-identical. Wire value kept; consolidation deferred until it has a measured victim.` | | `error-code-ledger.zod.ts:1625` (`FORBIDDEN`) | 13353, 8211 | 13353: dev report `5489077644` and ACCEPT `5489099444`. The provenance gate landed, and cloud-connection's own-route 403 got its `FORBIDDEN` row; this waiver's reason was extended, not endorsed. 8211 as above | `#13353 added the cloud-connection provenance row for the same pre-existing wire value` → `cloud-connection lists the same pre-existing wire value under its own provenance row`; the closing clause as for `CONFLICT` | | `error-code-ledger.zod.ts:1633` (`INTERNAL`) | 8211 | as above | `consolidation deferred per #8211.` → `consolidation deferred until it has a measured victim.` | | `error-code-ledger.zod.ts:1639` (`NOT_FOUND`) | 19441, 8211 | 19441: dev report `5788584892`. plugin-security's class-field stamps got their rows, the overlay-discard 404's `NOT_FOUND` among them, and this waiver's reason named the new emitter. 8211 as above | `#19441 added the plugin-security provenance row for the same pre-existing wire value` → `plugin-security lists the same pre-existing wire value under its own provenance row`; the closing clause as for `CONFLICT` | | `error-code-ledger.zod.ts:1646` (`UNAUTHORIZED`) | 8211 ×2 | as above. The card named four synonyms, and the detector found this fifth when it landed | `surfaced by the detector when the #8211 gate landed, beyond the four the card named` → `surfaced by the synonym detector when it landed, beyond the four first reported`; the closing clause as for `CONFLICT` | | `error-code-ledger.zod.ts:1752` (`UPDATE_ID_MISMATCH`) | 11142, 11230 | maintainer rulings `5383569192` (unequal scalar `where.id`) and `5386672884` (non-scalar `where.id`). Both shapes are refused 400 with one code, from metadata-core's shared constructor, thrown by `ObjectQL.update`. The objectql row's comment records it | `records "hence registered here" (#11142/#11230).` → `records "hence registered here".` (the sentence already said it) | | `error-code-ledger.zod.ts:1760` (`FLOW_DISABLED`) | 9415, 9446 | 9415: card body and delivery `5322602563`. A disabled flow answers 409 `FLOW_DISABLED`, registered under runtime because the door names the wire vocabulary. 9446: maintainer ruling `5322867819`. The status table is a property of the flow-dispatch contract, so every door converges | `answer 409 (#9415/#9446; the runtime row's comment records the decision).` → `answer 409 — every door that dispatches a flow answers from one status table, by ruling (the runtime row's comment records the decision).` | | `error-code-ledger.zod.ts:1766` (`FLOW_NO_START_NODE`) | 9415, 9446 | as above, the 422 row | `Same decision as FLOW_DISABLED, 422 arm (#9415/#9446):` → `Same decision as FLOW_DISABLED, its 422 arm:` | | `error-code-ledger.zod.ts:1773` (`FLOW_INPUT_SCHEMA_INVALID`) | 10025, 11504, 10413, 10576 | 10025: maintainer ruling `5353928368` (B). A definition-level input-schema refusal is non-retryable and becomes a never-dispatched exit with its own code. 11504 (the card answers 404; its comments are still served, claim `5429880388`, dev report `5430346333`): the contract half of that ruling, which minted the code ahead of its producer. 10413 / 10576: maintainer ruling `5364977280` filed the contract half as its own card, ahead of the implementing half. That is the split shape | `Registered ahead of its producer by design (#10025 → #11504, the #10413 → #10576 split shape):` → `Registered ahead of its producer by design: the ruling that a definition-level input-schema refusal is non-retryable and never dispatched split into a contract half, which minted this code, and a services half that emits it, the contract half landing first.` | | `error-code-ledger.zod.ts:1782` (`EXTERNAL_IMPORT_ERROR`) | 13353, 8016 | 13353: as above. This entry was the first adjudication: a door case, so a waiver, not a row. 8016: dispatch ruling `5267576256` and dev report `5268577504`. One thrown-error mapping (`resolveThrownHttpError`, `@objectstack/types`) shared by both doors honours a throw's own `status` and `code`. `importNameRefusedError` sets exactly those two (`external-datasource-service.ts:337-342`) | `Adjudicated on #13353:` → `Adjudicated when the provenance gate landed:`; `is the #8016 declaration shape,` → ``declares its own `status` and `code`, the shape the shared thrown-error resolver honours,`` | | `error-code-ledger.zod.ts:1793` (`UPLOAD_SESSION_EXPIRED`) | 7870, 13353 | 7870: promotion `5270541620` and dev report `5274803860`. `resumeUpload` short-circuits on `expired` with the server's 410 pair. 13353: ACCEPT `5489099444`. The scope question stays recorded on the waiver, with no new card, for want of pull | `Client-side synthesis (#7870):` → `Client-side synthesis:`; `is the open scope question #13353 recorded —` → `is an open scope question, recorded when the provenance gate landed and left unruled for want of pull —` | | `error-code-ledger.zod.ts:1812` (`VALIDATION_FAILED`) | 8016, 3918 | 3918: card body. The dispatcher's exits answer a validation failure 400 with its `fields[]`, never 500, matching rest. 8016: `validationFailure` moved into `@objectstack/types` beside the shared mapping (`validation-failure.ts` header) | ``Shared constructor by design (#8016/#3918): `validationFailure()` lives in the dependency-light package so BOTH doors recognise one shape;`` → ``Shared constructor by design: `validationFailure()` lives in the dependency-light package beside the one thrown-error mapping both doors share, so BOTH doors recognise one shape and answer it 400 with its `fields[]`, never 500;`` | | `error-code-ledger.zod.ts:1821` (`ANALYTICS_DATE_RANGE_UNRECOGNIZED`) | 8016, 16322 | 16322: card body (the driver half of the date-range contract, director ruling batch 57, option A). Memory and SQL analytics drivers refuse an unrecognised `dateRange` identically, held by one shared conformance fixture. 8016: the shared-constructor shape, which the sentence already states | `Shared constructor one package over, the #8016 shape (#16322):` → `Shared constructor one package over:`; `refuse identically — which is the property the card's shared conformance fixture` → ``refuse an unrecognised `dateRange` identically — which is the property the shared date-range conformance fixture`` | | `error-code-ledger.zod.ts:1838` (`TENANT_SCOPE_REQUIRED`) | 7780, 20492 | 7780: maintainer ruling `5261730657`. An uninstall across every tenant must be declared explicitly; no organization and no flag is a loud 400, never every tenant's rows. 20492: triage `5877470267` and dev report `5878755305`. Every refusal runs before `uninstallPackage`, so a refused request changes nothing | `refuses with (#7780), so a refused uninstall changes nothing (#20492).` → `refuses a scope-less uninstall with (an uninstall across every organization must be declared, never inferred from a missing one), so a refused uninstall changes nothing.` | | `public-auth-features.ts:197` (`deviceAuthorization`) | 2513 (objectui), 2874 | objectui 2513: card body, closed completed 2026-07-15. DeviceAuthPage reads `features.deviceAuthorization` and shows a not-enabled state instead of calling the endpoints; the client type gained the key. 2874: card body. P2② is the login-surface audit of objectui's consumption | `Known gap: objectui DeviceAuthPage hits the device-auth endpoints without checking this flag (absent from its client type) — tracked in objectui#2513 (#2874 P2②).` → `Login consumption verified: objectui DeviceAuthPage reads this flag and, when it is off, says device authorization is not enabled instead of calling the device-auth endpoints.` (see Acceptance notes: the old text was stale) | | `public-auth-features.ts:220` (`phoneNumber`) | 2871 | 2871 (a PR): body. The create-user form follows the plugin: the `phoneNumber` param is visible only on `features.phoneNumber == true`, and default-loading the plugin was rejected | `The original #2871 fix.` → `The fix this registry generalizes: create-user's phone field follows the opt-in phoneNumber plugin instead of offering a field the backend refuses.` | | `public-auth-features.ts:230` (`phoneNumberOtp`) | 2780 | 2780: card body. SMS provider and messaging `sms` channel for phone OTP. `isPhoneOtpDeliverable()` (`auth-manager.ts:7735-7740`) answers false without an SMS service, and false for a log-only one in production | `when SMS is actually deliverable (#2780).` → `when an SMS service can actually deliver the code; a log-only transport in production keeps it off.` | Six of the longer reasons (`FLOW_INPUT_SCHEMA_INVALID`, `EXTERNAL_IMPORT_ERROR`, `UPLOAD_SESSION_EXPIRED`, `VALIDATION_FAILED`, `ANALYTICS_DATE_RANGE_UNRECOGNIZED`, `TENANT_SCOPE_REQUIRED`) were re-wrapped after the edit so no continuation line is ragged. A script compared each message's joined string value before and after the re-wrap: identical, read back from disk. Code spans never break across a line. ## Readers (A3) - **Programmatic readers check non-emptiness only.** - `StandardSynonymWaiverSchema` / `ProvenanceWaiverSchema` require `reason: z.string().min(1)`. `error-code-ledger.test.ts` and `check:error-code-provenance` parse every waiver through them. - `public-auth-features.test.ts:54` requires `exempt.reason.length > 0`. - Control: all of them ran green on the head. 45 of 45 in the two spec files. `check:error-code-provenance`: "OK — every registered-code stamp site is listed under its own owner key or carries a recorded waiver (10 waiver(s), all live)", 330 sites, 311 listed, 19 waived. - **Nothing reads the text.** - Every 32-character window of the 17 old messages that the new text no longer contains (1274 windows) was searched with `git grep -F` across the tree, the two edited files excluded. One hit: a `//` section heading in `packages/runtime/src/domains/packages-uninstall-refuse-before-mutate.test.ts:221`, not an assertion. - No `content/docs/**` page, `skills/**` file, snapshot or generated artifact quotes a changed message. - The `platform-objects` and `plugin-auth` registry guards read keys, `semantics` and `gatedInputs`, never `notes` or `reason`. ## Text only (A4) The earlier stages' `skeleton.cjs` is not on disk either, so I re-implemented it from its stated semantics, with TypeScript 6.0.3: - **Leg 1** is an AST skeleton. Every string's text is masked, and a `+` chain's adjacent string operands read as one string. Identifiers, numbers, regex literals, keywords and punctuation are kept, and comments are never read. - **Leg 2** is the text of every string group. Each changed group must carry a tracker id before and none after; every other group must be byte-identical. Base copies against the committed head `151a8ce4ed`: | file | skeleton tokens | string groups | changed | verdict | |:--|--:|--:|--:|:--| | `error-code-ledger.zod.ts` | 3184 | 429 | 14 | SAME, exit 0 | | `public-auth-features.ts` | 1639 | 112 | 3 | SAME, exit 0 | Parse diagnostics were 0 / 0. The 17 changed groups are the 17 census messages. **Controls**, on scratch copies of the head ledger. Each mutation was counted on disk: anchor hits 1, replacement present 1, anchor left 0, differs from head. | mutation | expected | got | |:--|:--|:--| | `standardSynonymViolations` renamed | DIFF | DIFF, exit 1 | | `===` flipped to `!==` in the waiver check | DIFF | DIFF, exit 1 | | one literal re-split into two `+` operands | SAME | SAME, exit 0, still 14 changed | | text changed in a `.describe()` string that never carried an id | VIOLATION | VIOLATION, exit 3 | | a rewritten reason given a tracker id back | VIOLATION | VIOLATION, exit 3 | No repo file was mutated for the controls. Edits were applied by a script whose 20 anchors each had to hit exactly once in the original text before any write. They were read back from disk after it: 20 gone, 20 replacements present once. **Census after:** class (f) **0 / 0**. Non-test went from 62 / 231 to 45 / 198, all of it `migrations/registry.ts`. That file is the lit control: the instrument still sees its ids, so the zero is not blindness. Test strings are unchanged at 1804 / 1920. The gate's per-literal leg agrees: 198 ids, all in `migrations/registry.ts`. ## Gates All heavy runs went through `scripts/pm/os-verify-lock.sh` (slot `dev-20749-s7`). Every exit code was written to a file before it was read. All runs are at `151a8ce4ed`. - `pnpm --filter @objectstack/spec build`: exit 0, "38/38 declared declaration file(s) present". - `pnpm --filter @objectstack/spec check:generated`: exit 0, "All 15 generated artifacts are up to date". - `pnpm --filter @objectstack/spec test`: "Test Files 608 passed (608) / Tests 18017 passed | 1 todo (18018)". - `pnpm --filter @objectstack/spec run typecheck`: exit 0, "check:test-typecheck: OK … 52 file(s) / 246 error(s) / 135 pinned signature(s) held". - The `repo` project's one file that reads a changed module (`scripts/file-description.test.ts`, which reads the ledger file's opening docblock): 111 of 111. - `pnpm turbo run build --concurrency=2` over every package: 71 / 71. This fed the dist-reading gates. On the first pass, before this build, three of them answered PREREQUISITE NOT MET (exit 3): `check:doc-formula-expressions`, `check:dual-build-cjs-loads` and `check:lean-entry-closure`. The other two (`check:dts-closure`, `check:sourcemap-no-sources-content`) had swept only 1 built package. All five were re-run after the build. - **Derived union.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) derived 82 commands for 3 paths vs merge base `045b94625`: 139 changed lines (+85 / -54), under 5000. All 82 exit 0 on the built tree. `--ran`: "82 derived famil(ies) accounted for — 82 run, 0 NOT-MEASURED (a DERIVED zero — all 82 recorded an exit code and none of them is 3)". - `check:doc-authoring`: "17351 customer-facing string(s) across 1255 spec sources clean" / "0 pinned site(s) … no growth, no burn-down unrecorded". - `check:nul-bytes`: "OK (scanned 9998 text file(s) …)". A self-scan of the added lines for raw control bytes found none. - `check:issue-citations`, `check:api-surface` and `check:error-code-provenance` all exit 0. - **NOT MEASURED** here, reason: the derivation names them as taking a value from the workflow (`check-issue-citations --census`, the shard attestations, `check-test-completeness`). They are CI's. - **ESLint, a proven narrowing.** `eslint --no-inline-config --format json` over the 2 changed TS files read 2 files, 0 errors, 0 warnings. Population comes from ESLint's own config: `calculateConfigForFile` returns a config for each, and `isPathIgnored` is false. Invariance: no type-aware linting (`parserOptions.project` / `projectService` are null for both), so a string-text edit cannot move an untouched file's verdict. Repo-wide `pnpm lint` is CI's. - The PR-body gates (`check-changeset-no-major --event`, `check-partof-closing-keyword`) were run against this exact title and body before it was sent; their lines are in the dev report. ## Acceptance notes - **One reason was stale, and the rewrite states the delivered decision.** The `deviceAuthorization` exemption said objectui's `DeviceAuthPage` called the device-auth endpoints without checking the flag, a gap "tracked in" objectui 2513. That card closed completed on 2026-07-15. At the `.objectui-sha` pin `89cad75d55`, `apps/console/src/pages/auth/DeviceAuthPage.tsx:66-130` reads `features.deviceAuthorization` and renders "Device authorization not enabled" when it is off, and `packages/auth/src/types.ts:286` declares the key. Stating that card's decision in words makes the text say the gap is closed. The registry's semantics, surface and keys are untouched. - **A cited card answers 404.** Card 11504 returns 404 from the issues endpoint. Its comments are still served (claim, dev report, ACCEPT), and its decision was read from them; it did not read as unclear. - **Main moved two commits after the base** (`a4f0cb0a45`, `0fc80878f8`: service-automation and lint, plus their changesets). Neither touches this PR's three paths or `packages/spec`, so main was not merged; the merge queue rebuilds onto current `main`. No open PR touches these paths: 12 open PRs read, file lists included. - Left for later stages, untouched here: `migrations/registry.ts` (45 / 198 at this base), the test strings (1804 / 1920), the two `.mjs` gate scripts, and every comment, including the docblocks and the `//` headings beside these tables. - Not governed: no `.claude/**`, `skills/**`, ADR, NORTH-STAR or AGENTS.md path. ## Line budget 139 changed lines (+85 / -54) over 3 files vs merge base `045b94625` (dispatch-gates): the ledger (+59 / -48), the auth registry (+9 / -6), and the changeset (+17). No generated file, no governed surface. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3222c57 commit 36e4647

3 files changed

Lines changed: 85 additions & 54 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
The error-code waiver reasons and the public auth-feature registry's notes no longer cite tracker numbers; each one states the decision behind it in words
6+
7+
Clause-②: no
8+
9+
Two registries in `@objectstack/spec` carry a written reason beside each entry. In `@objectstack/spec/api`, every `STANDARD_SYNONYM_WAIVERS` and `PROVENANCE_WAIVERS` entry records why a registered error code is kept or placed where it is. In `@objectstack/spec/kernel`, `PUBLIC_AUTH_FEATURES` records how each public auth flag is consumed. Seventeen of those reasons pointed at an issue-tracker number for the decision behind them. The number goes; where the sentence did not already say what was decided, it now does. For example:
10+
11+
- The five grandfathered synonyms (`CONFLICT`, `FORBIDDEN`, `INTERNAL`, `NOT_FOUND`, `UNAUTHORIZED`) say their consolidation onto the standard member is deferred until a code has a measured victim.
12+
- The `FLOW_DISABLED` waiver says every door that dispatches a flow answers from one status table. The `TENANT_SCOPE_REQUIRED` waiver says an uninstall across every organization must be declared, never inferred from a missing one.
13+
- The `phoneNumber` note names the fix the registry generalizes: create-user's phone field follows the opt-in phoneNumber plugin.
14+
15+
One reason also corrects a stale fact. The `deviceAuthorization` exemption described a known gap in objectui's `DeviceAuthPage`. That gap was closed in objectui on 2026-07-15: the page reads the flag and says device authorization is not enabled, rather than calling the device-auth endpoints. The text now says so.
16+
17+
Text only: no waiver's code, package, shadowed member or registration, no flag's surface, semantics or gated inputs, and no export, type, schema, order or count moves. Each reason still parses under its schema's non-empty rule. A tool that matches one of these reasons by its old text (for example by a tracker-number substring) needs the new spelling.

‎packages/spec/src/api/error-code-ledger.zod.ts‎

Lines changed: 59 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,36 +1615,39 @@ export const STANDARD_SYNONYM_WAIVERS: readonly StandardSynonymWaiver[] = [
16151615
{
16161616
code: 'CONFLICT',
16171617
shadows: 'RESOURCE_CONFLICT',
1618-
reason: 'Pre-gate synonym on the wire (respondSharingError 409 arm; registered by #8111). ' +
1619-
'Wire value kept; consolidation deferred per #8211.',
1618+
reason: 'Pre-gate synonym on the wire (respondSharingError 409 arm), registered as it stood ' +
1619+
'when the record-sharing errors moved onto the ADR-0112 envelope, so the wire stayed ' +
1620+
'byte-identical. Wire value kept; consolidation deferred until it has a measured victim.',
16201621
},
16211622
{
16221623
code: 'FORBIDDEN',
16231624
shadows: 'PERMISSION_DENIED',
16241625
reason: 'Pre-gate synonym on the wire from @objectstack/rest, plugin-sharing and ' +
1625-
'plugin-approvals; #13353 added the cloud-connection provenance row for the same ' +
1626-
'pre-existing wire value (its marketplace-install plugin-route 403). ' +
1627-
'Wire value kept; consolidation deferred per #8211.',
1626+
'plugin-approvals; cloud-connection lists the same pre-existing wire value under its own ' +
1627+
'provenance row (its marketplace-install plugin-route 403). ' +
1628+
'Wire value kept; consolidation deferred until it has a measured victim.',
16281629
},
16291630
{
16301631
code: 'INTERNAL',
16311632
shadows: 'INTERNAL_ERROR',
16321633
reason: 'Pre-gate synonym on the wire from five packages. Wire value kept; ' +
1633-
'consolidation deferred per #8211.',
1634+
'consolidation deferred until it has a measured victim.',
16341635
},
16351636
{
16361637
code: 'NOT_FOUND',
16371638
shadows: 'RESOURCE_NOT_FOUND',
16381639
reason: 'Pre-gate synonym on the wire from @objectstack/rest and plugin-sharing; ' +
1639-
'#19441 added the plugin-security provenance row for the same pre-existing wire value ' +
1640-
'(its permission-set overlay-discard 404). Wire value kept; consolidation deferred per #8211.',
1640+
'plugin-security lists the same pre-existing wire value under its own provenance row ' +
1641+
'(its permission-set overlay-discard 404). Wire value kept; consolidation deferred until ' +
1642+
'it has a measured victim.',
16411643
},
16421644
{
16431645
code: 'UNAUTHORIZED',
16441646
shadows: 'UNAUTHENTICATED',
16451647
reason: 'Pre-gate synonym (401 reason phrase) on the wire from @objectstack/rest — ' +
1646-
'surfaced by the detector when the #8211 gate landed, beyond the four the card named; ' +
1647-
'same class, same grandfather rationale. Wire value kept; consolidation deferred per #8211.',
1648+
'surfaced by the synonym detector when it landed, beyond the four first reported; ' +
1649+
'same class, same grandfather rationale. Wire value kept; consolidation deferred until ' +
1650+
'it has a measured victim.',
16481651
},
16491652
];
16501653

@@ -1749,52 +1752,57 @@ export const PROVENANCE_WAIVERS: readonly ProvenanceWaiver[] = [
17491752
reason: 'Shared constructor one package over: metadata-core\'s ' +
17501753
'`engineUpdateDispatchRejectError` spells the string, but the throw ships in ' +
17511754
'production from `ObjectQL.update` (engine.ts) — the objectql row\'s own comment ' +
1752-
'records "hence registered here" (#11142/#11230).',
1755+
'records "hence registered here".',
17531756
},
17541757
{
17551758
package: '@objectstack/service-automation',
17561759
code: 'FLOW_DISABLED',
17571760
registeredUnder: '@objectstack/runtime',
17581761
reason: 'The trigger door, not the producer, names the wire vocabulary: the engine ' +
1759-
'returns `AutomationResult.code` and runtime\'s doors read it and answer 409 ' +
1760-
'(#9415/#9446; the runtime row\'s comment records the decision).',
1762+
'returns `AutomationResult.code` and runtime\'s doors read it and answer 409 — every ' +
1763+
'door that dispatches a flow answers from one status table, by ruling (the runtime ' +
1764+
'row\'s comment records the decision).',
17611765
},
17621766
{
17631767
package: '@objectstack/service-automation',
17641768
code: 'FLOW_NO_START_NODE',
17651769
registeredUnder: '@objectstack/runtime',
1766-
reason: 'Same decision as FLOW_DISABLED, 422 arm (#9415/#9446): the trigger door ' +
1770+
reason: 'Same decision as FLOW_DISABLED, its 422 arm: the trigger door ' +
17671771
'names the wire vocabulary; the engine result carries the classification.',
17681772
},
17691773
{
17701774
package: '@objectstack/service-automation',
17711775
code: 'FLOW_INPUT_SCHEMA_INVALID',
17721776
registeredUnder: '@objectstack/runtime',
1773-
reason: 'Registered ahead of its producer by design (#10025 → #11504, the #10413 → ' +
1774-
'#10576 split shape): the engine\'s `execute()` catch classifies the refusal, the ' +
1775-
'trigger door serves it — the runtime row\'s comment records "registered HERE and ' +
1776-
'not under the engine\'s package" with its three FLOW_* siblings.',
1777+
reason: 'Registered ahead of its producer by design: the ruling that a definition-level ' +
1778+
'input-schema refusal is non-retryable and never dispatched split into a contract ' +
1779+
'half, which minted this code, and a services half that emits it, the contract half ' +
1780+
'landing first. The engine\'s `execute()` catch classifies the refusal, the trigger ' +
1781+
'door serves it — the runtime row\'s comment records "registered HERE and not under ' +
1782+
'the engine\'s package" with its three FLOW_* siblings.',
17771783
},
17781784
{
17791785
package: '@objectstack/service-datasource',
17801786
code: 'EXTERNAL_IMPORT_ERROR',
17811787
registeredUnder: '@objectstack/rest',
1782-
reason: 'Adjudicated on #13353: the only door for `importObject` is rest\'s ' +
1783-
'`POST …/tables/:remote/import` (external-datasource-routes.ts), whose catch stamps ' +
1784-
'this code itself for EVERY importObject throw and never reads the producer\'s ' +
1785-
'declaration — the door names the wire vocabulary. The producer\'s `err.code` ' +
1786-
'(`importNameRefusedError`) is the #8016 declaration shape, agreeing with the door ' +
1787-
'by construction, not a second wire emitter.',
1788+
reason: 'Adjudicated when the provenance gate landed: the only door for `importObject` ' +
1789+
'is rest\'s `POST …/tables/:remote/import` (external-datasource-routes.ts), whose ' +
1790+
'catch stamps this code itself for EVERY importObject throw and never reads the ' +
1791+
'producer\'s declaration — the door names the wire vocabulary. The producer\'s ' +
1792+
'`err.code` (`importNameRefusedError`) declares its own `status` and `code`, the ' +
1793+
'shape the shared thrown-error resolver honours, agreeing with the door by ' +
1794+
'construction, not a second wire emitter.',
17881795
},
17891796
{
17901797
package: '@objectstack/client',
17911798
code: 'UPLOAD_SESSION_EXPIRED',
17921799
registeredUnder: '@objectstack/service-storage',
1793-
reason: 'Client-side synthesis (#7870): `resumeUpload` mirrors the server\'s 410 pair ' +
1794-
'when the progress poll reports `expired`, so caller branches fire identically. The ' +
1795-
'ledger\'s scope prose covers the SERVING side; whether a client-synthesised code ' +
1796-
'belongs in the ledger at all is the open scope question #13353 recorded — ' +
1797-
'deliberately a waiver, not a row, until that question is ruled.',
1800+
reason: 'Client-side synthesis: `resumeUpload` mirrors the server\'s 410 pair when the ' +
1801+
'progress poll reports `expired`, so caller branches fire identically. The ledger\'s ' +
1802+
'scope prose covers the SERVING side; whether a client-synthesised code belongs in ' +
1803+
'the ledger at all is an open scope question, recorded when the provenance gate ' +
1804+
'landed and left unruled for want of pull — deliberately a waiver, not a row, until ' +
1805+
'that question is ruled.',
17981806
},
17991807
{
18001808
package: '@objectstack/spec',
@@ -1809,35 +1817,38 @@ export const PROVENANCE_WAIVERS: readonly ProvenanceWaiver[] = [
18091817
package: '@objectstack/types',
18101818
code: 'VALIDATION_FAILED',
18111819
registeredUnder: '@objectstack/runtime',
1812-
reason: 'Shared constructor by design (#8016/#3918): `validationFailure()` lives in ' +
1813-
'the dependency-light package so BOTH doors recognise one shape; the throws are ' +
1814-
'served under the emitting doors\' own registrations (runtime\'s dispatcher exits, ' +
1815-
'rest\'s `mapDataError` — both packages list the code).',
1820+
reason: 'Shared constructor by design: `validationFailure()` lives in the ' +
1821+
'dependency-light package beside the one thrown-error mapping both doors share, so ' +
1822+
'BOTH doors recognise one shape and answer it 400 with its `fields[]`, never 500; the ' +
1823+
'throws are served under the emitting doors\' own registrations (runtime\'s ' +
1824+
'dispatcher exits, rest\'s `mapDataError` — both packages list the code).',
18161825
},
18171826
{
18181827
package: '@objectstack/core',
18191828
code: 'ANALYTICS_DATE_RANGE_UNRECOGNIZED',
18201829
registeredUnder: '@objectstack/runtime',
1821-
reason: 'Shared constructor one package over, the #8016 shape (#16322): ' +
1822-
'`analyticsDateRangeUnrecognizedError` (utils/analytics-date-range.ts) spells the ' +
1823-
'string ONCE so driver-memory\'s cube face and BOTH service-analytics strategies ' +
1824-
'refuse identically — which is the property the card\'s shared conformance fixture ' +
1825-
'exists to hold, and which two independent refusals could not give. Core ships no ' +
1826-
'HTTP door; the wire emission stays runtime\'s, whose row names this exact second ' +
1827-
'moment. ⛔ Deliberately ONE waiver rather than a row per driver: with one ' +
1828-
'constructor there is one stamp site, and rows for packages that stamp nothing ' +
1829-
'would be the dead weight this file\'s gate refuses.',
1830+
reason: 'Shared constructor one package over: `analyticsDateRangeUnrecognizedError` ' +
1831+
'(utils/analytics-date-range.ts) spells the string ONCE so driver-memory\'s cube face ' +
1832+
'and BOTH service-analytics strategies refuse an unrecognised `dateRange` identically ' +
1833+
'— which is the property the shared date-range conformance fixture exists to hold, ' +
1834+
'and which two independent refusals could not give. Core ships no HTTP door; the wire ' +
1835+
'emission stays runtime\'s, whose row names this exact second moment. ⛔ Deliberately ' +
1836+
'ONE waiver rather than a row per driver: with one constructor there is one stamp ' +
1837+
'site, and rows for packages that stamp nothing would be the dead weight this file\'s ' +
1838+
'gate refuses.',
18301839
},
18311840
{
18321841
package: '@objectstack/runtime',
18331842
code: 'TENANT_SCOPE_REQUIRED',
18341843
registeredUnder: '@objectstack/metadata-protocol',
18351844
reason: 'The door mirrors the producer\'s refusal; it is not a second emitter. ' +
1836-
'`DELETE /packages/:id` (domains/packages.ts, `requireUninstallOrganizationScope`) asks ' +
1837-
'`deletePackage`\'s organization-scope question BEFORE `registry.uninstallPackage`, and ' +
1838-
'answers with the code `deletePackage` refuses with (#7780), so a refused uninstall ' +
1839-
'changes nothing (#20492). The door never sends `allTenants`, so its condition is exactly ' +
1840-
'the producer\'s "no organization"; the protocol keeps its own refusal as the second line ' +
1841-
'and stays the registered emitter.',
1845+
'`DELETE /packages/:id` (domains/packages.ts, `requireUninstallOrganizationScope`) ' +
1846+
'asks `deletePackage`\'s organization-scope question BEFORE ' +
1847+
'`registry.uninstallPackage`, and answers with the code `deletePackage` refuses a ' +
1848+
'scope-less uninstall with (an uninstall across every organization must be declared, ' +
1849+
'never inferred from a missing one), so a refused uninstall changes nothing. The door ' +
1850+
'never sends `allTenants`, so its condition is exactly the producer\'s "no ' +
1851+
'organization"; the protocol keeps its own refusal as the second line and stays the ' +
1852+
'registered emitter.',
18421853
},
18431854
];

‎packages/spec/src/kernel/public-auth-features.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,10 @@ export const PUBLIC_AUTH_FEATURES = {
191191
semantics: 'opt-in',
192192
exempt: {
193193
reason:
194-
'No spec input (sys_device_code declares no actions). Known gap: ' +
195-
'objectui DeviceAuthPage hits the device-auth endpoints without ' +
196-
'checking this flag (absent from its client type) — tracked in ' +
197-
'objectui#2513 (#2874 P2②).',
194+
'No spec input (sys_device_code declares no actions). Login ' +
195+
'consumption verified: objectui DeviceAuthPage reads this flag and, ' +
196+
'when it is off, says device authorization is not enabled instead ' +
197+
'of calling the device-auth endpoints.',
198198
},
199199
},
200200
admin: {
@@ -217,7 +217,9 @@ export const PUBLIC_AUTH_FEATURES = {
217217
semantics: 'opt-in',
218218
gatedInputs: ['sys_user.actions.create_user.params.phoneNumber'],
219219
notes:
220-
'The original #2871 fix. Also read by objectui LoginForm for the ' +
220+
'The fix this registry generalizes: create-user\'s phone field ' +
221+
'follows the opt-in phoneNumber plugin instead of offering a field ' +
222+
'the backend refuses. Also read by objectui LoginForm for the ' +
221223
'phone+password sign-in mode.',
222224
},
223225
phoneNumberOtp: {
@@ -227,7 +229,8 @@ export const PUBLIC_AUTH_FEATURES = {
227229
reason:
228230
'Login-surface only: gates the "sign in with verification code" link ' +
229231
'(LoginForm) and the phone branch of forgot-password. Only advertised ' +
230-
'when SMS is actually deliverable (#2780).',
232+
'when an SMS service can actually deliver the code; a log-only ' +
233+
'transport in production keeps it off.',
231234
},
232235
},
233236
} as const satisfies Record<string, PublicAuthFeatureEntry>;

0 commit comments

Comments
 (0)