Repository navigation
fix(plugin-hono-server): a Hono door's 409 DELETE_RESTRICTED, and every structured refusal, carries RestServer's arm fields (#23587) - #23616
Conversation
…oth Hono doors (#23587) Pins first, against the unfixed recogniser: a direct-mount route and a real createHonoApp host answer 409 DELETE_RESTRICTED, 400 INVALID_FIELD, 404 OBJECT_NOT_FOUND, 409 METADATA_CONFLICT and the 403 access arms with the structured fields RestServer's classification carries, in error.details. Guard rows: an arm that disagrees on status or code is not consulted, and a DELETE_RESTRICTED stating no field keeps the two-key envelope. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
…tServer's arm fields (#23587) declaredEnvelopeForThrow asked RestServer's classification (structuredCodeAnswer) for CONCURRENT_UPDATE only, so a 409 DELETE_RESTRICTED reached a caller of createHonoApp or a HonoHttpServer route with code and message only, while the REST server's answer carries developerMessage, dependentObject and dependentCount. The helper, renamed declaredConflictContext -> declaredArmContext, now forwards the context of any arm that agrees with the envelope already decided (same status, same code), into error.details, under the arm's own rule (each field only when the producer stated it). currentRecord stays held by name until #23586. An arm that disagrees on status or code (DuplicateRecordError's UNIQUE_VIOLATION) is not consulted. The route's object is not substituted from the error's own object. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
📓 Docs Drift CheckThis PR changes 1 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b7aa9b9f251fcb867b53c0746e00132ba17d25fa && git checkout b7aa9b9f251fcb867b53c0746e00132ba17d25fa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dd29bbe60df87727664424521c3a0b9eed37e985 9675127f30b3099b7b8c1e609006b2c28ec83d6b && git checkout -B drift-repro dd29bbe60df87727664424521c3a0b9eed37e985 && git merge --no-ff 9675127f30b3099b7b8c1e609006b2c28ec83d6b
node scripts/docs-audit/affected-docs.mjs --json dd29bbe60df87727664424521c3a0b9eed37e985
|
🔁 REWORK, round 1 of 2: PR #23616 at
|
…heir fields in error.details (#23587) The error-handling page said DELETE_RESTRICTED, INVALID_FIELD and OBJECT_NOT_FOUND are always flat, that error.details appears on a handful of refusals and nowhere else, and that VALIDATION_FAILED has no details.fields bag on either envelope. On a createHonoApp host, and on a route mounted on HonoHttpServer, each of those refusals is answered nested, with the structured fields under error.details and no httpStatus (measured on a real createHonoApp host). The page now names that door, its writer and its fields per code; wire-format and the API overview scope their flat-envelope statements to the REST server and point at it. readError reads error.details.fields. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
🔁 REWORK, round 2 of 2: PR #23616 at
|
…atch-all serves /data (#23587) Round 1's sentences assumed every createHonoApp host serves /data through the runtime dispatcher. A host that registers @objectstack/rest's routes on the same Hono app before mounting createHonoApp, the hosted composition, answers those routes on the REST door, flat: the routes registered first win the match. Measured on one kernel: with the REST routes registered first, a restricted DELETE and an unknown field filter answered flat; with createHonoApp alone, the same requests answered nested with error.details. The three sentences are scoped to a catch-all that serves /data, and the split-by-door callout now covers /data as well as /meta. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
✅ ACCEPT: PR #23616 at
|
Part of #23587
Clause-②: no
This lands the card's three structured fields on both Hono doors, under one rule that covers every arm of
RestServer's classification. It also corrects the published error pages this answer changes (patch rounds 1 and 2, the seat's 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.What this does
The engine refuses the delete of a record that a
deleteBehavior: 'restrict'lookup still references with409 DELETE_RESTRICTED.RestServer's single-recordDELETEanswers it throughmapDataError, whose first step isstructuredCodeAnswer(@objectstack/types). That arm carries the engine'sdeveloperMessage,dependentObject,dependentCountand the route'sobjectbeside the code. Both Hono doors answered the same throw withcodeandmessageonly. They are@objectstack/hono'screateHonoApp(itsdispatch()catch-all, throughthrownJson) and a route mounted onHonoHttpServer(wrap(), and the transportonErrorseam). All of them render throughdeclaredEnvelopeForThrowinpackages/plugins/plugin-hono-server/src/adapter.ts.Measured on a real
createHonoAppover the real engine and protocol (SQLite WASM), at16abea884: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."}}"details":{"developerMessage":"Cannot delete parent (ID): 1 dependent child record(s) reference it via parent. Delete or reassign them first, or set deleteBehavior:'cascade' on child.parent.","dependentObject":"child","dependentCount":1}PR #23582 added a helper beside the recogniser that read the arm for
CONCURRENT_UPDATEonly. This PR widens it from one code to every arm that agrees with the envelope already decided, and renames it fromdeclaredConflictContexttodeclaredArmContext, since it no longer reads only conflicts:messageandcode), andcurrentRecord, which stays 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, unchanged from PR fix(plugin-hono-server): a Hono door's 409 CONCURRENT_UPDATE carries currentVersion (#23531) #23582).dependentCountwhen the caller's own identity would not see the same rows (dependentCountIsDisclosable), and then the door sends no count either.error.details, as PR fix(plugin-hono-server): a Hono door's 409 CONCURRENT_UPDATE carries currentVersion (#23531) #23582 placedcurrentVersion. On the REST wire every arm field is a top-level member of the flat body, and that body has nodetails. On the wrapped envelope the only declared context slot isApiErrorSchema.details, sincedeveloperMessage,dependentObjectanddependentCountare notApiErrorSchemamembers.@objectstack/clientreadserror.details = errorBody.details ?? errorBody.error.details ?? errorBody(packages/client/src/index.ts), so a client sees the same keys undererr.detailsfrom either door. ⛔ No third spelling of the refusal body.Status,
codeandmessagedo not move on any answer. The REST door's answer is unchanged.@objectstack/hono's source is unchanged too:createHonoApprenders throughdeclaredEnvelopeForThrow.Every arm, checked
Each arm of
structuredCodeAnsweris listed below, with whatRestServersends and what the Hono doors send before and after. Readings marked "measured" come from a realcreateHonoAppat16abea884(before) andd1a39b380(after). The rest come from the producer-shaped rows in the unit file and the code read.RestServer(mapDataError)DELETE_RESTRICTED(409)developerMessage,dependentObject,dependentCount,objectdeveloperMessage,dependentObject,dependentCount(measured)object: the arm takes it from the route onlyCONCURRENT_UPDATE(409)currentVersion,currentRecord,objectcurrentVersioncurrentVersion(unchanged)currentRecord: held, #23586.object: route onlyMETADATA_CONFLICT(409)currentVersion,objectcurrentVersion, on a route mounted onHonoHttpServerobject: route only. OncreateHonoAppthe/metasave is answered by the dispatcher's ownerrorFromThrown, so it never reaches the recogniser. Measured unchanged there; see Acceptance notes (#22222)INVALID_FIELD(400)field,objectfield,object(measured)OBJECT_NOT_FOUND(404)objectobject(measured)messageFEEDS_DISABLED,FILES_DISABLED,ATTACHMENT_PARENT_ACCESS,ATTACHMENT_DELETE_DENIED,RECORD_NOT_ACCESSIBLE(403)objectobjectVALIDATION_FAILED(400)fields,objectfields(the resolver's)fields, the same arrayobject: route only.validationFailureDetailsand the arm readfieldsalike, so the merge is byte-identicalDUPLICATE_RECORD, answered by REST asUNIQUE_VIOLATION(409)developerMessage,field,objectDUPLICATE_RECORD(measured)ERR_DATASOURCE_UNAVAILABLE(503)datasource,reason,objectDatasourceUnavailableError,@objectstack/objectql) declares nostatus, so the recogniser never answers itOBJECT_API_DISABLED,OBJECT_API_METHOD_NOT_ALLOWEDobject,allowedSystem-context reads (#23586's question, asked of every arm).
currentRecordis the only field read with a system context and left unnarrowed. Every other field is one of these:currentVersion);object,dependentObject,field);developerMessage).DELETE_RESTRICTED's dependents probe is elevated. But the engine discloses the count, independentCountand indeveloperMessage, only when the caller's own identity would have read the same rows (dependentCountIsDisclosable,@objectstack/objectql). It names the dependent object unconditionally, by that ruling's constraint 2.RestServeralready sends all of it to the same caller. SocurrentRecordstays the one exclusion, by name. The helper's docblock says that an arm which grows such a field must be held there by name in the same way.Sandboxed producers. A
SandboxErrorcarries onlycode,fields,status,userMessageandinnerMessage(runtime/src/sandbox/quickjs-runner.ts). So no arm adds a field from one.VALIDATION_FAILED'sfieldsis the resolver's own array, andINVALID_FIELDandOBJECT_NOT_FOUNDdecline a sandbox origin themselves.What stays off these doors
objectonDELETE_RESTRICTED,CONCURRENT_UPDATEandMETADATA_CONFLICT. Those arms add the object the ROUTE named (mapDataError(error, req.params.object)).declaredEnvelopeForThrowhas no route, so it asks the arm without one, as PR fix(plugin-hono-server): a Hono door's 409 CONCURRENT_UPDATE carries currentVersion (#23531) #23582 recorded forCONCURRENT_UPDATE. The caller named that object in its own URL. ⛔ It is not substituted from the error's ownobject. The engine's cascade recurses through the publicdelete(childName, …), so a restrict deeper in a cascade names the child it was deleting, not the object the route addressed, and REST would answer differently. Decided by the seat (review6185187566, option A): the field is 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, which landedCONCURRENT_UPDATEwithout it, and 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 comment6183626856, which namescurrentRecordas that card's only remainder.currentRecord: held, 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, as before. The two absence pins from PR fix(plugin-hono-server): a Hono door's 409 CONCURRENT_UPDATE carries currentVersion (#23531) #23582 are unchanged and green.Tests
409 CONCURRENT_UPDATErendered by #16545's recogniser (createHonoApp, the direct-mount adapter) carries nocurrentVersionorcurrentRecord, which the wire contract and objectui's conflict bar read #23531 blocks are unchanged.packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts, the new[#23587]block, 14 rows on direct-mount routes:DELETE_RESTRICTEDwith the count, with the count withheld, and in the subflow guard's shape, which states no field and keeps the two-key envelope;METADATA_CONFLICTwith a token and withnull;INVALID_FIELD,OBJECT_NOT_FOUND, and the five 403 access codes;codeanderror.detailsexactly.packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts, the new[#23587]block, 3 rows on a realcreateHonoApp:DELETE(account and contact,restrict):error.detailsequals thedeveloperMessage,dependentObjectanddependentCountof the throw the engine really raised, read off thedeleteDatacall. The row also asserts that throw stated a stringdeveloperMessage, so the equality cannot hold between two absences, and that the parent row is still stored;GET /data/task?nosuchfield=1:400 INVALID_FIELDwith{ field, object };GET /data/ghost:404 OBJECT_NOT_FOUNDwith{ object }.90574de90, on the unfixed recogniser): unitTests 11 failed | 26 passed (37), conformanceTests 3 failed | 15 passed (18). Every red row failed at itserror.detailsassertion withexpected undefined to deeply equal …. The three guard rows pass on the unfixed tree by construction; they are the regression half.d1a39b380. Two legs, each run withnode scripts/ablation-replace.mjsin wrap mode, with an outertrap … EXIT INT TERMrestore against the absolute path. Each leg rebuilt@objectstack/plugin-hono-server, andablation-dist-preflight.mjsfound the marker indist/index.jsanddist/index.mjsbefore either suite ran.const arm = structuredCodeAnswer(thrown);became a read guarded onresolved.code === 'CONCURRENT_UPDATE', carrying the markerABLATION_23587_ONE_CODE(blob7fd1f090b0to11a3ca30af). Every new[#23587]refusal row went RED, and the[#23531]rows stayed green: unitTests 11 failed | 26 passed (37), conformanceTests 3 failed | 15 passed (18), each atexpected undefined to deeply equal ….&& key !== 'currentRecord'),became a key no body carries,ABLATION_23587_HELD_FIELD(blob to1f5ac3cbb6). Exactly the fourcurrentRecordabsence pins went RED: unitTests 2 failed | 35 passed (37),expected { …(2) } to deeply equal { Object (currentVersion) }; conformanceTests 2 failed | 16 passed (18),to not have property "currentRecord". The conformance body then carried the system-context row the real protocol states (organization_id,owner_id,created_byand the rest). That is the disclosure the exclusion holds back.7fd1f090b0),git diff HEADis empty andgit status --porcelainis clean. After a rebuild, preflight--absentfound both markers gone from all 6 built files, with a clean tree. Both pin files were green again: unit37 passed (37), conformance18 passed (18).d1a39b380, after a rebuild of@objectstack/plugin-hono-server(the conformance file reads itsdist/):pnpm --filter @objectstack/plugin-hono-server typecheck(tsc,tsconfig.typecheck.json,check:test-typecheckOK, 0 debt) andtest:Test Files 29 passed (29),Tests 354 passed (354).pnpm --filter @objectstack/http-conformance typecheck(check:test-typecheckOK, ledger unchanged) andtest:Test Files 13 passed (13),Tests 142 passed (142).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)".@objectstack/hono's own suite is not run locally: its source is untouched, and its vitest config stubs@objectstack/runtime. The composed-host pins above cover its door. It is declared to CI.Gates
Local, at the final head
9675127f3:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths, derived 95 commands from the 7 changed paths (merge base16abea884); the 3 docs pages add 29 families to round 0's 66. All 95 ran, each exit code captured before any pipe.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/.pnpm 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.--ranreconciliation:95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN(a derived zero).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.pnpm check:route-envelopeandpnpm check:dispatcher-error-vocabulary(derived), andpnpm check:durability-log-level(run separately): exit 0.node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casing,pnpm check:filter-alias-parity.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.9675127f3, read once: 41 check-runs, 36 success, 5 skipped, none failed or in progress.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.Changeset
.changeset/23587-hono-structured-arm-fields.md:@objectstack/plugin-hono-serverpatch.@objectstack/honois not named, because its source does not change, and both packages are in one fixed version group. The body gives FROM and TO forDELETE_RESTRICTED, names the other arms the rule now covers, and says which two fields stay off and why.Docs corrected (patch rounds 1 and 2)
Head
9675127f3: round 1's 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:R1: a real
createHonoApphost 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.R2: one kernel (engine,
HonoServerPlugin,createRestApiPlugin) at9675127f3. Two hosts were measured:@objectstack/rest's routes registered on the Hono app, thencreateHonoAppmounted on it withroute('/', …)(theALL /api/v1/*catch-all present);createHonoAppalone, over the same kernel.A scratch file, not committed.
U: the
[#23587]unit pins on aHonoHttpServerroute.6110136480: the maintainer's hosted measurement on [finding] the
@objectstack/honocatch-all's409 METADATA_CONFLICTnames the current version only in prose: measure whether the hosted runtime'sPUT /metareaches it, and if so carrycurrentVersionas data (apackages/specchange) #22222, for/metaon the hosted composition (flat, from the REST door).R1 measured, per code, on the Hono door and then the REST twin:
DELETE_RESTRICTED: nested,details{developerMessage, dependentObject, dependentCount}/ flat with those three andobject.CONCURRENT_UPDATE: nested,details{currentVersion}/ flat withcurrentVersion,currentRecord,object.INVALID_FIELD: nested,{field, object}/ flat with both.OBJECT_NOT_FOUND: nested,{object}, messageObject 'ghost' not found/ flat,errorObject 'ghost' is not registered.VALIDATION_FAILED(a missing required field): nested,{fields}/ flat withfieldsandobject.DUPLICATE_RECORD, nodetails/ flatUNIQUE_VIOLATIONwithdeveloperMessage,field,object.httpStatus.R2 measured, on the hosted composition and then on
createHonoAppalone:DELETE /api/v1/data/account/ID:409flat{error, code: DELETE_RESTRICTED, developerMessage, dependentObject, dependentCount: 1, object: "account"}/409nested with those three undererror.details;GET /api/v1/data/account?nosuchfield=1:400flatINVALID_FIELDwithfieldandobject/400nested with{field, object}undererror.details.content/docs/protocol/kernel/error-handling.mdx:main):75-:77(:75-:78)UNIQUE_VIOLATION"always flat" and dropsDELETE_RESTRICTED/INVALID_FIELDfrom itUNIQUE_VIOLATIONanswered by no Hono door):81-:84(:82)DELETE_RESTRICTED,INVALID_FIELD,OBJECT_NOT_FOUND: flat on the@objectstack/rest/datadoor, nested on a Hono door with their fields undererror.details:107(:105):113-:114(new)declaredEnvelopeForThrow, the Hono doors' renderer:133(:129)error.httpStatus: the Hono doors' renderer never writes it:134-:137(:130-:132)error.details: "and nowhere else" removed; the Hono doors put the per-condition siblings there:183(:178){base}/data/*row is scoped to the@objectstack/restdoor:193(new; round 2 scoped)createHonoApphost, with/dataincluded only where no@objectstack/restroute is registered ahead of its catch-all; or out of aHonoHttpServerroute. Nested:196-:206(:190-:194; round 2)/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/data), 6110136480 (/meta):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)DELETE_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:406-:407(:350)VALIDATION_FAILEDenvelope: flat on the rest door, nested on a Hono door (in-place, below):409-:410(:352-:353)VALIDATION_FAILED:fieldsiserror.details.fieldson a Hono door (in-place):598-:600(:541-:542)INVALID_FIELDenvelope: nested on a Hono door,error.details.field/.object:655-:656(:597)OBJECT_NOT_FOUND: nested on a Hono door,objectinerror.details:695-:697(:636-:637)DELETE_RESTRICTEDenvelope: nested on a Hono door, the three fields inerror.details, noobject:1052-:1054(:992-:993)readErrorreadserr.details?.fieldson a nested body (in-place):1103-:1105(:1042-:1043)error.details.fieldson a Hono door; "nodetails.fieldsbag on either envelope" removed (in-place)content/docs/api/wire-format.mdx:main):328(:328; round 2 scoped)createHonoApphost answers nested when its catch-all serves/data; one that registers the REST routes before it mountscreateHonoApp(the hosted composition) answers flat: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"content/docs/api/index.mdx:main):221-:222(new; round 2 scoped)createHonoApphost 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 flatThe 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-:1054and:1103-:1105. Each condition was checked:details, which is false at the Hono doors.error.details.fields, andhandler-throw-declared-envelope.test.tsalready pins it on a direct-mount route.6185197160read all 15 open PRs.Read and left unchanged:
content/docs/api/error-handling-server.mdx:10: it is scoped to the kernel REST server, and true there.content/docs/api/data-api.mdx:45-:52: theINVALID_FIELDexample is the REST server's body and claims no exclusivity.content/docs/protocol/objectql/types.mdx:717("the response carriesdependentObjectanddependentCount"): true on both doors.error-handling.mdx:1399-:1401(DELETE_RESTRICTEDwithholding): true on both doors.RECORD_NOT_FOUNDcallout at:642-:658: it names the/datadoor and makes no exclusivity claim.METADATA_CONFLICT's or the five 403 codes' body shape. The 403 codes appear only in status tables (permissions/attachments-access.mdx).content/docs/releases/is untouched.Found, not fixed here
createHonoApphost 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:content/docs/protocol/kernel/error-handling.mdx:668(:609onorigin/main):UNIQUE_VIOLATION"flat … so it is never nested".:680-:683(:621-:624onorigin/main): "every route answers the wire codeUNIQUE_VIOLATIONand the in-process spelling never crosses HTTP".content/docs/api/error-catalog.mdx:507-:510: the same sentence.: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.Acceptance notes
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 [finding] the@objectstack/honocatch-all's409 METADATA_CONFLICTnames the current version only in prose: measure whether the hosted runtime'sPUT /metareaches it, and if so carrycurrentVersionas data (apackages/specchange) #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.409 CONCURRENT_UPDATErendered by #16545's recogniser (createHonoApp, the direct-mount adapter) carries nocurrentVersionorcurrentRecord, which the wire contract and objectui's conflict bar read #23531 card's remainder (currentRecord) now lives indeclaredArmContext, the renamed helper. The exclusion line, its comment pointing at 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 and the two absence pins are unchanged. The card's Landed comment names the old helper name.OBJECT_NOT_FOUND's sentence differs between the doors:RestServer's arm composesObject 'ghost' is not registered, and these doors keep the thrownObject 'ghost' not found. This PR does not movemessage.mapDataErrortakesERR_DATASOURCE_UNAVAILABLE's arm andresolveErrorResponsedoes not. No producer declares that status today, so the Hono doors never meet it. If one ever does, this rule matchesmapDataError.dependentObjectordependentCount(git grepat28fb2a0: 0 hits). The reach is any API client of acreateHonoApporHonoHttpServerhost, througherr.details.Generated by Claude Code