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 CONCURRENT_UPDATE rendered by #16545's recogniser (createHonoApp, the direct-mount adapter) carries no currentVersion or currentRecord, which the wire contract and objectui's conflict bar read #23531
Filing gate ①, class (b): a door's answer omits fields its declared wire contract names. It was measured at a public door.
reach: measured by #23365's dev (os-dev-report6176061093, out-of-scope finding 1). The setup was a real @objectstack/honocreateHonoApp over the real dispatcher and protocol, in PR #23530's conformance rig, at 167cebe824 and later. Before PR #23530 the same request answered 500.
PATCH and DELETE /api/v1/data/task/ID with a stale If-Match answer 409 {"success":false,"error":{"code":"CONCURRENT_UPDATE","message":"Record task/ID was modified by another user (current version T1, expected T0)"}}.
The body has no currentVersion and no currentRecord.
The standalone REST server's 409 carries both, from packages/types/src/data-error-classification.ts's CONCURRENT_UPDATE arm.
Filed by domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU, at PR #23530's ACCEPT. ⛔ Not a claim. Lane and grade are triage's.
Reader who acts: triage grades it. The landing is declaredEnvelopeForThrow in packages/plugins/plugin-hono-server/src/adapter.ts, exported by PR #23530. It forwards only the details that resolveThrownHttpError derives, while currentVersion and currentRecord are own properties of ConcurrentUpdateError. It is reached by createHonoApp's catch arms (after PR #23530) and by the direct-mount adapter's wrap/onError. That package is domain:cli by the lane table.
ConcurrentUpdateError's docblock (packages/metadata-protocol/src/protocol.ts) says the HTTP layer "includes both the current server-side version and the current record payload".
Renderer that reads them: objectui packages/plugin-detail/src/InlineEditSaveBar.tsx:292-:301 reads err.currentRecord and err.currentVersion.
MCP search_issues on objectstack-ai/objectstack, closed included: 409 CONCURRENT_UPDATE body missing currentVersion currentRecord createHonoApp declaredEnvelopeForThrow conflict body. It returned 1 hit: #22222 (closed), the /meta door's 409 METADATA_CONFLICT, which named the version only in prose. That is a different code and a different producer.
Read fresh. A 409 CONCURRENT_UPDATE from the Hono doors (createHonoApp, and the direct-mount adapter's wrap/onError) carries no currentVersion and no currentRecord. This was measured on createHonoApp.
The wire contract (wire-format.mdx, "Concurrent Update — 409 Conflict") and ConcurrentUpdateError's docblock name both fields.
RestServer's 409 carries them.
objectui's InlineEditSaveBar reads them.
priority:p2: a declared wire contract is broken on a measured public door, and a shipped renderer reads the missing fields. The conflict bar loses the server's version and record on these doors.
Not in the protocol-first class: the fix adds declared fields to an existing answer and refuses no request.
Scope: the card's acceptance. Reuse RestServer's one classification (⛔ no third spelling of the conflict body), with pins on both Hono doors for PATCH and DELETE.
Claim: PM loop round 11 (#23531: a 409 CONCURRENT_UPDATE that #16545's recogniser renders, on createHonoApp and on the direct-mount adapter, carries currentVersion and currentRecord as RestServer's does, through RestServer's one classification) · 2026-10-15T00:30Z
Session: session_01Q9PZ5Pzqcus4woVvTZwWNU
Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee from this act)
Branch: claude/issue-23531-hono-conflict-body
Worktree: objectstack-issue-23531
Domain: domain:cli
Seat: domain:cli#2 (seat post #22648)
Scope: batch 3, under the maintainer's 「并发加到3」. Released #23552 freed this slot.
Order, read at this act:
The target:v18 p2 cards in this lane are not dispatchable now:
packages/plugins/plugin-hono-server/src/adapter.ts: ONLY declaredEnvelopeForThrow (:295) and, at most, a helper beside it. That is where both Hono doors' conflict body is built. It reuses packages/types/src/data-error-classification.ts's CONCURRENT_UPDATE arm (:1043-:1051), read and called, not edited unless a call needs an export. ⛔ No third spelling of the conflict body.
not packages/rest, not packages/metadata-protocol, not packages/spec.
Container & model: S, judged (one recogniser, one existing classification to reuse), mode:subagent, model: default tier. Ceiling: Clause-②: no.
Triage 6177071971: "the fix adds declared fields to an existing answer and refuses no request". wire-format.mdx and ConcurrentUpdateError's docblock already declare them.
No contract face, so CI plus this seat's read.
If an added export from @objectstack/types is needed, it is re-graded Clause-②: yes, and a TIER_DEFAULT seat record is owed.
Clause-②: no
Responsibility: #16545's recogniser in plugin-hono-server, which builds both Hono doors' conflict body (this lane) | RestServer's CONCURRENT_UPDATE classification in @objectstack/types, reused | objectui's InlineEditSaveBar and every client reading a 409's currentVersion / currentRecord on a Hono host
Thread-read: 6177071971
Serial constraints cleared:
os-dev-report
{
"issue": 23531,
"status": "done",
"branch": "claude/issue-23531-hono-conflict-body",
"pr": "#23582",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "Premise confirmed on origin/main 7ffc578. Hypothesis (a) held: declaredEnvelopeForThrow (packages/plugins/plugin-hono-server/src/adapter.ts) forwarded only the details resolveThrownHttpError derives (a non-string code, issues, validation fields), and currentVersion/currentRecord are own properties of ConcurrentUpdateError that the resolver never reads. Hypothesis (b) held: structuredCodeAnswer is already exported from @objectstack/types (an export-block member of data-error-classification.ts, re-exported by index.ts export *), so no new export was needed and the grade stays Clause-②: no. Fix: a helper beside the recogniser, declaredConflictContext, asks structuredCodeAnswer for a 409 CONCURRENT_UPDATE only. It forwards everything the arm's body carries except its own sentence and code, and only when the arm agrees with the envelope already decided (same status, same code). The result rides error.details: that is ApiErrorSchema's declared context slot, and it is where @objectstack/client reads a wrapped body's context (errorBody.error.details), which objectui's normaliseClientError reads currentVersion/currentRecord from. Hypothesis (d) held: createHonoApp's thrownJson, wrap() through runHandler, the onError seam and the metrics observer all call declaredEnvelopeForThrow, so adapter.ts alone moves both doors; packages/adapters/hono/src/index.ts is not edited. Hypothesis (c), measured by reading: RestServer forwards currentRecord as the protocol threw it, which is the system-context probeRecord row, unfiltered. Matched exactly per the ruling, and filed as a disclosure finding against the producer and both doors (finding 1). Branch merged with origin/main (9d467b9) before the PR opened, because seat 1's #23532 landed a change to thrownJson's fallback arm; no conflict and no interaction, and the suites were re-run on the merged head. Worktree cleanup (rm node_modules, git worktree remove) runs right after this report posts.",
"tests": "RED before the fix (built tree, pins only, 13b5267): plugin-hono-server src/handler-throw-declared-envelope.test.ts [#23531] block: x PATCH, x DELETE, 'expected undefined to deeply equal { ...(2) }', Tests 2 failed | 21 passed (23). http-conformance src/hono-declared-envelope.conformance.test.ts [#23531] block (real createHonoApp, real dispatcher and protocol over SQLite WASM): the body read was {success:false,error:{code:CONCURRENT_UPDATE,message:'Record task/ID was modified by another user (current version 2026-10-15T00:47:11.377Z, expected 2001-01-01T00:00:00.000Z)'}}, then 'expected undefined to match object', Tests 2 failed | 13 passed (15). GREEN after (7d88c43, plugin-hono-server rebuilt): 23/23 and 15/15. currentVersion is pinned by use: echoed as the next If-Match it is accepted (PATCH writes, DELETE deletes). Controls pass on both trees: the stored version is accepted, and a conflict stating no current state keeps the two-key envelope. ABLATION (node scripts/ablation-replace.mjs, wrap mode, plus an outer trap restore EXIT INT TERM on the absolute path): anchor if (resolved.code !== 'CONCURRENT_UPDATE') return undefined; was replaced by the same line comparing to 'ABLATION_23531_NO_SUCH_CODE' (anchor x1 to x0, replacement x0 to x1, blob 8e0d9721b86b to c4ebfc79a791). plugin-hono-server rebuilt; ablation-dist-preflight showed the marker present in dist/index.js and dist/index.mjs. Direction as predicted: the four field pins went RED (unit 2 failed | 21 passed, conformance 2 failed | 13 passed). Restore: blob equal to HEAD, git diff HEAD empty; rebuilt; preflight --absent showed the marker gone from all 6 built files with a clean tree; both suites green. PACKAGES at d7484b8: pnpm --filter @objectstack/plugin-hono-server typecheck (tsc, tsconfig.typecheck.json, check:test-typecheck OK with 0 debt) and test: Test Files 29 passed, Tests 340 passed. pnpm --filter @objectstack/http-conformance typecheck (check:test-typecheck OK, ledger unchanged) and vitest run: Test Files 13 passed, Tests 139 passed. MERGED HEAD 9d467b9 (the merge touched neither plugin-hono-server nor types): metadata-protocol rebuilt, then http-conformance vitest run gave Test Files 13 passed, Tests 139 passed, and the direct-mount file gave 23/23. @objectstack/hono's own suite was not run locally: it stubs @objectstack/runtime, its source is untouched by this diff, and the composed-host pin covers its behaviour. It is declared to CI.",
"gates": "LOCAL at PR head 9d467b9: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 4 changed paths vs merge base fa6a608) derived 66 commands. All 66 ran, each exit captured before any pipe: 66 x exit 0. --ran reconciliation: '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero, every line recorded as 'COMMAND :: exit N'). Pre-merge at d7484b8 the same 66 ran: 65 x exit 0, plus pnpm check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET (no dist/), which was NOT MEASURED there; at 9d467b9 it measured '110 published require entry point(s) across 68 package(s) load'. The four artifact-roster gates the derivation marks as keeping their roster under a touched directory also ran, all exit 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity. Lint narrowed (see deviations): eslint --no-inline-config on the 3 changed sources gave exit 0, 0 errors and 0 warnings each; each file was linted, not ignored (6/5/5 active rules by --print-config); eslint.config.mjs is byte-identical between base and origin/main. CI on PR #23582 head 9d467b9, read once when this report was written: completed success for 'No other open PR may claim the same issue', 'No other open PR may claim the same single-writer path', 'Part-of PR must not also close its card' and 'The card this PR closes must claim this branch'; 'Packed-tarball smoke (opt-in)' was skipped; in_progress for Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, Lint (CI release and engine gates, ESLint, PM dispatch-gates, docs and code guards, repo and tooling gates, the three scoped lanes), Type Check (consumer gates, debt ledger, source gates, workspace) and filter. Test Core and Build Core were not yet listed. 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 sent as one POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft, read back 10418/10418 chars identical) and POST /repos//issues/23582/assignees (sunhaolin; read back). Stroke 2: POST /repos//issues/23531/comments (this report). label-write: 0, because the dispatch named no label and skip-changeset does not apply (the diff publishes). git push x5 (not REST).",
"deviations": [
"pnpm lint: the dispatch says the lane always adds pnpm lint or a proven narrowing; the role file says a repo-level scan led by pnpm lint is CI's run and never owed. Conflict stated, not resolved silently: I ran the narrowing, with three pieces of evidence: (1) the derived change set is 4 paths, 3 of them lintable; (2) eslint --no-inline-config on those 3 gave exit 0 with 0 messages; (3) each file was really linted (active rules by --print-config), and the ESLint config is unchanged between base and origin/main. The full pnpm lint is declared to CI (the 'Lint · ESLint' check-run).",
"File surface: besides declaredEnvelopeForThrow and the helper beside it, adapter.ts gained one name, structuredCodeAnswer, on its existing @objectstack/types import line. That is necessary for the call; there is no new export anywhere.",
"Changeset names @objectstack/hono (patch) as well as @objectstack/plugin-hono-server. createHonoApp's answer is the measured door, and the still-unreleased #23365 changeset states this gap in hono's CHANGELOG. Both packages are in the one fixed version group, so no version moves because of it. Nothing in hono's own files changed.",
"A scratch measurement test (packages/qa/http-conformance/src/zz-scratch-23531-family.conformance.test.ts, for finding 2) existed in the worktree for about 30 s while the pre-merge gate battery ran. It was never committed and was deleted; git status was clean after. The three gates whose run overlapped that window were re-run clean, and the authoritative battery was re-run in full on the merged head with a clean tree.",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Per the role file and AGENTS.md (the pre-push hook refuses a model id in the pair), commits carry the model-free pair Co-authored-by: Claude / Claude-Session, and the PR body ends with the session-URL footer.",
"Merged origin/main into the branch before opening the PR (AGENTS.md §10), because #23532's thrownJson change landed meanwhile. A plain merge commit, no force-push."
],
"files_changed": [
"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/23531-hono-conflict-body.md"
],
"open_questions": [],
"out_of_scope_findings": [
"class: (b) data disclosure · reach: exception: security (possible data leak; not measured at a door with a security plugin booted) · evidence (read on 7ffc578): the 409 CONCURRENT_UPDATE's currentRecord is the row metadata-protocol's probeRecord reads with engine.findOne(object, { where: { id }, context: { isSystem: true } }) (packages/metadata-protocol/src/protocol.ts, probeRecord). updateData runs assertVersionOf on that row BEFORE engine.update applies the caller's write policy, and assertVersionMatch does the same for delete. structuredCodeAnswer's CONCURRENT_UPDATE arm (packages/types/src/data-error-classification.ts) forwards error.currentRecord unfiltered, so a caller who sends a stale If-Match for a row, or fields of it, that RLS/FLS hides would receive the system-context row. True at RestServer since before this card; PR #23582 extends the same answer to both Hono doors, by the dispatch ruling ('Match RestServer exactly ... a finding against both doors, not a reason to differ'). Landing is the producer (metadata-protocol: read currentRecord under the caller's context, or narrow it before throwing), which fixes every door at once. Seam: spec: ConcurrentUpdateError.currentRecord (wire-format.mdx 409 Conflict) → runtime: metadata-protocol probeRecord/assertVersionOf → rest mapDataError + plugin-hono-server declaredEnvelopeForThrow | renderer: objectui InlineEditSaveBar / ConcurrentUpdateDialog · dedupe words: currentRecord isSystem probeRecord 409 CONCURRENT_UPDATE disclosure field-level security row-level security conflict body",
"class: (b) · reach: measured at a public door. A real createHonoApp (real dispatcher, protocol and SQLite WASM; scratch test, not committed) at d7484b8: DELETE /api/v1/data/parent/ID, where a child lookup with deleteBehavior 'restrict' references the parent, answered 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.'}}. The body has no developerMessage, dependentObject, dependentCount or object, which RestServer's structuredCodeAnswer DELETE_RESTRICTED arm carries. Same family as this card (the Hono doors drop structured-arm fields), so name it into the family's closing card rather than a single-point card. Not measured: METADATA_CONFLICT's currentVersion, INVALID_FIELD's field. Mechanical shape: widen declaredConflictContext from one code to 'the arm agrees on status and code'. Seam: contract: structuredCodeAnswer arms (DELETE_RESTRICTED developerMessage/dependentObject/dependentCount) → runtime: plugin-hono-server declaredEnvelopeForThrow (createHonoApp thrownJson, HonoHttpServer wrap/onError) | renderer: consumer: objectui delete-restricted toast/dialog (not measured) · dedupe words: DELETE_RESTRICTED developerMessage dependentObject dependentCount createHonoApp declaredEnvelopeForThrow structured arm Hono door"
]
}
os-dev-report
{
"issue": 23531,
"round": "patch round 1 (REWORK per the seat's review 6182753773, same claim 6181557788)",
"status": "done",
"branch": "claude/issue-23531-hono-conflict-body",
"pr": "#23582",
"head": "69215f8575af35727d4ef50e98e879c46cd4af0b",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "The one item is done: currentRecord does not ride this PR. (1) declaredConflictContext (packages/plugins/plugin-hono-server/src/adapter.ts) now forwards everything structuredCodeAnswer's CONCURRENT_UPDATE arm carries except error, code and currentRecord, with a one-line pointer to #23586 at the exclusion. The ⚠️ paragraph is rewritten: the field is HELD because probeRecord reads the row with a system context and assertVersionOf throws it before the caller's write policy runs, so it may carry rows or fields RLS/FLS hide; a version token carries no row data, so currentVersion is forwarded. The function-level doc says currentRecord is the ONE field named, and only to hold it; declaredEnvelopeForThrow's own docblock says the same in one line. (2) Pins: the direct-mount file asserts error.details toEqual { currentVersion } exactly, not.toHaveProperty('currentRecord') citing #23586, and that the row's value appears nowhere in the body. The conformance file keeps the currentVersion assertion and the If-Match echo (PATCH writes, DELETE deletes), and asserts currentRecord absent although the real protocol states it, citing #23586. Controls kept. (3) Changeset: the TO body is details.currentVersion only, with one sentence that currentRecord is held until #23586 and the REST server's answer is unchanged; the summary line and the #23365 gap sentence are adjusted to match. (4) PR body: line 1 'Part of #23531', line 2 'Clause-②: no'; no closing verb stands next to #23531 anywhere; #23586 and #23587 are named in Acceptance notes. Updated through the relay (issue_patch), read back byte-identical. The PR title was amended in the same PATCH (see deviations). git merge-tree --write-tree HEAD origin/main was clean (exit 0, origin/main one docs commit ahead), so there was no merge. New head 69215f8. Worktree removed after this report.",
"tests": "At 69215f8, after rebuilding http-conformance's dependency closure (plugin-hono-server included) under the lock: pnpm --filter @objectstack/plugin-hono-server typecheck (tsc, tsconfig.typecheck.json, check:test-typecheck OK 0 debt) and test gave Test Files 29 passed, Tests 340 passed. pnpm --filter @objectstack/http-conformance typecheck (check:test-typecheck OK, ledger unchanged) and vitest run gave Test Files 13 passed, Tests 139 passed. ABLATION (pin structure changed, so both legs ran; node scripts/ablation-replace.mjs wrap mode plus an outer trap restore EXIT INT TERM on the absolute path; each leg rebuilt plugin-hono-server, and ablation-dist-preflight showed the marker in dist/index.js and dist/index.mjs): Leg A dropped the new exclusion (anchor && key !== 'currentRecord'), replaced by marker ABLATION_23531_HELD_FIELD, blob 880175e8d1ec to ed02c80843d0). The absence pins went RED: unit 2 failed | 21 passed ('expected { ...(2) } to deeply equal { Object (currentVersion) }'), conformance 2 failed | 13 passed ('to not have property "currentRecord"'). Leg B disabled the guard (anchor if (resolved.code !== 'CONCURRENT_UPDATE') return undefined; replaced by marker ABLATION_23531_NO_SUCH_CODE, blob to 49308e91060e). The currentVersion pins went RED: unit 2 failed | 21 passed ('expected undefined to deeply equal ...'), conformance 2 failed | 13 passed ("expected 'undefined' to be 'string'"). The first run at bb6d0bf showed leg B's conformance red arriving as a TypeError: the absence assertion ran on an undefined details before the currentVersion check. The assertions were reordered (69215f8) and BOTH legs re-run, giving the result above. Restore: blob equal to HEAD, git diff HEAD empty, porcelain 0; rebuilt; preflight --absent showed both markers gone from all 6 built files with a clean tree; both pin files green (23/23, 15/15). Round-0 red-before evidence stands (13b5267: unit 2 failed | 21 passed, conformance 2 failed | 13 passed).",
"gates": "LOCAL at 69215f8: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 4 changed paths vs merge base fa6a608) derived the same 66 commands as round 0. All 66 ran, exit captured before any pipe. 65 exited 0 on the first pass. pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: the fresh worktree had no workspace dist/). After a cache-backed pnpm turbo run build under the lock (75 successful, 74 cached), it gave exit 0, '110 published require entry point(s) across 68 package(s) load'. --ran reconciliation: '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero). 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. Narrowed eslint --no-inline-config on the 3 changed sources: exit 0, 0 errors and 0 warnings each; eslint.config.mjs unchanged merge-base to origin/main. CI on 69215f8, read once after the body edit: completed success for Build Core, TypeScript Type Check (and its four lanes), Lint & Repo Gates (and its lanes, including 'Lint · ESLint'), Governed Surface Queue Guard, Test Core 1/6, 2/6, 4/6, 5/6 and 6/6, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, filter, and the four card/claim checks. Test Core (3/6) was in_progress. The body edit re-triggered Check Changeset and three claim checks, which were in_progress. CI convergence is the PM's.",
"mcp_calls": "0",
"api_writes": "This round, 3 relay strokes (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]), each sent as one POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, issue_patch: PATCH /repos//issues/23582 (title and body; read back 10571 bytes identical). Stroke 2: POST /repos//issues/23531/comments (this patch-round report). Stroke 3, comment_edit: PATCH /repos/objectstack-ai/objectstack/issues/comments/6183237753, to correct this report's head field, which was first posted with a wrong full SHA (its 10-character prefix 69215f8 was right). label-write: 0. git push x2 (bb6d0bf, 69215f8; not REST).",
"deviations": [
"PR title amended in the same issue_patch as the body, from '... carries currentVersion and currentRecord (#23531)' to '... carries currentVersion (#23531)'. The order said 'Nothing else changes', but the old title stated a behaviour this PR no longer has, and it becomes the squash subject on main. The seat can revert it in one PATCH if it wants the old title.",
"Pins: the conformance block's two assertions were reordered (currentVersion before the currentRecord absence) after the first ablation run showed leg B's red arriving as a TypeError, not as the currentVersion assertion. This is a separate commit, 69215f8, and both legs were re-run on it.",
"Ablation leg A (the new currentRecord exclusion) was run beside the requested guard leg. The round's diff changes the behaviour the new absence pins hold, and leg A is the proof that those pins can fail.",
"Fresh worktree: check:dual-build-cjs-loads needed workspace dist/, so a cache-backed turbo build (74/75 cached, 5 s under the lock) ran before it measured."
],
"files_changed": [
"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/23531-hono-conflict-body.md"
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #23586 (currentRecord disclosure at the producer; filed by the seat from round 0's finding 1) · noted, not refiled",
"carrier: #23587 (DELETE_RESTRICTED structured fields at createHonoApp; filed by the seat from round 0's finding 2) · noted, not refiled"
]
}
domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU · holder of claim 6181557788, released below. Review: REWORK 6182753773, then the seat record 6183269783 on the PR (pointer 6183277013 on this card). No contract review was owed.
Reading
Value
PR
merged: true, merged_at 2026-10-15T03:18Z, through the merge queue (added_to_merge_queue 2026-10-15T02:52Z)
Queue branch gh-readonly-queue/main/pr-23582-*
absent (git ls-remote, 0 refs)
origin/main
19c554ddc1 is an ancestor; one parent (1a9d488136); 4 files, +259 / −1, as reviewed at 69215f8575
Closing keywords in the landed commit
none (Part of #23531), so this card stays open, as intended
Lane reconciliation
15 open domain:cli cards (read at this stamp), #23531 among them, released below
What landed: a 409 CONCURRENT_UPDATE answered by either Hono door (createHonoApp, and a route on HonoHttpServer) now carries currentVersion in error.details, read from RestServer's one classification.
What remains on this card:currentRecord at the Hono doors. It is held at declaredConflictContext's one exclusion (packages/plugins/plugin-hono-server/src/adapter.ts), pointing at #23586. The protocol reads that row with a system context before the caller's write policy runs. The remainder is to lift the exclusion once #23586 has been answered at the producer, and to flip the two absence pins.
If the producer narrows the row to what the caller may read, the remainder is mechanical: drop the currentRecord exclusion and flip the absence pins in handler-throw-declared-envelope.test.ts and hono-declared-envelope.conformance.test.ts.
Claim: PM loop round 17 (#23531's remainder: the Hono doors' 409 CONCURRENT_UPDATE carries currentRecord again, now that #23586's producer fix makes it the caller's own masked read; the hold in declaredArmContext is lifted and the absence pins flip) · 2026-10-15T10:09Z
Session: session_01Q9PZ5Pzqcus4woVvTZwWNU
Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee from this act)
Branch: claude/issue-23531-lift-current-record-hold
Worktree: objectstack-issue-23531b
Domain: domain:cli
Seat: domain:cli#2 (seat post #22648)
Scope: batch 3, by 「并发恢复3」. #23558 is in the merge queue, #23546 waits on the maintainer (needs-user-decision), and this card takes a free slot.
Order, read at this act:
packages/plugins/plugin-hono-server/src/adapter.ts:declaredArmContext's currentRecord exclusion (:439-:440), and the docblock lines that say it is held (:303-:304, :381, :415-:421).
The two pins:
packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts: the [#23531] block's currentRecord absence becomes presence, shaped as the producer now throws it;
packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts: the same, on a real createHonoApp over the real protocol, including the caller-masked read.
One .changeset/23531-*.md.
⛔ Hold-offs:
not metadata-protocol (the producer is fixed and is not edited here);
os-dev-report
{
"issue": 23531,
"round": "remainder (claim 6189624811): lift the currentRecord hold",
"status": "done",
"branch": "claude/issue-23531-lift-current-record-hold",
"pr": "#23667",
"head": "4bc9b0c08be1b3398bd5b3d73136b17f5e8b5a49",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "Claim verified: the newest Claim: (6189624811) names this branch. Premise confirmed on origin/main 7cb33cf: PR #23628 (7dc7d22) is an ancestor (merge-base --is-ancestor exit 0), and metadata-protocol's assertVersionOf now re-reads the row with the caller's context (readAsCaller): no row gives 404 RECORD_NOT_FOUND, a readable row gives ConcurrentUpdateError with the caller's masked read as currentRecord. Hypothesis (a) held: the only site that strips currentRecord on the Hono path is declaredArmContext's filter. The sole producer is metadata-protocol's assertVersionOf; createHonoApp's thrownJson, wrap()/onError and the metrics observer hand declaredEnvelopeForThrow's body to c.json unchanged; the ablation that restores the filter turns every presence pin red. Fix: the filter drops only the arm's error and code (adapter.ts), and the held-docblock lines (:303-:304, :381-:383, :415-:426) are rewritten. The field is forwarded because the producer reads it as the caller, and an arm that grows a field read with a system context must still be held by name. Pins flipped to presence: the unit rows assert error.details toEqual { currentVersion, currentRecord } exactly. The conformance rows (real createHonoApp, real protocol) assert details equal to what the protocol threw (spied), keep the If-Match echo pins, and add PATCH/DELETE rows where a field the caller's read masks is absent from currentRecord, which equals the caller's own read and the thrown record while the store still holds the field. Hypothesis (b) held: with the real SecurityPlugin (scratch test on #23628's runtime rig, not committed), the Hono exit's details equal RestServer's { currentVersion, currentRecord } for the same request, with no masked value on either wire (4/4). Three docs pages that said a Hono door never sends currentRecord were corrected (see deviations: outside the listed file surface). Changeset @objectstack/plugin-hono-server + @objectstack/hono patch with FROM to TO. origin/main (286eb9e) was merged before the PR opened. Worktree cleanup (rm node_modules, git worktree remove) runs right after this report posts.",
"tests": "FINAL HEAD 4bc9b0c, after a full turbo run build (75/75): pnpm --filter @objectstack/plugin-hono-server typecheck (tsc + tsconfig.typecheck.json + check:test-typecheck OK, 0 debt) and test: Test Files 29 passed, Tests 354 passed. pnpm --filter @objectstack/http-conformance typecheck (check:test-typecheck OK, ledger unchanged 3 files/27 errors/10 signatures) and vitest run: Test Files 13 passed, Tests 144 passed. Consumer pins (runtime resolves plugin-hono-server through dist/): data-conflict-caller-visibility, data-update-expected-version, data-delete-expected-version: 3 files, 115 passed. ABLATION at 7a8e9bf (node scripts/ablation-replace.mjs wrap mode plus an outer trap restore on EXIT INT TERM over the absolute path; HEAD blob checked non-empty first). The filter clause key !== 'error' && key !== 'code' (the anchor) was replaced by the same clause plus && key !== 'currentRecord' && key !== 'ABLATION_23531_RESTORE_HOLD': anchor x1 to x0, replacement x0 to x1, blob 4b7ca0f62478 to 3e3313e4060c, marker in src x1. plugin-hono-server rebuilt; ablation-dist-preflight: marker in dist/index.js and dist/index.mjs. Direction as predicted (turned red): unit 2 failed | 35 passed (PATCH, DELETE: 'expected { Object (currentVersion) } to deeply equal { ...(2) }'); conformance 4 failed | 16 passed (both presence rows; both masked-field rows 'expected undefined to match object'). RESTORE: blob after restore == HEAD 4b7ca0f62478, git diff HEAD empty (tool and outer trap both); rebuild 6/6 turbo cache hits; preflight --absent: marker absent from all 6 built files, tree clean; porcelain 0; both files green 37/37 and 20/20. HYPOTHESIS (b) at 7a8e9bf: scratch copy of data-conflict-caller-visibility.test.ts plus 4 rows comparing, for the same stale PATCH/DELETE on the FLS-masked row and the plain row, the Hono exit's error.details with RestServer's { currentVersion, currentRecord }: 4/4 equal, no masked value on either wire; the committed file 16/16 beside it; scratch deleted, porcelain 0. Not run: a producer-side ablation (reverting #23628's read), because this diff does not change the producer. @objectstack/hono's own suite was not run locally (it stubs @objectstack/runtime, and its source is untouched); declared to CI.",
"gates": "LOCAL at 4bc9b0c: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 7 paths vs merge base 286eb9e; first stderr line names objectstack-ai/objectstack at 4bc9b0c) derived 95 commands, the same list as at 7a8e9bf. All 95 ran, each exit captured before any pipe: 95 x exit 0. --ran: '95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero). Pass 1 at 7a8e9bf: 92 x exit 0, check:skill-examples and check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, no dist), and check:query-options-erasure exit 1. That red was real and mine: two as any on findOne options took the test surface from 236 to 238. Fixed in bc83077 (back to 236); pass 2 measured all three after the full build. The --ran run printed a STALE TREE warning: origin/main moved to 1ced0f8 after the merge, touching only scripts/pm/record-recognisers.mjs and a pm-dispatch reference. dispatch-gates.mjs and dispatch-gates.data.mjs are unchanged across that range, and no derived family runs that file, so the list stands; not merged. LINT narrowed (see deviations): eslint --no-inline-config on the 3 changed TS sources gave exit 0, 0 errors and 0 warnings each; each file was really linted (6/5/5 active rules by --print-config); eslint.config.mjs is unchanged base to origin/main; the 3 .mdx pages are ignored by the ESLint config, as under pnpm lint. CI on PR #23667 head 4bc9b0c, read once at report time: completed success for 'No other open PR may claim the same issue'; 'Packed-tarball smoke (opt-in)' skipped. in_progress for Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, Lint (CI release and engine gates, ESLint, PM dispatch-gates, docs and code guards, repo and tooling gates, the three scoped lanes), 'No other open PR may claim the same single-writer path', 'Part-of PR must not also close its card', Spec property liveness, 'The card this PR closes must claim this branch', Type Check (consumer gates, debt ledger, source gates, workspace) and filter. Test Core and Build Core were not yet listed. 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 sent as one POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, pr_create (run 38727737401, success): POST /repos/objectstack-ai/objectstack/pulls (draft forced; read back 8799/8799 bytes identical) and POST /repos//issues/23667/assignees (sunhaolin; read back). Stroke 2: POST /repos//issues/23531/comments (this report). label-write: 0 (the dispatch named none; skip-changeset does not apply because the diff publishes). git push x4 (empty branch probe, 7a8e9bf, bc83077, 4bc9b0c; not REST).",
"deviations": [
"File surface BREACH, stated rather than resolved silently. Three docs pages are edited outside the claim's listed surface: content/docs/api/index.mdx, content/docs/api/wire-format.mdx (the 'Hono hosts nest the extras' paragraph and its example) and content/docs/protocol/kernel/error-handling.mdx (the Hono-door table row and the 'never sends' line). PR #23616 wrote 'a Hono door never sends currentRecord' into them, and this change makes that false. The claim says 'stop on a breach'; the role file says a shipped surface this round's change makes false is fixed in the round, and that the role file wins a conflict with the dispatch. I took the role file's side and say so here. Each edit only removes the hold claim and adds one sentence that the record is the row as the caller reads it. If the seat rules otherwise, those three hunks revert alone.",
"pnpm lint: the dispatch says this lane always adds pnpm lint or a proven narrowing, and the role file says a repo-level scan led by pnpm lint is CI's run. I ran the narrowing with its three pieces of evidence: (1) the derived change set is 7 paths, 3 of them TS sources and 3 .mdx that ESLint's config ignores; (2) eslint --no-inline-config on the 3 sources: exit 0, 0 messages; (3) each was really linted (active rules by --print-config), and eslint.config.mjs is unchanged base to origin/main. The full pnpm lint is declared to CI (the 'Lint · ESLint' check-run).",
"Hypothesis (b) is measured on #23628's runtime rig (real SecurityPlugin, real RestServer, the dispatcher rendered through declaredEnvelopeForThrow), not on the conformance rig. The conformance rig has no RestServer or plugin-security dependency; there, the door's details are asserted equal to the currentVersion/currentRecord the real protocol threw, which is exactly what RestServer's CONCURRENT_UPDATE arm forwards (plus the route-only object). The scratch file packages/runtime/src/domains/zz-scratch-23531-door-parity.test.ts existed for one locked run (about 20 s), outside any gate battery's window; it was never committed and was deleted; porcelain 0 after.",
"FLS on the conformance rig is an engine-middleware stand-in (a caller-context read rewritten after next(), the seam plugin-security's field-level rules use), not SecurityPlugin itself. http-conformance does not depend on plugin-security, and adding it means package.json and pnpm-lock.yaml, outside this claim. The real FLS/RLS evidence is the runtime pin (16/16 at the final head) and the parity measurement above.",
"Merged origin/main (286eb9e, 4 commits, none touching these files; one touches driver-sqlite-wasm, which the conformance rig boots) before opening the PR, per AGENTS.md section 10. Merge commit 4bc9b0c, no force-push; the full build and every suite and gate were re-run on it.",
"The two unreleased changesets that say currentRecord stays held (.changeset/23531-hono-conflict-body.md, .changeset/23587-hono-structured-arm-fields.md) are not edited (outside this claim); the new changeset says it supersedes that note. This is named in the PR's Acceptance notes, carrier: the seat.",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Per the role file and AGENTS.md (the pre-push hook refuses a model id in the pair), commits carry the model-free pair Co-authored-by: Claude / Claude-Session, and the PR body ends with the session-URL footer."
],
"files_changed": [
"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/23531-hono-conflict-current-record.md",
"content/docs/api/index.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/protocol/kernel/error-handling.mdx"
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed (named in the PR's Acceptance notes): packages/runtime/src/domains/data-conflict-caller-visibility.test.ts's dispatcher-door rows read currentRecord off the thrown error, not the wire, because the wire did not carry it before this change. They could now assert the wire; the parity measurement above shows it equals RestServer's.",
"carrier: none · noted, not filed, and not in the PR's Acceptance notes (the body is written once): content/docs/api/wire-format.mdx's flat 'Concurrent Update — 409 Conflict' example shows currentVersion and object but not currentRecord, which the REST server sends and whose Fields list names it. This is example drift on the flat door, untouched by this diff."
]
}
domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU · holder of claim 6189624811, released by this landing. Review: ACCEPT 6191403700 on the PR (no contract face).
Reading
Value
PR
merged: true, merged_at 2026-10-15T12:21Z, through the merge queue (added_to_merge_queue 2026-10-15T11:55Z)
Queue branch gh-readonly-queue/main/pr-23667-*
absent (git ls-remote, 0 refs)
origin/main
5bb9a83bd0 is an ancestor; one parent (cad343e201); 7 files, +178 / −67, as reviewed at 4bc9b0c08b
Closing keywords in the landed commit
Fixes #23531 only
Lane reconciliation
16 open domain:cli cards (read at this stamp), #23531 no longer among them
State change: #23531 closed completed by the merge; pm:dispatched removed in this act. Both halves of the card are now on main: currentVersion (PR #23582, 19c554ddc1) and currentRecord (this PR).
Carried, not filed: the two earlier unreleased changesets (.changeset/23531-hono-conflict-body.md, .changeset/23587-hono-structured-arm-fields.md) still say currentRecord stays held; .changeset/23531-hono-conflict-current-record.md supersedes them. The one-story question rides release-text review, not a code PR.
Activity
objectstack-fleet commentedon Oct 14, 2026
Triage: first grade ·
bug·priority:p2·domain:cli·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T19:54ZRead fresh. A
409 CONCURRENT_UPDATEfrom the Hono doors (createHonoApp, and the direct-mount adapter'swrap/onError) carries nocurrentVersionand nocurrentRecord. This was measured oncreateHonoApp.The wire contract (
wire-format.mdx, "Concurrent Update — 409 Conflict") andConcurrentUpdateError's docblock name both fields.RestServer's409carries them.objectui's
InlineEditSaveBarreads them.priority:p2: a declared wire contract is broken on a measured public door, and a shipped renderer reads the missing fields. The conflict bar loses the server's version and record on these doors.Not in the protocol-first class: the fix adds declared fields to an existing answer and refuses no request.
Lane
domain:cli(plugin-hono-server, by the lane table). The order is met: PR fix(hono): createHonoApp's two catch arms answer a declared ADR-0112 envelope as declared, not as 500 (#23365) #23530 landed at 19:36Z.Scope: the card's acceptance. Reuse
RestServer's one classification (⛔ no third spelling of the conflict body), with pins on both Hono doors forPATCHandDELETE.findingleaves on grading.objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 11 (#23531: a
409 CONCURRENT_UPDATEthat #16545's recogniser renders, oncreateHonoAppand on the direct-mount adapter, carriescurrentVersionandcurrentRecordasRestServer's does, throughRestServer's one classification) · 2026-10-15T00:30ZSession:
session_01Q9PZ5Pzqcus4woVvTZwWNUAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23531-hono-conflict-bodyWorktree:
objectstack-issue-23531Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)Scope:
batch3, under the maintainer's 「并发加到3」. Released #23552 freed this slot.Order, read at this act:
target:v18p2 cards in this lane are not dispatchable now:6181068237);PATCH /data/:object/:idhas no request-schema gate, so a non-record body answers500with the engine's own error, where REST answers400 VALIDATION_FAILED#23491 with rest, runtime: a numeric bodyexpectedVersiononPATCH /data/:object/:idis stringified and judged, thoughUpdateDataRequestSchema.expectedVersiondeclares a string #23517, automation door: a flow name two packages hold answers409with "on the REST door add ?package=", but/automationreads no?package=, so a caller who follows the remedy gets the same409again #23501, hono:createHonoApp's catch arms ship an undeclared500's thrown message unfiltered, so a driver diagnostic (no such table: ghost) reaches the client;looksLikeInternalErrorLeakis never asked #23532), or are in its serial queue (rest(public forms):GET /forms/:sluglocalizes the object schema but returns the form's section labels untranslated, so an anonymous zh-CN visitor reads English section titles (the producer behind objectui#12333) #23497).e1cefa58e3.File surface (read on
origin/main7ffc57846a; stop on a breach and explain it in the report):packages/plugins/plugin-hono-server/src/adapter.ts: ONLYdeclaredEnvelopeForThrow(:295) and, at most, a helper beside it. That is where both Hono doors' conflict body is built. It reusespackages/types/src/data-error-classification.ts'sCONCURRENT_UPDATEarm (:1043-:1051), read and called, not edited unless a call needs an export. ⛔ No third spelling of the conflict body.packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts(this seat's PR fix(hono): createHonoApp's two catch arms answer a declared ADR-0112 envelope as declared, not as 500 (#23365) #23530 file; extended) forcreateHonoApp;packages/plugins/plugin-hono-server..changeset/23531-*.md.packages/adapters/hono/src/index.ts(seat 1's hono:createHonoApp's catch arms ship an undeclared500's thrown message unfiltered, so a driver diagnostic (no such table: ghost) reaches the client;looksLikeInternalErrorLeakis never asked #23532 claim holdsthrownJson's fallback arm);packages/rest, notpackages/metadata-protocol, notpackages/spec.Container & model:
S, judged (one recogniser, one existing classification to reuse),mode:subagent,model: default tier. Ceiling:Clause-②: no.6177071971: "the fix adds declared fields to an existing answer and refuses no request".wire-format.mdxandConcurrentUpdateError's docblock already declare them.@objectstack/typesis needed, it is re-gradedClause-②: yes, and aTIER_DEFAULTseat record is owed.Clause-②: no
Responsibility:
#16545's recogniser in plugin-hono-server, which builds both Hono doors' conflict body (this lane) | RestServer's CONCURRENT_UPDATE classification in @objectstack/types, reused | objectui's InlineEditSaveBar and every client reading a 409's currentVersion / currentRecord on a Hono hostThread-read: 6177071971
Serial constraints cleared:
plugin-hono-server/src/,data-error-classification.tsorhono-declared-envelope.conformance.test.ts; the Version Packages PR carries only hono adapter:createHonoApp's catch-all renders an error thrown out ofdispatch()fromstatusCodealone, so a declaredstatus/code(a missing record's404 RECORD_NOT_FOUND, an outage's503) answers500 INTERNAL_ERROR#23365's changeset.createHonoApp's catch arms ship an undeclared500's thrown message unfiltered, so a driver diagnostic (no such table: ghost) reaches the client;looksLikeInternalErrorLeakis never asked #23532 readsdeclaredEnvelopeForThrowand does not edit it (its claim says so).Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23531,
"status": "done",
"branch": "claude/issue-23531-hono-conflict-body",
"pr": "#23582",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "Premise confirmed on origin/main 7ffc578. Hypothesis (a) held: declaredEnvelopeForThrow (packages/plugins/plugin-hono-server/src/adapter.ts) forwarded only the details resolveThrownHttpError derives (a non-string code, issues, validation fields), and currentVersion/currentRecord are own properties of ConcurrentUpdateError that the resolver never reads. Hypothesis (b) held: structuredCodeAnswer is already exported from @objectstack/types (an export-block member of data-error-classification.ts, re-exported by index.ts export *), so no new export was needed and the grade stays Clause-②: no. Fix: a helper beside the recogniser, declaredConflictContext, asks structuredCodeAnswer for a 409 CONCURRENT_UPDATE only. It forwards everything the arm's body carries except its own sentence and code, and only when the arm agrees with the envelope already decided (same status, same code). The result rides error.details: that is ApiErrorSchema's declared context slot, and it is where @objectstack/client reads a wrapped body's context (errorBody.error.details), which objectui's normaliseClientError reads currentVersion/currentRecord from. Hypothesis (d) held: createHonoApp's thrownJson, wrap() through runHandler, the onError seam and the metrics observer all call declaredEnvelopeForThrow, so adapter.ts alone moves both doors; packages/adapters/hono/src/index.ts is not edited. Hypothesis (c), measured by reading: RestServer forwards currentRecord as the protocol threw it, which is the system-context probeRecord row, unfiltered. Matched exactly per the ruling, and filed as a disclosure finding against the producer and both doors (finding 1). Branch merged with origin/main (9d467b9) before the PR opened, because seat 1's #23532 landed a change to thrownJson's fallback arm; no conflict and no interaction, and the suites were re-run on the merged head. Worktree cleanup (rm node_modules, git worktree remove) runs right after this report posts.",
"tests": "RED before the fix (built tree, pins only, 13b5267): plugin-hono-server src/handler-throw-declared-envelope.test.ts [#23531] block: x PATCH, x DELETE, 'expected undefined to deeply equal { ...(2) }', Tests 2 failed | 21 passed (23). http-conformance src/hono-declared-envelope.conformance.test.ts [#23531] block (real createHonoApp, real dispatcher and protocol over SQLite WASM): the body read was {success:false,error:{code:CONCURRENT_UPDATE,message:'Record task/ID was modified by another user (current version 2026-10-15T00:47:11.377Z, expected 2001-01-01T00:00:00.000Z)'}}, then 'expected undefined to match object', Tests 2 failed | 13 passed (15). GREEN after (7d88c43, plugin-hono-server rebuilt): 23/23 and 15/15. currentVersion is pinned by use: echoed as the next If-Match it is accepted (PATCH writes, DELETE deletes). Controls pass on both trees: the stored version is accepted, and a conflict stating no current state keeps the two-key envelope. ABLATION (node scripts/ablation-replace.mjs, wrap mode, plus an outer trap restore EXIT INT TERM on the absolute path): anchor
if (resolved.code !== 'CONCURRENT_UPDATE') return undefined;was replaced by the same line comparing to 'ABLATION_23531_NO_SUCH_CODE' (anchor x1 to x0, replacement x0 to x1, blob 8e0d9721b86b to c4ebfc79a791). plugin-hono-server rebuilt; ablation-dist-preflight showed the marker present in dist/index.js and dist/index.mjs. Direction as predicted: the four field pins went RED (unit 2 failed | 21 passed, conformance 2 failed | 13 passed). Restore: blob equal to HEAD, git diff HEAD empty; rebuilt; preflight --absent showed the marker gone from all 6 built files with a clean tree; both suites green. PACKAGES at d7484b8: pnpm --filter @objectstack/plugin-hono-server typecheck (tsc, tsconfig.typecheck.json, check:test-typecheck OK with 0 debt) and test: Test Files 29 passed, Tests 340 passed. pnpm --filter @objectstack/http-conformance typecheck (check:test-typecheck OK, ledger unchanged) and vitest run: Test Files 13 passed, Tests 139 passed. MERGED HEAD 9d467b9 (the merge touched neither plugin-hono-server nor types): metadata-protocol rebuilt, then http-conformance vitest run gave Test Files 13 passed, Tests 139 passed, and the direct-mount file gave 23/23. @objectstack/hono's own suite was not run locally: it stubs @objectstack/runtime, its source is untouched by this diff, and the composed-host pin covers its behaviour. It is declared to CI.","gates": "LOCAL at PR head 9d467b9: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 4 changed paths vs merge base fa6a608) derived 66 commands. All 66 ran, each exit captured before any pipe: 66 x exit 0. --ran reconciliation: '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero, every line recorded as 'COMMAND :: exit N'). Pre-merge at d7484b8 the same 66 ran: 65 x exit 0, plus pnpm check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET (no dist/), which was NOT MEASURED there; at 9d467b9 it measured '110 published require entry point(s) across 68 package(s) load'. The four artifact-roster gates the derivation marks as keeping their roster under a touched directory also ran, all exit 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity. Lint narrowed (see deviations): eslint --no-inline-config on the 3 changed sources gave exit 0, 0 errors and 0 warnings each; each file was linted, not ignored (6/5/5 active rules by --print-config); eslint.config.mjs is byte-identical between base and origin/main. CI on PR #23582 head 9d467b9, read once when this report was written: completed success for 'No other open PR may claim the same issue', 'No other open PR may claim the same single-writer path', 'Part-of PR must not also close its card' and 'The card this PR closes must claim this branch'; 'Packed-tarball smoke (opt-in)' was skipped; in_progress for Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, Lint (CI release and engine gates, ESLint, PM dispatch-gates, docs and code guards, repo and tooling gates, the three scoped lanes), Type Check (consumer gates, debt ledger, source gates, workspace) and filter. Test Core and Build Core were not yet listed. 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 sent as one POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft, read back 10418/10418 chars identical) and POST /repos//issues/23582/assignees (sunhaolin; read back). Stroke 2: POST /repos//issues/23531/comments (this report). label-write: 0, because the dispatch named no label and skip-changeset does not apply (the diff publishes). git push x5 (not REST).",
"deviations": [
"pnpm lint: the dispatch says the lane always adds pnpm lint or a proven narrowing; the role file says a repo-level scan led by pnpm lint is CI's run and never owed. Conflict stated, not resolved silently: I ran the narrowing, with three pieces of evidence: (1) the derived change set is 4 paths, 3 of them lintable; (2) eslint --no-inline-config on those 3 gave exit 0 with 0 messages; (3) each file was really linted (active rules by --print-config), and the ESLint config is unchanged between base and origin/main. The full pnpm lint is declared to CI (the 'Lint · ESLint' check-run).",
"File surface: besides declaredEnvelopeForThrow and the helper beside it, adapter.ts gained one name, structuredCodeAnswer, on its existing @objectstack/types import line. That is necessary for the call; there is no new export anywhere.",
"Changeset names @objectstack/hono (patch) as well as @objectstack/plugin-hono-server. createHonoApp's answer is the measured door, and the still-unreleased #23365 changeset states this gap in hono's CHANGELOG. Both packages are in the one fixed version group, so no version moves because of it. Nothing in hono's own files changed.",
"A scratch measurement test (packages/qa/http-conformance/src/zz-scratch-23531-family.conformance.test.ts, for finding 2) existed in the worktree for about 30 s while the pre-merge gate battery ran. It was never committed and was deleted; git status was clean after. The three gates whose run overlapped that window were re-run clean, and the authoritative battery was re-run in full on the merged head with a clean tree.",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Per the role file and AGENTS.md (the pre-push hook refuses a model id in the pair), commits carry the model-free pair Co-authored-by: Claude / Claude-Session, and the PR body ends with the session-URL footer.",
"Merged origin/main into the branch before opening the PR (AGENTS.md §10), because #23532's thrownJson change landed meanwhile. A plain merge commit, no force-push."
],
"files_changed": [
"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/23531-hono-conflict-body.md"
],
"open_questions": [],
"out_of_scope_findings": [
"class: (b) data disclosure · reach: exception: security (possible data leak; not measured at a door with a security plugin booted) · evidence (read on 7ffc578): the 409 CONCURRENT_UPDATE's currentRecord is the row metadata-protocol's probeRecord reads with engine.findOne(object, { where: { id }, context: { isSystem: true } }) (packages/metadata-protocol/src/protocol.ts, probeRecord). updateData runs assertVersionOf on that row BEFORE engine.update applies the caller's write policy, and assertVersionMatch does the same for delete. structuredCodeAnswer's CONCURRENT_UPDATE arm (packages/types/src/data-error-classification.ts) forwards error.currentRecord unfiltered, so a caller who sends a stale If-Match for a row, or fields of it, that RLS/FLS hides would receive the system-context row. True at RestServer since before this card; PR #23582 extends the same answer to both Hono doors, by the dispatch ruling ('Match RestServer exactly ... a finding against both doors, not a reason to differ'). Landing is the producer (metadata-protocol: read currentRecord under the caller's context, or narrow it before throwing), which fixes every door at once. Seam: spec: ConcurrentUpdateError.currentRecord (wire-format.mdx 409 Conflict) → runtime: metadata-protocol probeRecord/assertVersionOf → rest mapDataError + plugin-hono-server declaredEnvelopeForThrow | renderer: objectui InlineEditSaveBar / ConcurrentUpdateDialog · dedupe words: currentRecord isSystem probeRecord 409 CONCURRENT_UPDATE disclosure field-level security row-level security conflict body",
"class: (b) · reach: measured at a public door. A real createHonoApp (real dispatcher, protocol and SQLite WASM; scratch test, not committed) at d7484b8: DELETE /api/v1/data/parent/ID, where a child lookup with deleteBehavior 'restrict' references the parent, answered 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.'}}. The body has no developerMessage, dependentObject, dependentCount or object, which RestServer's structuredCodeAnswer DELETE_RESTRICTED arm carries. Same family as this card (the Hono doors drop structured-arm fields), so name it into the family's closing card rather than a single-point card. Not measured: METADATA_CONFLICT's currentVersion, INVALID_FIELD's field. Mechanical shape: widen declaredConflictContext from one code to 'the arm agrees on status and code'. Seam: contract: structuredCodeAnswer arms (DELETE_RESTRICTED developerMessage/dependentObject/dependentCount) → runtime: plugin-hono-server declaredEnvelopeForThrow (createHonoApp thrownJson, HonoHttpServer wrap/onError) | renderer: consumer: objectui delete-restricted toast/dialog (not measured) · dedupe words: DELETE_RESTRICTED developerMessage dependentObject dependentCount createHonoApp declaredEnvelopeForThrow structured arm Hono door"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report⚠️ paragraph is rewritten: the field is HELD because probeRecord reads the row with a system context and assertVersionOf throws it before the caller's write policy runs, so it may carry rows or fields RLS/FLS hide; a version token carries no row data, so currentVersion is forwarded. The function-level doc says currentRecord is the ONE field named, and only to hold it; declaredEnvelopeForThrow's own docblock says the same in one line. (2) Pins: the direct-mount file asserts error.details toEqual { currentVersion } exactly, not.toHaveProperty('currentRecord') citing #23586, and that the row's value appears nowhere in the body. The conformance file keeps the currentVersion assertion and the If-Match echo (PATCH writes, DELETE deletes), and asserts currentRecord absent although the real protocol states it, citing #23586. Controls kept. (3) Changeset: the TO body is details.currentVersion only, with one sentence that currentRecord is held until #23586 and the REST server's answer is unchanged; the summary line and the #23365 gap sentence are adjusted to match. (4) PR body: line 1 'Part of #23531', line 2 'Clause-②: no'; no closing verb stands next to #23531 anywhere; #23586 and #23587 are named in Acceptance notes. Updated through the relay (issue_patch), read back byte-identical. The PR title was amended in the same PATCH (see deviations). git merge-tree --write-tree HEAD origin/main was clean (exit 0, origin/main one docs commit ahead), so there was no merge. New head 69215f8. Worktree removed after this report.",
{
"issue": 23531,
"round": "patch round 1 (REWORK per the seat's review 6182753773, same claim 6181557788)",
"status": "done",
"branch": "claude/issue-23531-hono-conflict-body",
"pr": "#23582",
"head": "69215f8575af35727d4ef50e98e879c46cd4af0b",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "The one item is done: currentRecord does not ride this PR. (1) declaredConflictContext (packages/plugins/plugin-hono-server/src/adapter.ts) now forwards everything structuredCodeAnswer's CONCURRENT_UPDATE arm carries except error, code and currentRecord, with a one-line pointer to #23586 at the exclusion. The
"tests": "At 69215f8, after rebuilding http-conformance's dependency closure (plugin-hono-server included) under the lock: pnpm --filter @objectstack/plugin-hono-server typecheck (tsc, tsconfig.typecheck.json, check:test-typecheck OK 0 debt) and test gave Test Files 29 passed, Tests 340 passed. pnpm --filter @objectstack/http-conformance typecheck (check:test-typecheck OK, ledger unchanged) and vitest run gave Test Files 13 passed, Tests 139 passed. ABLATION (pin structure changed, so both legs ran; node scripts/ablation-replace.mjs wrap mode plus an outer trap restore EXIT INT TERM on the absolute path; each leg rebuilt plugin-hono-server, and ablation-dist-preflight showed the marker in dist/index.js and dist/index.mjs): Leg A dropped the new exclusion (anchor
&& key !== 'currentRecord'),replaced by marker ABLATION_23531_HELD_FIELD, blob 880175e8d1ec to ed02c80843d0). The absence pins went RED: unit 2 failed | 21 passed ('expected { ...(2) } to deeply equal { Object (currentVersion) }'), conformance 2 failed | 13 passed ('to not have property "currentRecord"'). Leg B disabled the guard (anchorif (resolved.code !== 'CONCURRENT_UPDATE') return undefined;replaced by marker ABLATION_23531_NO_SUCH_CODE, blob to 49308e91060e). The currentVersion pins went RED: unit 2 failed | 21 passed ('expected undefined to deeply equal ...'), conformance 2 failed | 13 passed ("expected 'undefined' to be 'string'"). The first run at bb6d0bf showed leg B's conformance red arriving as a TypeError: the absence assertion ran on an undefined details before the currentVersion check. The assertions were reordered (69215f8) and BOTH legs re-run, giving the result above. Restore: blob equal to HEAD, git diff HEAD empty, porcelain 0; rebuilt; preflight --absent showed both markers gone from all 6 built files with a clean tree; both pin files green (23/23, 15/15). Round-0 red-before evidence stands (13b5267: unit 2 failed | 21 passed, conformance 2 failed | 13 passed).","gates": "LOCAL at 69215f8: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 4 changed paths vs merge base fa6a608) derived the same 66 commands as round 0. All 66 ran, exit captured before any pipe. 65 exited 0 on the first pass. pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: the fresh worktree had no workspace dist/). After a cache-backed pnpm turbo run build under the lock (75 successful, 74 cached), it gave exit 0, '110 published require entry point(s) across 68 package(s) load'. --ran reconciliation: '66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero). 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. Narrowed eslint --no-inline-config on the 3 changed sources: exit 0, 0 errors and 0 warnings each; eslint.config.mjs unchanged merge-base to origin/main. CI on 69215f8, read once after the body edit: completed success for Build Core, TypeScript Type Check (and its four lanes), Lint & Repo Gates (and its lanes, including 'Lint · ESLint'), Governed Surface Queue Guard, Test Core 1/6, 2/6, 4/6, 5/6 and 6/6, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, filter, and the four card/claim checks. Test Core (3/6) was in_progress. The body edit re-triggered Check Changeset and three claim checks, which were in_progress. CI convergence is the PM's.",
"mcp_calls": "0",
"api_writes": "This round, 3 relay strokes (scripts/pm/fleet-write/dispatch.mjs, executed as objectstack-fleet[bot]), each sent as one POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, issue_patch: PATCH /repos//issues/23582 (title and body; read back 10571 bytes identical). Stroke 2: POST /repos//issues/23531/comments (this patch-round report). Stroke 3, comment_edit: PATCH /repos/objectstack-ai/objectstack/issues/comments/6183237753, to correct this report's head field, which was first posted with a wrong full SHA (its 10-character prefix 69215f8 was right). label-write: 0. git push x2 (bb6d0bf, 69215f8; not REST).",
"deviations": [
"PR title amended in the same issue_patch as the body, from '... carries currentVersion and currentRecord (#23531)' to '... carries currentVersion (#23531)'. The order said 'Nothing else changes', but the old title stated a behaviour this PR no longer has, and it becomes the squash subject on main. The seat can revert it in one PATCH if it wants the old title.",
"Pins: the conformance block's two assertions were reordered (currentVersion before the currentRecord absence) after the first ablation run showed leg B's red arriving as a TypeError, not as the currentVersion assertion. This is a separate commit, 69215f8, and both legs were re-run on it.",
"Ablation leg A (the new currentRecord exclusion) was run beside the requested guard leg. The round's diff changes the behaviour the new absence pins hold, and leg A is the proof that those pins can fail.",
"Fresh worktree: check:dual-build-cjs-loads needed workspace dist/, so a cache-backed turbo build (74/75 cached, 5 s under the lock) ran before it measured."
],
"files_changed": [
"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/23531-hono-conflict-body.md"
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #23586 (currentRecord disclosure at the producer; filed by the seat from round 0's finding 1) · noted, not refiled",
"carrier: #23587 (DELETE_RESTRICTED structured fields at createHonoApp; filed by the seat from round 0's finding 2) · noted, not refiled"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23582 comment 6183269783 · 2026-10-15T02:49Z
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed: PR #23582 →
19c554ddc1(Part of; the card stays open) · 2026-10-15T03:19Zdomain:cliseat 2 (#22648) ·sunhaolin· sessionsession_01Q9PZ5Pzqcus4woVvTZwWNU· holder of claim6181557788, released below. Review: REWORK6182753773, then the seat record6183269783on the PR (pointer6183277013on this card). No contract review was owed.merged: true,merged_at2026-10-15T03:18Z, through the merge queue (added_to_merge_queue2026-10-15T02:52Z)gh-readonly-queue/main/pr-23582-*git ls-remote, 0 refs)origin/main19c554ddc1is an ancestor; one parent (1a9d488136); 4 files, +259 / −1, as reviewed at69215f8575Part of #23531), so this card stays open, as intendeddomain:clicards (read at this stamp), #23531 among them, released belowWhat landed: a
409 CONCURRENT_UPDATEanswered by either Hono door (createHonoApp, and a route onHonoHttpServer) now carriescurrentVersioninerror.details, read fromRestServer's one classification.What remains on this card:
currentRecordat the Hono doors. It is held atdeclaredConflictContext's one exclusion (packages/plugins/plugin-hono-server/src/adapter.ts), pointing at #23586. The protocol reads that row with a system context before the caller's write policy runs. The remainder is to lift the exclusion once #23586 has been answered at the producer, and to flip the two absence pins.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Release:
domain:cliseat 2 ·sunhaolin·session_01Q9PZ5Pzqcus4woVvTZwWNU· 2026-10-15T03:19Z · cause: PR #23582 landed thecurrentVersionhalf (Part of), and thecurrentRecordhalf waits on #23586 · destination:pm:queue, unassignedcurrentRecorddisclosure, a security exception that is still ungraded. Whoever claims the remainder next reads 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's answer first.currentRecordexclusion and flip the absence pins inhandler-throw-declared-envelope.test.tsandhono-declared-envelope.conformance.test.ts.DELETE_RESTRICTED's structured fields atcreateHonoApp). Do not widen the helper's code guard while 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 is open.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 17 (#23531's remainder: the Hono doors'
409 CONCURRENT_UPDATEcarriescurrentRecordagain, now that #23586's producer fix makes it the caller's own masked read; the hold indeclaredArmContextis lifted and the absence pins flip) · 2026-10-15T10:09ZSession:
session_01Q9PZ5Pzqcus4woVvTZwWNUAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23531-lift-current-record-holdWorktree:
objectstack-issue-23531bDomain:
domain:cliSeat:
domain:cli#2(seat post #22648)Scope:
batch3, by 「并发恢复3」. #23558 is in the merge queue, #23546 waits on the maintainer (needs-user-decision), and this card takes a free slot.Order, read at this act:
6183630660ordered this remainder after 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.completedat 09:00Z: PR fix(metadata-protocol): an optimistic-concurrency conflict reports only what the caller may read #23628,7dc7d2213, "an optimistic-concurrency conflict reports only what the caller may read".6188428565hands the Hono doors' hold over to be lifted.File surface (read on
origin/main; stop on a breach and explain it in the report):packages/plugins/plugin-hono-server/src/adapter.ts:declaredArmContext'scurrentRecordexclusion (:439-:440), and the docblock lines that say it is held (:303-:304,:381,:415-:421).packages/plugins/plugin-hono-server/src/handler-throw-declared-envelope.test.ts: the[#23531]block'scurrentRecordabsence becomes presence, shaped as the producer now throws it;packages/qa/http-conformance/src/hono-declared-envelope.conformance.test.ts: the same, on a realcreateHonoAppover the real protocol, including the caller-masked read..changeset/23531-*.md.metadata-protocol(the producer is fixed and is not edited here);packages/types' classification;adapters/hono/src/index.ts;Container & model:
S, judged (one exclusion lifted, two pins flipped),mode:subagent,model: default tier. Ceiling:Clause-②: no. The field is declared bywire-format.mdxandConcurrentUpdateError, andRestServeralready answers it.Clause-②: no
Responsibility:
the Hono doors' conflict context in plugin-hono-server (this lane) | metadata-protocol's caller-scoped currentRecord (#23586, PR #23628), read | objectui's conflict bar and every client reading a 409's currentRecord on a Hono hostThread-read: 6188428565
Serial constraints cleared: no open PR touches
plugin-hono-server/src/, the conformance file oradapters/hono/src/(read at this act). The Version Packages PR chore: version packages (next) #23481 touches onlyadapters/hono's CHANGELOG and package.json.Generated by Claude Code
test(http-conformance): type the masked-read pin's findOne options in…
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23531,
"round": "remainder (claim 6189624811): lift the currentRecord hold",
"status": "done",
"branch": "claude/issue-23531-lift-current-record-hold",
"pr": "#23667",
"head": "4bc9b0c08be1b3398bd5b3d73136b17f5e8b5a49",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (this run's harness-stamped id; subagent, so it is the parent's)",
"premise_still_valid": true,
"summary": "Claim verified: the newest Claim: (6189624811) names this branch. Premise confirmed on origin/main 7cb33cf: PR #23628 (7dc7d22) is an ancestor (merge-base --is-ancestor exit 0), and metadata-protocol's assertVersionOf now re-reads the row with the caller's context (readAsCaller): no row gives 404 RECORD_NOT_FOUND, a readable row gives ConcurrentUpdateError with the caller's masked read as currentRecord. Hypothesis (a) held: the only site that strips currentRecord on the Hono path is declaredArmContext's filter. The sole producer is metadata-protocol's assertVersionOf; createHonoApp's thrownJson, wrap()/onError and the metrics observer hand declaredEnvelopeForThrow's body to c.json unchanged; the ablation that restores the filter turns every presence pin red. Fix: the filter drops only the arm's error and code (adapter.ts), and the held-docblock lines (:303-:304, :381-:383, :415-:426) are rewritten. The field is forwarded because the producer reads it as the caller, and an arm that grows a field read with a system context must still be held by name. Pins flipped to presence: the unit rows assert error.details toEqual { currentVersion, currentRecord } exactly. The conformance rows (real createHonoApp, real protocol) assert details equal to what the protocol threw (spied), keep the If-Match echo pins, and add PATCH/DELETE rows where a field the caller's read masks is absent from currentRecord, which equals the caller's own read and the thrown record while the store still holds the field. Hypothesis (b) held: with the real SecurityPlugin (scratch test on #23628's runtime rig, not committed), the Hono exit's details equal RestServer's { currentVersion, currentRecord } for the same request, with no masked value on either wire (4/4). Three docs pages that said a Hono door never sends currentRecord were corrected (see deviations: outside the listed file surface). Changeset @objectstack/plugin-hono-server + @objectstack/hono patch with FROM to TO. origin/main (286eb9e) was merged before the PR opened. Worktree cleanup (rm node_modules, git worktree remove) runs right after this report posts.",
"tests": "FINAL HEAD 4bc9b0c, after a full turbo run build (75/75): pnpm --filter @objectstack/plugin-hono-server typecheck (tsc + tsconfig.typecheck.json + check:test-typecheck OK, 0 debt) and test: Test Files 29 passed, Tests 354 passed. pnpm --filter @objectstack/http-conformance typecheck (check:test-typecheck OK, ledger unchanged 3 files/27 errors/10 signatures) and vitest run: Test Files 13 passed, Tests 144 passed. Consumer pins (runtime resolves plugin-hono-server through dist/): data-conflict-caller-visibility, data-update-expected-version, data-delete-expected-version: 3 files, 115 passed. ABLATION at 7a8e9bf (node scripts/ablation-replace.mjs wrap mode plus an outer trap restore on EXIT INT TERM over the absolute path; HEAD blob checked non-empty first). The filter clause
key !== 'error' && key !== 'code'(the anchor) was replaced by the same clause plus&& key !== 'currentRecord' && key !== 'ABLATION_23531_RESTORE_HOLD': anchor x1 to x0, replacement x0 to x1, blob 4b7ca0f62478 to 3e3313e4060c, marker in src x1. plugin-hono-server rebuilt; ablation-dist-preflight: marker in dist/index.js and dist/index.mjs. Direction as predicted (turned red): unit 2 failed | 35 passed (PATCH, DELETE: 'expected { Object (currentVersion) } to deeply equal { ...(2) }'); conformance 4 failed | 16 passed (both presence rows; both masked-field rows 'expected undefined to match object'). RESTORE: blob after restore == HEAD 4b7ca0f62478, git diff HEAD empty (tool and outer trap both); rebuild 6/6 turbo cache hits; preflight --absent: marker absent from all 6 built files, tree clean; porcelain 0; both files green 37/37 and 20/20. HYPOTHESIS (b) at 7a8e9bf: scratch copy of data-conflict-caller-visibility.test.ts plus 4 rows comparing, for the same stale PATCH/DELETE on the FLS-masked row and the plain row, the Hono exit's error.details with RestServer's { currentVersion, currentRecord }: 4/4 equal, no masked value on either wire; the committed file 16/16 beside it; scratch deleted, porcelain 0. Not run: a producer-side ablation (reverting #23628's read), because this diff does not change the producer. @objectstack/hono's own suite was not run locally (it stubs @objectstack/runtime, and its source is untouched); declared to CI.","gates": "LOCAL at 4bc9b0c: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 7 paths vs merge base 286eb9e; first stderr line names objectstack-ai/objectstack at 4bc9b0c) derived 95 commands, the same list as at 7a8e9bf. All 95 ran, each exit captured before any pipe: 95 x exit 0. --ran: '95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero). Pass 1 at 7a8e9bf: 92 x exit 0, check:skill-examples and check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, no dist), and check:query-options-erasure exit 1. That red was real and mine: two
as anyon findOne options took the test surface from 236 to 238. Fixed in bc83077 (back to 236); pass 2 measured all three after the full build. The --ran run printed a STALE TREE warning: origin/main moved to 1ced0f8 after the merge, touching only scripts/pm/record-recognisers.mjs and a pm-dispatch reference. dispatch-gates.mjs and dispatch-gates.data.mjs are unchanged across that range, and no derived family runs that file, so the list stands; not merged. LINT narrowed (see deviations): eslint --no-inline-config on the 3 changed TS sources gave exit 0, 0 errors and 0 warnings each; each file was really linted (6/5/5 active rules by --print-config); eslint.config.mjs is unchanged base to origin/main; the 3 .mdx pages are ignored by the ESLint config, as under pnpm lint. CI on PR #23667 head 4bc9b0c, read once at report time: completed success for 'No other open PR may claim the same issue'; 'Packed-tarball smoke (opt-in)' skipped. in_progress for Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, Lint (CI release and engine gates, ESLint, PM dispatch-gates, docs and code guards, repo and tooling gates, the three scoped lanes), 'No other open PR may claim the same single-writer path', 'Part-of PR must not also close its card', Spec property liveness, 'The card this PR closes must claim this branch', Type Check (consumer gates, debt ledger, source gates, workspace) and filter. Test Core and Build Core were not yet listed. 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 sent as one POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, pr_create (run 38727737401, success): POST /repos/objectstack-ai/objectstack/pulls (draft forced; read back 8799/8799 bytes identical) and POST /repos//issues/23667/assignees (sunhaolin; read back). Stroke 2: POST /repos//issues/23531/comments (this report). label-write: 0 (the dispatch named none; skip-changeset does not apply because the diff publishes). git push x4 (empty branch probe, 7a8e9bf, bc83077, 4bc9b0c; not REST).",
"deviations": [
"File surface BREACH, stated rather than resolved silently. Three docs pages are edited outside the claim's listed surface: content/docs/api/index.mdx, content/docs/api/wire-format.mdx (the 'Hono hosts nest the extras' paragraph and its example) and content/docs/protocol/kernel/error-handling.mdx (the Hono-door table row and the 'never sends' line). PR #23616 wrote 'a Hono door never sends currentRecord' into them, and this change makes that false. The claim says 'stop on a breach'; the role file says a shipped surface this round's change makes false is fixed in the round, and that the role file wins a conflict with the dispatch. I took the role file's side and say so here. Each edit only removes the hold claim and adds one sentence that the record is the row as the caller reads it. If the seat rules otherwise, those three hunks revert alone.",
"pnpm lint: the dispatch says this lane always adds pnpm lint or a proven narrowing, and the role file says a repo-level scan led by pnpm lint is CI's run. I ran the narrowing with its three pieces of evidence: (1) the derived change set is 7 paths, 3 of them TS sources and 3 .mdx that ESLint's config ignores; (2) eslint --no-inline-config on the 3 sources: exit 0, 0 messages; (3) each was really linted (active rules by --print-config), and eslint.config.mjs is unchanged base to origin/main. The full pnpm lint is declared to CI (the 'Lint · ESLint' check-run).",
"Hypothesis (b) is measured on #23628's runtime rig (real SecurityPlugin, real RestServer, the dispatcher rendered through declaredEnvelopeForThrow), not on the conformance rig. The conformance rig has no RestServer or plugin-security dependency; there, the door's details are asserted equal to the currentVersion/currentRecord the real protocol threw, which is exactly what RestServer's CONCURRENT_UPDATE arm forwards (plus the route-only object). The scratch file packages/runtime/src/domains/zz-scratch-23531-door-parity.test.ts existed for one locked run (about 20 s), outside any gate battery's window; it was never committed and was deleted; porcelain 0 after.",
"FLS on the conformance rig is an engine-middleware stand-in (a caller-context read rewritten after next(), the seam plugin-security's field-level rules use), not SecurityPlugin itself. http-conformance does not depend on plugin-security, and adding it means package.json and pnpm-lock.yaml, outside this claim. The real FLS/RLS evidence is the runtime pin (16/16 at the final head) and the parity measurement above.",
"Merged origin/main (286eb9e, 4 commits, none touching these files; one touches driver-sqlite-wasm, which the conformance rig boots) before opening the PR, per AGENTS.md section 10. Merge commit 4bc9b0c, no force-push; the full build and every suite and gate were re-run on it.",
"The two unreleased changesets that say currentRecord stays held (.changeset/23531-hono-conflict-body.md, .changeset/23587-hono-structured-arm-fields.md) are not edited (outside this claim); the new changeset says it supersedes that note. This is named in the PR's Acceptance notes, carrier: the seat.",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Per the role file and AGENTS.md (the pre-push hook refuses a model id in the pair), commits carry the model-free pair Co-authored-by: Claude / Claude-Session, and the PR body ends with the session-URL footer."
],
"files_changed": [
"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/23531-hono-conflict-current-record.md",
"content/docs/api/index.mdx",
"content/docs/api/wire-format.mdx",
"content/docs/protocol/kernel/error-handling.mdx"
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed (named in the PR's Acceptance notes): packages/runtime/src/domains/data-conflict-caller-visibility.test.ts's dispatcher-door rows read currentRecord off the thrown error, not the wire, because the wire did not carry it before this change. They could now assert the wire; the parity measurement above shows it equals RestServer's.",
"carrier: none · noted, not filed, and not in the PR's Acceptance notes (the body is written once): content/docs/api/wire-format.mdx's flat 'Concurrent Update — 409 Conflict' example shows currentVersion and object but not currentRecord, which the REST server sends and whose Fields list names it. This is example drift on the flat door, untouched by this diff."
]
}
objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23667 comment
6191403700·domain:cliseat 2 · 2026-10-15T11:53ZGenerated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed: PR #23667 →
5bb9a83bd0· 2026-10-15T12:22Zdomain:cliseat 2 (#22648) ·sunhaolin· sessionsession_01Q9PZ5Pzqcus4woVvTZwWNU· holder of claim6189624811, released by this landing. Review: ACCEPT6191403700on the PR (no contract face).merged: true,merged_at2026-10-15T12:21Z, through the merge queue (added_to_merge_queue2026-10-15T11:55Z)gh-readonly-queue/main/pr-23667-*git ls-remote, 0 refs)origin/main5bb9a83bd0is an ancestor; one parent (cad343e201); 7 files, +178 / −67, as reviewed at4bc9b0c08bFixes #23531onlydomain:clicards (read at this stamp), #23531 no longer among themState change: #23531 closed
completedby the merge;pm:dispatchedremoved in this act. Both halves of the card are now onmain:currentVersion(PR #23582,19c554ddc1) andcurrentRecord(this PR).Carried, not filed: the two earlier unreleased changesets (
.changeset/23531-hono-conflict-body.md,.changeset/23587-hono-structured-arm-fields.md) still saycurrentRecordstays held;.changeset/23531-hono-conflict-current-record.mdsupersedes them. The one-story question rides release-text review, not a code PR.Generated by Claude Code