You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
hono: a 409 DELETE_RESTRICTED answered by createHonoApp carries no developerMessage, dependentObject, dependentCount or object, which RestServer's structuredCodeAnswer arm carries (the #23531 family beyond CONCURRENT_UPDATE) #23587
Filing gate ①, class (b). reach:measured at a public door by the #23531 dev at d7484b8621, with a scratch test that was not committed (the os-dev-report on #23531, finding 2). The rig was a real createHonoApp with the real dispatcher and protocol over SQLite WASM.
Filed by domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU. ⛔ Not a claim. Lane and grade are triage's.
What was measured
Request:DELETE /api/v1/data/parent/ID, where a child lookup with deleteBehavior: 'restrict' references the parent.
Answer: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.'}}.
What is missing:developerMessage, dependentObject, dependentCount and object. RestServer answers the same refusal through structuredCodeAnswer's DELETE_RESTRICTED arm (packages/types/src/data-error-classification.ts, just above the CONCURRENT_UPDATE arm), which carries all four.
Not measured:METADATA_CONFLICT's currentVersion and INVALID_FIELD's field, at the same door.
declaredEnvelopeForThrow (packages/plugins/plugin-hono-server/src/adapter.ts) forwards only the details that resolveThrownHttpError derives. PR #23582 adds declaredConflictContext for CONCURRENT_UPDATE only, by #23531's scope.
The mechanical shape is to widen that helper from one code to "the arm agrees on status and code". ⚠️ Read #23586 first: a blanket widening would also forward CONCURRENT_UPDATE's currentRecord, which this seat holds back until that card is answered.
os-dev-report
{
"issue": 23587,
"round": "patch round 1 (REWORK per the seat's review 6185187566 on PR #23616, pointer 6185197160; same claim 6183773313)",
"status": "done",
"branch": "claude/issue-23587-hono-delete-restricted-fields",
"pr": "#23616",
"head": "83c41843689e3eed0e018be8f1cebf6d4faea3df",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "The one item is done in a docs-only commit, 83c4184, on top of d1a39b3. adapter.ts, the pins and the changeset are untouched. Every published content/docs statement about the answer to DELETE_RESTRICTED, INVALID_FIELD, OBJECT_NOT_FOUND, CONCURRENT_UPDATE and the five 403 codes that the Hono doors make false is corrected. That covers the seat's three known lines (error-handling.mdx :130-:132, :541-:542, :636-:637 on origin/main) and the statements around them: corollary 1 named DELETE_RESTRICTED/INVALID_FIELD 'always flat'; the nested writer list said 'two writers'; the httpStatus bullet; the route-family table; OBJECT_NOT_FOUND 'also flat'. A new '### Hono doors' section gives a per-code error.details table. wire-format.mdx :328 is scoped to the kernel REST server, with a new '### Hono hosts nest the extras' section carrying a CONCURRENT_UPDATE example. api/index.mdx gains :221-:222. Every corrected sentence agrees with a reading taken on the same createHonoApp rig at d1a39b3 (the code this round leaves unchanged): DELETE_RESTRICTED, CONCURRENT_UPDATE, INVALID_FIELD, OBJECT_NOT_FOUND and VALIDATION_FAILED were nested with their fields under error.details, the duplicate insert nested as DUPLICATE_RECORD, and none of the six carried httpStatus. The REST twin was computed by mapDataError on each same thrown error. The five 403 rows rest on the unit pins. VALIDATION_FAILED's four lines (incl. :1043 and readError) are corrected under the bounded in-place rule, all four conditions checked. The duplicate-record statements (:609 and :621-:624 on main, error-catalog.mdx :507-:510) are left, because condition 2 fails: the card's remainder changes that code. They are listed under 'Found, not fixed here' with carrier #23587's remainder. No METADATA_CONFLICT body statement exists in the docs, and the 403 codes appear only in status tables. Per the coordinator's correction, NO PR-body PATCH was sent. Every body change, including the seat's three rulings, is in pr_body_changes as old/new pairs, checked to apply once each to the live body (17769 bytes, identical to what was sent at create) and to compose (25030 bytes, no tag-shaped text, no closing keyword). The run was interrupted by an API session limit after the gate battery and resumed from its logs; no step was repeated. The worktree is removed right after this report.",
"tests": "No package file changed this round, so round 0's suites at d1a39b3 stand: plugin-hono-server 'Test Files 29 passed (29)', 'Tests 354 passed (354)'; http-conformance 'Test Files 13 passed (13)', 'Tests 142 passed (142)'; both typechecks OK. The docs readings came from a scratch conformance file (deleted, never committed), run under the verify lock after pnpm turbo run build --filter='@objectstack/http-conformance^...' ('36 successful', '36 cached'), with exit 0. On a real createHonoApp host over the real engine and protocol (SQLite WASM), each request's Hono body was read, and mapDataError was applied to the same thrown error. DELETE_RESTRICTED: 409 nested, details {developerMessage, dependentObject, dependentCount} (REST adds object). CONCURRENT_UPDATE: 409 nested, details {currentVersion} (REST adds currentRecord, object). INVALID_FIELD: 400 nested, details {field, object}. OBJECT_NOT_FOUND: 404 nested, details {object}, message "Object 'ghost' not found" (REST: 'is not registered'). VALIDATION_FAILED (missing required title): 400 nested, details {fields:[{field:'title',code:'required',…}]} (REST adds object). Duplicate insert: 409 nested DUPLICATE_RECORD, no details (REST: UNIQUE_VIOLATION with developerMessage, field, object). None of the six Hono bodies carries httpStatus.",
"gates": "LOCAL at 83c4184. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 7 changed paths vs merge base 16abea8) derived 95 commands (round 0's 66 plus 29 docs families). All 95 ran, each exit code captured before any pipe into a ':: exit N' record. 93 exited 0 on the first pass. pnpm --filter @objectstack/spec run check:skill-examples and pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: no packages/client-react/dist, no workspace dist/ in the recreated worktree). Under the lock, after pnpm turbo run build --concurrency=2 ('76 successful', '75 cached'), both exited 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' (exit 0). The docs gates on the touched pages all exited 0, among them 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. check:route-envelope and check:dispatcher-error-vocabulary (derived) and check:durability-log-level (run separately): exit 0. The four roster gates under touched directories, all exit 0: check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity. The derivation warns STALE TREE: the branch is 12 commits behind origin/main 93f3526, and 7 gate files changed in that range, so the local run read the branch's copies. None of those commits touches the 7 changed paths and git merge-tree --write-tree HEAD origin/main is clean, so the branch was not merged (after the PR opened, main is merged only on a conflict, a main-caused red or a dequeue). CI runs the merge ref. CI on 83c4184, read once: 41 check-runs, 36 success, 5 skipped, 0 failed, 0 in progress. No lintable source changed this round. Round 0's eslint --no-inline-config over both packages (exit 0) stands.",
"mcp_calls": "0",
"api_writes": "This round: 1 relay stroke (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]) = POST /repos/objectstack-ai/objectstack/dispatches carrying POST /repos//issues/23587/comments (this report). PR-body PATCH: 0 (per the coordinator's correction; the changes are in pr_body_changes). label-write: 0. git push x1 (83c4184), not REST. Reads were REST GETs through gh api.",
"line_budget": "n/a",
"files_changed": [
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/api/index.mdx"
],
"files_changed_pr_total": [
"packages/plugins/plugin-hono-server/src/adapter.ts",
"packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts",
"packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts",
".changeset/23587-hono-structured-arm-fields.md",
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/api/index.mdx"
],
"docs_lines_changed": [
"content/docs/protocol/kernel/error-handling.mdx (head 83c4184; origin/main lines in brackets): :75-:77 [:75-:78] corollary 1 keeps only UNIQUE_VIOLATION 'always flat'; :81-:84 [:82] corollary 2 adds DELETE_RESTRICTED/INVALID_FIELD/OBJECT_NOT_FOUND, flat on @objectstack/rest, nested on a Hono door with fields in error.details; :107 [:105] 'three writers'; :113-:114 [new] the declaredEnvelopeForThrow writer; :133 [:129] Hono renderer never writes httpStatus; :134-:137 [:130-:132] error.details bullet; :183 [:178] /data row scoped to the rest door; :193 [new] Hono route-family row; :212-:252 [new] '### Hono doors' section (example, per-code table, never-sent list); :397-:398 and :400-:401 [:350, :352-:353] VALIDATION_FAILED (in-place rule); :589-:591 [:541-:542] INVALID_FIELD; :646-:647 [:597] OBJECT_NOT_FOUND; :686-:688 [:636-:637] DELETE_RESTRICTED; :1043-:1045 [:992-:993] readError reads err.details?.fields (in-place rule); :1094-:1096 [:1042-:1043] field-validation guidance (in-place rule)",
"content/docs/api/wire-format.mdx: :328 [:328] scoped to the kernel REST server plus pointer; :485-:507 [new] '### Hono hosts nest the extras' with the CONCURRENT_UPDATE example",
"content/docs/api/index.mdx: :221-:222 [new] createHonoApp / HonoHttpServer answer in this envelope without httpStatus, classification fields in details except currentRecord and a route-taken object"
],
"pr_body_changes": [
{
"old": "This lands the card's three structured fields on both Hono doors, under one rule that covers every arm of RestServer's classification. What stays on the card is one decision, not work: whether these doors should also carry DELETE_RESTRICTED's object, the object the request's ROUTE named (see "What stays off these doors").",
"new": "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 round 1, the seat's review 6185187566). 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."
},
{
"old": "Measured unchanged there; see "Found, not fixed here" |",
"new": "Measured unchanged there; see Acceptance notes (#22222) |"
},
{
"old": "the arm answers a different code, so it is not consulted. See "Found, not fixed here" |",
"new": "the arm answers a different code, so it is not consulted. This is the card's remainder (opening paragraph) |"
},
{
"old": "REST would answer differently. Whether to thread a route into the recogniser is the open question this card keeps.",
"new": "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."
},
{
"old": "- @objectstack/hono's own suite is not run locally:",
"new": "- Patch round 1 (docs only, head 83c418436). No package file changed since d1a39b380, so the package suites above stand. The docs readings were taken on the same createHonoApp rig with a scratch file that was not committed; see "Docs corrected (patch round 1)".\n- @objectstack/hono's own suite is not run locally:"
},
{
"old": "## Gates\n\nLocal, at the final head d1a39b380:\n\n- node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths, derived 66 commands from the 4 changed paths (merge base 16abea884). All 66 ran, each exit code captured before any pipe.\n - 64 exited 0 on the first pass.\n - pnpm check:dual-build-cjs-loads and pnpm check:type-check-debt first exited 3, PREREQUISITE NOT MET: the fresh worktree had no workspace dist/, and the debt gate's closure build did not get a turn on the verify lock.\n - Under the lock, after a cache-backed pnpm turbo run build --concurrency=2 (76 successful, 49 cached), both measured exit 0. The first read 110 published require entry point(s) across 68 package(s) load. The second read 1 ledger entr(ies) re-measured … none above its recorded number.\n- --ran reconciliation: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero).\n- Named by the dispatch, all exit 0. pnpm check:route-envelope and pnpm check:dispatcher-error-vocabulary are in the derived list. pnpm check:durability-log-level is not, and ran separately: 46 durability-critical catch seam(s), all loud ….\n- The four artifact-roster gates the derivation marks as keeping their roster in a directory this diff touches, all exit 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity.\n- Lint: pnpm lint is the repo-level scan CI owns (Lint · ESLint). Locally it was narrowed to the two touched packages with the same flags, eslint --no-inline-config packages/plugins/plugin-hono-server packages/qa/http-conformance: exit 0, no findings. eslint.config.mjs is unchanged between the merge base and origin/main.\n- git merge-tree --write-tree HEAD origin/main (dd4ef3850) is clean. None of the five commits since the merge base touches these packages, @objectstack/types, the adapter or the dispatcher, so the branch was not merged again.\n- Every family CI runs on this PR is CI's to report on its check-runs.\n\n",
"new": "## Gates\n\nLocal, at the patch-round head 83c418436:\n\n- 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.\n - 93 exited 0 on the first pass.\n - 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/.\n - 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.\n- --ran reconciliation: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero).\n- 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.\n- pnpm check:route-envelope and pnpm check:dispatcher-error-vocabulary (derived), and pnpm check:durability-log-level (run separately): exit 0.\n- 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.\n- ⚠️ The derivation reports a stale tree: this branch is 12 commits behind origin/main (93f3526e6), and 7 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.\n- CI on 83c418436, read once: 41 check-runs, 36 success, 5 skipped, none failed or in progress.\n- Round 0 (code head d1a39b380): 66 derived, 66 run, 0 NOT-MEASURED; eslint --no-inline-config over the two touched packages exit 0. This round changes no lintable source.\n\n"
},
{
"old": "## Found, not fixed here (for the seat to file)\n\n- createHonoApp's /meta save answers 409 METADATA_CONFLICT without currentVersion. Class (b), measured at 16abea884: PUT /api/v1/meta/view/case_grid with a stale If-Match answers 409 {\"success\":false,\"error\":{\"code\":\"METADATA_CONFLICT\",\"message\":\"… (current is hmac-sha256:…)\",\"httpStatus\":409}}. The spec's MetadataConflictErrorSchema.currentVersion says "Send it as If-Match to overwrite deliberately … Opaque — echo it verbatim, never parse it", and RestServer sends it. The httpStatus shows the answer comes from the dispatcher's own door (HttpDispatcher.errorFromThrown, packages/runtime/src/http-dispatcher.ts), which forwards only the resolver's details. That is the same family, but a different door with its own envelope suite, so it is out of this file surface. Seam: spec:MetadataConflictErrorSchema.currentVersion → runtime:HttpDispatcher.errorFromThrown | consumer: not measured.\n- The Hono doors answer a duplicate insert as 409 DUPLICATE_RECORD. Class (b), measured at 16abea884: POST /api/v1/data/udoc repeating a unique email. The maintainer ruling recorded at structuredCodeAnswer (2026-09-03, commit 65846bc46) says "a unique-constraint refusal has ONE wire spelling on every route, UNIQUE_VIOLATION". The engine's own developerMessage says "over the HTTP API this refusal is reported as code === 'UNIQUE_VIOLATION' on every route". RestServer answers UNIQUE_VIOLATION. This PR's agreement rule deliberately leaves the code alone (an arm adds context, never changes the answer), so a change of code is a separate decision. Seam: spec:ErrorCode UNIQUE_VIOLATION (ruling 65846bc46) → runtime:plugin-hono-server declaredEnvelopeForThrow (createHonoApp thrownJson, HonoHttpServer wrap/onError) | consumer: not measured.\n\n",
"new": "## Docs corrected (patch round 1)\n\nHead 83c418436 (commit docs(api): …). 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:\n- R1: a real createHonoApp host over the real engine and protocol (SQLite WASM), at d1a39b380, the code this round does not change. A scratch file, not committed, with RestServer's body for each request computed from the same thrown error by mapDataError.\n- U: the [#23587] unit pins on a HonoHttpServer route.\n\nR1 measured, per code, on the Hono door and then the REST twin:\n- DELETE_RESTRICTED: nested, details{developerMessage, dependentObject, dependentCount} / flat with those three and object.\n- CONCURRENT_UPDATE: nested, details{currentVersion} / flat with currentVersion, currentRecord, object.\n- INVALID_FIELD: nested, {field, object} / flat with both.\n- OBJECT_NOT_FOUND: nested, {object}, message Object 'ghost' not found / flat, errorObject 'ghost' is not registered.\n- VALIDATION_FAILED (a missing required field): nested, {fields} / flat with fields and object.\n- A duplicate insert: nested DUPLICATE_RECORD, no details / flat UNIQUE_VIOLATION with developerMessage, field, object.\n- None of the six Hono bodies carries httpStatus.\n\ncontent/docs/protocol/kernel/error-handling.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n| :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) |\n| :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 |\n| :107 (:105) | "two writers" → "three writers" | R1 |\n| :113-:114 (new) | the third writer, declaredEnvelopeForThrow, the Hono doors' renderer | R1, U |\n| :133 (:129) | error.httpStatus: the Hono doors' renderer never writes it | R1 (0 of 6 bodies) |\n| :134-:137 (:130-:132) | error.details: "and nowhere else" removed; the Hono doors put the per-condition siblings there | R1, U |\n| :183 (:178) | the {base}/data/* row is scoped to the @objectstack/rest door | R1 (the same routes answer nested on createHonoApp) |\n| :193 (new) | route-family row: a declared refusal thrown out of the dispatcher on a createHonoApp host, or out of a HonoHttpServer route, is nested | R1, U |\n| :212-:252 (new) | ### Hono doors: who renders, the DELETE_RESTRICTED example, the per-code details table and what a Hono door never sends (httpStatus, currentRecord, a route-taken object; the thrown message) | Example and the DELETE_RESTRICTED, CONCURRENT_UPDATE, INVALID_FIELD, OBJECT_NOT_FOUND, VALIDATION_FAILED rows: R1. The five 403 rows and "dependentCount when the flat door would send it": U |\n| :397-:398 (:350) | VALIDATION_FAILED envelope: flat on the rest door, nested on a Hono door (in-place, below) | R1 |\n| :400-:401 (:352-:353) | VALIDATION_FAILED: fields is error.details.fields on a Hono door (in-place) | R1 |\n| :589-:591 (:541-:542) | INVALID_FIELD envelope: nested on a Hono door, error.details.field / .object | R1 |\n| :646-:647 (:597) | OBJECT_NOT_FOUND: nested on a Hono door, object in error.details | R1 |\n| :686-:688 (:636-:637) | DELETE_RESTRICTED envelope: nested on a Hono door, the three fields in error.details, no object | R1 |\n| :1043-:1045 (:992-:993) | readError reads err.details?.fields on a nested body (in-place) | R1 |\n| :1094-:1096 (:1042-:1043) | field-validation guidance: error.details.fields on a Hono door; "no details.fields bag on either envelope" removed (in-place) | R1 |\n\ncontent/docs/api/wire-format.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n| :328 (:328) | "Data route errors" scoped to the kernel REST server, with a pointer to the Hono hosts | R1 |\n| :485-:507 (new) | ### Hono hosts nest the extras: nested, no httpStatus; the classification's extras in error.details, currentRecord and a route-taken object not sent; the CONCURRENT_UPDATE example | R1 |\n\ncontent/docs/api/index.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n| :221-:222 (new) | on a createHonoApp host or a HonoHttpServer route, a declared refusal is nested withouthttpStatus, and the classification's fields ride in details, except currentRecord and a route-taken object | R1 |\n\nThe 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: :397-:398, :400-:401, :1043-:1045 and :1094-:1096. Each condition was checked:\n1. Same defect class: a published statement that a refusal is never nested, or has no details, which is false at the Hono doors.\n2. 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.\n3. No other claim holds the file: the seat's pointer 6185197160 read all 15 open PRs.\n4. Same gate family: the docs gates on the same page, with no new verification surface.\n\nRead and left unchanged:\n- content/docs/api/error-handling-server.mdx:10: it is scoped to the kernel REST server, and true there.\n- content/docs/api/data-api.mdx:45-:52: the INVALID_FIELD example is the REST server's body and claims no exclusivity.\n- content/docs/protocol/objectql/types.mdx:717 ("the response carries dependentObject and dependentCount"): true on both doors now.\n- error-handling.mdx:1390-:1392 (DELETE_RESTRICTED withholding): true on both doors.\n- The RECORD_NOT_FOUND callout at :633-:649: it names the /data door and makes no exclusivity claim.\n- 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).\n- ⛔ content/docs/releases/ is untouched.\n\n## Found, not fixed here\n\n- 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:\n - content/docs/protocol/kernel/error-handling.mdx:659 (:609 on origin/main): UNIQUE_VIOLATION "flat … so it is never nested".\n - :671-:674 (:621-:624 on origin/main): "every route answers the wire code UNIQUE_VIOLATION and the in-process spelling never crosses HTTP".\n - content/docs/api/error-catalog.mdx:507-:510: the same sentence.\n - :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.\n\n"
},
{
"old": "## Acceptance notes\n\n",
"new": "## Acceptance notes\n\n- createHonoApp's /meta save answers 409 METADATA_CONFLICT through the runtime dispatcher's own writer (HttpDispatcher.errorFromThrown), without currentVersion. Measured at 16abea884. 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.\n"
}
],
"deviations": [
"No PR-body PATCH was sent: the coordinator's correction overrides the seat's offered PATCH (the role file wins). Every body change is in pr_body_changes: 8 old/new pairs, each old text occurring exactly once in the live body, applied in order to give a 25030-byte body.",
"Scope beyond the seat's three known lines: corollary 1/2 (:75-:84), the writer list (:107, :113-:114), the httpStatus bullet (:133), the route-family table (:183, :193), the new '### Hono doors' section, wire-format :328 plus its new section, and api/index :221-:222. Each states the answer to a listed code on a Hono host, and each matches an R1 reading. The writer and httpStatus lines were needed for the corrected bullets to stay consistent with the page's own writer model.",
"CONCURRENT_UPDATE's Hono body is not changed by this PR (PR #23582 landed it). Its statements were corrected anyway, because the seat's list names the code.",
"VALIDATION_FAILED, a code this PR does not change: 4 lines were corrected under the bounded in-place rule (conditions in the proposed PR body). The duplicate-record statements were NOT corrected: condition 2 fails because the card's remainder changes that code, so they are under 'Found, not fixed here'.",
"Stale tree: the branch is 12 commits behind origin/main, and 7 gate scripts changed in that range. The branch was not merged (rule: only on a conflict, a main red or a dequeue). The local gate run read the branch's script copies, and CI's merge-ref run is the current-script reading.",
"The run stopped on an API session limit after the 95-command battery; it resumed from the on-disk logs, and no measurement was repeated. The worktree was recreated at the start of the round from the local branch ref, verified equal to origin's head d1a39b3."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #23587's remainder (the duplicate-record code at the Hono doors) · noted, not filed: the published statements error-handling.mdx:659 and :671-:674 (origin/main :609, :621-:624), error-catalog.mdx:507-:510, and the corrected corollary's UNIQUE_VIOLATION 'always flat' (:75-:76) move with that remainder; measured today: a duplicate insert on createHonoApp answers 409 nested DUPLICATE_RECORD",
"carrier: none (承接者:无) · noted in the PR's Acceptance notes only, unchanged from round 0: OBJECT_NOT_FOUND's sentence differs between doors; the REST doors differ on a declared-5xx arm (unreachable today)"
]
}
os-dev-report
{
"issue": 23587,
"round": "patch round 2 of 2 (REWORK per the seat's review 6186145904 on PR #23616; same claim 6183773313)",
"status": "done",
"branch": "claude/issue-23587-hono-delete-restricted-fields",
"pr": "#23616",
"head": "9675127f30b3099b7b8c1e609006b2c28ec83d6b",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "The one item is done in a docs-only commit, 9675127, on top of 83c4184. No package file changed. The sentences that assumed every createHonoApp host serves /data through the runtime dispatcher are scoped to where they hold (a catch-all that serves /data, i.e. no @objectstack/rest route registered ahead of it), and each says the hosted composition answers flat from the REST door: wire-format.mdx:328; api/index.mdx:221; error-handling.mdx's route-table row :193; '### Hono doors' :220-:224 and its example caption :230-:231. The wire-format example caption :492 has the same assumption and is scoped too. The page's split-by-door callout (:196-:206, was :190-:194) now covers /data as well as /meta: a createHonoApp host can be either; REST routes registered before the mount win the match and answer flat; with none ahead of the catch-all, both families answer nested through the dispatcher. MEASURED on a composed rig (R2, scratch, not committed, at 9675127), one kernel with HonoServerPlugin + createRestApiPlugin. The hosted composition (REST routes registered, then createHonoApp mounted with route('/', ...); the ALL /api/v1/* catch-all present) answered the same restricted DELETE /api/v1/data/account/ID as 409 FLAT {error, code: DELETE_RESTRICTED, developerMessage, dependentObject, dependentCount: 1, object: 'account'}, and GET ?nosuchfield=1 as 400 FLAT INVALID_FIELD with field and object. createHonoApp alone, over the same kernel, answered both NESTED with the fields under error.details. The /meta half rests on the maintainer's hosted measurement 6110136480. pr_body_changes is the COMPLETE set against the live PR-open body (17769 bytes, re-read unchanged): round 1's 8 pairs, updated for this round (opening, tests, gates, the docs section with round-2 rows and head line numbers, Found lines renumbered to :668 and :680-:683, Acceptance note). Each old text was checked to occur once, and the set composes to 27537 bytes with no tag-shaped text, no closing keyword and no control byte. No PR-body PATCH was sent. The worktree is removed right after this report.",
"tests": "No package file changed in either patch round; round 0's suites at d1a39b3 stand (plugin-hono-server 'Tests 354 passed (354)', http-conformance 'Tests 142 passed (142)', both typechecks OK). R2 was a scratch conformance file (deleted, never committed), run under the verify lock after a cache-backed pnpm turbo run build --filter='@objectstack/http-conformance^...' ('36 successful', '36 cached'). One kernel: ObjectQLPlugin, a stub auth service, HonoServerPlugin({port:0}), createRestApiPlugin({}), the SQLite WASM driver, account/contact with a restrict lookup, and the conformance suite's authz-resolver objects. The raw Hono app had 88 routes; after raw.route('/', createHonoApp({kernel, prefix:'/api/v1'})) it had 93, with ALL /api/v1/*. Composed host: DELETE /api/v1/data/account/ID gave 409 {"error":"This Account is still referenced by 1 Contact record(s) through “Account”. …","code":"DELETE_RESTRICTED","developerMessage":"Cannot delete account (ID): 1 dependent contact record(s) …","dependentObject":"contact","dependentCount":1,"object":"account"}. GET /api/v1/data/account?nosuchfield=1 gave 400 {"error":"Unknown field 'nosuchfield' on object 'account'. …","code":"INVALID_FIELD","field":"nosuchfield","object":"account"}. createHonoApp alone, same kernel: 409 {success:false,error:{code:DELETE_RESTRICTED,message,details:{developerMessage,dependentObject,dependentCount:1}}} and 400 {success:false,error:{code:INVALID_FIELD,message,details:{field,object}}}. The rig's first run, before the full authz-resolver object set was copied in, answered both composed requests as flat 503 SERVICE_UNAVAILABLE. That was also the REST door's dialect, but not the refusal under test; it was re-run with the objects and the reading above is from that run.",
"gates": "LOCAL at 9675127. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 7 changed paths vs merge base 16abea8) derived 95 commands, the same list as round 1. All 95 ran, each exit code captured before any pipe into a ':: exit N' record. 93 exited 0 on the first pass. pnpm --filter @objectstack/spec run check:skill-examples and pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: no packages/client-react/dist, no workspace dist/ in the recreated worktree). Under the lock, after pnpm turbo run build --concurrency=2 ('76 successful', '75 cached'), both exited 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' (exit 0). Docs gates on the touched pages, 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, check-section-landing-index. check:route-envelope and check:dispatcher-error-vocabulary (derived), check:durability-log-level (separately): exit 0. Roster gates under touched directories, all exit 0: check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity. STALE TREE warning: the branch is 16 commits behind origin/main 7c6b7f4, and 11 gate files changed in that range. None of those commits touches the 7 changed paths, and git merge-tree --write-tree HEAD origin/main is clean, so the branch was not merged (rule: only on a conflict, a main-caused red or a dequeue). CI runs the merge ref. CI on 9675127, read once: 41 check-runs, 36 success, 5 skipped, 0 failed, 0 in progress.",
"mcp_calls": "0",
"api_writes": "This round: 1 relay stroke (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]) = POST /repos/objectstack-ai/objectstack/dispatches carrying POST /repos//issues/23587/comments (this report). PR-body PATCH: 0. label-write: 0. git push x1 (9675127), not REST. Reads were REST GETs through gh api.",
"line_budget": "n/a",
"files_changed": [
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/api/index.mdx"
],
"files_changed_pr_total": [
"packages/plugins/plugin-hono-server/src/adapter.ts",
"packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts",
"packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts",
".changeset/23587-hono-structured-arm-fields.md",
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/api/index.mdx"
],
"docs_lines_changed_this_round": [
"content/docs/protocol/kernel/error-handling.mdx (head 9675127): :193 route-table row, '/data included only where no @objectstack/rest route is registered ahead of its catch-all (see the callout below)'; :196-:206 (origin/main :190-:194) the split-by-door callout widened from /meta to /meta and /data, with the hosted composition (REST routes before the mount) answering flat; :220-:224 '### Hono doors' first sentence scoped ('on a host whose catch-all serves /data; a host that registers @objectstack/rest's routes ahead of it answers those flat', with a pointer to the route-family heading); :230-:231 example caption 'on a createHonoApp host whose catch-all serves /data'",
"content/docs/api/wire-format.mdx: :328 'answers the same refusals nested instead when its catch-all serves /data … One that registers the kernel REST server's routes on the same Hono app before it mounts createHonoApp, the hosted composition, answers them flat, as below'; :492 example caption 'on a createHonoApp host whose catch-all serves /data'",
"content/docs/api/index.mdx: :221 '(the /data routes' refusals among them, where its catch-all serves /data; a host that registers the kernel REST server's routes ahead of the catch-all, the hosted composition, answers those flat, as above)'"
],
"docs_lines_changed_pr_total": "Listed with head line numbers, origin/main line numbers and the reading for each line in pr_body_changes[6].new, section 'Docs corrected (patch rounds 1 and 2)'.",
"pr_body_changes": [
{
"old": "This lands the card's three structured fields on both Hono doors, under one rule that covers every arm of RestServer's classification. What stays on the card is one decision, not work: whether these doors should also carry DELETE_RESTRICTED's object, the object the request's ROUTE named (see "What stays off these doors").",
"new": "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."
},
{
"old": "Measured unchanged there; see "Found, not fixed here" |",
"new": "Measured unchanged there; see Acceptance notes (#22222) |"
},
{
"old": "the arm answers a different code, so it is not consulted. See "Found, not fixed here" |",
"new": "the arm answers a different code, so it is not consulted. This is the card's remainder (opening paragraph) |"
},
{
"old": "REST would answer differently. Whether to thread a route into the recogniser is the open question this card keeps.",
"new": "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."
},
{
"old": "- @objectstack/hono's own suite is not run locally:",
"new": "- 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)".\n- @objectstack/hono's own suite is not run locally:"
},
{
"old": "## Gates\n\nLocal, at the final head d1a39b380:\n\n- node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths, derived 66 commands from the 4 changed paths (merge base 16abea884). All 66 ran, each exit code captured before any pipe.\n - 64 exited 0 on the first pass.\n - pnpm check:dual-build-cjs-loads and pnpm check:type-check-debt first exited 3, PREREQUISITE NOT MET: the fresh worktree had no workspace dist/, and the debt gate's closure build did not get a turn on the verify lock.\n - Under the lock, after a cache-backed pnpm turbo run build --concurrency=2 (76 successful, 49 cached), both measured exit 0. The first read 110 published require entry point(s) across 68 package(s) load. The second read 1 ledger entr(ies) re-measured … none above its recorded number.\n- --ran reconciliation: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero).\n- Named by the dispatch, all exit 0. pnpm check:route-envelope and pnpm check:dispatcher-error-vocabulary are in the derived list. pnpm check:durability-log-level is not, and ran separately: 46 durability-critical catch seam(s), all loud ….\n- The four artifact-roster gates the derivation marks as keeping their roster in a directory this diff touches, all exit 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity.\n- Lint: pnpm lint is the repo-level scan CI owns (Lint · ESLint). Locally it was narrowed to the two touched packages with the same flags, eslint --no-inline-config packages/plugins/plugin-hono-server packages/qa/http-conformance: exit 0, no findings. eslint.config.mjs is unchanged between the merge base and origin/main.\n- git merge-tree --write-tree HEAD origin/main (dd4ef3850) is clean. None of the five commits since the merge base touches these packages, @objectstack/types, the adapter or the dispatcher, so the branch was not merged again.\n- Every family CI runs on this PR is CI's to report on its check-runs.\n\n",
"new": "## Gates\n\nLocal, at the final head 9675127f3:\n\n- 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.\n - 93 exited 0 on the first pass.\n - 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/.\n - 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.\n- --ran reconciliation: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero).\n- 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.\n- pnpm check:route-envelope and pnpm check:dispatcher-error-vocabulary (derived), and pnpm check:durability-log-level (run separately): exit 0.\n- 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.\n- ⚠️ 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.\n- CI on 9675127f3, read once: 41 check-runs, 36 success, 5 skipped, none failed or in progress.\n- 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.\n\n"
},
{
"old": "## Found, not fixed here (for the seat to file)\n\n- createHonoApp's /meta save answers 409 METADATA_CONFLICT without currentVersion. Class (b), measured at 16abea884: PUT /api/v1/meta/view/case_grid with a stale If-Match answers 409 {\"success\":false,\"error\":{\"code\":\"METADATA_CONFLICT\",\"message\":\"… (current is hmac-sha256:…)\",\"httpStatus\":409}}. The spec's MetadataConflictErrorSchema.currentVersion says "Send it as If-Match to overwrite deliberately … Opaque — echo it verbatim, never parse it", and RestServer sends it. The httpStatus shows the answer comes from the dispatcher's own door (HttpDispatcher.errorFromThrown, packages/runtime/src/http-dispatcher.ts), which forwards only the resolver's details. That is the same family, but a different door with its own envelope suite, so it is out of this file surface. Seam: spec:MetadataConflictErrorSchema.currentVersion → runtime:HttpDispatcher.errorFromThrown | consumer: not measured.\n- The Hono doors answer a duplicate insert as 409 DUPLICATE_RECORD. Class (b), measured at 16abea884: POST /api/v1/data/udoc repeating a unique email. The maintainer ruling recorded at structuredCodeAnswer (2026-09-03, commit 65846bc46) says "a unique-constraint refusal has ONE wire spelling on every route, UNIQUE_VIOLATION". The engine's own developerMessage says "over the HTTP API this refusal is reported as code === 'UNIQUE_VIOLATION' on every route". RestServer answers UNIQUE_VIOLATION. This PR's agreement rule deliberately leaves the code alone (an arm adds context, never changes the answer), so a change of code is a separate decision. Seam: spec:ErrorCode UNIQUE_VIOLATION (ruling 65846bc46) → runtime:plugin-hono-server declaredEnvelopeForThrow (createHonoApp thrownJson, HonoHttpServer wrap/onError) | consumer: not measured.\n\n",
"new": "## Docs corrected (patch rounds 1 and 2)\n\nHead 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:\n- 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.\n- R2: one kernel (engine, HonoServerPlugin, createRestApiPlugin) at 9675127f3. Two hosts were measured:\n - 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);\n - createHonoApp alone, over the same kernel.\n\n A scratch file, not committed.\n- U: the [#23587] unit pins on a HonoHttpServer route.\n- 6110136480: the maintainer's hosted measurement on #22222, for /meta on the hosted composition (flat, from the REST door).\n\nR1 measured, per code, on the Hono door and then the REST twin:\n- DELETE_RESTRICTED: nested, details{developerMessage, dependentObject, dependentCount} / flat with those three and object.\n- CONCURRENT_UPDATE: nested, details{currentVersion} / flat with currentVersion, currentRecord, object.\n- INVALID_FIELD: nested, {field, object} / flat with both.\n- OBJECT_NOT_FOUND: nested, {object}, message Object 'ghost' not found / flat, errorObject 'ghost' is not registered.\n- VALIDATION_FAILED (a missing required field): nested, {fields} / flat with fields and object.\n- A duplicate insert: nested DUPLICATE_RECORD, no details / flat UNIQUE_VIOLATION with developerMessage, field, object.\n- None of the six Hono bodies carries httpStatus.\n\nR2 measured, on the hosted composition and then on createHonoApp alone:\n- the same restricted DELETE /api/v1/data/account/ID: 409flat{error, code: DELETE_RESTRICTED, developerMessage, dependentObject, dependentCount: 1, object: \"account\"} / 409nested with those three under error.details;\n- GET /api/v1/data/account?nosuchfield=1: 400flatINVALID_FIELD with field and object / 400nested with {field, object} under error.details.\n\ncontent/docs/protocol/kernel/error-handling.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n| :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) |\n| :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 |\n| :107 (:105) | "two writers" → "three writers" | R1 |\n| :113-:114 (new) | the third writer, declaredEnvelopeForThrow, the Hono doors' renderer | R1, U |\n| :133 (:129) | error.httpStatus: the Hono doors' renderer never writes it | R1 (0 of 6 bodies) |\n| :134-:137 (:130-:132) | error.details: "and nowhere else" removed; the Hono doors put the per-condition siblings there | R1, U |\n| :183 (:178) | the {base}/data/* row is scoped to the @objectstack/rest door | R1, R2 |\n| :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 |\n| :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) |\n| :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 |\n| :406-:407 (:350) | VALIDATION_FAILED envelope: flat on the rest door, nested on a Hono door (in-place, below) | R1 |\n| :409-:410 (:352-:353) | VALIDATION_FAILED: fields is error.details.fields on a Hono door (in-place) | R1 |\n| :598-:600 (:541-:542) | INVALID_FIELD envelope: nested on a Hono door, error.details.field / .object | R1, R2 |\n| :655-:656 (:597) | OBJECT_NOT_FOUND: nested on a Hono door, object in error.details | R1 |\n| :695-:697 (:636-:637) | DELETE_RESTRICTED envelope: nested on a Hono door, the three fields in error.details, no object | R1, R2 |\n| :1052-:1054 (:992-:993) | readError reads err.details?.fields on a nested body (in-place) | R1 |\n| :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 |\n\ncontent/docs/api/wire-format.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n| :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 |\n| :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 |\n\ncontent/docs/api/index.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n| :221-:222 (new; round 2 scoped) | On a createHonoApp host or a HonoHttpServer route, a declared refusal is nested withouthttpStatus, 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 |\n\nThe 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:\n1. Same defect class: a published statement that a refusal is never nested, or has no details, which is false at the Hono doors.\n2. 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.\n3. No other claim holds the file: the seat's pointer 6185197160 read all 15 open PRs.\n4. Same gate family: the docs gates on the same page, with no new verification surface.\n\nRead and left unchanged:\n- content/docs/api/error-handling-server.mdx:10: it is scoped to the kernel REST server, and true there.\n- content/docs/api/data-api.mdx:45-:52: the INVALID_FIELD example is the REST server's body and claims no exclusivity.\n- content/docs/protocol/objectql/types.mdx:717 ("the response carries dependentObject and dependentCount"): true on both doors.\n- error-handling.mdx:1399-:1401 (DELETE_RESTRICTED withholding): true on both doors.\n- The RECORD_NOT_FOUND callout at :642-:658: it names the /data door and makes no exclusivity claim.\n- 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).\n- ⛔ content/docs/releases/ is untouched.\n\n## Found, not fixed here\n\n- 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:\n - content/docs/protocol/kernel/error-handling.mdx:668 (:609 on origin/main): UNIQUE_VIOLATION "flat … so it is never nested".\n - :680-:683 (:621-:624 on origin/main): "every route answers the wire code UNIQUE_VIOLATION and the in-process spelling never crosses HTTP".\n - content/docs/api/error-catalog.mdx:507-:510: the same sentence.\n - :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.\n\n"
},
{
"old": "## Acceptance notes\n\n",
"new": "## Acceptance notes\n\n- 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.\n"
}
],
"deviations": [
"No PR-body PATCH was sent (role file, coordinator's correction). pr_body_changes is the complete set against the live PR-open body: 8 pairs, each old text occurring exactly once, composing to 27537 bytes.",
"Beyond the seat's three named sentences, the two example captions (error-handling.mdx :230-:231, wire-format.mdx :492) carried the same 'every createHonoApp host' assumption and are scoped too.",
"Measured rather than cited: the composed rig was cheap, so the /data half is a reading (R2), not a source read. The /meta half of the widened callout rests on 6110136480.",
"The composed rig's first run answered flat 503 SERVICE_UNAVAILABLE on both requests, because the authz-resolver object set was incomplete. It was re-run with the conformance suite's full set, and only the second run's reading is cited.",
"Stale tree: the branch is 16 commits behind origin/main and was not merged (no overlap, merge-tree clean; the post-open merge rule). The local gate run read the branch's copies of 11 changed gate files, and CI's merge-ref run reads the current ones.",
"The worktree was gone at the start of the round and was recreated from the local branch ref at origin's head 83c4184, verified equal."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #23587's remainder (the duplicate-record code at the Hono doors) · noted, not filed: the published statements error-handling.mdx:668 and :680-:683 (origin/main :609, :621-:624), error-catalog.mdx:507-:510, and the corrected corollary's UNIQUE_VIOLATION 'always flat' (:75-:76) move with that remainder",
"carrier: none (承接者:无) · noted in the PR's Acceptance notes only, unchanged: OBJECT_NOT_FOUND's sentence differs between doors; the REST doors differ on a declared-5xx arm (unreachable today)"
]
}
Release: domain:cli seat 1 · os-bill · session session_01FLwgbTSmGMrqsXkBDsiwfG · 2026-10-15T07:49Z · cause: a partial landing (Part of; the card keeps a folded remainder) · destination: pm:queue, unassigned
Landed: PR #23616 → f7cd580bff (Part of #23587). The card goes back to pm:queue for its remainder
reading
value
queue
added_to_merge_queue 07:22Z, merged 07:48Z
origin/main
f7cd580bff: one parent (a queue squash), an ancestor of origin/main, 7 files +464 / −72 as reviewed at 9675127f30; no queue branch left
card
stays open (the PR said Part of). In the stroke after this comment, pm:dispatched becomes pm:queue and the assignee is cleared
Review chain: REWORK 6185187566 (the published error pages the answer made false), REWORK 6186145904 (the createHonoApp-composition scoping), then ACCEPT 6186735512. No contract review was owed.
What landed:
Both Hono doors (createHonoApp's catch-all and a route on HonoHttpServer) now carry RestServer's structured-arm fields in error.details, by one rule over every arm that agrees on status and code:
The published error pages (error-handling.mdx, wire-format.mdx, api/index.mdx) now say which composition answers which envelope, measured on both compositions.
The location:declaredEnvelopeForThrow (packages/plugins/plugin-hono-server/src/adapter.ts) answers the engine's DuplicateRecordError as 409 DUPLICATE_RECORD on both Hono doors. RestServer answers UNIQUE_VIOLATION with developerMessage, field and object.
Governing text: the maintainer ruling recorded at commit 65846bc46, one wire spelling on every route.
Cross-card note from domain:spec seat 2 · session session_01LFqdRsuM5nkqoJdHd5ztVk · 2026-10-15T09:02Z · ⛔ not a claim
#23586 landed as 7dc7d2213 (PR #23628). metadata-protocol now reads a conflicting row under the caller's own context before it reports a version conflict. A row the caller cannot read answers 404 with no currentVersion and no currentRecord, and a readable row's currentRecord is the caller's own field-masked read.
So the hold triage set on this card's Hono doors (declaredConflictContext forwarding no currentRecord until #23586 lands, 6184070020) no longer guards anything: forwarding the producer's currentRecord is now safe at those doors. Lifting it is this card's remainder, for whichever seat claims it.
Claim: PM loop round 4, label order: the oldest bug at priority:p3 in domain:cli's open queue (the lane's queue holds no priority:p0, pm:blocking, target: or p1/p2 card that is open for dispatch). #23587's folded remainder: both Hono doors answer the engine's DuplicateRecordError with the ruled wire spelling, 409 UNIQUE_VIOLATION · 2026-10-15T12:55Z
Session: session_01FLwgbTSmGMrqsXkBDsiwfG
Account: os-bill (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-23587-hono-unique-violation
Worktree: objectstack-issue-23587
Domain: domain:cli
Seat: domain:cli#1 (seat post #6024)
File surface (read on origin/main7e6e4e1da; stop on a breach and explain it in the report):
packages/plugins/plugin-hono-server/src/adapter.ts: declaredEnvelopeForThrow and the arm-agreement rule (its docblock near :375-:391 names this exact case).
Its pins, under packages/plugins/plugin-hono-server/src/**.
One changeset.
Container & model: M (one mapping at one seam, measured at two doors, with prose to reconcile), mode:subagent, model: TIER_DEFAULT (dispatch-gates --tier --repo objectstack-ai/objectstack over the three surface paths at 7e6e4e1da: "no path-derived mandate … The tier stays the PM's per-card judgment call")
Clause-②: no
Basis for the Clause-② line: no accepted input moves. The maintainer ruled one wire spelling on every route, UNIQUE_VIOLATION (recorded in commit 65846bc46, restated at packages/metadata-protocol/src/protocol.ts near :3371-:3398 and packages/core/src/utils/import-runner.ts:382). The published error pages already state that spelling for every route. The Hono doors are brought to the ruling; the engine's in-process DuplicateRecordError.code stays DUPLICATE_RECORD, as the ruling says. No export is added, and no packages/spec text moves.
Responsibility: plugin-hono-server's adapter, platform code in this lane | RestServer's door already answers UNIQUE_VIOLATION, so a client reaching the platform through it is unaffected | a client of a createHonoApp or HonoHttpServer route that repeats a unique value; measured at POST /api/v1/data/udoc by this card's earlier dev at 16abea884 (6185197160)
Thread-read: 6188445836
Serial constraints cleared, and the landing order:
os-dev-report
{
"issue": 23587,
"round": "the folded remainder (claim 6192578085, PM loop round 4)",
"status": "done",
"branch": "claude/issue-23587-hono-unique-violation",
"pr": "#23682",
"head": "3b01195d174f54d4c445c6e9f241db52dec120a1",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "H1 holds, measured: at 7e6e4e1 both Hono doors (createHonoApp's dispatch() catch-all; a HonoHttpServer route and its raw mount) answered a repeated unique value from the real engine as 409 DUPLICATE_RECORD with code and message only, because declaredArmContext consults RestServer's arm only when status and code already agree. H2 holds: the fix decides the envelope's code before the arm is consulted, in declaredEnvelopeForThrow (packages/plugins/plugin-hono-server/src/adapter.ts): isEngineDuplicateRecordEnvelope(thrown) ? 'UNIQUE_VIOLATION' : resolved.code. The key is the registered code AND the class name, the same predicate RestServer's arm spells and the import runner already imports from @objectstack/types (an existing export; no new export, no packages/spec edit). The agreement rule is unchanged and is asked with the decided code, so the duplicate arm now agrees and its developerMessage, object and (when the driver named a column) field ride error.details. message stays the thrown engine sentence; declaredCode stays absent. Measured after on the same rig: all three doors 409 UNIQUE_VIOLATION with details {developerMessage, object}; no field because the tenancy-scoped unique index is composite (uniq_udoc_organization_id_email); unit pins cover a producer-named field. Every control is unchanged before/after: a hook throwing {code:'DUPLICATE_RECORD',status:409} keeps DUPLICATE_RECORD on both doors; DELETE_RESTRICTED, INVALID_FIELD, CONCURRENT_UPDATE and OBJECT_NOT_FOUND keep code and details. H3 partly holds: the published 'every route answers UNIQUE_VIOLATION' sentences become true at the Hono doors, but two statements became FALSE with this change and are corrected (error-handling.mdx corollary 1 'UNIQUE_VIOLATION ... always flat'; its #### UNIQUE_VIOLATION Envelope line 'never nested'), and the Hono doors table gains a row. The 'every route' claim is still false on a third door: the runtime dispatcher's own catch arms (HttpDispatcher.errorFromThrown), measured at POST /api/v1/actions/udoc/dup answering 409 DUPLICATE_RECORD with httpStatus (out_of_scope_findings[0]). Base merged with origin/main 09dd537 before opening (no overlap). The worktree is removed right after this report.",
"tests": "Pins: packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts, new [#23587] block, 4 rows x 3 doors (HonoHttpServer route, its raw mount, declaredEnvelopeForThrow as createHonoApp's renderer). Red before the fix with the pins committed alone at df28bfd: 'Tests 6 failed | 43 passed (49)', each red row "expected 'DUPLICATE_RECORD' to be 'UNIQUE_VIOLATION'", the 6 control rows green. After the fix at 66aed12: 49 passed. At the final head 3b01195: pnpm --filter @objectstack/plugin-hono-server test 'Test Files 29 passed (29)', 'Tests 366 passed (366)'; typecheck exit 0 (tsc, tsconfig.typecheck.json, 'check:test-typecheck: OK', 0 debt; the test layer is compiled there). ABLATION at 66aed12 with node scripts/ablation-replace.mjs (wrap mode) under an outer trap restore EXIT INT TERM on the absolute path: fix line replaced by 'const code: string = resolved.code ?? (isEngineDuplicateRecordEnvelope(thrown) ? ABLATION_23587_NO_WIRE_MAP : ...)' (anchor 1 to 0, blob 401887f362 to 0630ef01a7; on disk marker 1, fix line 0); plugin-hono-server rebuilt; ablation-dist-preflight: marker present in dist/index.js and dist/index.mjs. Unit: 'Tests 6 failed | 43 passed (49)', exactly the 6 positive rows red, all controls green. Real rig: the three doors back to 409 DUPLICATE_RECORD without details; every control unchanged. Restore: blob == HEAD 401887f362, git diff HEAD empty; after rebuild, preflight --absent: marker absent from all 6 built files, 'working tree clean against HEAD'. Consumer suites (the wire shape of a public door changed), under the lock, exit 0: @objectstack/verify unique-scope + handle.observe-writes 'Tests 24 passed (24)'; @objectstack/http-conformance '13 passed' files, 'Tests 144 passed (144)'; @objectstack/hono '8 passed' files, 'Tests 161 passed (161)'. Readings came from an uncommitted scratch rig in packages/qa/http-conformance/src (real createHonoApp, real engine and protocol over SQLite WASM, plus a HonoHttpServer route and raw mount on the same kernel), deleted before the restore proof; a copy is in the session scratchpad.",
"gates": "LOCAL at the final head 3b01195. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 5 changed paths vs merge base 09dd537) derived 95 commands; all 95 ran sequentially, each exit code recorded 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 exited 3 (PREREQUISITE NOT MET: no packages/client-react/dist and no workspace dist/ after the merge). After pnpm turbo run build --filter='!@objectstack/docs' --concurrency=2 under the lock ('75 successful', '74 cached'), both exited 0: '263 prose examples type-check across 3 surface(s)' and the cjs-loads provenance 'entries/packages ... 110/68'. --ran: 'Run reconciliation — 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN.' (exit 0; ran.list carries the re-run codes, first-pass record kept in the scratchpad). Named among them, all exit 0: check:route-envelope, check:dispatcher-error-vocabulary, check:error-status-conformance ('every derivable runtime status is documented, and every documented status is reachable'), check:doc-anchors ('518 internal #fragment link(s) ... all resolve'), @objectstack/spec check:docs, check:nul-bytes, check:type-check-debt ('none above its recorded number'). Roster gates under touched directories, exit 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity; plus pnpm check:durability-log-level exit 0. Lint lane blind spot: eslint --no-inline-config packages/plugins/plugin-hono-server exit 0, no findings (eslint.config.mjs unchanged by the merge); repo-wide pnpm lint is CI's. Post-merge (packages/spec moved on main's side): pnpm --filter @objectstack/spec build && check:generated exit 0, 'All 14 generated artifacts are up to date'. CI on 3b01195, read once at PR open: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 22 check-runs in_progress (among them Check Changeset, Lint · ESLint, Type Check · workspace, Governed Surface Queue Guard, 'The card this PR closes must claim this branch'). CI convergence is the PM's.",
"mcp_calls": "0",
"api_writes": "2 relay strokes (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]), each one POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, pr_create (run 38750288654): POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/23682/assignees ['os-bill']; read back 13729 bytes identical, assignees os-bill. Stroke 2: POST /repos//issues/23587/comments (this report). PR-body PATCH: 0. label-write: 0. git push x5 (empty-branch probe, df28bfd, 66aed12, 9a6754f, 3b01195), not REST. Reads were REST GETs through gh api.",
"line_budget": "n/a",
"files_changed": [
"packages/plugins/plugin-hono-server/src/adapter.ts",
"packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts",
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/error-catalog.mdx",
".changeset/23587-hono-unique-violation.md"
],
"before_after": [
"createHonoApp POST /api/v1/data/udoc repeating a unique email · before (7e6e4e1): 409 {success:false,error:{code:DUPLICATE_RECORD,message:"Duplicate record refused on 'udoc': a unique constraint already holds these values. No record was written."}} · after (66aed12 rebuilt): 409 {success:false,error:{code:UNIQUE_VIOLATION,message:(the same sentence),details:{developerMessage:(the same sentence),object:udoc}}}",
"HonoHttpServer route (wrap) whose handler lets engine.insert's throw escape · before: the same DUPLICATE_RECORD body · after: the same UNIQUE_VIOLATION body",
"HonoHttpServer raw mount (transport onError) · before: the same DUPLICATE_RECORD body · after: the same UNIQUE_VIOLATION body",
"field: absent in every real reading (composite tenancy-scoped index uniq_udoc_organization_id_email, no single column named); with a producer-named field the unit pins read details keys exactly developerMessage, field, object on all three doors"
],
"controls": [
"hook-thrown DUPLICATE_RECORD (beforeInsert hook, {code,status:409}, not the engine's class) · createHonoApp: before DUPLICATE_RECORD, keys code+message · after DUPLICATE_RECORD, keys code+message",
"hook-thrown DUPLICATE_RECORD · HonoHttpServer route: before DUPLICATE_RECORD · after DUPLICATE_RECORD",
"DELETE_RESTRICTED (restrict lookup) · before 409 DELETE_RESTRICTED, details developerMessage/dependentObject/dependentCount · after identical",
"INVALID_FIELD (?nosuchfield=1) · before 400 INVALID_FIELD, details field/object · after identical",
"CONCURRENT_UPDATE (stale If-Match) · before 409 CONCURRENT_UPDATE, details currentVersion/currentRecord · after identical",
"OBJECT_NOT_FOUND (/data/ghost) · before 404 OBJECT_NOT_FOUND, details object · after identical",
"unit: class name DuplicateRecordError with code RESOURCE_CONFLICT keeps RESOURCE_CONFLICT (3 doors); the existing [#23587]/[#23531] arm rows and both agreement-rule controls stay green"
],
"deviations": [
"origin/main 09dd537 was merged before opening (AGENTS.md section 10; 3 incoming commits, none touching the 5 changed paths), as merge commit 3b01195 with git's default message and no trailer lines. packages/spec moved on main's side, so spec was rebuilt and check:generated ran (exit 0).",
"Two derived gates first exited 3 (PREREQUISITE NOT MET) and were re-run after a cache-backed build; ran.list carries the re-run codes.",
"Beyond the package's own suites, three consumer suites ran (verify, http-conformance, hono), because the wire shape of a public door changed for one class.",
"createHonoApp's door is pinned through declaredEnvelopeForThrow directly (the function its catch arms call): plugin-hono-server cannot import @objectstack/hono, which depends on it, and packages/qa/http-conformance is outside this file surface. The real createHonoApp door is covered by the uncommitted rig's before/after/ablation readings.",
"Docs: the 'every route answers the wire code UNIQUE_VIOLATION' sentences were kept and now name the Hono doors beside the REST door. They were not narrowed, though measured still false on the runtime dispatcher's own door; that door is out_of_scope_findings[0], and the sentences move with it.",
"The first package-test call got no lock turn (exit 99 behind the CLI dev's unit run) and the harness moved the call to the background; its typecheck half was waited on with tail --pid. Both test and typecheck were then re-run in the foreground at the final head, and only those readings are cited.",
"The unreleased .changeset/23587-hono-structured-arm-fields.md says a duplicate insert's DUPLICATE_RECORD 'gains nothing' at these doors, which this change contradicts. Not rewritten, per the dispatch; noted for release-text review 6191938402 (out_of_scope_findings[1])."
],
"open_questions": [
{
"question": "message for the engine's duplicate at the Hono doors: keep the thrown engine sentence (then details.developerMessage repeats it byte for byte), or take RestServer's arm sentence ('A record with this email already exists')?",
"options": [
"A: keep the thrown sentence (this PR). One message rule at these doors, the published Hono-door text ('the message is the thrown one'), and the batch-row precedent (toRowApiError's message is the engine's sentence). Cost: developerMessage duplicates message for this one code.",
"B: use the arm's curated sentence as message for a translated code, making the body the exact nested image of RestServer's. Cost: an exception to the thrown-message rule in declaredEnvelopeForThrow, a second wire change in the same release, and docs to re-state."
],
"recommendation": "A: the ruling moves the code, not the sentence; A adds no second rule and no second wire change, and nothing measured reads the sentence (no consumer measured). B can be its own card if a client needs the curated text."
}
],
"out_of_scope_findings": [
"class: b · reach: measured at a public door: on a createHonoApp host at 9a6754f (runtime dist at 7e6e4e1), POST /api/v1/actions/udoc/dup, a code-registered script action whose body repeats a unique email, answered 409 {success:false,error:{code:DUPLICATE_RECORD,message:'Duplicate record refused on udoc: ...',httpStatus:409}} · evidence: the maintainer ruling recorded in commit 65846bc ('one wire spelling on every route, UNIQUE_VIOLATION'); httpStatus shows the writer is HttpDispatcher.errorFromThrown (packages/runtime/src/http-dispatcher.ts), reached from the actions domain's declared-status arm (packages/runtime/src/domains/actions.ts), which never reaches declaredEnvelopeForThrow; the same writer serves the other dispatcher domains' catch arms (meta, automation, mcp, catalog-activation), not measured there · Seam: spec:ErrorCode UNIQUE_VIOLATION (ruling 65846bc) → runtime:HttpDispatcher.errorFromThrown | consumer: not measured · carries the published 'every route answers the wire code UNIQUE_VIOLATION' sentences (error-handling.mdx #### UNIQUE_VIOLATION, error-catalog.mdx ### UNIQUE_VIOLATION) and DuplicateRecordError.developerMessage's 'on every route' · same ruling family as this card, different door and package (packages/runtime), so its own card · dedupe words: DUPLICATE_RECORD UNIQUE_VIOLATION errorFromThrown HttpDispatcher actions dispatcher httpStatus 409 wire spelling",
"carrier: release-text review 6191938402 (the seat's one-story question) · noted, not filed: .changeset/23587-hono-structured-arm-fields.md (unreleased) says a duplicate insert's DUPLICATE_RECORD 'gains nothing' at the Hono doors; .changeset/23587-hono-unique-violation.md (this PR) moves that code to UNIQUE_VIOLATION, so a release carrying both prints two stories"
],
"pr_body_changes": []
}
Landed: PR #23682 → c9810dd9d1 (#23587's remainder, Fixes #23587). With PR #23616's first half, the card is done, and pm:dispatched is stripped
domain:cli seat 1 (#6024) · os-bill · session session_01FLwgbTSmGMrqsXkBDsiwfG · 2026-10-15T15:08Z · holder of claim 6192578085, released by this landing.
reading
value
queue
added_to_merge_queue 14:46Z, merged 15:07Z
origin/main
c9810dd9d1: one parent (a queue squash), an ancestor of origin/main, 5 files +212 / −24 as reviewed at 3b01195d1; no queue branch left
card
closed completed by the PR's Fixes #23587 (15:07:47Z); pm:dispatched removed by targeted delete
Both Hono doors (createHonoApp's catch-all, and a HonoHttpServer route or its raw mount) answer the engine's DuplicateRecordError as 409 UNIQUE_VIOLATION, with developerMessage, object, and field where the driver named it. This follows the maintainer's ruling recorded in 65846bc46.
A hook that only speaks DUPLICATE_RECORD keeps its code. The engine's in-process code is unchanged.
The changeset states the wire change and the one-line client fix.
Carried to the release-text review 6191938402: the unreleased .changeset/23587-hono-structured-arm-fields.md says a duplicate's code "gains nothing" at these doors, while .changeset/23587-hono-unique-violation.md moves it. A release carrying both tells two stories.
Holds released:packages/plugins/plugin-hono-server/src/adapter.ts and its pin file, and the two docs pages.
Activity
docs(api): a Hono door answers the structured refusals nested, with t…
objectstack-fleet commentedon Oct 15, 2026
os-dev-report⚠️ The derivation reports a stale tree: this branch is 12 commits behind
{
"issue": 23587,
"round": "patch round 1 (REWORK per the seat's review 6185187566 on PR #23616, pointer 6185197160; same claim 6183773313)",
"status": "done",
"branch": "claude/issue-23587-hono-delete-restricted-fields",
"pr": "#23616",
"head": "83c41843689e3eed0e018be8f1cebf6d4faea3df",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "The one item is done in a docs-only commit, 83c4184, on top of d1a39b3. adapter.ts, the pins and the changeset are untouched. Every published content/docs statement about the answer to DELETE_RESTRICTED, INVALID_FIELD, OBJECT_NOT_FOUND, CONCURRENT_UPDATE and the five 403 codes that the Hono doors make false is corrected. That covers the seat's three known lines (error-handling.mdx :130-:132, :541-:542, :636-:637 on origin/main) and the statements around them: corollary 1 named DELETE_RESTRICTED/INVALID_FIELD 'always flat'; the nested writer list said 'two writers'; the httpStatus bullet; the route-family table; OBJECT_NOT_FOUND 'also flat'. A new '### Hono doors' section gives a per-code error.details table. wire-format.mdx :328 is scoped to the kernel REST server, with a new '### Hono hosts nest the extras' section carrying a CONCURRENT_UPDATE example. api/index.mdx gains :221-:222. Every corrected sentence agrees with a reading taken on the same createHonoApp rig at d1a39b3 (the code this round leaves unchanged): DELETE_RESTRICTED, CONCURRENT_UPDATE, INVALID_FIELD, OBJECT_NOT_FOUND and VALIDATION_FAILED were nested with their fields under error.details, the duplicate insert nested as DUPLICATE_RECORD, and none of the six carried httpStatus. The REST twin was computed by mapDataError on each same thrown error. The five 403 rows rest on the unit pins. VALIDATION_FAILED's four lines (incl. :1043 and readError) are corrected under the bounded in-place rule, all four conditions checked. The duplicate-record statements (:609 and :621-:624 on main, error-catalog.mdx :507-:510) are left, because condition 2 fails: the card's remainder changes that code. They are listed under 'Found, not fixed here' with carrier #23587's remainder. No METADATA_CONFLICT body statement exists in the docs, and the 403 codes appear only in status tables. Per the coordinator's correction, NO PR-body PATCH was sent. Every body change, including the seat's three rulings, is in pr_body_changes as old/new pairs, checked to apply once each to the live body (17769 bytes, identical to what was sent at create) and to compose (25030 bytes, no tag-shaped text, no closing keyword). The run was interrupted by an API session limit after the gate battery and resumed from its logs; no step was repeated. The worktree is removed right after this report.",
"tests": "No package file changed this round, so round 0's suites at d1a39b3 stand: plugin-hono-server 'Test Files 29 passed (29)', 'Tests 354 passed (354)'; http-conformance 'Test Files 13 passed (13)', 'Tests 142 passed (142)'; both typechecks OK. The docs readings came from a scratch conformance file (deleted, never committed), run under the verify lock after pnpm turbo run build --filter='@objectstack/http-conformance^...' ('36 successful', '36 cached'), with exit 0. On a real createHonoApp host over the real engine and protocol (SQLite WASM), each request's Hono body was read, and mapDataError was applied to the same thrown error. DELETE_RESTRICTED: 409 nested, details {developerMessage, dependentObject, dependentCount} (REST adds object). CONCURRENT_UPDATE: 409 nested, details {currentVersion} (REST adds currentRecord, object). INVALID_FIELD: 400 nested, details {field, object}. OBJECT_NOT_FOUND: 404 nested, details {object}, message "Object 'ghost' not found" (REST: 'is not registered'). VALIDATION_FAILED (missing required title): 400 nested, details {fields:[{field:'title',code:'required',…}]} (REST adds object). Duplicate insert: 409 nested DUPLICATE_RECORD, no details (REST: UNIQUE_VIOLATION with developerMessage, field, object). None of the six Hono bodies carries httpStatus.",
"gates": "LOCAL at 83c4184. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 7 changed paths vs merge base 16abea8) derived 95 commands (round 0's 66 plus 29 docs families). All 95 ran, each exit code captured before any pipe into a ':: exit N' record. 93 exited 0 on the first pass. pnpm --filter @objectstack/spec run check:skill-examples and pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: no packages/client-react/dist, no workspace dist/ in the recreated worktree). Under the lock, after pnpm turbo run build --concurrency=2 ('76 successful', '75 cached'), both exited 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' (exit 0). The docs gates on the touched pages all exited 0, among them 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. check:route-envelope and check:dispatcher-error-vocabulary (derived) and check:durability-log-level (run separately): exit 0. The four roster gates under touched directories, all exit 0: check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity. The derivation warns STALE TREE: the branch is 12 commits behind origin/main 93f3526, and 7 gate files changed in that range, so the local run read the branch's copies. None of those commits touches the 7 changed paths and git merge-tree --write-tree HEAD origin/main is clean, so the branch was not merged (after the PR opened, main is merged only on a conflict, a main-caused red or a dequeue). CI runs the merge ref. CI on 83c4184, read once: 41 check-runs, 36 success, 5 skipped, 0 failed, 0 in progress. No lintable source changed this round. Round 0's eslint --no-inline-config over both packages (exit 0) stands.",
"mcp_calls": "0",
"api_writes": "This round: 1 relay stroke (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]) = POST /repos/objectstack-ai/objectstack/dispatches carrying POST /repos//issues/23587/comments (this report). PR-body PATCH: 0 (per the coordinator's correction; the changes are in pr_body_changes). label-write: 0. git push x1 (83c4184), not REST. Reads were REST GETs through gh api.",
"line_budget": "n/a",
"files_changed": [
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/api/index.mdx"
],
"files_changed_pr_total": [
"packages/plugins/plugin-hono-server/src/adapter.ts",
"packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts",
"packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts",
".changeset/23587-hono-structured-arm-fields.md",
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/api/index.mdx"
],
"docs_lines_changed": [
"content/docs/protocol/kernel/error-handling.mdx (head 83c4184; origin/main lines in brackets): :75-:77 [:75-:78] corollary 1 keeps only UNIQUE_VIOLATION 'always flat'; :81-:84 [:82] corollary 2 adds DELETE_RESTRICTED/INVALID_FIELD/OBJECT_NOT_FOUND, flat on @objectstack/rest, nested on a Hono door with fields in error.details; :107 [:105] 'three writers'; :113-:114 [new] the declaredEnvelopeForThrow writer; :133 [:129] Hono renderer never writes httpStatus; :134-:137 [:130-:132] error.details bullet; :183 [:178] /data row scoped to the rest door; :193 [new] Hono route-family row; :212-:252 [new] '### Hono doors' section (example, per-code table, never-sent list); :397-:398 and :400-:401 [:350, :352-:353] VALIDATION_FAILED (in-place rule); :589-:591 [:541-:542] INVALID_FIELD; :646-:647 [:597] OBJECT_NOT_FOUND; :686-:688 [:636-:637] DELETE_RESTRICTED; :1043-:1045 [:992-:993] readError reads err.details?.fields (in-place rule); :1094-:1096 [:1042-:1043] field-validation guidance (in-place rule)",
"content/docs/api/wire-format.mdx: :328 [:328] scoped to the kernel REST server plus pointer; :485-:507 [new] '### Hono hosts nest the extras' with the CONCURRENT_UPDATE example",
"content/docs/api/index.mdx: :221-:222 [new] createHonoApp / HonoHttpServer answer in this envelope without httpStatus, classification fields in details except currentRecord and a route-taken object"
],
"pr_body_changes": [
{
"old": "This lands the card's three structured fields on both Hono doors, under one rule that covers every arm of
RestServer's classification. What stays on the card is one decision, not work: whether these doors should also carryDELETE_RESTRICTED'sobject, the object the request's ROUTE named (see "What stays off these doors").","new": "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 round 1, the seat's review6185187566). What stays on the card is one location, folded there as its remainder by the seat's pointer6185197160:declaredEnvelopeForThrowanswers the engine'sDuplicateRecordErroras409 DUPLICATE_RECORDon both Hono doors, whereRestServeranswersUNIQUE_VIOLATIONwithdeveloperMessage,fieldandobject. The governing text is the maintainer ruling recorded at commit65846bc46: one wire spelling on every route. This PR's agreement rule leaves that code alone on purpose."},
{
"old": "Measured unchanged there; see "Found, not fixed here" |",
"new": "Measured unchanged there; see Acceptance notes (#22222) |"
},
{
"old": "the arm answers a different code, so it is not consulted. See "Found, not fixed here" |",
"new": "the arm answers a different code, so it is not consulted. This is the card's remainder (opening paragraph) |"
},
{
"old": "REST would answer differently. Whether to thread a route into the recogniser is the open question this card keeps.",
"new": "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 landedCONCURRENT_UPDATEwithout it, and #23531's Landed comment6183626856, which namescurrentRecordas that card's only remainder."},
{
"old": "-
@objectstack/hono's own suite is not run locally:","new": "- Patch round 1 (docs only, head
83c418436). No package file changed sinced1a39b380, so the package suites above stand. The docs readings were taken on the samecreateHonoApprig with a scratch file that was not committed; see "Docs corrected (patch round 1)".\n-@objectstack/hono's own suite is not run locally:"},
{
"old": "## Gates\n\nLocal, at the final head
d1a39b380:\n\n-node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths, derived 66 commands from the 4 changed paths (merge base16abea884). All 66 ran, each exit code captured before any pipe.\n - 64 exited 0 on the first pass.\n -pnpm check:dual-build-cjs-loadsandpnpm check:type-check-debtfirst exited 3,PREREQUISITE NOT MET: the fresh worktree had no workspacedist/, and the debt gate's closure build did not get a turn on the verify lock.\n - Under the lock, after a cache-backedpnpm turbo run build --concurrency=2(76 successful,49 cached), both measured exit 0. The first read110 published require entry point(s) across 68 package(s) load. The second read1 ledger entr(ies) re-measured … none above its recorded number.\n---ranreconciliation:66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN(a derived zero).\n- Named by the dispatch, all exit 0.pnpm check:route-envelopeandpnpm check:dispatcher-error-vocabularyare in the derived list.pnpm check:durability-log-levelis not, and ran separately:46 durability-critical catch seam(s), all loud ….\n- The four artifact-roster gates the derivation marks as keeping their roster in a directory this diff touches, all exit 0:node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casing,pnpm check:filter-alias-parity.\n- Lint:pnpm lintis the repo-level scan CI owns (Lint · ESLint). Locally it was narrowed to the two touched packages with the same flags,eslint --no-inline-config packages/plugins/plugin-hono-server packages/qa/http-conformance: exit 0, no findings.eslint.config.mjsis unchanged between the merge base andorigin/main.\n-git merge-tree --write-tree HEAD origin/main(dd4ef3850) is clean. None of the five commits since the merge base touches these packages,@objectstack/types, the adapter or the dispatcher, so the branch was not merged again.\n- Every family CI runs on this PR is CI's to report on its check-runs.\n\n","new": "## Gates\n\nLocal, at the patch-round head
83c418436:\n\n-node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths, derived 95 commands from the 7 changed paths (merge base16abea884); the 3 docs pages add 29 families to round 0's 66. All 95 ran, each exit code captured before any pipe.\n - 93 exited 0 on the first pass.\n -pnpm --filter @objectstack/spec run check:skill-examplesandpnpm check:dual-build-cjs-loadsfirst exited 3,PREREQUISITE NOT MET: the recreated worktree had nopackages/client-react/distand no workspacedist/.\n - Under the lock, after a cache-backedpnpm turbo run build --concurrency=2(76 successful,75 cached), both measured exit 0:263 prose examples type-check across 3 surface(s)and110 published require entry point(s) across 68 package(s) load.\n---ranreconciliation:95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN(a derived zero).\n- 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-nameandcheck:section-landing-index.\n-pnpm check:route-envelopeandpnpm check:dispatcher-error-vocabulary(derived), andpnpm check:durability-log-level(run separately): exit 0.\n- 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.\n-origin/main(93f3526e6), and 7 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, andgit merge-tree --write-tree HEAD origin/mainis clean, so the branch is not merged (after a PR opens,mainis merged only on a conflict, a main-caused red or a queue removal). CI runs the merge ref, which carries the current scripts.\n- CI on83c418436, read once: 41 check-runs, 36 success, 5 skipped, none failed or in progress.\n- Round 0 (code headd1a39b380): 66 derived, 66 run, 0 NOT-MEASURED;eslint --no-inline-configover the two touched packages exit 0. This round changes no lintable source.\n\n"},
{
"old": "## Found, not fixed here (for the seat to file)\n\n-
createHonoApp's/metasave answers409 METADATA_CONFLICTwithoutcurrentVersion. Class (b), measured at16abea884:PUT /api/v1/meta/view/case_gridwith a staleIf-Matchanswers409 {\"success\":false,\"error\":{\"code\":\"METADATA_CONFLICT\",\"message\":\"… (current is hmac-sha256:…)\",\"httpStatus\":409}}. The spec'sMetadataConflictErrorSchema.currentVersionsays "Send it asIf-Matchto overwrite deliberately … Opaque — echo it verbatim, never parse it", andRestServersends it. ThehttpStatusshows the answer comes from the dispatcher's own door (HttpDispatcher.errorFromThrown,packages/runtime/src/http-dispatcher.ts), which forwards only the resolver'sdetails. That is the same family, but a different door with its own envelope suite, so it is out of this file surface.Seam: spec:MetadataConflictErrorSchema.currentVersion → runtime:HttpDispatcher.errorFromThrown | consumer: not measured.\n- The Hono doors answer a duplicate insert as409 DUPLICATE_RECORD. Class (b), measured at16abea884:POST /api/v1/data/udocrepeating a uniqueemail. The maintainer ruling recorded atstructuredCodeAnswer(2026-09-03, commit65846bc46) says "a unique-constraint refusal has ONE wire spelling on every route,UNIQUE_VIOLATION". The engine's owndeveloperMessagesays "over the HTTP API this refusal is reported ascode === 'UNIQUE_VIOLATION'on every route".RestServeranswersUNIQUE_VIOLATION. This PR's agreement rule deliberately leaves the code alone (an arm adds context, never changes the answer), so a change of code is a separate decision.Seam: spec:ErrorCode UNIQUE_VIOLATION (ruling 65846bc46) → runtime:plugin-hono-server declaredEnvelopeForThrow (createHonoApp thrownJson, HonoHttpServer wrap/onError) | consumer: not measured.\n\n","new": "## Docs corrected (patch round 1)\n\nHead
83c418436(commitdocs(api): …). Line numbers are given on that head, with theorigin/mainlines in brackets; those three files are unchanged onmainsince the merge base. Readings:\n- R1: a realcreateHonoApphost over the real engine and protocol (SQLite WASM), atd1a39b380, the code this round does not change. A scratch file, not committed, withRestServer's body for each request computed from the same thrown error bymapDataError.\n- U: the[#23587]unit pins on aHonoHttpServerroute.\n\nR1 measured, per code, on the Hono door and then the REST twin:\n-DELETE_RESTRICTED: nested,details{developerMessage, dependentObject, dependentCount}/ flat with those three andobject.\n-CONCURRENT_UPDATE: nested,details{currentVersion}/ flat withcurrentVersion,currentRecord,object.\n-INVALID_FIELD: nested,{field, object}/ flat with both.\n-OBJECT_NOT_FOUND: nested,{object}, messageObject 'ghost' not found/ flat,errorObject 'ghost' is not registered.\n-VALIDATION_FAILED(a missing required field): nested,{fields}/ flat withfieldsandobject.\n- A duplicate insert: nestedDUPLICATE_RECORD, nodetails/ flatUNIQUE_VIOLATIONwithdeveloperMessage,field,object.\n- None of the six Hono bodies carrieshttpStatus.\n\ncontent/docs/protocol/kernel/error-handling.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n|:75-:77(:75-:78) | Corollary 1 keepsUNIQUE_VIOLATION"always flat" and dropsDELETE_RESTRICTED/INVALID_FIELDfrom it | R1 (UNIQUE_VIOLATIONanswered by no Hono door) |\n|:81-:84(:82) | Corollary 2 gainsDELETE_RESTRICTED,INVALID_FIELD,OBJECT_NOT_FOUND: flat on the@objectstack/rest/datadoor, nested on a Hono door with their fields undererror.details| R1 |\n|:107(:105) | "two writers" → "three writers" | R1 |\n|:113-:114(new) | the third writer,declaredEnvelopeForThrow, the Hono doors' renderer | R1, U |\n|:133(:129) |error.httpStatus: the Hono doors' renderer never writes it | R1 (0 of 6 bodies) |\n|:134-:137(:130-:132) |error.details: "and nowhere else" removed; the Hono doors put the per-condition siblings there | R1, U |\n|:183(:178) | the{base}/data/*row is scoped to the@objectstack/restdoor | R1 (the same routes answer nested oncreateHonoApp) |\n|:193(new) | route-family row: a declared refusal thrown out of the dispatcher on acreateHonoApphost, or out of aHonoHttpServerroute, is nested | R1, U |\n|:212-:252(new) |### Hono doors: who renders, theDELETE_RESTRICTEDexample, the per-codedetailstable and what a Hono door never sends (httpStatus,currentRecord, a route-takenobject; the thrownmessage) | Example and theDELETE_RESTRICTED,CONCURRENT_UPDATE,INVALID_FIELD,OBJECT_NOT_FOUND,VALIDATION_FAILEDrows: R1. The five 403 rows and "dependentCountwhen the flat door would send it": U |\n|:397-:398(:350) |VALIDATION_FAILEDenvelope: flat on the rest door, nested on a Hono door (in-place, below) | R1 |\n|:400-:401(:352-:353) |VALIDATION_FAILED:fieldsiserror.details.fieldson a Hono door (in-place) | R1 |\n|:589-:591(:541-:542) |INVALID_FIELDenvelope: nested on a Hono door,error.details.field/.object| R1 |\n|:646-:647(:597) |OBJECT_NOT_FOUND: nested on a Hono door,objectinerror.details| R1 |\n|:686-:688(:636-:637) |DELETE_RESTRICTEDenvelope: nested on a Hono door, the three fields inerror.details, noobject| R1 |\n|:1043-:1045(:992-:993) |readErrorreadserr.details?.fieldson a nested body (in-place) | R1 |\n|:1094-:1096(:1042-:1043) | field-validation guidance:error.details.fieldson a Hono door; "nodetails.fieldsbag on either envelope" removed (in-place) | R1 |\n\ncontent/docs/api/wire-format.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n|:328(:328) | "Data route errors" scoped to the kernel REST server, with a pointer to the Hono hosts | R1 |\n|:485-:507(new) |### Hono hosts nest the extras: nested, nohttpStatus; the classification's extras inerror.details,currentRecordand a route-takenobjectnot sent; theCONCURRENT_UPDATEexample | R1 |\n\ncontent/docs/api/index.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n|:221-:222(new) | on acreateHonoApphost or aHonoHttpServerroute, a declared refusal is nested withouthttpStatus, and the classification's fields ride indetails, exceptcurrentRecordand a route-takenobject| R1 |\n\nThe bounded in-place rule, forVALIDATION_FAILED. This PR leaves that code's Hono-door body byte-identical. Four lines were false in the same way and are corrected::397-:398,:400-:401,:1043-:1045and:1094-:1096. Each condition was checked:\n1. Same defect class: a published statement that a refusal is never nested, or has nodetails, which is false at the Hono doors.\n2. Mechanical, with the shape pinned: R1 measurederror.details.fields, andhandler-throw-declared-envelope.test.tsalready pins it on a direct-mount route.\n3. No other claim holds the file: the seat's pointer6185197160read all 15 open PRs.\n4. Same gate family: the docs gates on the same page, with no new verification surface.\n\nRead and left unchanged:\n-content/docs/api/error-handling-server.mdx:10: it is scoped to the kernel REST server, and true there.\n-content/docs/api/data-api.mdx:45-:52: theINVALID_FIELDexample is the REST server's body and claims no exclusivity.\n-content/docs/protocol/objectql/types.mdx:717("the response carriesdependentObjectanddependentCount"): true on both doors now.\n-error-handling.mdx:1390-:1392(DELETE_RESTRICTEDwithholding): true on both doors.\n- TheRECORD_NOT_FOUNDcallout at:633-:649: it names the/datadoor and makes no exclusivity claim.\n- No published statement namesMETADATA_CONFLICT's or the five 403 codes' body shape. The 403 codes appear only in status tables (permissions/attachments-access.mdx).\n- ⛔content/docs/releases/is untouched.\n\n## Found, not fixed here\n\n- 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 acreateHonoApphost a duplicate insert was measured as409 {\"success\":false,\"error\":{\"code\":\"DUPLICATE_RECORD\",\"message\":\"Duplicate record refused on 'task': …\"}}, at16abea884and again atd1a39b380. These lines say otherwise, and they are left as they stand:\n -content/docs/protocol/kernel/error-handling.mdx:659(:609onorigin/main):UNIQUE_VIOLATION"flat … so it is never nested".\n -:671-:674(:621-:624onorigin/main): "every route answers the wire codeUNIQUE_VIOLATIONand the in-process spelling never crosses HTTP".\n -content/docs/api/error-catalog.mdx:507-:510: the same sentence.\n -:75-:76of the corrected corollary keepsUNIQUE_VIOLATION"always flat". That holds for every reading taken: no Hono door answeredUNIQUE_VIOLATION. It moves with the remainder.\n\n"},
{
"old": "## Acceptance notes\n\n",
"new": "## Acceptance notes\n\n-
createHonoApp's/metasave answers409 METADATA_CONFLICTthrough the runtime dispatcher's own writer (HttpDispatcher.errorFromThrown), withoutcurrentVersion. Measured at16abea884. This is #22222's question, closednot_plannedon the maintainer's hosted measurement6110136480: a hostedPUT /metareachesRestServer, not the catch-all. Its triage6056494314set the build shape for the catch-all case: a declaredApiErrorSchemamember inpackages/spec, not this file. Not refiled.\n"}
],
"deviations": [
"No PR-body PATCH was sent: the coordinator's correction overrides the seat's offered PATCH (the role file wins). Every body change is in pr_body_changes: 8 old/new pairs, each old text occurring exactly once in the live body, applied in order to give a 25030-byte body.",
"Scope beyond the seat's three known lines: corollary 1/2 (:75-:84), the writer list (:107, :113-:114), the httpStatus bullet (:133), the route-family table (:183, :193), the new '### Hono doors' section, wire-format :328 plus its new section, and api/index :221-:222. Each states the answer to a listed code on a Hono host, and each matches an R1 reading. The writer and httpStatus lines were needed for the corrected bullets to stay consistent with the page's own writer model.",
"CONCURRENT_UPDATE's Hono body is not changed by this PR (PR #23582 landed it). Its statements were corrected anyway, because the seat's list names the code.",
"VALIDATION_FAILED, a code this PR does not change: 4 lines were corrected under the bounded in-place rule (conditions in the proposed PR body). The duplicate-record statements were NOT corrected: condition 2 fails because the card's remainder changes that code, so they are under 'Found, not fixed here'.",
"Stale tree: the branch is 12 commits behind origin/main, and 7 gate scripts changed in that range. The branch was not merged (rule: only on a conflict, a main red or a dequeue). The local gate run read the branch's script copies, and CI's merge-ref run is the current-script reading.",
"The run stopped on an API session limit after the 95-command battery; it resumed from the on-disk logs, and no measurement was repeated. The worktree was recreated at the start of the round from the local branch ref, verified equal to origin's head d1a39b3."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #23587's remainder (the duplicate-record code at the Hono doors) · noted, not filed: the published statements error-handling.mdx:659 and :671-:674 (origin/main :609, :621-:624), error-catalog.mdx:507-:510, and the corrected corollary's UNIQUE_VIOLATION 'always flat' (:75-:76) move with that remainder; measured today: a duplicate insert on createHonoApp answers 409 nested DUPLICATE_RECORD",
"carrier: none (承接者:无) · noted in the PR's Acceptance notes only, unchanged from round 0: OBJECT_NOT_FOUND's sentence differs between doors; the REST doors differ on a declared-5xx arm (unreachable today)"
]
}
Generated by Claude Code
docs(api): a createHonoApp host answers /data nested only where its c…
objectstack-fleet commentedon Oct 15, 2026
os-dev-report⚠️ The derivation reports a stale tree: this branch is 16 commits behind
{
"issue": 23587,
"round": "patch round 2 of 2 (REWORK per the seat's review 6186145904 on PR #23616; same claim 6183773313)",
"status": "done",
"branch": "claude/issue-23587-hono-delete-restricted-fields",
"pr": "#23616",
"head": "9675127f30b3099b7b8c1e609006b2c28ec83d6b",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "The one item is done in a docs-only commit, 9675127, on top of 83c4184. No package file changed. The sentences that assumed every createHonoApp host serves /data through the runtime dispatcher are scoped to where they hold (a catch-all that serves /data, i.e. no @objectstack/rest route registered ahead of it), and each says the hosted composition answers flat from the REST door: wire-format.mdx:328; api/index.mdx:221; error-handling.mdx's route-table row :193; '### Hono doors' :220-:224 and its example caption :230-:231. The wire-format example caption :492 has the same assumption and is scoped too. The page's split-by-door callout (:196-:206, was :190-:194) now covers /data as well as /meta: a createHonoApp host can be either; REST routes registered before the mount win the match and answer flat; with none ahead of the catch-all, both families answer nested through the dispatcher. MEASURED on a composed rig (R2, scratch, not committed, at 9675127), one kernel with HonoServerPlugin + createRestApiPlugin. The hosted composition (REST routes registered, then createHonoApp mounted with route('/', ...); the ALL /api/v1/* catch-all present) answered the same restricted DELETE /api/v1/data/account/ID as 409 FLAT {error, code: DELETE_RESTRICTED, developerMessage, dependentObject, dependentCount: 1, object: 'account'}, and GET ?nosuchfield=1 as 400 FLAT INVALID_FIELD with field and object. createHonoApp alone, over the same kernel, answered both NESTED with the fields under error.details. The /meta half rests on the maintainer's hosted measurement 6110136480. pr_body_changes is the COMPLETE set against the live PR-open body (17769 bytes, re-read unchanged): round 1's 8 pairs, updated for this round (opening, tests, gates, the docs section with round-2 rows and head line numbers, Found lines renumbered to :668 and :680-:683, Acceptance note). Each old text was checked to occur once, and the set composes to 27537 bytes with no tag-shaped text, no closing keyword and no control byte. No PR-body PATCH was sent. The worktree is removed right after this report.",
"tests": "No package file changed in either patch round; round 0's suites at d1a39b3 stand (plugin-hono-server 'Tests 354 passed (354)', http-conformance 'Tests 142 passed (142)', both typechecks OK). R2 was a scratch conformance file (deleted, never committed), run under the verify lock after a cache-backed pnpm turbo run build --filter='@objectstack/http-conformance^...' ('36 successful', '36 cached'). One kernel: ObjectQLPlugin, a stub auth service, HonoServerPlugin({port:0}), createRestApiPlugin({}), the SQLite WASM driver, account/contact with a restrict lookup, and the conformance suite's authz-resolver objects. The raw Hono app had 88 routes; after raw.route('/', createHonoApp({kernel, prefix:'/api/v1'})) it had 93, with ALL /api/v1/*. Composed host: DELETE /api/v1/data/account/ID gave 409 {"error":"This Account is still referenced by 1 Contact record(s) through “Account”. …","code":"DELETE_RESTRICTED","developerMessage":"Cannot delete account (ID): 1 dependent contact record(s) …","dependentObject":"contact","dependentCount":1,"object":"account"}. GET /api/v1/data/account?nosuchfield=1 gave 400 {"error":"Unknown field 'nosuchfield' on object 'account'. …","code":"INVALID_FIELD","field":"nosuchfield","object":"account"}. createHonoApp alone, same kernel: 409 {success:false,error:{code:DELETE_RESTRICTED,message,details:{developerMessage,dependentObject,dependentCount:1}}} and 400 {success:false,error:{code:INVALID_FIELD,message,details:{field,object}}}. The rig's first run, before the full authz-resolver object set was copied in, answered both composed requests as flat 503 SERVICE_UNAVAILABLE. That was also the REST door's dialect, but not the refusal under test; it was re-run with the objects and the reading above is from that run.",
"gates": "LOCAL at 9675127. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 7 changed paths vs merge base 16abea8) derived 95 commands, the same list as round 1. All 95 ran, each exit code captured before any pipe into a ':: exit N' record. 93 exited 0 on the first pass. pnpm --filter @objectstack/spec run check:skill-examples and pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: no packages/client-react/dist, no workspace dist/ in the recreated worktree). Under the lock, after pnpm turbo run build --concurrency=2 ('76 successful', '75 cached'), both exited 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' (exit 0). Docs gates on the touched pages, 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, check-section-landing-index. check:route-envelope and check:dispatcher-error-vocabulary (derived), check:durability-log-level (separately): exit 0. Roster gates under touched directories, all exit 0: check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity. STALE TREE warning: the branch is 16 commits behind origin/main 7c6b7f4, and 11 gate files changed in that range. None of those commits touches the 7 changed paths, and git merge-tree --write-tree HEAD origin/main is clean, so the branch was not merged (rule: only on a conflict, a main-caused red or a dequeue). CI runs the merge ref. CI on 9675127, read once: 41 check-runs, 36 success, 5 skipped, 0 failed, 0 in progress.",
"mcp_calls": "0",
"api_writes": "This round: 1 relay stroke (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]) = POST /repos/objectstack-ai/objectstack/dispatches carrying POST /repos//issues/23587/comments (this report). PR-body PATCH: 0. label-write: 0. git push x1 (9675127), not REST. Reads were REST GETs through gh api.",
"line_budget": "n/a",
"files_changed": [
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/api/index.mdx"
],
"files_changed_pr_total": [
"packages/plugins/plugin-hono-server/src/adapter.ts",
"packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts",
"packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts",
".changeset/23587-hono-structured-arm-fields.md",
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/api/index.mdx"
],
"docs_lines_changed_this_round": [
"content/docs/protocol/kernel/error-handling.mdx (head 9675127): :193 route-table row, '/data included only where no @objectstack/rest route is registered ahead of its catch-all (see the callout below)'; :196-:206 (origin/main :190-:194) the split-by-door callout widened from /meta to /meta and /data, with the hosted composition (REST routes before the mount) answering flat; :220-:224 '### Hono doors' first sentence scoped ('on a host whose catch-all serves /data; a host that registers @objectstack/rest's routes ahead of it answers those flat', with a pointer to the route-family heading); :230-:231 example caption 'on a createHonoApp host whose catch-all serves /data'",
"content/docs/api/wire-format.mdx: :328 'answers the same refusals nested instead when its catch-all serves /data … One that registers the kernel REST server's routes on the same Hono app before it mounts createHonoApp, the hosted composition, answers them flat, as below'; :492 example caption 'on a createHonoApp host whose catch-all serves /data'",
"content/docs/api/index.mdx: :221 '(the /data routes' refusals among them, where its catch-all serves /data; a host that registers the kernel REST server's routes ahead of the catch-all, the hosted composition, answers those flat, as above)'"
],
"docs_lines_changed_pr_total": "Listed with head line numbers, origin/main line numbers and the reading for each line in pr_body_changes[6].new, section 'Docs corrected (patch rounds 1 and 2)'.",
"pr_body_changes": [
{
"old": "This lands the card's three structured fields on both Hono doors, under one rule that covers every arm of
RestServer's classification. What stays on the card is one decision, not work: whether these doors should also carryDELETE_RESTRICTED'sobject, the object the request's ROUTE named (see "What stays off these doors").","new": "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 reviews6185187566and6186145904). What stays on the card is one location, folded there as its remainder by the seat's pointer6185197160:declaredEnvelopeForThrowanswers the engine'sDuplicateRecordErroras409 DUPLICATE_RECORDon both Hono doors, whereRestServeranswersUNIQUE_VIOLATIONwithdeveloperMessage,fieldandobject. The governing text is the maintainer ruling recorded at commit65846bc46: one wire spelling on every route. This PR's agreement rule leaves that code alone on purpose."},
{
"old": "Measured unchanged there; see "Found, not fixed here" |",
"new": "Measured unchanged there; see Acceptance notes (#22222) |"
},
{
"old": "the arm answers a different code, so it is not consulted. See "Found, not fixed here" |",
"new": "the arm answers a different code, so it is not consulted. This is the card's remainder (opening paragraph) |"
},
{
"old": "REST would answer differently. Whether to thread a route into the recogniser is the open question this card keeps.",
"new": "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 landedCONCURRENT_UPDATEwithout it, and #23531's Landed comment6183626856, which namescurrentRecordas that card's only remainder."},
{
"old": "-
@objectstack/hono's own suite is not run locally:","new": "- Patch rounds 1 and 2 (docs only, heads
83c418436and9675127f3). No package file changed sinced1a39b380, so the package suites above stand. The docs readings were taken with scratch files that were not committed: R1 on thecreateHonoApprig, and R2 on one kernel, comparing a composed host (@objectstack/rest's routes registered, thencreateHonoAppmounted) withcreateHonoAppalone. See "Docs corrected (patch rounds 1 and 2)".\n-@objectstack/hono's own suite is not run locally:"},
{
"old": "## Gates\n\nLocal, at the final head
d1a39b380:\n\n-node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths, derived 66 commands from the 4 changed paths (merge base16abea884). All 66 ran, each exit code captured before any pipe.\n - 64 exited 0 on the first pass.\n -pnpm check:dual-build-cjs-loadsandpnpm check:type-check-debtfirst exited 3,PREREQUISITE NOT MET: the fresh worktree had no workspacedist/, and the debt gate's closure build did not get a turn on the verify lock.\n - Under the lock, after a cache-backedpnpm turbo run build --concurrency=2(76 successful,49 cached), both measured exit 0. The first read110 published require entry point(s) across 68 package(s) load. The second read1 ledger entr(ies) re-measured … none above its recorded number.\n---ranreconciliation:66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN(a derived zero).\n- Named by the dispatch, all exit 0.pnpm check:route-envelopeandpnpm check:dispatcher-error-vocabularyare in the derived list.pnpm check:durability-log-levelis not, and ran separately:46 durability-critical catch seam(s), all loud ….\n- The four artifact-roster gates the derivation marks as keeping their roster in a directory this diff touches, all exit 0:node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casing,pnpm check:filter-alias-parity.\n- Lint:pnpm lintis the repo-level scan CI owns (Lint · ESLint). Locally it was narrowed to the two touched packages with the same flags,eslint --no-inline-config packages/plugins/plugin-hono-server packages/qa/http-conformance: exit 0, no findings.eslint.config.mjsis unchanged between the merge base andorigin/main.\n-git merge-tree --write-tree HEAD origin/main(dd4ef3850) is clean. None of the five commits since the merge base touches these packages,@objectstack/types, the adapter or the dispatcher, so the branch was not merged again.\n- Every family CI runs on this PR is CI's to report on its check-runs.\n\n","new": "## Gates\n\nLocal, at the final head
9675127f3:\n\n-node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths, derived 95 commands from the 7 changed paths (merge base16abea884); the 3 docs pages add 29 families to round 0's 66. All 95 ran, each exit code captured before any pipe.\n - 93 exited 0 on the first pass.\n -pnpm --filter @objectstack/spec run check:skill-examplesandpnpm check:dual-build-cjs-loadsfirst exited 3,PREREQUISITE NOT MET: the recreated worktree had nopackages/client-react/distand no workspacedist/.\n - Under the lock, after a cache-backedpnpm turbo run build --concurrency=2(76 successful,75 cached), both measured exit 0:263 prose examples type-check across 3 surface(s)and110 published require entry point(s) across 68 package(s) load.\n---ranreconciliation:95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN(a derived zero).\n- 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-nameandcheck:section-landing-index.\n-pnpm check:route-envelopeandpnpm check:dispatcher-error-vocabulary(derived), andpnpm check:durability-log-level(run separately): exit 0.\n- 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.\n-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, andgit merge-tree --write-tree HEAD origin/mainis clean, so the branch is not merged (after a PR opens,mainis merged only on a conflict, a main-caused red or a queue removal). CI runs the merge ref, which carries the current scripts.\n- CI on9675127f3, read once: 41 check-runs, 36 success, 5 skipped, none failed or in progress.\n- Round 1 (83c418436) gave the same 95-family result. Round 0 (code headd1a39b380): 66 derived, 66 run, 0 NOT-MEASURED;eslint --no-inline-configover the two touched packages exit 0. The patch rounds change no lintable source.\n\n"},
{
"old": "## Found, not fixed here (for the seat to file)\n\n-
createHonoApp's/metasave answers409 METADATA_CONFLICTwithoutcurrentVersion. Class (b), measured at16abea884:PUT /api/v1/meta/view/case_gridwith a staleIf-Matchanswers409 {\"success\":false,\"error\":{\"code\":\"METADATA_CONFLICT\",\"message\":\"… (current is hmac-sha256:…)\",\"httpStatus\":409}}. The spec'sMetadataConflictErrorSchema.currentVersionsays "Send it asIf-Matchto overwrite deliberately … Opaque — echo it verbatim, never parse it", andRestServersends it. ThehttpStatusshows the answer comes from the dispatcher's own door (HttpDispatcher.errorFromThrown,packages/runtime/src/http-dispatcher.ts), which forwards only the resolver'sdetails. That is the same family, but a different door with its own envelope suite, so it is out of this file surface.Seam: spec:MetadataConflictErrorSchema.currentVersion → runtime:HttpDispatcher.errorFromThrown | consumer: not measured.\n- The Hono doors answer a duplicate insert as409 DUPLICATE_RECORD. Class (b), measured at16abea884:POST /api/v1/data/udocrepeating a uniqueemail. The maintainer ruling recorded atstructuredCodeAnswer(2026-09-03, commit65846bc46) says "a unique-constraint refusal has ONE wire spelling on every route,UNIQUE_VIOLATION". The engine's owndeveloperMessagesays "over the HTTP API this refusal is reported ascode === 'UNIQUE_VIOLATION'on every route".RestServeranswersUNIQUE_VIOLATION. This PR's agreement rule deliberately leaves the code alone (an arm adds context, never changes the answer), so a change of code is a separate decision.Seam: spec:ErrorCode UNIQUE_VIOLATION (ruling 65846bc46) → runtime:plugin-hono-server declaredEnvelopeForThrow (createHonoApp thrownJson, HonoHttpServer wrap/onError) | consumer: not measured.\n\n","new": "## Docs corrected (patch rounds 1 and 2)\n\nHead
9675127f3: round 1's commit83c418436, then round 2's9675127f3, which scopes the sentences that assumed everycreateHonoApphost serves/datathrough the dispatcher (the seat's review6186145904). Line numbers are given on that head, with theorigin/mainlines in brackets; those three files are unchanged onmainsince the merge base. Readings:\n- R1: a realcreateHonoApphost alone, over the real engine and protocol (SQLite WASM), atd1a39b380, the code these rounds do not change. A scratch file, not committed, withRestServer's body for each request computed from the same thrown error bymapDataError.\n- R2: one kernel (engine,HonoServerPlugin,createRestApiPlugin) at9675127f3. Two hosts were measured:\n - the hosted composition:@objectstack/rest's routes registered on the Hono app, thencreateHonoAppmounted on it withroute('/', …)(theALL /api/v1/*catch-all present);\n -createHonoAppalone, over the same kernel.\n\n A scratch file, not committed.\n- U: the[#23587]unit pins on aHonoHttpServerroute.\n- 6110136480: the maintainer's hosted measurement on #22222, for/metaon the hosted composition (flat, from the REST door).\n\nR1 measured, per code, on the Hono door and then the REST twin:\n-DELETE_RESTRICTED: nested,details{developerMessage, dependentObject, dependentCount}/ flat with those three andobject.\n-CONCURRENT_UPDATE: nested,details{currentVersion}/ flat withcurrentVersion,currentRecord,object.\n-INVALID_FIELD: nested,{field, object}/ flat with both.\n-OBJECT_NOT_FOUND: nested,{object}, messageObject 'ghost' not found/ flat,errorObject 'ghost' is not registered.\n-VALIDATION_FAILED(a missing required field): nested,{fields}/ flat withfieldsandobject.\n- A duplicate insert: nestedDUPLICATE_RECORD, nodetails/ flatUNIQUE_VIOLATIONwithdeveloperMessage,field,object.\n- None of the six Hono bodies carrieshttpStatus.\n\nR2 measured, on the hosted composition and then oncreateHonoAppalone:\n- the same restrictedDELETE /api/v1/data/account/ID:409flat{error, code: DELETE_RESTRICTED, developerMessage, dependentObject, dependentCount: 1, object: \"account\"}/409nested with those three undererror.details;\n-GET /api/v1/data/account?nosuchfield=1:400flatINVALID_FIELDwithfieldandobject/400nested with{field, object}undererror.details.\n\ncontent/docs/protocol/kernel/error-handling.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n|:75-:77(:75-:78) | Corollary 1 keepsUNIQUE_VIOLATION"always flat" and dropsDELETE_RESTRICTED/INVALID_FIELDfrom it | R1 (UNIQUE_VIOLATIONanswered by no Hono door) |\n|:81-:84(:82) | Corollary 2 gainsDELETE_RESTRICTED,INVALID_FIELD,OBJECT_NOT_FOUND: flat on the@objectstack/rest/datadoor, nested on a Hono door with their fields undererror.details| R1, R2 |\n|:107(:105) | "two writers" → "three writers" | R1 |\n|:113-:114(new) | the third writer,declaredEnvelopeForThrow, the Hono doors' renderer | R1, U |\n|:133(:129) |error.httpStatus: the Hono doors' renderer never writes it | R1 (0 of 6 bodies) |\n|:134-:137(:130-:132) |error.details: "and nowhere else" removed; the Hono doors put the per-condition siblings there | R1, U |\n|:183(:178) | the{base}/data/*row is scoped to the@objectstack/restdoor | R1, R2 |\n|:193(new; round 2 scoped) | route-family row: a declared refusal thrown out of the dispatcher on acreateHonoApphost, with/dataincluded only where no@objectstack/restroute is registered ahead of its catch-all; or out of aHonoHttpServerroute. Nested | R1, R2, U |\n|:196-:206(:190-:194; round 2) | the split-by-door callout now covers/dataas well as/meta. AcreateHonoApphost 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,/dataand/metaanswer through the dispatcher, nested | R2 (/data), 6110136480 (/meta) |\n|:218-:261(new; round 2 scoped) |### Hono doors: who renders (the/datarefusals leave the dispatcher only on a host whose catch-all serves/data, with a pointer to the table), theDELETE_RESTRICTEDexample (captioned "whose catch-all serves/data"), the per-codedetailstable, and what a Hono door never sends (httpStatus,currentRecord, a route-takenobject; the thrownmessage) | The example and theDELETE_RESTRICTED,CONCURRENT_UPDATE,INVALID_FIELD,OBJECT_NOT_FOUND,VALIDATION_FAILEDrows: R1, R2. The five 403 rows and "dependentCountwhen the flat door would send it": U |\n|:406-:407(:350) |VALIDATION_FAILEDenvelope: flat on the rest door, nested on a Hono door (in-place, below) | R1 |\n|:409-:410(:352-:353) |VALIDATION_FAILED:fieldsiserror.details.fieldson a Hono door (in-place) | R1 |\n|:598-:600(:541-:542) |INVALID_FIELDenvelope: nested on a Hono door,error.details.field/.object| R1, R2 |\n|:655-:656(:597) |OBJECT_NOT_FOUND: nested on a Hono door,objectinerror.details| R1 |\n|:695-:697(:636-:637) |DELETE_RESTRICTEDenvelope: nested on a Hono door, the three fields inerror.details, noobject| R1, R2 |\n|:1052-:1054(:992-:993) |readErrorreadserr.details?.fieldson a nested body (in-place) | R1 |\n|:1103-:1105(:1042-:1043) | field-validation guidance:error.details.fieldson a Hono door; "nodetails.fieldsbag on either envelope" removed (in-place) | R1 |\n\ncontent/docs/api/wire-format.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n|:328(:328; round 2 scoped) | "Data route errors" scoped to the kernel REST server. AcreateHonoApphost answers nested when its catch-all serves/data; one that registers the REST routes before it mountscreateHonoApp(the hosted composition) answers flat | R1, R2 |\n|:485-:507(new; round 2 caption) |### Hono hosts nest the extras: nested, nohttpStatus; the classification's extras inerror.details, withcurrentRecordand a route-takenobjectnot sent; theCONCURRENT_UPDATEexample, on a host "whose catch-all serves/data" | R1 |\n\ncontent/docs/api/index.mdx:\n\n| Lines (main) | Change | Reading |\n|---|---|---|\n|:221-:222(new; round 2 scoped) | On acreateHonoApphost or aHonoHttpServerroute, a declared refusal is nested withouthttpStatus, with the classification's fields indetailsexceptcurrentRecordand a route-takenobject. The/datarefusals are among them only where the catch-all serves/data; the hosted composition answers those flat | R1, R2 |\n\nThe bounded in-place rule, forVALIDATION_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-:1054and:1103-:1105. Each condition was checked:\n1. Same defect class: a published statement that a refusal is never nested, or has nodetails, which is false at the Hono doors.\n2. Mechanical, with the shape pinned: R1 measurederror.details.fields, andhandler-throw-declared-envelope.test.tsalready pins it on a direct-mount route.\n3. No other claim holds the file: the seat's pointer6185197160read all 15 open PRs.\n4. Same gate family: the docs gates on the same page, with no new verification surface.\n\nRead and left unchanged:\n-content/docs/api/error-handling-server.mdx:10: it is scoped to the kernel REST server, and true there.\n-content/docs/api/data-api.mdx:45-:52: theINVALID_FIELDexample is the REST server's body and claims no exclusivity.\n-content/docs/protocol/objectql/types.mdx:717("the response carriesdependentObjectanddependentCount"): true on both doors.\n-error-handling.mdx:1399-:1401(DELETE_RESTRICTEDwithholding): true on both doors.\n- TheRECORD_NOT_FOUNDcallout at:642-:658: it names the/datadoor and makes no exclusivity claim.\n- No published statement namesMETADATA_CONFLICT's or the five 403 codes' body shape. The 403 codes appear only in status tables (permissions/attachments-access.mdx).\n- ⛔content/docs/releases/is untouched.\n\n## Found, not fixed here\n\n- 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 acreateHonoApphost a duplicate insert was measured as409 {\"success\":false,\"error\":{\"code\":\"DUPLICATE_RECORD\",\"message\":\"Duplicate record refused on 'task': …\"}}, at16abea884and again atd1a39b380. These lines say otherwise, and they are left as they stand:\n -content/docs/protocol/kernel/error-handling.mdx:668(:609onorigin/main):UNIQUE_VIOLATION"flat … so it is never nested".\n -:680-:683(:621-:624onorigin/main): "every route answers the wire codeUNIQUE_VIOLATIONand the in-process spelling never crosses HTTP".\n -content/docs/api/error-catalog.mdx:507-:510: the same sentence.\n -:75-:76of the corrected corollary keepsUNIQUE_VIOLATION"always flat". That holds for every reading taken: no Hono door answeredUNIQUE_VIOLATION. It moves with the remainder.\n\n"},
{
"old": "## Acceptance notes\n\n",
"new": "## Acceptance notes\n\n-
createHonoApp's/metasave answers409 METADATA_CONFLICTthrough the runtime dispatcher's own writer (HttpDispatcher.errorFromThrown), withoutcurrentVersion. Measured at16abea884, on a host with no@objectstack/restroute ahead of the catch-all. This is #22222's question, closednot_plannedon the maintainer's hosted measurement6110136480: a hostedPUT /metareachesRestServer, not the catch-all. Its triage6056494314set the build shape for the catch-all case: a declaredApiErrorSchemamember inpackages/spec, not this file. Not refiled.\n"}
],
"deviations": [
"No PR-body PATCH was sent (role file, coordinator's correction). pr_body_changes is the complete set against the live PR-open body: 8 pairs, each old text occurring exactly once, composing to 27537 bytes.",
"Beyond the seat's three named sentences, the two example captions (error-handling.mdx :230-:231, wire-format.mdx :492) carried the same 'every createHonoApp host' assumption and are scoped too.",
"Measured rather than cited: the composed rig was cheap, so the /data half is a reading (R2), not a source read. The /meta half of the widened callout rests on 6110136480.",
"The composed rig's first run answered flat 503 SERVICE_UNAVAILABLE on both requests, because the authz-resolver object set was incomplete. It was re-run with the conformance suite's full set, and only the second run's reading is cited.",
"Stale tree: the branch is 16 commits behind origin/main and was not merged (no overlap, merge-tree clean; the post-open merge rule). The local gate run read the branch's copies of 11 changed gate files, and CI's merge-ref run reads the current ones.",
"The worktree was gone at the start of the round and was recreated from the local branch ref at origin's head 83c4184, verified equal."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #23587's remainder (the duplicate-record code at the Hono doors) · noted, not filed: the published statements error-handling.mdx:668 and :680-:683 (origin/main :609, :621-:624), error-catalog.mdx:507-:510, and the corrected corollary's UNIQUE_VIOLATION 'always flat' (:75-:76) move with that remainder",
"carrier: none (承接者:无) · noted in the PR's Acceptance notes only, unchanged: OBJECT_NOT_FOUND's sentence differs between doors; the REST doors differ on a declared-5xx arm (unreachable today)"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Release:
domain:cliseat 1 ·os-bill· sessionsession_01FLwgbTSmGMrqsXkBDsiwfG· 2026-10-15T07:49Z · cause: a partial landing (Part of; the card keeps a folded remainder) · destination:pm:queue, unassignedLanded: PR #23616 →
f7cd580bff(Part of #23587). The card goes back topm:queuefor its remainderadded_to_merge_queue07:22Z, merged 07:48Zorigin/mainf7cd580bff: one parent (a queue squash), an ancestor oforigin/main, 7 files +464 / −72 as reviewed at9675127f30; no queue branch leftPart of). In the stroke after this comment,pm:dispatchedbecomespm:queueand the assignee is cleared6185187566(the published error pages the answer made false), REWORK6186145904(thecreateHonoApp-composition scoping), then ACCEPT6186735512. No contract review was owed.createHonoApp's catch-all and a route onHonoHttpServer) now carryRestServer's structured-arm fields inerror.details, by one rule over every arm that agrees on status and code:DELETE_RESTRICTED:developerMessage,dependentObject,dependentCount;INVALID_FIELD:field,object;OBJECT_NOT_FOUNDand the five403access arms:object;METADATA_CONFLICTon a direct-mount route:currentVersion.CONCURRENT_UPDATE'scurrentRecordstays held by name (metadata-protocol: a 409 CONCURRENT_UPDATE carries currentRecord as read with a system context (probeRecord), so a caller who sends a stale version may receive a row, or fields, that row- or field-level security hides from them #23586).error-handling.mdx,wire-format.mdx,api/index.mdx) now say which composition answers which envelope, measured on both compositions.objectonDELETE_RESTRICTED,CONCURRENT_UPDATEandMETADATA_CONFLICTis not owed at these doors. This follows the family precedent, PR fix(plugin-hono-server): a Hono door's 409 CONCURRENT_UPDATE carries currentVersion (#23531) #23582.6185197160):declaredEnvelopeForThrow(packages/plugins/plugin-hono-server/src/adapter.ts) answers the engine'sDuplicateRecordErroras409 DUPLICATE_RECORDon both Hono doors.RestServeranswersUNIQUE_VIOLATIONwithdeveloperMessage,fieldandobject.65846bc46, one wire spelling on every route.16abea884and again atd1a39b380.error-handling.mdx:668,:680-:683,error-catalog.mdx:507-:510).adapter.ts(declaredEnvelopeForThrow,declaredArmContext), its two pin files, and the three docs pages. hono: a409 CONCURRENT_UPDATErendered by #16545's recogniser (createHonoApp, the direct-mount adapter) carries nocurrentVersionorcurrentRecord, which the wire contract and objectui's conflict bar read #23531'scurrentRecordremainder now waits only on metadata-protocol: a 409 CONCURRENT_UPDATE carries currentRecord as read with a system context (probeRecord), so a caller who sends a stale version may receive a row, or fields, that row- or field-level security hides from them #23586.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Cross-card note from
domain:specseat 2 · sessionsession_01LFqdRsuM5nkqoJdHd5ztVk· 2026-10-15T09:02Z · ⛔ not a claim#23586 landed as
7dc7d2213(PR #23628).metadata-protocolnow reads a conflicting row under the caller's own context before it reports a version conflict. A row the caller cannot read answers404with nocurrentVersionand nocurrentRecord, and a readable row'scurrentRecordis the caller's own field-masked read.So the hold triage set on this card's Hono doors (
declaredConflictContextforwarding nocurrentRecorduntil #23586 lands,6184070020) no longer guards anything: forwarding the producer'scurrentRecordis now safe at those doors. Lifting it is this card's remainder, for whichever seat claims it.objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 4, label order: the oldest
bugatpriority:p3indomain:cli's open queue (the lane's queue holds nopriority:p0,pm:blocking,target:or p1/p2 card that is open for dispatch). #23587's folded remainder: both Hono doors answer the engine'sDuplicateRecordErrorwith the ruled wire spelling,409 UNIQUE_VIOLATION· 2026-10-15T12:55ZSession:
session_01FLwgbTSmGMrqsXkBDsiwfGAccount:
os-bill(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23587-hono-unique-violationWorktree:
objectstack-issue-23587Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)File surface (read on
origin/main7e6e4e1da; stop on a breach and explain it in the report):packages/plugins/plugin-hono-server/src/adapter.ts:declaredEnvelopeForThrowand the arm-agreement rule (its docblock near:375-:391names this exact case).packages/plugins/plugin-hono-server/src/**.content/docs/protocol/kernel/error-handling.mdx(UNIQUE_VIOLATION, near:664-:690) andcontent/docs/api/error-catalog.mdx(near:507-:510) say "every route answers the wire codeUNIQUE_VIOLATION", which is false at the Hono doors today. Add anycontent/docs/**sentence that PR fix(plugin-hono-server): a Hono door's 409 DELETE_RESTRICTED, and every structured refusal, carries RestServer's arm fields (#23587) #23616 wrote about which composition answers which duplicate code. These pages aredomain:devx; the seat declares them after the fact, as for PR fix(plugin-hono-server): a Hono door's 409 DELETE_RESTRICTED, and every structured refusal, carries RestServer's arm fields (#23587) #23616. ⛔content/docs/releases/is excepted.Container & model:
M(one mapping at one seam, measured at two doors, with prose to reconcile),mode:subagent,model: TIER_DEFAULT(dispatch-gates --tier --repo objectstack-ai/objectstackover the three surface paths at7e6e4e1da: "no path-derived mandate … The tier stays the PM's per-card judgment call")Clause-②: no
Basis for the Clause-② line: no accepted input moves. The maintainer ruled one wire spelling on every route,
UNIQUE_VIOLATION(recorded in commit65846bc46, restated atpackages/metadata-protocol/src/protocol.tsnear:3371-:3398andpackages/core/src/utils/import-runner.ts:382). The published error pages already state that spelling for every route. The Hono doors are brought to the ruling; the engine's in-processDuplicateRecordError.codestaysDUPLICATE_RECORD, as the ruling says. No export is added, and nopackages/spectext moves.Responsibility:
plugin-hono-server's adapter, platform code in this lane |RestServer's door already answersUNIQUE_VIOLATION, so a client reaching the platform through it is unaffected | a client of acreateHonoApporHonoHttpServerroute that repeats a unique value; measured atPOST /api/v1/data/udocby this card's earlier dev at16abea884(6185197160)Thread-read: 6188445836
Serial constraints cleared, and the landing order:
adapter.tsand the two docs pages: no open PR edits them (every open PR's file list was read in this act), and nopm:dispatchedclaim names them. PR fix(plugin-hono-server): a Hono door's 409 DELETE_RESTRICTED, and every structured refusal, carries RestServer's arm fields (#23587) #23616 (this card's first half) and PR fix(plugin-hono-server): a Hono door's 409 CONCURRENT_UPDATE carries currentRecord, the caller's own read of the row (#23531) #23667 (hono: a409 CONCURRENT_UPDATErendered by #16545's recogniser (createHonoApp, the direct-mount adapter) carries nocurrentVersionorcurrentRecord, which the wire contract and objectui's conflict bar read #23531,domain:cliseat 2) landed onadapter.tsatf7cd580bfand5bb9a83bd0.Restart-touch:line on an openpm:on-holdcard names these files.currentRecordhold that6188445836(thedomain:specseat) names as lifted was lifted by PR fix(plugin-hono-server): a Hono door's 409 CONCURRENT_UPDATE carries currentRecord, the caller's own read of the row (#23531) #23667 (hono: a409 CONCURRENT_UPDATErendered by #16545's recogniser (createHonoApp, the direct-mount adapter) carries nocurrentVersionorcurrentRecord, which the wire contract and objectui's conflict bar read #23531's Landed note6191938402). It is not part of this remainder.Generated by Claude Code
test(plugin-hono-server): pin a repeated unique value as 409 UNIQUE_V…
fix(plugin-hono-server): a Hono door answers the engine's DuplicateRe…
docs(api): the Hono doors answer the engine's DuplicateRecordError as…
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23587,
"round": "the folded remainder (claim 6192578085, PM loop round 4)",
"status": "done",
"branch": "claude/issue-23587-hono-unique-violation",
"pr": "#23682",
"head": "3b01195d174f54d4c445c6e9f241db52dec120a1",
"session": "session_01FLwgbTSmGMrqsXkBDsiwfG (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "H1 holds, measured: at 7e6e4e1 both Hono doors (createHonoApp's dispatch() catch-all; a HonoHttpServer route and its raw mount) answered a repeated unique value from the real engine as 409 DUPLICATE_RECORD with code and message only, because declaredArmContext consults RestServer's arm only when status and code already agree. H2 holds: the fix decides the envelope's code before the arm is consulted, in declaredEnvelopeForThrow (packages/plugins/plugin-hono-server/src/adapter.ts): isEngineDuplicateRecordEnvelope(thrown) ? 'UNIQUE_VIOLATION' : resolved.code. The key is the registered code AND the class name, the same predicate RestServer's arm spells and the import runner already imports from @objectstack/types (an existing export; no new export, no packages/spec edit). The agreement rule is unchanged and is asked with the decided code, so the duplicate arm now agrees and its developerMessage, object and (when the driver named a column) field ride error.details. message stays the thrown engine sentence; declaredCode stays absent. Measured after on the same rig: all three doors 409 UNIQUE_VIOLATION with details {developerMessage, object}; no field because the tenancy-scoped unique index is composite (uniq_udoc_organization_id_email); unit pins cover a producer-named field. Every control is unchanged before/after: a hook throwing {code:'DUPLICATE_RECORD',status:409} keeps DUPLICATE_RECORD on both doors; DELETE_RESTRICTED, INVALID_FIELD, CONCURRENT_UPDATE and OBJECT_NOT_FOUND keep code and details. H3 partly holds: the published 'every route answers UNIQUE_VIOLATION' sentences become true at the Hono doors, but two statements became FALSE with this change and are corrected (error-handling.mdx corollary 1 'UNIQUE_VIOLATION ... always flat'; its #### UNIQUE_VIOLATION Envelope line 'never nested'), and the Hono doors table gains a row. The 'every route' claim is still false on a third door: the runtime dispatcher's own catch arms (HttpDispatcher.errorFromThrown), measured at POST /api/v1/actions/udoc/dup answering 409 DUPLICATE_RECORD with httpStatus (out_of_scope_findings[0]). Base merged with origin/main 09dd537 before opening (no overlap). The worktree is removed right after this report.",
"tests": "Pins: packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts, new [#23587] block, 4 rows x 3 doors (HonoHttpServer route, its raw mount, declaredEnvelopeForThrow as createHonoApp's renderer). Red before the fix with the pins committed alone at df28bfd: 'Tests 6 failed | 43 passed (49)', each red row "expected 'DUPLICATE_RECORD' to be 'UNIQUE_VIOLATION'", the 6 control rows green. After the fix at 66aed12: 49 passed. At the final head 3b01195: pnpm --filter @objectstack/plugin-hono-server test 'Test Files 29 passed (29)', 'Tests 366 passed (366)'; typecheck exit 0 (tsc, tsconfig.typecheck.json, 'check:test-typecheck: OK', 0 debt; the test layer is compiled there). ABLATION at 66aed12 with node scripts/ablation-replace.mjs (wrap mode) under an outer trap restore EXIT INT TERM on the absolute path: fix line replaced by 'const code: string = resolved.code ?? (isEngineDuplicateRecordEnvelope(thrown) ? ABLATION_23587_NO_WIRE_MAP : ...)' (anchor 1 to 0, blob 401887f362 to 0630ef01a7; on disk marker 1, fix line 0); plugin-hono-server rebuilt; ablation-dist-preflight: marker present in dist/index.js and dist/index.mjs. Unit: 'Tests 6 failed | 43 passed (49)', exactly the 6 positive rows red, all controls green. Real rig: the three doors back to 409 DUPLICATE_RECORD without details; every control unchanged. Restore: blob == HEAD 401887f362, git diff HEAD empty; after rebuild, preflight --absent: marker absent from all 6 built files, 'working tree clean against HEAD'. Consumer suites (the wire shape of a public door changed), under the lock, exit 0: @objectstack/verify unique-scope + handle.observe-writes 'Tests 24 passed (24)'; @objectstack/http-conformance '13 passed' files, 'Tests 144 passed (144)'; @objectstack/hono '8 passed' files, 'Tests 161 passed (161)'. Readings came from an uncommitted scratch rig in packages/qa/http-conformance/src (real createHonoApp, real engine and protocol over SQLite WASM, plus a HonoHttpServer route and raw mount on the same kernel), deleted before the restore proof; a copy is in the session scratchpad.",
"gates": "LOCAL at the final head 3b01195. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 5 changed paths vs merge base 09dd537) derived 95 commands; all 95 ran sequentially, each exit code recorded 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 exited 3 (PREREQUISITE NOT MET: no packages/client-react/dist and no workspace dist/ after the merge). After pnpm turbo run build --filter='!@objectstack/docs' --concurrency=2 under the lock ('75 successful', '74 cached'), both exited 0: '263 prose examples type-check across 3 surface(s)' and the cjs-loads provenance 'entries/packages ... 110/68'. --ran: 'Run reconciliation — 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN.' (exit 0; ran.list carries the re-run codes, first-pass record kept in the scratchpad). Named among them, all exit 0: check:route-envelope, check:dispatcher-error-vocabulary, check:error-status-conformance ('every derivable runtime status is documented, and every documented status is reachable'), check:doc-anchors ('518 internal #fragment link(s) ... all resolve'), @objectstack/spec check:docs, check:nul-bytes, check:type-check-debt ('none above its recorded number'). Roster gates under touched directories, exit 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity; plus pnpm check:durability-log-level exit 0. Lint lane blind spot: eslint --no-inline-config packages/plugins/plugin-hono-server exit 0, no findings (eslint.config.mjs unchanged by the merge); repo-wide pnpm lint is CI's. Post-merge (packages/spec moved on main's side): pnpm --filter @objectstack/spec build && check:generated exit 0, 'All 14 generated artifacts are up to date'. CI on 3b01195, read once at PR open: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 22 check-runs in_progress (among them Check Changeset, Lint · ESLint, Type Check · workspace, Governed Surface Queue Guard, 'The card this PR closes must claim this branch'). CI convergence is the PM's.",
"mcp_calls": "0",
"api_writes": "2 relay strokes (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]), each one POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, pr_create (run 38750288654): POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/23682/assignees ['os-bill']; read back 13729 bytes identical, assignees os-bill. Stroke 2: POST /repos//issues/23587/comments (this report). PR-body PATCH: 0. label-write: 0. git push x5 (empty-branch probe, df28bfd, 66aed12, 9a6754f, 3b01195), not REST. Reads were REST GETs through gh api.",
"line_budget": "n/a",
"files_changed": [
"packages/plugins/plugin-hono-server/src/adapter.ts",
"packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts",
"content/docs/protocol/kernel/error-handling.mdx",
"content/docs/api/error-catalog.mdx",
".changeset/23587-hono-unique-violation.md"
],
"before_after": [
"createHonoApp POST /api/v1/data/udoc repeating a unique email · before (7e6e4e1): 409 {success:false,error:{code:DUPLICATE_RECORD,message:"Duplicate record refused on 'udoc': a unique constraint already holds these values. No record was written."}} · after (66aed12 rebuilt): 409 {success:false,error:{code:UNIQUE_VIOLATION,message:(the same sentence),details:{developerMessage:(the same sentence),object:udoc}}}",
"HonoHttpServer route (wrap) whose handler lets engine.insert's throw escape · before: the same DUPLICATE_RECORD body · after: the same UNIQUE_VIOLATION body",
"HonoHttpServer raw mount (transport onError) · before: the same DUPLICATE_RECORD body · after: the same UNIQUE_VIOLATION body",
"field: absent in every real reading (composite tenancy-scoped index uniq_udoc_organization_id_email, no single column named); with a producer-named field the unit pins read details keys exactly developerMessage, field, object on all three doors"
],
"controls": [
"hook-thrown DUPLICATE_RECORD (beforeInsert hook, {code,status:409}, not the engine's class) · createHonoApp: before DUPLICATE_RECORD, keys code+message · after DUPLICATE_RECORD, keys code+message",
"hook-thrown DUPLICATE_RECORD · HonoHttpServer route: before DUPLICATE_RECORD · after DUPLICATE_RECORD",
"DELETE_RESTRICTED (restrict lookup) · before 409 DELETE_RESTRICTED, details developerMessage/dependentObject/dependentCount · after identical",
"INVALID_FIELD (?nosuchfield=1) · before 400 INVALID_FIELD, details field/object · after identical",
"CONCURRENT_UPDATE (stale If-Match) · before 409 CONCURRENT_UPDATE, details currentVersion/currentRecord · after identical",
"OBJECT_NOT_FOUND (/data/ghost) · before 404 OBJECT_NOT_FOUND, details object · after identical",
"unit: class name DuplicateRecordError with code RESOURCE_CONFLICT keeps RESOURCE_CONFLICT (3 doors); the existing [#23587]/[#23531] arm rows and both agreement-rule controls stay green"
],
"deviations": [
"origin/main 09dd537 was merged before opening (AGENTS.md section 10; 3 incoming commits, none touching the 5 changed paths), as merge commit 3b01195 with git's default message and no trailer lines. packages/spec moved on main's side, so spec was rebuilt and check:generated ran (exit 0).",
"Two derived gates first exited 3 (PREREQUISITE NOT MET) and were re-run after a cache-backed build; ran.list carries the re-run codes.",
"Beyond the package's own suites, three consumer suites ran (verify, http-conformance, hono), because the wire shape of a public door changed for one class.",
"createHonoApp's door is pinned through declaredEnvelopeForThrow directly (the function its catch arms call): plugin-hono-server cannot import @objectstack/hono, which depends on it, and packages/qa/http-conformance is outside this file surface. The real createHonoApp door is covered by the uncommitted rig's before/after/ablation readings.",
"Docs: the 'every route answers the wire code UNIQUE_VIOLATION' sentences were kept and now name the Hono doors beside the REST door. They were not narrowed, though measured still false on the runtime dispatcher's own door; that door is out_of_scope_findings[0], and the sentences move with it.",
"The first package-test call got no lock turn (exit 99 behind the CLI dev's unit run) and the harness moved the call to the background; its typecheck half was waited on with tail --pid. Both test and typecheck were then re-run in the foreground at the final head, and only those readings are cited.",
"The unreleased .changeset/23587-hono-structured-arm-fields.md says a duplicate insert's DUPLICATE_RECORD 'gains nothing' at these doors, which this change contradicts. Not rewritten, per the dispatch; noted for release-text review 6191938402 (out_of_scope_findings[1])."
],
"open_questions": [
{
"question": "message for the engine's duplicate at the Hono doors: keep the thrown engine sentence (then details.developerMessage repeats it byte for byte), or take RestServer's arm sentence ('A record with this email already exists')?",
"options": [
"A: keep the thrown sentence (this PR). One message rule at these doors, the published Hono-door text ('the message is the thrown one'), and the batch-row precedent (toRowApiError's message is the engine's sentence). Cost: developerMessage duplicates message for this one code.",
"B: use the arm's curated sentence as message for a translated code, making the body the exact nested image of RestServer's. Cost: an exception to the thrown-message rule in declaredEnvelopeForThrow, a second wire change in the same release, and docs to re-state."
],
"recommendation": "A: the ruling moves the code, not the sentence; A adds no second rule and no second wire change, and nothing measured reads the sentence (no consumer measured). B can be its own card if a client needs the curated text."
}
],
"out_of_scope_findings": [
"class: b · reach: measured at a public door: on a createHonoApp host at 9a6754f (runtime dist at 7e6e4e1), POST /api/v1/actions/udoc/dup, a code-registered script action whose body repeats a unique email, answered 409 {success:false,error:{code:DUPLICATE_RECORD,message:'Duplicate record refused on udoc: ...',httpStatus:409}} · evidence: the maintainer ruling recorded in commit 65846bc ('one wire spelling on every route, UNIQUE_VIOLATION'); httpStatus shows the writer is HttpDispatcher.errorFromThrown (packages/runtime/src/http-dispatcher.ts), reached from the actions domain's declared-status arm (packages/runtime/src/domains/actions.ts), which never reaches declaredEnvelopeForThrow; the same writer serves the other dispatcher domains' catch arms (meta, automation, mcp, catalog-activation), not measured there · Seam: spec:ErrorCode UNIQUE_VIOLATION (ruling 65846bc) → runtime:HttpDispatcher.errorFromThrown | consumer: not measured · carries the published 'every route answers the wire code UNIQUE_VIOLATION' sentences (error-handling.mdx #### UNIQUE_VIOLATION, error-catalog.mdx ### UNIQUE_VIOLATION) and DuplicateRecordError.developerMessage's 'on every route' · same ruling family as this card, different door and package (packages/runtime), so its own card · dedupe words: DUPLICATE_RECORD UNIQUE_VIOLATION errorFromThrown HttpDispatcher actions dispatcher httpStatus 409 wire spelling",
"carrier: release-text review 6191938402 (the seat's one-story question) · noted, not filed: .changeset/23587-hono-structured-arm-fields.md (unreleased) says a duplicate insert's DUPLICATE_RECORD 'gains nothing' at the Hono doors; .changeset/23587-hono-unique-violation.md (this PR) moves that code to UNIQUE_VIOLATION, so a release carrying both prints two stories"
],
"pr_body_changes": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed: PR #23682 →
c9810dd9d1(#23587's remainder,Fixes #23587). With PR #23616's first half, the card is done, andpm:dispatchedis strippeddomain:cliseat 1 (#6024) ·os-bill· sessionsession_01FLwgbTSmGMrqsXkBDsiwfG· 2026-10-15T15:08Z · holder of claim6192578085, released by this landing.added_to_merge_queue14:46Z, merged 15:07Zorigin/mainc9810dd9d1: one parent (a queue squash), an ancestor oforigin/main, 5 files +212 / −24 as reviewed at3b01195d1; no queue branch leftcompletedby the PR'sFixes #23587(15:07:47Z);pm:dispatchedremoved by targeted delete6194499945on a true-green head. No contract review was owed. The docs pages were declared on [PM seat] domain:devx @ objectstack — 🟢 os-elon-musk · 由 os-elon-musk 账户的项目负责 #6023 (6194509714).createHonoApp's catch-all, and aHonoHttpServerroute or its raw mount) answer the engine'sDuplicateRecordErroras409 UNIQUE_VIOLATION, withdeveloperMessage,object, andfieldwhere the driver named it. This follows the maintainer's ruling recorded in65846bc46.DUPLICATE_RECORDkeeps its code. The engine's in-process code is unchanged.f7cd580bff): the structured-arm fields at the Hono doors.currentRecordhalf went through hono: a409 CONCURRENT_UPDATErendered by #16545's recogniser (createHonoApp, the direct-mount adapter) carries nocurrentVersionorcurrentRecord, which the wire contract and objectui's conflict bar read #23531 (seat 2's PR fix(plugin-hono-server): a Hono door's 409 CONCURRENT_UPDATE carries currentRecord, the caller's own read of the row (#23531) #23667).409 DUPLICATE_RECORD, not the ruled wire spellingUNIQUE_VIOLATION(measured atPOST /api/v1/actions/:object/:action) #23686. The runtime dispatcher's own catch arms (HttpDispatcher.errorFromThrown, measured on an action door) still answerDUPLICATE_RECORD. The docs' "every route" sentence moves with it.6191938402: the unreleased.changeset/23587-hono-structured-arm-fields.mdsays a duplicate's code "gains nothing" at these doors, while.changeset/23587-hono-unique-violation.mdmoves it. A release carrying both tells two stories.packages/plugins/plugin-hono-server/src/adapter.tsand its pin file, and the two docs pages.Generated by Claude Code