Repository navigation
Commit f7cd580
Part of #23587
Clause-②: no
This lands the card's three structured fields on both Hono doors, under
one rule that covers every arm of `RestServer`'s classification. It also
corrects the published error pages this answer changes (patch rounds 1
and 2, the seat's reviews `6185187566` and `6186145904`). What stays on
the card is one location, folded there as its remainder by the seat's
pointer `6185197160`: `declaredEnvelopeForThrow` answers the engine's
`DuplicateRecordError` as `409 DUPLICATE_RECORD` on both Hono doors,
where `RestServer` answers `UNIQUE_VIOLATION` with `developerMessage`,
`field` and `object`. The governing text is the maintainer ruling
recorded at commit `65846bc46`: one wire spelling on every route. This
PR's agreement rule leaves that code alone on purpose.
## What this does
The engine refuses the delete of a record that a `deleteBehavior:
'restrict'` lookup still references with `409 DELETE_RESTRICTED`.
`RestServer`'s single-record `DELETE` answers it through `mapDataError`,
whose first step is `structuredCodeAnswer` (`@objectstack/types`). That
arm carries the engine's `developerMessage`, `dependentObject`,
`dependentCount` and the route's `object` beside the code. Both Hono
doors answered the same throw with `code` and `message` only. They are
`@objectstack/hono`'s `createHonoApp` (its `dispatch()` catch-all,
through `thrownJson`) and a route mounted on `HonoHttpServer` (`wrap()`,
and the transport `onError` seam). All of them render through
`declaredEnvelopeForThrow` in
`packages/plugins/plugin-hono-server/src/adapter.ts`.
Measured on a real `createHonoApp` over the real engine and protocol
(SQLite WASM), at `16abea884`:
- FROM `409
{"success":false,"error":{"code":"DELETE_RESTRICTED","message":"This
Parent is still referenced by 1 Child record(s) through “Parent”. Delete
or reassign them first."}}`
- TO the same, plus `"details":{"developerMessage":"Cannot delete parent
(ID): 1 dependent child record(s) reference it via parent. Delete or
reassign them first, or set deleteBehavior:'cascade' on
child.parent.","dependentObject":"child","dependentCount":1}`
PR #23582 added a helper beside the recogniser that read the arm for
`CONCURRENT_UPDATE` only. This PR widens it from one code to every arm
that agrees with the envelope already decided, and renames it from
`declaredConflictContext` to `declaredArmContext`, since it no longer
reads only conflicts:
- **The arm must agree on status and code.** It can add context to the
envelope, never change it. An arm that answers a different code is not
consulted.
- **Everything the arm's body carries is forwarded except three keys:**
its own sentence and code (the envelope already carries them as
`message` and `code`), and `currentRecord`, which stays held by name
(#23586, unchanged from PR #23582).
- ⛔ No field and no code is listed. Each field appears only when the
producer stated it, by the arm's own rule, so a producer's narrowing
passes through as it stands. The engine withholds `dependentCount` when
the caller's own identity would not see the same rows
(`dependentCountIsDisclosable`), and then the door sends no count
either.
- **Where they land:** `error.details`, as PR #23582 placed
`currentVersion`. On the REST wire every arm field is a top-level member
of the flat body, and that body has no `details`. On the wrapped
envelope the only declared context slot is `ApiErrorSchema.details`,
since `developerMessage`, `dependentObject` and `dependentCount` are not
`ApiErrorSchema` members. `@objectstack/client` reads `error.details =
errorBody.details ?? errorBody.error.details ?? errorBody`
(`packages/client/src/index.ts`), so a client sees the same keys under
`err.details` from either door. ⛔ No third spelling of the refusal body.
Status, `code` and `message` do not move on any answer. The REST door's
answer is unchanged. `@objectstack/hono`'s source is unchanged too:
`createHonoApp` renders through `declaredEnvelopeForThrow`.
## Every arm, checked
Each arm of `structuredCodeAnswer` is listed below, with what
`RestServer` sends and what the Hono doors send before and after.
Readings marked "measured" come from a real `createHonoApp` at
`16abea884` (before) and `d1a39b380` (after). The rest come from the
producer-shaped rows in the unit file and the code read.
| code (status) | `RestServer` (`mapDataError`) | Hono doors before |
Hono doors after | not forwarded, and why |
|---|---|---|---|---|
| `DELETE_RESTRICTED` (409) | `developerMessage`, `dependentObject`,
`dependentCount`, `object` | none (measured) | `developerMessage`,
`dependentObject`, `dependentCount` (measured) | `object`: the arm takes
it from the route only |
| `CONCURRENT_UPDATE` (409) | `currentVersion`, `currentRecord`,
`object` | `currentVersion` | `currentVersion` (unchanged) |
`currentRecord`: held, #23586. `object`: route only |
| `METADATA_CONFLICT` (409) | `currentVersion`, `object` | none |
`currentVersion`, on a route mounted on `HonoHttpServer` | `object`:
route only. On `createHonoApp` the `/meta` save is answered by the
dispatcher's own `errorFromThrown`, so it never reaches the recogniser.
Measured unchanged there; see Acceptance notes (#22222) |
| `INVALID_FIELD` (400) | `field`, `object` | none (measured) | `field`,
`object` (measured) | none |
| `OBJECT_NOT_FOUND` (404) | `object` | none (measured) | `object`
(measured) | none. The arm's fixed sentence is not taken: the envelope
keeps the thrown `message` |
| `FEEDS_DISABLED`, `FILES_DISABLED`, `ATTACHMENT_PARENT_ACCESS`,
`ATTACHMENT_DELETE_DENIED`, `RECORD_NOT_ACCESSIBLE` (403) | `object` |
none | `object` | none |
| `VALIDATION_FAILED` (400) | `fields`, `object` | `fields` (the
resolver's) | `fields`, the same array | `object`: route only.
`validationFailureDetails` and the arm read `fields` alike, so the merge
is byte-identical |
| `DUPLICATE_RECORD`, answered by REST as `UNIQUE_VIOLATION` (409) |
`developerMessage`, `field`, `object` | none, code `DUPLICATE_RECORD`
(measured) | unchanged (measured) | the arm answers a different code, so
it is not consulted. This is the card's remainder (opening paragraph) |
| `ERR_DATASOURCE_UNAVAILABLE` (503) | `datasource`, `reason`, `object`
| the recogniser declines | unchanged | its one producer
(`DatasourceUnavailableError`, `@objectstack/objectql`) declares no
`status`, so the recogniser never answers it |
| `OBJECT_API_DISABLED`, `OBJECT_API_METHOD_NOT_ALLOWED` | `object`,
`allowed` | none | unchanged | the arm fires only when it is given a
route object that differs from the error's own, and the recogniser gives
none |
**System-context reads (#23586's question, asked of every arm).**
`currentRecord` is the only field read with a system context and left
unnarrowed. Every other field is one of these:
- a version token (`currentVersion`);
- an API name (`object`, `dependentObject`, `field`);
- the caller's own input;
- a sentence that names the same API names (`developerMessage`).
`DELETE_RESTRICTED`'s dependents probe is elevated. But the engine
discloses the count, in `dependentCount` and in `developerMessage`, only
when the caller's own identity would have read the same rows
(`dependentCountIsDisclosable`, `@objectstack/objectql`). It names the
dependent object unconditionally, by that ruling's constraint 2.
`RestServer` already sends all of it to the same caller. So
`currentRecord` stays the one exclusion, by name. The helper's docblock
says that an arm which grows such a field must be held there by name in
the same way.
**Sandboxed producers.** A `SandboxError` carries only `code`, `fields`,
`status`, `userMessage` and `innerMessage`
(`runtime/src/sandbox/quickjs-runner.ts`). So no arm adds a field from
one. `VALIDATION_FAILED`'s `fields` is the resolver's own array, and
`INVALID_FIELD` and `OBJECT_NOT_FOUND` decline a sandbox origin
themselves.
## What stays off these doors
- **`object` on `DELETE_RESTRICTED`, `CONCURRENT_UPDATE` and
`METADATA_CONFLICT`.** Those arms add the object the ROUTE named
(`mapDataError(error, req.params.object)`). `declaredEnvelopeForThrow`
has no route, so it asks the arm without one, as PR #23582 recorded for
`CONCURRENT_UPDATE`. The caller named that object in its own URL. ⛔ It
is not substituted from the error's own `object`. The engine's cascade
recurses through the public `delete(childName, …)`, so a restrict deeper
in a cascade names the child it was deleting, not the object the route
addressed, and REST would answer differently. **Decided** by the seat
(review `6185187566`, option A): the field is not owed at these doors.
This follows the family precedent, PR #23582, which landed
`CONCURRENT_UPDATE` without it, and #23531's Landed comment
`6183626856`, which names `currentRecord` as that card's only remainder.
- **`currentRecord`:** held, #23586, as before. The two absence pins
from PR #23582 are unchanged and green.
## Tests
- **Pins.** Both files keep their existing controls, and the #23531
blocks are unchanged.
-
`packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts`,
the new `[#23587]` block, 14 rows on direct-mount routes:
- `DELETE_RESTRICTED` with the count, with the count withheld, and in
the subflow guard's shape, which states no field and keeps the two-key
envelope;
- `METADATA_CONFLICT` with a token and with `null`;
- `INVALID_FIELD`, `OBJECT_NOT_FOUND`, and the five 403 access codes;
- two guard rows: an arm whose status, or whose code, differs from the
declared one is not consulted.
- Each refusal row asserts status, `code` and `error.details` exactly.
-
`packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts`,
the new `[#23587]` block, 3 rows on a real `createHonoApp`:
- a restricted `DELETE` (account and contact, `restrict`):
`error.details` equals the `developerMessage`, `dependentObject` and
`dependentCount` of the throw the engine really raised, read off the
`deleteData` call. The row also asserts that throw stated a string
`developerMessage`, so the equality cannot hold between two absences,
and that the parent row is still stored;
- `GET /data/task?nosuchfield=1`: `400 INVALID_FIELD` with `{ field,
object }`;
- `GET /data/ghost`: `404 OBJECT_NOT_FOUND` with `{ object }`.
- **Red before the fix** (pins only, `90574de90`, on the unfixed
recogniser): unit `Tests 11 failed | 26 passed (37)`, conformance `Tests
3 failed | 15 passed (18)`. Every red row failed at its `error.details`
assertion with `expected undefined to deeply equal …`. The three guard
rows pass on the unfixed tree by construction; they are the regression
half.
- **Ablation**, on `d1a39b380`. Two legs, each run with `node
scripts/ablation-replace.mjs` in wrap mode, with an outer `trap … EXIT
INT TERM` restore against the absolute path. Each leg rebuilt
`@objectstack/plugin-hono-server`, and `ablation-dist-preflight.mjs`
found the marker in `dist/index.js` and `dist/index.mjs` before either
suite ran.
- **Leg A** narrowed the helper back to one code. The anchor ``const arm
= structuredCodeAnswer(thrown);`` became a read guarded on
`resolved.code === 'CONCURRENT_UPDATE'`, carrying the marker
`ABLATION_23587_ONE_CODE` (blob `7fd1f090b0` to `11a3ca30af`). Every new
`[#23587]` refusal row went RED, and the `[#23531]` rows stayed green:
unit `Tests 11 failed | 26 passed (37)`, conformance `Tests 3 failed |
15 passed (18)`, each at `expected undefined to deeply equal …`.
- **Leg B** dropped the held field. The anchor ``&& key !==
'currentRecord'),`` became a key no body carries,
`ABLATION_23587_HELD_FIELD` (blob to `1f5ac3cbb6`). Exactly the four
`currentRecord` absence pins went RED: unit `Tests 2 failed | 35 passed
(37)`, `expected { …(2) } to deeply equal { Object (currentVersion) }`;
conformance `Tests 2 failed | 16 passed (18)`, `to not have property
"currentRecord"`. The conformance body then carried the system-context
row the real protocol states (`organization_id`, `owner_id`,
`created_by` and the rest). That is the disclosure the exclusion holds
back.
- **Restore:** the blob equals HEAD (`7fd1f090b0`), `git diff HEAD` is
empty and `git status --porcelain` is clean. After a rebuild, preflight
`--absent` found both markers gone from all 6 built files, with a clean
tree. Both pin files were green again: unit `37 passed (37)`,
conformance `18 passed (18)`.
- **Packages**, at `d1a39b380`, after a rebuild of
`@objectstack/plugin-hono-server` (the conformance file reads its
`dist/`):
- `pnpm --filter @objectstack/plugin-hono-server typecheck` (tsc,
`tsconfig.typecheck.json`, `check:test-typecheck` OK, 0 debt) and
`test`: `Test Files 29 passed (29)`, `Tests 354 passed (354)`.
- `pnpm --filter @objectstack/http-conformance typecheck`
(`check:test-typecheck` OK, ledger unchanged) and `test`: `Test Files 13
passed (13)`, `Tests 142 passed (142)`.
- **Patch rounds 1 and 2 (docs only, heads `83c418436` and
`9675127f3`).** No package file changed since `d1a39b380`, so the
package suites above stand. The docs readings were taken with scratch
files that were not committed: R1 on the `createHonoApp` rig, and R2 on
one kernel, comparing a composed host (`@objectstack/rest`'s routes
registered, then `createHonoApp` mounted) with `createHonoApp` alone.
See "Docs corrected (patch rounds 1 and 2)".
- `@objectstack/hono`'s own suite is not run locally: its source is
untouched, and its vitest config stubs `@objectstack/runtime`. The
composed-host pins above cover its door. It is declared to CI.
## Gates
Local, at the final head `9675127f3`:
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, run with no paths, derived **95** commands
from the 7 changed paths (merge base `16abea884`); the 3 docs pages add
29 families to round 0's 66. All 95 ran, each exit code captured before
any pipe.
- 93 exited 0 on the first pass.
- `pnpm --filter @objectstack/spec run check:skill-examples` and `pnpm
check:dual-build-cjs-loads` first exited 3, `PREREQUISITE NOT MET`: the
recreated worktree had no `packages/client-react/dist` and no workspace
`dist/`.
- Under the lock, after a cache-backed `pnpm turbo run build
--concurrency=2` (`76 successful`, `75 cached`), both measured exit 0:
`263 prose examples type-check across 3 surface(s)` and `110 published
require entry point(s) across 68 package(s) load`.
- `--ran` reconciliation: `95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN`
(a derived zero).
- The docs gates on the touched pages, among them, all exit 0:
`check:doc-anchors`, `@objectstack/spec check:docs`,
`check:docs-transcript-drift`, `check:corpus-claim-drift`,
`check:docs-redirects`, `check:docs-single-h1`,
`check:docs-spec-enumerations`, `check:doc-frontmatter`,
`check:docs-section-name` and `check:section-landing-index`.
- `pnpm check:route-envelope` and `pnpm
check:dispatcher-error-vocabulary` (derived), and `pnpm
check:durability-log-level` (run separately): exit 0.
- The four artifact-roster gates under touched directories, all exit 0:
`node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`,
`pnpm check:error-code-casing`, `pnpm check:filter-alias-parity`.
- ⚠️ The derivation reports a stale tree: this branch is 16 commits
behind `origin/main` (`7c6b7f4a3`), and 11 gate files changed across
that range. So the local run read this branch's copies of those scripts.
None of those commits touches the 7 changed paths, and `git merge-tree
--write-tree HEAD origin/main` is clean, so the branch is not merged
(after a PR opens, `main` is merged only on a conflict, a main-caused
red or a queue removal). CI runs the merge ref, which carries the
current scripts.
- CI on `9675127f3`, read once: 41 check-runs, 36 success, 5 skipped,
none failed or in progress.
- Round 1 (`83c418436`) gave the same 95-family result. Round 0 (code
head `d1a39b380`): 66 derived, 66 run, 0 NOT-MEASURED; `eslint
--no-inline-config` over the two touched packages exit 0. The patch
rounds change no lintable source.
## Changeset
`.changeset/23587-hono-structured-arm-fields.md`:
`@objectstack/plugin-hono-server` `patch`. `@objectstack/hono` is not
named, because its source does not change, and both packages are in one
fixed version group. The body gives FROM and TO for `DELETE_RESTRICTED`,
names the other arms the rule now covers, and says which two fields stay
off and why.
## Docs corrected (patch rounds 1 and 2)
Head `9675127f3`: round 1's commit `83c418436`, then round 2's
`9675127f3`, which scopes the sentences that assumed every
`createHonoApp` host serves `/data` through the dispatcher (the seat's
review `6186145904`). Line numbers are given on that head, with the
`origin/main` lines in brackets; those three files are unchanged on
`main` since the merge base. Readings:
- **R1:** a real `createHonoApp` host alone, over the real engine and
protocol (SQLite WASM), at `d1a39b380`, the code these rounds do not
change. A scratch file, not committed, with `RestServer`'s body for each
request computed from the same thrown error by `mapDataError`.
- **R2:** one kernel (engine, `HonoServerPlugin`, `createRestApiPlugin`)
at `9675127f3`. Two hosts were measured:
- the hosted composition: `@objectstack/rest`'s routes registered on the
Hono app, then `createHonoApp` mounted on it with `route('/', …)` (the
`ALL /api/v1/*` catch-all present);
- `createHonoApp` alone, over the same kernel.
A scratch file, not committed.
- **U:** the `[#23587]` unit pins on a `HonoHttpServer` route.
- **6110136480:** the maintainer's hosted measurement on #22222, for
`/meta` on the hosted composition (flat, from the REST door).
R1 measured, per code, on the Hono door and then the REST twin:
- `DELETE_RESTRICTED`: nested, `details` `{developerMessage,
dependentObject, dependentCount}` / flat with those three and `object`.
- `CONCURRENT_UPDATE`: nested, `details` `{currentVersion}` / flat with
`currentVersion`, `currentRecord`, `object`.
- `INVALID_FIELD`: nested, `{field, object}` / flat with both.
- `OBJECT_NOT_FOUND`: nested, `{object}`, message `Object 'ghost' not
found` / flat, `error` `Object 'ghost' is not registered`.
- `VALIDATION_FAILED` (a missing required field): nested, `{fields}` /
flat with `fields` and `object`.
- A duplicate insert: nested `DUPLICATE_RECORD`, no `details` / flat
`UNIQUE_VIOLATION` with `developerMessage`, `field`, `object`.
- None of the six Hono bodies carries `httpStatus`.
R2 measured, on the hosted composition and then on `createHonoApp`
alone:
- the same restricted `DELETE /api/v1/data/account/ID`: `409` **flat**
`{error, code: DELETE_RESTRICTED, developerMessage, dependentObject,
dependentCount: 1, object: "account"}` / `409` **nested** with those
three under `error.details`;
- `GET /api/v1/data/account?nosuchfield=1`: `400` **flat**
`INVALID_FIELD` with `field` and `object` / `400` **nested** with
`{field, object}` under `error.details`.
`content/docs/protocol/kernel/error-handling.mdx`:
| Lines (`main`) | Change | Reading |
|---|---|---|
| `:75`-`:77` (`:75`-`:78`) | Corollary 1 keeps `UNIQUE_VIOLATION`
"always flat" and drops `DELETE_RESTRICTED` / `INVALID_FIELD` from it |
R1 (`UNIQUE_VIOLATION` answered by no Hono door) |
| `:81`-`:84` (`:82`) | Corollary 2 gains `DELETE_RESTRICTED`,
`INVALID_FIELD`, `OBJECT_NOT_FOUND`: flat on the `@objectstack/rest`
`/data` door, nested on a Hono door with their fields under
`error.details` | R1, R2 |
| `:107` (`:105`) | "two writers" → "three writers" | R1 |
| `:113`-`:114` (new) | the third writer, `declaredEnvelopeForThrow`,
the Hono doors' renderer | R1, U |
| `:133` (`:129`) | `error.httpStatus`: the Hono doors' renderer never
writes it | R1 (0 of 6 bodies) |
| `:134`-`:137` (`:130`-`:132`) | `error.details`: "and nowhere else"
removed; the Hono doors put the per-condition siblings there | R1, U |
| `:183` (`:178`) | the `{base}/data/*` row is scoped to the
`@objectstack/rest` door | R1, R2 |
| `:193` (new; round 2 scoped) | route-family row: a declared refusal
thrown out of the dispatcher on a `createHonoApp` host, with `/data`
included only where no `@objectstack/rest` route is registered ahead of
its catch-all; or out of a `HonoHttpServer` route. Nested | R1, R2, U |
| `:196`-`:206` (`:190`-`:194`; round 2) | the split-by-door callout now
covers `/data` as well as `/meta`. A `createHonoApp` host can be either:
REST routes registered before the mount (the hosted composition) answer
flat on the REST door; with none ahead of the catch-all, `/data` and
`/meta` answer through the dispatcher, nested | R2 (`/data`), 6110136480
(`/meta`) |
| `:218`-`:261` (new; round 2 scoped) | `### Hono doors`: who renders
(the `/data` refusals leave the dispatcher only on a host whose
catch-all serves `/data`, with a pointer to the table), the
`DELETE_RESTRICTED` example (captioned "whose catch-all serves
`/data`"), the per-code `details` table, and what a Hono door never
sends (`httpStatus`, `currentRecord`, a route-taken `object`; the thrown
`message`) | The example and the `DELETE_RESTRICTED`,
`CONCURRENT_UPDATE`, `INVALID_FIELD`, `OBJECT_NOT_FOUND`,
`VALIDATION_FAILED` rows: R1, R2. The five 403 rows and
"`dependentCount` when the flat door would send it": U |
| `:406`-`:407` (`:350`) | `VALIDATION_FAILED` envelope: flat on the
rest door, nested on a Hono door (in-place, below) | R1 |
| `:409`-`:410` (`:352`-`:353`) | `VALIDATION_FAILED`: `fields` is
`error.details.fields` on a Hono door (in-place) | R1 |
| `:598`-`:600` (`:541`-`:542`) | `INVALID_FIELD` envelope: nested on a
Hono door, `error.details.field` / `.object` | R1, R2 |
| `:655`-`:656` (`:597`) | `OBJECT_NOT_FOUND`: nested on a Hono door,
`object` in `error.details` | R1 |
| `:695`-`:697` (`:636`-`:637`) | `DELETE_RESTRICTED` envelope: nested
on a Hono door, the three fields in `error.details`, no `object` | R1,
R2 |
| `:1052`-`:1054` (`:992`-`:993`) | `readError` reads
`err.details?.fields` on a nested body (in-place) | R1 |
| `:1103`-`:1105` (`:1042`-`:1043`) | field-validation guidance:
`error.details.fields` on a Hono door; "no `details.fields` bag on
either envelope" removed (in-place) | R1 |
`content/docs/api/wire-format.mdx`:
| Lines (`main`) | Change | Reading |
|---|---|---|
| `:328` (`:328`; round 2 scoped) | "Data route errors" scoped to the
kernel REST server. A `createHonoApp` host answers nested when its
catch-all serves `/data`; one that registers the REST routes before it
mounts `createHonoApp` (the hosted composition) answers flat | R1, R2 |
| `:485`-`:507` (new; round 2 caption) | `### Hono hosts nest the
extras`: nested, no `httpStatus`; the classification's extras in
`error.details`, with `currentRecord` and a route-taken `object` not
sent; the `CONCURRENT_UPDATE` example, on a host "whose catch-all serves
`/data`" | R1 |
`content/docs/api/index.mdx`:
| Lines (`main`) | Change | Reading |
|---|---|---|
| `:221`-`:222` (new; round 2 scoped) | On a `createHonoApp` host or a
`HonoHttpServer` route, a declared refusal is nested **without**
`httpStatus`, with the classification's fields in `details` except
`currentRecord` and a route-taken `object`. The `/data` refusals are
among them only where the catch-all serves `/data`; the hosted
composition answers those flat | R1, R2 |
**The bounded in-place rule, for `VALIDATION_FAILED`.** This PR leaves
that code's Hono-door body byte-identical. Four lines were false in the
same way and are corrected: `:406`-`:407`, `:409`-`:410`,
`:1052`-`:1054` and `:1103`-`:1105`. Each condition was checked:
1. **Same defect class:** a published statement that a refusal is never
nested, or has no `details`, which is false at the Hono doors.
2. **Mechanical, with the shape pinned:** R1 measured
`error.details.fields`, and `handler-throw-declared-envelope.test.ts`
already pins it on a direct-mount route.
3. **No other claim holds the file:** the seat's pointer `6185197160`
read all 15 open PRs.
4. **Same gate family:** the docs gates on the same page, with no new
verification surface.
**Read and left unchanged:**
- `content/docs/api/error-handling-server.mdx:10`: it is scoped to the
kernel REST server, and true there.
- `content/docs/api/data-api.mdx:45`-`:52`: the `INVALID_FIELD` example
is the REST server's body and claims no exclusivity.
- `content/docs/protocol/objectql/types.mdx:717` ("the response carries
`dependentObject` and `dependentCount`"): true on both doors.
- `error-handling.mdx:1399`-`:1401` (`DELETE_RESTRICTED` withholding):
true on both doors.
- The `RECORD_NOT_FOUND` callout at `:642`-`:658`: it names the `/data`
door and makes no exclusivity claim.
- No published statement names `METADATA_CONFLICT`'s or the five 403
codes' body shape. The 403 codes appear only in status tables
(`permissions/attachments-access.mdx`).
- ⛔ `content/docs/releases/` is untouched.
## Found, not fixed here
- **The published duplicate-record statements**, which this PR does not
change. The bounded in-place rule fails its second condition here: the
shape is not settled, because the card's remainder changes the code
these doors answer. Carrier: #23587's remainder. On a `createHonoApp`
host a duplicate insert was measured as `409
{"success":false,"error":{"code":"DUPLICATE_RECORD","message":"Duplicate
record refused on 'task': …"}}`, at `16abea884` and again at
`d1a39b380`. These lines say otherwise, and they are left as they stand:
- `content/docs/protocol/kernel/error-handling.mdx:668` (`:609` on
`origin/main`): `UNIQUE_VIOLATION` "flat … so it is never nested".
- `:680`-`:683` (`:621`-`:624` on `origin/main`): "every route answers
the wire code `UNIQUE_VIOLATION` and the in-process spelling never
crosses HTTP".
- `content/docs/api/error-catalog.mdx:507`-`:510`: the same sentence.
- `:75`-`:76` of the corrected corollary keeps `UNIQUE_VIOLATION`
"always flat". That holds for every reading taken: no Hono door answered
`UNIQUE_VIOLATION`. It moves with the remainder.
## Acceptance notes
- `createHonoApp`'s `/meta` save answers `409 METADATA_CONFLICT` through
the runtime dispatcher's own writer (`HttpDispatcher.errorFromThrown`),
without `currentVersion`. Measured at `16abea884`, on a host with no
`@objectstack/rest` route ahead of the catch-all. This is #22222's
question, closed `not_planned` on the maintainer's hosted measurement
`6110136480`: a hosted `PUT /meta` reaches `RestServer`, not the
catch-all. Its triage `6056494314` set the build shape for the catch-all
case: a declared `ApiErrorSchema` member in `packages/spec`, not this
file. Not refiled.
- The #23531 card's remainder (`currentRecord`) now lives in
`declaredArmContext`, the renamed helper. The exclusion line, its
comment pointing at #23586 and the two absence pins are unchanged. The
card's Landed comment names the old helper name.
- `OBJECT_NOT_FOUND`'s sentence differs between the doors:
`RestServer`'s arm composes `Object 'ghost' is not registered`, and
these doors keep the thrown `Object 'ghost' not found`. This PR does not
move `message`.
- The two REST doors differ on a declared 5xx arm: `mapDataError` takes
`ERR_DATASOURCE_UNAVAILABLE`'s arm and `resolveErrorResponse` does not.
No producer declares that status today, so the Hono doors never meet it.
If one ever does, this rule matches `mapDataError`.
- objectui has no reader of `dependentObject` or `dependentCount` (`git
grep` at `28fb2a0`: 0 hits). The reach is any API client of a
`createHonoApp` or `HonoHttpServer` host, through `err.details`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7a96bf8 commit f7cd580
7 files changed
Lines changed: 464 additions & 72 deletions
File tree
- .changeset
- content/docs
- api
- protocol/kernel
- packages
- plugins/plugin-hono-server/src
- qa/http-conformance/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
218 | 218 | | |
219 | 219 | | |
220 | 220 | | |
| 221 | + | |
| 222 | + | |
221 | 223 | | |
222 | 224 | | |
223 | 225 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
325 | 325 | | |
326 | 326 | | |
327 | 327 | | |
328 | | - | |
| 328 | + | |
329 | 329 | | |
330 | 330 | | |
331 | 331 | | |
| |||
482 | 482 | | |
483 | 483 | | |
484 | 484 | | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
485 | 508 | | |
486 | 509 | | |
487 | 510 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
79 | 78 | | |
80 | 79 | | |
81 | 80 | | |
82 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
83 | 85 | | |
84 | 86 | | |
85 | 87 | | |
| |||
102 | 104 | | |
103 | 105 | | |
104 | 106 | | |
105 | | - | |
| 107 | + | |
106 | 108 | | |
107 | 109 | | |
108 | 110 | | |
109 | 111 | | |
110 | 112 | | |
| 113 | + | |
| 114 | + | |
111 | 115 | | |
112 | 116 | | |
113 | 117 | | |
| |||
126 | 130 | | |
127 | 131 | | |
128 | 132 | | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
133 | 138 | | |
134 | 139 | | |
135 | 140 | | |
| |||
175 | 180 | | |
176 | 181 | | |
177 | 182 | | |
178 | | - | |
| 183 | + | |
179 | 184 | | |
180 | 185 | | |
181 | 186 | | |
| |||
185 | 190 | | |
186 | 191 | | |
187 | 192 | | |
| 193 | + | |
188 | 194 | | |
189 | 195 | | |
190 | | - | |
191 | | - | |
192 | | - | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
193 | 200 | | |
194 | | - | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
195 | 207 | | |
196 | 208 | | |
197 | 209 | | |
| |||
203 | 215 | | |
204 | 216 | | |
205 | 217 | | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
206 | 262 | | |
207 | 263 | | |
208 | 264 | | |
| |||
347 | 403 | | |
348 | 404 | | |
349 | 405 | | |
350 | | - | |
| 406 | + | |
| 407 | + | |
351 | 408 | | |
352 | | - | |
353 | | - | |
| 409 | + | |
| 410 | + | |
354 | 411 | | |
355 | 412 | | |
356 | 413 | | |
| |||
538 | 595 | | |
539 | 596 | | |
540 | 597 | | |
541 | | - | |
542 | | - | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
543 | 601 | | |
544 | 602 | | |
545 | 603 | | |
| |||
594 | 652 | | |
595 | 653 | | |
596 | 654 | | |
597 | | - | |
| 655 | + | |
| 656 | + | |
598 | 657 | | |
599 | 658 | | |
600 | 659 | | |
| |||
633 | 692 | | |
634 | 693 | | |
635 | 694 | | |
636 | | - | |
637 | | - | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
638 | 698 | | |
639 | 699 | | |
640 | 700 | | |
| |||
989 | 1049 | | |
990 | 1050 | | |
991 | 1051 | | |
992 | | - | |
993 | | - | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
994 | 1055 | | |
995 | 1056 | | |
996 | 1057 | | |
| |||
1039 | 1100 | | |
1040 | 1101 | | |
1041 | 1102 | | |
1042 | | - | |
1043 | | - | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
1044 | 1106 | | |
1045 | 1107 | | |
1046 | 1108 | | |
| |||
0 commit comments