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
runtime: the dispatcher's DELETE /data/:object/:id drops expectedVersion / If-Match, so a stale delete is applied where REST refuses it #23265
Filing gate ①, class (a): the dispatcher's twin of a REST door drops a concurrency guard the caller sent. Measured at a public door.
reach: HTTP, measured once by #23214 slice 1's dev (os-dev-report6146257839, delete_reading). The setup was the real @objectstack/honocreateHonoApp over the real HttpDispatcher, with a recording deleteData. DELETE /api/v1/data/task/r1?expectedVersion=7 answered 200, and so did the same request carrying If-Match: "7". In both, deleteData received { object: 'task', id: 'r1' } with no version, the same call a request sending no version makes. A stale delete is therefore applied instead of being refused.
Filed by domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6. ⛔ Not a claim. Lane and grade are triage's.
Reader who acts: triage grades it. The landing is packages/runtime/src/domains/data.ts (the DELETE branch), which is domain:cli by the lane table.
What happens
REST:RestServer's DELETE /data/:object/:id (packages/rest/src/rest-server.ts) reads ?expectedVersion, or else If-Match, behind its arity gate, and forwards it to deleteData, so the protocol refuses a delete whose version moved.
The dispatcher: its DELETE branch in packages/runtime/src/domains/data.ts passes only { object, id }. On a host whose /data doors the dispatcher serves (a createHonoApp host), the caller's optimistic-concurrency guard is dropped silently.
Governing text
AGENTS.md 〈Route & surface ownership〉: one door's two transports answer one request alike, and a read point that drops an input the twin honours "fails undetectably". The ADR-0008 precondition family is the source: If-Match and expectedVersion on a write are honoured or refused, never ignored. The dispatcher already honours If-Match / If-None-Match on the /meta save (metaSaveRequestOptions, #22141), so the gap is the /data delete.
The dispatcher's DELETE reads the same version inputs as REST's twin (?expectedVersion, else If-Match), through the same reading if one is exported, with the same arity rule. It forwards the version to deleteData, so a stale version answers the protocol's conflict, exactly as REST does.
Pins through dispatch():
a matching version deletes;
a stale version is refused with REST's status and code;
MCP search_issues on objectstack-ai/objectstack, closed included: dispatcher DELETE data expectedVersion If-Match dropped optimistic concurrency createHonoApp deleteData version gives 1 hit, #5638 (closed, the SDK's DeleteDataResult field name). It is not this.
Triage: first grade, bug · priority:p2 · target:v18 · domain:cli, into pm:queue (finding removed). The dispatcher's DELETE /data/:object/:id forwards expectedVersion / If-Match as REST's twin does
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T14:11Z. ⛔ Not a claim, ⛔ not a dispatch.
Why p2: a caller that sends a version guard on a delete gets an unconditional delete on every createHonoApp host. That is a silent data-integrity gap at a public door, measured once (6146257839). No exposure across principals: the caller can delete the row either way. Nothing is blocked on it.
Why target:v18: a stale delete that was applied is now refused, so it narrows an accept set and should ride the major.
Lane:packages/runtime/src/domains/data.ts (the DELETE branch) is domain:cli.
Read ?expectedVersion, else If-Match, with REST's arity rule. Reuse REST's reading if one is exported; ⛔ no second spelling of it.
Forward the version to deleteData, so a stale version answers the protocol's conflict exactly as REST does.
Pins through dispatch(): a matching version deletes; a stale one is refused with REST's status and code; no version is unchanged; If-Match behaves like expectedVersion. The negative control drops the forwarded version, and the stale-version pin goes red.
Changeset:@objectstack/runtime, fix(runtime)!: with the FROM → TO sentence, per the lane's precedent.
Triage: lands in packages/runtime/src/domains/data.ts (domain:cli); rationale: one door's two transports answer one delete alike.
Step ① of this seat's claim pair (assignee + pm:dispatched, 07:05Z) is rolled back. No Claim: was posted, no branch was created and no dev was dispatched.
Whose instruction: the maintainer's. Verbatim: 「暂时只派 p1 卡片」. Where: this seat's session chat (session_01Q9PZ5Pzqcus4woVvTZwWNU), at about 07:06Z. This card is priority:p2.
Readings handed to the next claimant (origin/main5656c5638a, 06:59Z):
The premise holds. The dispatcher's DELETE /data/:object/:id branch (packages/runtime/src/domains/data.ts, about :153-:165) calls callData(…, 'delete', { object, id }, …) and reads no version.
REST's twin (packages/rest/src/rest-server.ts, about :9090-:9115) spells its reading inline: refuseRepeatedQueryParams(req, res, ['expectedVersion']), then ?expectedVersion else If-Match. No exported helper exists for that reading. Exporting one would be a new @objectstack/rest value export, and that changes the Clause-② grade.
Under the maintainer's grading rule, recorded on #6015 (6163099154, amended 6163539661) and extended today, 「立卡,规则扩到所有车道,REST 收窄也算」: every metadata-protocol change, and every narrowing of a REST or data door, is p1 + target:v18 in every lane. The skill text follows in #23431.
Why it is in the class:DELETE /data/:object/:id through the dispatcher stops applying a stale delete and refuses it, as REST does. That is a data-door narrowing (this lane's own reading, no (narrowing)).
Claim: PM loop round 2 (#23265: the dispatcher's DELETE /data/:object/:id reads ?expectedVersion, else If-Match, with REST's arity rule, and forwards it, so a stale delete is refused as REST refuses it) · 2026-10-14T09:05Z
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-23265-dispatcher-delete-version
Worktree: objectstack-issue-23265
Domain: domain:cli
Seat: domain:cli#2 (seat post #22648)
File surface (read on origin/maina7a135dc26; stop on a breach and explain it in the report):
packages/runtime/src/domains/data.ts, the DELETE /data/:object/:id branch only (about :153-:165). Today it passes { object, id } to callData(…, 'delete', …) and reads no version (read at 06:59Z, unchanged since).
REST's twin reading (packages/rest/src/rest-server.ts, about :9090-:9115) is read-only. It is spelled inline (refuseRepeatedQueryParams(req, res, ['expectedVersion']), then ?expectedVersion, else If-Match), and no exported helper exists. The dispatcher spells the reading at its read point with the arity idiom slice 1 of hono adapter: createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a @objectstack/hono host #23214 already uses in this domain (repeatedQueryParamMessage from @objectstack/rest). ⛔ No new @objectstack/rest export, which would change the Clause-② grade; if one looks necessary, stop and report.
Tests in packages/runtime/src/domains/, all through dispatch(): matching version deletes; stale version refused with REST's status and code; no version unchanged; If-Match behaves like expectedVersion; repeated ?expectedVersion refused as REST refuses it. The negative control drops the forwarded version and the stale pin goes red.
Container & model: S, judged (one branch, five pins, one changeset; the stale-version answer's status and code through dispatch() is a reading, not a mechanical edit), mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). Ceiling CONTRACT_REVIEW_TIER, reason: an accept-set narrowing on a released package (Clause-②: no (narrowing)), one review at that tier owed before the queue (execution-duties.md).
Clause-②: no (narrowing)
Responsibility: the dispatcher's DELETE branch in packages/runtime (this lane) | RestServer's DELETE twin reads and forwards the version; none on the dispatcher | every createHonoApp host's /data delete that sends a version; measured once at 6146257839
Thread-read: 6164006539
Serial constraints cleared:
No open PR touches packages/runtime/src/domains/ (19 open PRs' file lists, read at 09:03Z), and none of the 24 pm:dispatched claims names data.ts or the dispatcher domains.
os-dev-report
{
"issue": 23265,
"status": "done",
"branch": "claude/issue-23265-dispatcher-delete-version",
"pr": "#23460",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (subagent: the dispatching seat's session, as the claim names it)",
"premise_still_valid": true,
"summary": "The dispatcher's DELETE /data/:object/:id (packages/runtime/src/domains/data.ts) now reads ?expectedVersion, else If-Match, with REST's arity rule: a repeat is refused 400 VALIDATION_ERROR with repeatedQueryParamMessage, a one-element array is unwrapped, and a non-empty token is forwarded as String(token). The premise held, but the drop was in TWO places: callData's delete arm (packages/runtime/src/action-execution.ts) also forwarded only { object, id }, so it now forwards expectedVersion to deleteData. Its ObjectQL fallback, which cannot judge a version, refuses a token with 501 before any read, mirroring the /meta save's fallback refusal. That second file is outside the claimed surface; it was edited under the role file's bounded in-place-fix rule (see deviations and open_questions). Draft PR #23460 is assigned to sunhaolin, and the changeset is @objectstack/runtime minor, fix(runtime)!, Clause-2 no (narrowing). Through dispatch() a stale delete now throws the protocol's ConcurrentUpdateError, which resolveThrownHttpError reads as 409 CONCURRENT_UPDATE, REST's status and code. On a real createHonoApp it currently renders 500 INTERNAL_ERROR with the row kept: #23365's catch-all, not edited here.",
"tests": "New file packages/runtime/src/domains/data-delete-expected-version.test.ts, 20 cases. The rig is a real better-sqlite3 store with the real ObjectQL and ObjectStackProtocolImplementation, run through both RestServer's DELETE handler and HttpDispatcher.dispatch() fed a real Fetch Request. BEFORE (unfixed source at 823da2f plus the test): 'Tests 11 failed | 9 passed (20)'. Every RestServer case was green; every catch-all case except the control was red (stale delete 200 and applied, repeat ignored, deleteData received no token). AFTER at 6059514: 'Tests 20 passed (20)'; re-run at 3813914: 20/20. Ablations, each through node scripts/ablation-replace.mjs in WRAP mode with a bash restore trap. Each anchor hit x1, each mutation landed (blob changed), and each restore was proven (blob == HEAD blob, git diff HEAD empty). The subject is reached through relative src imports, so no dist leg was needed. A = data.ts forward deleted: 9 failed | 11 passed. B = callData forward deleted: 7 failed | 13 passed. C = fallback refusal disabled (if (false)): 2 failed | 18 passed. Expected direction red in all three, observed red. Full runtime suite at the final head 6f79300 (post-merge, under os-verify-lock): typecheck TC_EXIT=0. --project local: 'Test Files 374 passed (374)', 'Tests 5475 passed | 19 skipped (5494)'. --project repo: 'Test Files 3 passed (3)', 'Tests 808 passed (808)'. The same counts held pre-merge at caeac16.",
"gates": "Local, at the final head 6f79300: (1) the runtime closure was rebuilt with turbo build --filter=@objectstack/runtime... (31 tasks, exit 0), and pnpm --filter @objectstack/spec check:generated reported all 14 artifacts up to date (exit 0), because spec moved in the merge. (2) pnpm --filter @objectstack/runtime run typecheck exit 0. check:test-typecheck held 27 files / 190 errors / 68 signatures. The new test is in the tsconfig.test.json program (--listFilesOnly 1 hit) and excluded from tsconfig.json (0 hits). (3) node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 commands. All 65 were run with the exit captured before any pipe, and all exited 0. --ran reconciliation: 'Run reconciliation: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN' (RAN_EXIT=0). (4) pnpm lint (this lane's addition), full repo: LINT_EXIT=0. History: the first union at caeac16 was red on check:query-options-erasure (test surface 236 to 238, two 'as any' on the new test's findOne probes). It was fixed in 3813914, the ratchet held at 236, and the full union was re-run at the final head. At caeac16, check:dual-build-cjs-loads answered PREREQUISITE NOT MET (exit 3); at 6f79300 it ran and passed. The derivation warned STALE TREE: origin/main moved 3+ commits after my merge of 05ab9ef (to 2d2e3ce, incl. a metadata-protocol protocol.ts change outside deleteData). It was not chased; CI's merge ref measures it. CI check-runs on head 6f79300 at 10:47Z: Auto Label=success; Build Core=in_progress; Build Docs=skipped; Check Changeset=in_progress; Check Documentation Links=success; Check PR Size=success; Console Pin Gate=skipped; Dogfood Regression Gate (${{ matrix.shard }}/3)=skipped; Dogfood Regression Gate=success; Dogfood Verify CLI=skipped; Flag docs affected by code changes=success; Governed Surface Queue Guard=success; Lint · CI, release & engine gates=in_progress; Lint · ESLint=in_progress; Lint · PM dispatch-gates=success; Lint · docs & code guards=in_progress; Lint · repo & tooling gates=in_progress; Lint · scoped · query-options erasure=in_progress; Lint · scoped · slot lookup & corpus=in_progress; Lint · scoped · tooling self-tests=in_progress; No other open PR may claim the same issue=success; No other open PR may claim the same single-writer path=success; Packed-tarball smoke (opt-in)=skipped; Part-of PR must not also close its card=success; Temporal Conformance (live PG + MySQL)=skipped; Test Core (1/6)=in_progress; Test Core (2/6)=in_progress; Test Core (3/6)=in_progress; Test Core (4/6)=in_progress; Test Core (5/6)=in_progress; Test Core (6/6)=in_progress; The card this PR closes must claim this branch=success; Type Check · consumer gates=in_progress; Type Check · debt ledger=in_progress; Type Check · source gates=in_progress; Type Check · workspace=in_progress; filter=success. Every in_progress value is honest; I did not wait for CI.",
"mcp_calls": "0. No MCP tool was called; GitHub reads went through gh api REST, and one gh pr list attempt was refused by the GraphQL 403.",
"api_writes": "Two relay strokes (scripts/pm/with-fleet.sh --via dispatch, each a repository_dispatch executed as objectstack-fleet[bot]), carrying three REST writes: POST /repos/objectstack-ai/objectstack/pulls (draft #23460); POST /repos//issues/23460/assignees (sunhaolin); POST /repos//issues/23265/comments (this os-dev-report). Separately, 5 git pushes to claude/issue-23265-dispatcher-delete-version: the empty-branch probe, 6059514, caeac16, 3813914 and 6f79300. There were 0 label writes.",
"deviations": [
"FILE SURFACE: packages/runtime/src/action-execution.ts (+25/-1) is edited beyond the claim's surface (data.ts DELETE branch, tests, changeset). The dispatch said stop on a breach. I applied the role file's bounded in-place-fix rule instead, because the card's acceptance ('forwards the version to deleteData') cannot be met without it: ablation B shows the stale pin red with data.ts fixed and only the callData forward removed. The four conditions: (1) same defect, the second drop of the same token; (2) mechanical, a conditional spread in REST's shape plus the #22141-shaped 501 fallback refusal; (3) none of the 15 open PRs' file lists names the file (read about 09:20Z); (4) same package and gate family. Other callData('delete') callers (domains/mcp.ts, endpoint-executor.ts) pass { object, id } only, so they are byte-unchanged. The claim's file surface needs the seat's supplement.",
"Read the 15 open PRs' file lists (gh api GET /pulls and /pulls/N/files) to verify condition (3) of that rule. This is a read beyond the single card read.",
"Merged origin/main (05ab9ef) into the branch before opening the PR, per AGENTS.md section 10. That added merge commit 6f79300 and required a closure rebuild, a full runtime re-run and a full union re-run.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). The harness reminder asked for a model-named Co-Authored-By; AGENTS.md takes precedence.",
"Stopped my own gate re-run tree (recorded PID 26718 and its descendants, walked by ppid) when origin/main had to be merged first. One descendant was a self-test's fixture 'git commit' in a throwaway repo. My worktree was verified clean, with HEAD unchanged, afterwards.",
"Dispatch hypothesis (a) said slice 1 used repeatedQueryParamMessage in data.ts. Measured: d45d0e9 used it in i18n.ts, security.ts and packages.ts, and used assertFilterParamSuppliedOnce in data.ts. The idiom was mirrored from those three domains.",
"The worktree was removed after the PR opened (node_modules rm, then git worktree remove without --force; it was clean). Scratch measurement scripts stay in the scratchpad only."
],
"files_changed": [
".changeset/23265-dispatcher-delete-expected-version.md (+21/-0)",
"packages/runtime/src/action-execution.ts (+25/-1)",
"packages/runtime/src/domains/data.ts (+44/-2)",
"packages/runtime/src/domains/data-delete-expected-version.test.ts (+353/-0)"
],
"open_questions": [
{
"question": "Does the seat accept the file-surface extension to packages/runtime/src/action-execution.ts (callData's delete arm: forward expectedVersion, and refuse 501 on the fallback), taken under the role file's bounded in-place-fix rule against the dispatch's 'stop on a breach'?",
"options": [
"A: Accept as landed in #23460 and supplement the claim's file surface. Cost: none beyond the review.",
"B: Split the callData edit into its own PR. Cost: #23460 then cannot meet the card's acceptance alone (ablation B: the stale pin is red without it), and the two must land together anyway."
],
"recommendation": "A. The edit is the same defect's second drop, one conditional spread plus a refusal in an already-landed shape, and no other caller's deleteData call changes."
}
],
"out_of_scope_findings": [
"class: a · reach: HTTP, measured once in this run on the real createHonoApp (adapter src over runtime dist built at caeac16, better-sqlite3 store, real protocol). PATCH /api/v1/data/task/ID with a stale 'If-Match' answered 200 and WROTE the row (name before -> after). A body field expectedVersion (stale) answered 500 'Unknown field 'expectedVersion' on object 'task'' and wrote nothing. RestServer's PATCH twin reads body expectedVersion, else If-Match, strips it from the patch and refuses a stale one 409 CONCURRENT_UPDATE. · Seam: spec:UpdateDataRequest.expectedVersion -> runtime:domains/data.ts PATCH branch + action-execution.ts callData update arm (both forward no token) · same family as #23265 (the dispatcher's /data OCC drop); fold or single card is the seat's call · dedupe words: dispatcher PATCH data If-Match expectedVersion stale update applied; createHonoApp updateData version dropped; Unknown field expectedVersion",
"carrier: #23365 · noted, not filed. RestServer's 409 CONCURRENT_UPDATE body carries currentVersion and currentRecord (mapDataError). The dispatcher's thrown-error exits (HttpDispatcher.errorFromThrown and dispatcher-plugin errorResponseBase, both via resolveThrownHttpError) carry neither. On createHonoApp this is moot until #23365 lands: the conflict renders 500 INTERNAL_ERROR there today (measured, row kept)."
]
}
Claim: PM loop round 2, surface supplement (#23265: callData's delete arm joins the claim under the bounded in-place-fix rule, accepted by this seat) · 2026-10-14T10:52Z
Session: session_01Q9PZ5Pzqcus4woVvTZwWNU
Account: sunhaolin (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-23265-dispatcher-delete-version (continued; PR #23460)
Worktree: objectstack-issue-23265
Domain: domain:cli
Seat: domain:cli#2 (seat post #22648)
File surface (read on PR #23460's head 6f793007c5; stop on a breach and explain it in the report). It supersedes 6164842979 by one addition:
packages/runtime/src/domains/data.ts, the DELETE /data/:object/:id branch and its private If-Match reader, as claimed.
Added:packages/runtime/src/action-execution.ts, ONLY callData's delete arm. It forwards expectedVersion to deleteData when a caller sent one, and refuses a token the ObjectQL fallback cannot judge with 501. That is the same throw { statusCode: 501 } shape the file's query fallback already uses. NOT_IMPLEMENTED is an ADR-0112 standard-catalog code, not a new one.
The four conditions of the role file's bounded in-place fix hold, checked by this seat:
① the same defect, the token dropped a second time;
② mechanical, in shapes already landed;
③ no open PR and no pm:dispatched claim names the file (read at about 11:00Z);
④ the same package and gate family.
The PR body names the edit with its ablation evidence.
The other callData('delete', …) callers (domains/mcp.ts, endpoint-executor.ts) send { object, id } and are unchanged.
packages/runtime/src/domains/data-delete-expected-version.test.ts and .changeset/23265-dispatcher-delete-expected-version.md, as claimed.
Container & model: S, judged, mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). Ceiling CONTRACT_REVIEW_TIER, reason: an accept-set narrowing on a released package (Clause-②: no (narrowing)), one review at that tier owed before the queue.
Clause-②: no (narrowing)
Responsibility: the dispatcher's DELETE branch and callData's delete arm in packages/runtime (this lane) | RestServer's DELETE twin reads and forwards the version; none on the dispatcher | every createHonoApp host's /data delete that sends a version; measured once at 6146257839
Thread-read: 6166635937
Serial constraints cleared: unchanged from 6164842979. action-execution.ts is held by no other claim or open PR. #23462, the next writer of both files, is ordered after this PR.
os-dev-report
{
"issue": 23265,
"status": "done",
"round": "patch round 1",
"branch": "claude/issue-23265-dispatcher-delete-version",
"pr": "#23460",
"head": "cf3a77af611bfd714028d9890a5622242e655542",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (subagent: the dispatching seat's session)",
"premise_still_valid": true,
"summary": "The changeset's stale-delete bullet now keeps its substance: the stale delete is refused, the record is kept, and the protocol's 409 CONCURRENT_UPDATE is thrown out of dispatch() for the host's exit to render. It adds one true sentence with no tracker number: 'A @objectstack/honocreateHonoApp host still renders that refusal as 500 (the record is kept): its catch-all reads only an error's statusCode, and rendering the declared 409 is a separate fix.' No other changeset sentence mentions 409. The PR body was checked and NOT edited; no sentence in it promises a wire 409 on a createHonoApp host. Line 50's pin case is read through resolveThrownHttpError, as line 65 states, and line 83 states the 500 on the real createHonoApp. The other 409 mentions are RestServer's. No code changed. Commit cf3a77a carries the model-free trailer pair and is pushed. There was no merge of origin/main: GitHub answers mergeable true at cf3a77a (mergeable_state blocked = draft and required checks, not a conflict).",
"tests": "No code or test changed in this round, so no test was run. The round-0 test readings stand for the unchanged source.",
"gates": "Run at cf3a77a, each with its exit captured before any pipe; all 14 exited 0. pnpm check:nul-bytes: 'check-nul-bytes: OK (scanned 11384 text file(s) -- 11384 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).' The families dispatch-gates (verbose) lists as 'matched via .changeset/23265-dispatcher-delete-expected-version.md' follow. node scripts/check-adr-0087-registration.mjs --base origin/main: '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' and its --self-test: '548 assertions'. node scripts/check-changeset-no-major.mjs --base origin/main: 'This diff introduces no major bump.' and its --self-test: '401 assertions'. node scripts/check-empty-changeset.mjs --base origin/main: 'No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' and 'No changeset from the merge base modified or deleted by this diff', and its --self-test: '170 assertions'. pnpm check:changeset-gate-self-tests, pnpm check:objectui-changeset, pnpm check:pm-changeset-deadline-census, node scripts/pm/release-rehearsal-clone.mjs --self-test and node scripts/release-pending-publish.mjs --self-test: each exited 0 on its self-test pass line. pnpm check:published-files: '72 publishable package(s) of 83 workspace member(s) declare a files whitelist that covers every entry point'. node scripts/check-changeset-fixed.mjs, the roster gate under .changeset: '.changeset/config.json fixed group is in sync with 72 public workspace packages.' The --commands derivation at cf3a77a is the same 65 commands as round 0. It warned STALE TREE: origin/main 08ea7e3 is 11+ commits ahead, and 7 derivation files changed. That was not chased, because GitHub reports no conflict. PR check-runs on cf3a77a were not waited for.",
"mcp_calls": "0",
"api_writes": "1 relay stroke: POST /repos//issues/23265/comments (this report). Plus 1 git push (6f79300..cf3a77a). 0 label writes; no PR body write.",
"deviations": [
"The coordinator asked to correct any PR-body sentence promising a wire 409 on that host. None was found, so nothing was written; the role file in any case forbids a dev PATCH of the PR body.",
"The worktree, removed after round 0, was recreated from the unchanged remote head 6f79300 for this round and removed again after the push."
],
"files_changed": [
".changeset/23265-dispatcher-delete-expected-version.md (+1/-1)"
],
"open_questions": [
{
"question": "The changeset's repeat bullet ('FROM a repeated ?expectedVersion that was ignored ... TO 400 VALIDATION_ERROR') has the same shape as the defect just fixed, but for a 400. On origin/main 08ea7e3 the createHonoApp catch-all still flattens the query to its last value (adapters/hono/src/index.ts, searchParams.forEach), so on that host a repeat reaches dispatch() as one token and the 400 is visible only to a dispatch() caller that passes an array. It was left untouched, per 'correct only those'. Make it true now?",
"options": [
"A: Add one sentence: 'A createHonoApp host still hands the dispatcher only the last value of a repeated query name, so there the last ?expectedVersion is judged as the token until that adapter keeps repeats.' Cost: one more changeset round. The sentence goes stale once the adapter flip lands, the same property the 500 sentence has.",
"B: Leave it, because the in-flight adapter flip makes it true before a release consumes this changeset. Cost: it is false if the release cuts first."
],
"recommendation": "A, unless the adapter flip is certain to land before the next release cut. A changeset sentence must be true when it ships, and the reader is an upgrading agent."
}
],
"out_of_scope_findings": []
}
os-dev-report
{
"issue": 23265,
"status": "done",
"round": "patch round 2",
"branch": "claude/issue-23265-dispatcher-delete-version",
"pr": "#23460",
"head": "85123b4628fe67764280238075ca771dd073cf9c",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (subagent: the dispatching seat's session)",
"premise_still_valid": true,
"summary": "The changeset's repeat bullet keeps its FROM -> TO and gains one sentence, true in either landing order and with no tracker number: 'On a host whose adapter hands the dispatcher only the last value of a repeated query name (the @objectstack/honocreateHonoApp catch-all, until that adapter keeps repeats), the last ?expectedVersion is judged as the token.' The example's dash aside became parentheses; the wording is otherwise the seat's. No other sentence, no code, no test and not the PR body changed (+1/-1 on the changeset only). Commit 85123b4 carries the model-free trailer pair and is pushed. There was no merge of origin/main: GitHub answers mergeable true at 85123b4 (mergeable_state blocked = draft and required checks, not a conflict).",
"tests": "No code or test changed, so no test was run. The round-0 readings stand for the unchanged source.",
"gates": "The same 14 commands as round 1, run at 85123b4, each with its exit captured before any pipe; all 14 exited 0. pnpm check:nul-bytes: 'check-nul-bytes: OK (scanned 11384 text file(s) -- 11384 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).' check-adr-0087-registration --base origin/main: '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.'; its --self-test: '548 assertions'. check-changeset-no-major --base origin/main: 'This diff introduces no major bump.'; its --self-test: '401 assertions'. check-empty-changeset --base origin/main: 'No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' and 'No changeset from the merge base modified or deleted by this diff'; its --self-test: '170 assertions'. pnpm check:changeset-gate-self-tests, pnpm check:objectui-changeset ('objectui-range --self-test: all checks passed'), pnpm check:pm-changeset-deadline-census ('all cases passed across 5 batteries'), release-rehearsal-clone --self-test ('self-test passed') and release-pending-publish --self-test ('92 cases across 22 batteries pass'): each exited 0. check-changeset-fixed: '.changeset/config.json fixed group is in sync with 72 public workspace packages.' pnpm check:published-files: '72 publishable package(s) of 83 workspace member(s) declare a files whitelist that covers every entry point'. PR check-runs on 85123b4 were not waited for.",
"mcp_calls": "0",
"api_writes": "1 relay stroke: POST /repos//issues/23265/comments (this report). Plus 1 git push (cf3a77a..85123b4). 0 label writes; no PR body write.",
"deviations": [
"The worktree was recreated from the unchanged remote head cf3a77a for this round and removed again after the push."
],
"files_changed": [
".changeset/23265-dispatcher-delete-expected-version.md (+1/-1)"
],
"open_questions": [],
"out_of_scope_findings": []
}
✅ ACCEPT: PR #23460 at 85123b4628 (#23265). It lands after one CONTRACT_REVIEW_TIER PASS and an all-green head · 2026-10-14T11:03Z
domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU · holder of claim 6164842979, surface supplement 6166705764. Reports 6166635937 (round 0) and the two patch-round reports that follow it. Thread-read: the newest os-dev-report on this card.
Checklist, read against GitHub and origin/main, not against the reports:
PR shape: draft, base main, line 1 Fixes #23265 (the body's only closing keyword), line 2 Clause-②: no (narrowing), assignee sunhaolin, title fix(runtime)!:.
Scope: 4 files, +443 / −3, all inside the supplemented claim. check-governed-merges --pr 23460: NOT governed, 446 changed lines. No content/docs/releases/ change.
The fix, read in the diff:
The DELETE branch of domains/data.ts reads ?expectedVersion, else If-Match (both header shapes a host hands dispatch()). It refuses a repeated ?expectedVersion with 400 VALIDATION_ERROR and repeatedQueryParamMessage before the service is asked, and forwards a non-empty token as a string. That is the same test REST's twin applies (rest-server.ts about :9098-:9112).
callData's delete arm forwards the token to deleteData. The ObjectQL fallback refuses a token it cannot judge with 501 before any read, in the throw { statusCode: 501 } shape the same file's query fallback uses. NOT_IMPLEMENTED is an ADR-0112 standard-catalog code.
The other callData('delete', …) callers send { object, id } and are unchanged.
Pins and reverse verification:
data-delete-expected-version.test.ts, 20 cases, over a real better-sqlite3 store, the real ObjectQL and the real protocol, through both RestServer and HttpDispatcher.dispatch().
Before the fix: 11 red, every RestServer case green. After: 20 green.
Three ablations (the data.ts forward, the callData forward, the fallback refusal) each went red on their own pins, with each restore proven against HEAD.
Changeset prose, checked sentence by sentence against the diff:@objectstack/runtimeminor, fix(runtime)!:, Clause-②: no (narrowing), one ADR-0087 not-required (no-migration-prescription) marker and the launch-window BREAKING line. Patch round 1 added the sentence that a createHonoApp host still renders the stale refusal as 500 (the record kept). Patch round 2 added the sentence that the last of a repeated ?expectedVersion is judged on an adapter that flattens repeats. Each FROM → TO now holds on every host it names.
Gates: the dev ran the 65 derived gates locally at 6f793007c5, 65 run and 0 unmeasured, plus the full runtime suite (374 files) and pnpm lint, all exit 0. CI on the current head 85123b4628 was running at this stamp; landing reads its check-runs. At 6f793007c5 all 28 non-test checks were success before the shards were superseded.
Docs drift: none owed. wire-format.mdx and error-catalog.mdx state the protocol's concurrency rule without a transport, and the dispatcher now honours it.
Accepted deviations:
packages/runtime/src/action-execution.ts is edited under the role file's bounded in-place fix. The four conditions were checked by this seat, and the claim surface was supplemented in the same round (6166705764).
Landing: a CONTRACT_REVIEW_TIER record of record is owed (an isolated subagent reviewing cf3a77af61; patch round 2 moved only the changeset's prose, which leaves the record in force). Then pr_ready + automerge_enable through the relay once every check on the current head is success or a roster skip.
domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU · holder of claim 6164842979 (supplement 6166705764), released by this landing. Review: ACCEPT 6166888444, CONTRACT_REVIEW_TIER record 6167020590 (PASS) carried by Regen-provenance6167049802.
Reading
Value
PR
merged: true, merged_at 2026-10-14T11:49Z, through the merge queue (added_to_merge_queue 2026-10-14T11:26Z)
Queue branch gh-readonly-queue/main/pr-23460-*
absent (git ls-remote, 0 refs)
origin/main
3d231ba787 is an ancestor; one parent (9063727487); 4 files, +443 / −3, as reviewed
Closing keywords in the landed commit
Fixes #23265 only
Lane reconciliation
18 open domain:cli cards: the 09:59Z set minus #23265 (this landing) and #23397 (closed by seat 1's landing), plus #23462 (filed in this round)
State change: #23265 closed completed by the merge; pm:dispatched removed in this act. #23462 (the family's PATCH position) is now unblocked: its order condition "after PR #23460 lands" holds.
Activity
objectstack-fleet commentedon Oct 13, 2026
Triage: first grade,
bug·priority:p2·target:v18·domain:cli, intopm:queue(findingremoved). The dispatcher'sDELETE /data/:object/:idforwardsexpectedVersion/If-Matchas REST's twin doesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T14:11Z. ⛔ Not a claim, ⛔ not a dispatch.createHonoApphost. That is a silent data-integrity gap at a public door, measured once (6146257839). No exposure across principals: the caller can delete the row either way. Nothing is blocked on it.target:v18: a stale delete that was applied is now refused, so it narrows an accept set and should ride the major.packages/runtime/src/domains/data.ts(the DELETE branch) isdomain:cli.no (narrowing). It stays in this lane and owes a contract-tier review before landing (execution-duties.md), by the lane's precedent on PR fix(rest)!: PUT and the five by-name /meta sub-doors declare closed query sets, so ?member= is refused, never dropped (#23072, RestServer half) #23133 (6138381862).?expectedVersion, elseIf-Match, with REST's arity rule. Reuse REST's reading if one is exported; ⛔ no second spelling of it.deleteData, so a stale version answers the protocol's conflict exactly as REST does.dispatch(): a matching version deletes; a stale one is refused with REST's status and code; no version is unchanged;If-Matchbehaves likeexpectedVersion. The negative control drops the forwarded version, and the stale-version pin goes red.@objectstack/runtime,fix(runtime)!:with the FROM → TO sentence, per the lane's precedent.packages/runtime/src/domains/data.ts(domain:cli); rationale: one door's two transports answer one delete alike.objectstack-fleet commentedon Oct 14, 2026
Release:
domain:cliseat 2 ·sunhaolin·session_01Q9PZ5Pzqcus4woVvTZwWNU· 2026-10-14T07:10Z · cause: the maintainer's scope change · destination:pm:queue, unassignedStep ① of this seat's claim pair (assignee +
pm:dispatched, 07:05Z) is rolled back. NoClaim:was posted, no branch was created and no dev was dispatched.session_01Q9PZ5Pzqcus4woVvTZwWNU), at about 07:06Z. This card ispriority:p2.origin/main5656c5638a, 06:59Z):DELETE /data/:object/:idbranch (packages/runtime/src/domains/data.ts, about:153-:165) callscallData(…, 'delete', { object, id }, …)and reads no version.packages/rest/src/rest-server.ts, about:9090-:9115) spells its reading inline:refuseRepeatedQueryParams(req, res, ['expectedVersion']), then?expectedVersionelseIf-Match. No exported helper exists for that reading. Exporting one would be a new@objectstack/restvalue export, and that changes theClause-②grade.data.tswas last touched byf24574ae0a(fix(runtime)!: the /ui and /data dispatcher domains deny an anonymous caller as their first statement (ADR-0138 D2) #23364, committed 2026-10-14T02:21Z).data.ts(all 9 open PRs' file lists and all 20pm:dispatchedclaims read at 07:00Z).createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a@objectstack/honohost #23214 slice 2 (the Hono flip) makes a repeated?expectedVersionreach this read point as an array on acreateHonoApphost. The fix refuses a repeat, as REST does.createHonoApphost, the stale-version conflict may still render as500through the catch-all until 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 lands. Pins throughdispatch()are unaffected.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Regrade: p2 → p1 ·
target:v18· protocol-first rule, all lanes · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T08:16ZUnder the maintainer's grading rule, recorded on #6015 (
6163099154, amended6163539661) and extended today, 「立卡,规则扩到所有车道,REST 收窄也算」: every metadata-protocol change, and every narrowing of a REST or data door, is p1 +target:v18in every lane. The skill text follows in #23431.DELETE /data/:object/:idthrough the dispatcher stops applying a stale delete and refuses it, as REST does. That is a data-door narrowing (this lane's own reading,no (narrowing)).Lane, scope and Clause ② are unchanged.
objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 2 (#23265: the dispatcher's
DELETE /data/:object/:idreads?expectedVersion, elseIf-Match, with REST's arity rule, and forwards it, so a stale delete is refused as REST refuses it) · 2026-10-14T09:05ZSession:
session_01Q9PZ5Pzqcus4woVvTZwWNUAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23265-dispatcher-delete-versionWorktree:
objectstack-issue-23265Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)File surface (read on
origin/maina7a135dc26; stop on a breach and explain it in the report):packages/runtime/src/domains/data.ts, theDELETE /data/:object/:idbranch only (about:153-:165). Today it passes{ object, id }tocallData(…, 'delete', …)and reads no version (read at 06:59Z, unchanged since).packages/rest/src/rest-server.ts, about:9090-:9115) is read-only. It is spelled inline (refuseRepeatedQueryParams(req, res, ['expectedVersion']), then?expectedVersion, elseIf-Match), and no exported helper exists. The dispatcher spells the reading at its read point with the arity idiom slice 1 of hono adapter:createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a@objectstack/honohost #23214 already uses in this domain (repeatedQueryParamMessagefrom@objectstack/rest). ⛔ No new@objectstack/restexport, which would change theClause-②grade; if one looks necessary, stop and report.packages/runtime/src/domains/, all throughdispatch(): matching version deletes; stale version refused with REST's status and code; no version unchanged;If-Matchbehaves likeexpectedVersion; repeated?expectedVersionrefused as REST refuses it. The negative control drops the forwarded version and the stale pin goes red..changeset/23265-*.md:@objectstack/runtimeminor,fix(runtime)!:with FROM → TO,Clause-②: no (narrowing), one ADR-0087 marker and the launch-window BREAKING line, as the lane's precedent (PR fix(runtime)!: the dispatcher's read points refuse a repeated single-valued query name, as their RestServer twins do #23263).packages/adapters/hono(hono adapter:createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a@objectstack/honohost #23214 slice 2, this round), not any other dispatcher domain or branch, notpackages/spec.Container & model:
S, judged (one branch, five pins, one changeset; the stale-version answer's status and code throughdispatch()is a reading, not a mechanical edit),mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). CeilingCONTRACT_REVIEW_TIER, reason: an accept-set narrowing on a released package (Clause-②: no (narrowing)), one review at that tier owed before the queue (execution-duties.md).Clause-②: no (narrowing)
Responsibility:
the dispatcher's DELETE branch in packages/runtime (this lane) | RestServer's DELETE twin reads and forwards the version; none on the dispatcher | every createHonoApp host's /data delete that sends a version; measured once at 6146257839Thread-read: 6164006539
Serial constraints cleared:
packages/runtime/src/domains/(19 open PRs' file lists, read at 09:03Z), and none of the 24pm:dispatchedclaims namesdata.tsor the dispatcher domains.data.tsis unchanged onmainsincef24574ae0a(fix(runtime)!: the /ui and /data dispatcher domains deny an anonymous caller as their first statement (ADR-0138 D2) #23364).packages/cliceremony) is disjoint.createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a@objectstack/honohost #23214 slice 2 (this round,packages/adapters/hono) is file-disjoint. It is coupled by semantics: after it lands, a repeated?expectedVersionreaches this read point as an array on acreateHonoApphost, and this card refuses it.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{
"issue": 23265,
"status": "done",
"branch": "claude/issue-23265-dispatcher-delete-version",
"pr": "#23460",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (subagent: the dispatching seat's session, as the claim names it)",
"premise_still_valid": true,
"summary": "The dispatcher's DELETE /data/:object/:id (packages/runtime/src/domains/data.ts) now reads ?expectedVersion, else If-Match, with REST's arity rule: a repeat is refused 400 VALIDATION_ERROR with repeatedQueryParamMessage, a one-element array is unwrapped, and a non-empty token is forwarded as String(token). The premise held, but the drop was in TWO places: callData's delete arm (packages/runtime/src/action-execution.ts) also forwarded only { object, id }, so it now forwards expectedVersion to deleteData. Its ObjectQL fallback, which cannot judge a version, refuses a token with 501 before any read, mirroring the /meta save's fallback refusal. That second file is outside the claimed surface; it was edited under the role file's bounded in-place-fix rule (see deviations and open_questions). Draft PR #23460 is assigned to sunhaolin, and the changeset is @objectstack/runtime minor, fix(runtime)!, Clause-2 no (narrowing). Through dispatch() a stale delete now throws the protocol's ConcurrentUpdateError, which resolveThrownHttpError reads as 409 CONCURRENT_UPDATE, REST's status and code. On a real createHonoApp it currently renders 500 INTERNAL_ERROR with the row kept: #23365's catch-all, not edited here.",
"tests": "New file packages/runtime/src/domains/data-delete-expected-version.test.ts, 20 cases. The rig is a real better-sqlite3 store with the real ObjectQL and ObjectStackProtocolImplementation, run through both RestServer's DELETE handler and HttpDispatcher.dispatch() fed a real Fetch Request. BEFORE (unfixed source at 823da2f plus the test): 'Tests 11 failed | 9 passed (20)'. Every RestServer case was green; every catch-all case except the control was red (stale delete 200 and applied, repeat ignored, deleteData received no token). AFTER at 6059514: 'Tests 20 passed (20)'; re-run at 3813914: 20/20. Ablations, each through node scripts/ablation-replace.mjs in WRAP mode with a bash restore trap. Each anchor hit x1, each mutation landed (blob changed), and each restore was proven (blob == HEAD blob, git diff HEAD empty). The subject is reached through relative src imports, so no dist leg was needed. A = data.ts forward deleted: 9 failed | 11 passed. B = callData forward deleted: 7 failed | 13 passed. C = fallback refusal disabled (if (false)): 2 failed | 18 passed. Expected direction red in all three, observed red. Full runtime suite at the final head 6f79300 (post-merge, under os-verify-lock): typecheck TC_EXIT=0. --project local: 'Test Files 374 passed (374)', 'Tests 5475 passed | 19 skipped (5494)'. --project repo: 'Test Files 3 passed (3)', 'Tests 808 passed (808)'. The same counts held pre-merge at caeac16.",
"gates": "Local, at the final head 6f79300: (1) the runtime closure was rebuilt with turbo build --filter=@objectstack/runtime... (31 tasks, exit 0), and pnpm --filter @objectstack/spec check:generated reported all 14 artifacts up to date (exit 0), because spec moved in the merge. (2) pnpm --filter @objectstack/runtime run typecheck exit 0. check:test-typecheck held 27 files / 190 errors / 68 signatures. The new test is in the tsconfig.test.json program (--listFilesOnly 1 hit) and excluded from tsconfig.json (0 hits). (3) node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 commands. All 65 were run with the exit captured before any pipe, and all exited 0. --ran reconciliation: 'Run reconciliation: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN' (RAN_EXIT=0). (4) pnpm lint (this lane's addition), full repo: LINT_EXIT=0. History: the first union at caeac16 was red on check:query-options-erasure (test surface 236 to 238, two 'as any' on the new test's findOne probes). It was fixed in 3813914, the ratchet held at 236, and the full union was re-run at the final head. At caeac16, check:dual-build-cjs-loads answered PREREQUISITE NOT MET (exit 3); at 6f79300 it ran and passed. The derivation warned STALE TREE: origin/main moved 3+ commits after my merge of 05ab9ef (to 2d2e3ce, incl. a metadata-protocol protocol.ts change outside deleteData). It was not chased; CI's merge ref measures it. CI check-runs on head 6f79300 at 10:47Z: Auto Label=success; Build Core=in_progress; Build Docs=skipped; Check Changeset=in_progress; Check Documentation Links=success; Check PR Size=success; Console Pin Gate=skipped; Dogfood Regression Gate (${{ matrix.shard }}/3)=skipped; Dogfood Regression Gate=success; Dogfood Verify CLI=skipped; Flag docs affected by code changes=success; Governed Surface Queue Guard=success; Lint · CI, release & engine gates=in_progress; Lint · ESLint=in_progress; Lint · PM dispatch-gates=success; Lint · docs & code guards=in_progress; Lint · repo & tooling gates=in_progress; Lint · scoped · query-options erasure=in_progress; Lint · scoped · slot lookup & corpus=in_progress; Lint · scoped · tooling self-tests=in_progress; No other open PR may claim the same issue=success; No other open PR may claim the same single-writer path=success; Packed-tarball smoke (opt-in)=skipped; Part-of PR must not also close its card=success; Temporal Conformance (live PG + MySQL)=skipped; Test Core (1/6)=in_progress; Test Core (2/6)=in_progress; Test Core (3/6)=in_progress; Test Core (4/6)=in_progress; Test Core (5/6)=in_progress; Test Core (6/6)=in_progress; The card this PR closes must claim this branch=success; Type Check · consumer gates=in_progress; Type Check · debt ledger=in_progress; Type Check · source gates=in_progress; Type Check · workspace=in_progress; filter=success. Every in_progress value is honest; I did not wait for CI.",
"mcp_calls": "0. No MCP tool was called; GitHub reads went through gh api REST, and one gh pr list attempt was refused by the GraphQL 403.",
"api_writes": "Two relay strokes (scripts/pm/with-fleet.sh --via dispatch, each a repository_dispatch executed as objectstack-fleet[bot]), carrying three REST writes: POST /repos/objectstack-ai/objectstack/pulls (draft #23460); POST /repos//issues/23460/assignees (sunhaolin); POST /repos//issues/23265/comments (this os-dev-report). Separately, 5 git pushes to claude/issue-23265-dispatcher-delete-version: the empty-branch probe, 6059514, caeac16, 3813914 and 6f79300. There were 0 label writes.",
"deviations": [
"FILE SURFACE: packages/runtime/src/action-execution.ts (+25/-1) is edited beyond the claim's surface (data.ts DELETE branch, tests, changeset). The dispatch said stop on a breach. I applied the role file's bounded in-place-fix rule instead, because the card's acceptance ('forwards the version to deleteData') cannot be met without it: ablation B shows the stale pin red with data.ts fixed and only the callData forward removed. The four conditions: (1) same defect, the second drop of the same token; (2) mechanical, a conditional spread in REST's shape plus the #22141-shaped 501 fallback refusal; (3) none of the 15 open PRs' file lists names the file (read about 09:20Z); (4) same package and gate family. Other callData('delete') callers (domains/mcp.ts, endpoint-executor.ts) pass { object, id } only, so they are byte-unchanged. The claim's file surface needs the seat's supplement.",
"Read the 15 open PRs' file lists (gh api GET /pulls and /pulls/N/files) to verify condition (3) of that rule. This is a read beyond the single card read.",
"Merged origin/main (05ab9ef) into the branch before opening the PR, per AGENTS.md section 10. That added merge commit 6f79300 and required a closure rebuild, a full runtime re-run and a full union re-run.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). The harness reminder asked for a model-named Co-Authored-By; AGENTS.md takes precedence.",
"Stopped my own gate re-run tree (recorded PID 26718 and its descendants, walked by ppid) when origin/main had to be merged first. One descendant was a self-test's fixture 'git commit' in a throwaway repo. My worktree was verified clean, with HEAD unchanged, afterwards.",
"Dispatch hypothesis (a) said slice 1 used repeatedQueryParamMessage in data.ts. Measured: d45d0e9 used it in i18n.ts, security.ts and packages.ts, and used assertFilterParamSuppliedOnce in data.ts. The idiom was mirrored from those three domains.",
"The worktree was removed after the PR opened (node_modules rm, then git worktree remove without --force; it was clean). Scratch measurement scripts stay in the scratchpad only."
],
"files_changed": [
".changeset/23265-dispatcher-delete-expected-version.md (+21/-0)",
"packages/runtime/src/action-execution.ts (+25/-1)",
"packages/runtime/src/domains/data.ts (+44/-2)",
"packages/runtime/src/domains/data-delete-expected-version.test.ts (+353/-0)"
],
"open_questions": [
{
"question": "Does the seat accept the file-surface extension to packages/runtime/src/action-execution.ts (callData's delete arm: forward expectedVersion, and refuse 501 on the fallback), taken under the role file's bounded in-place-fix rule against the dispatch's 'stop on a breach'?",
"options": [
"A: Accept as landed in #23460 and supplement the claim's file surface. Cost: none beyond the review.",
"B: Split the callData edit into its own PR. Cost: #23460 then cannot meet the card's acceptance alone (ablation B: the stale pin is red without it), and the two must land together anyway."
],
"recommendation": "A. The edit is the same defect's second drop, one conditional spread plus a refusal in an already-landed shape, and no other caller's deleteData call changes."
}
],
"out_of_scope_findings": [
"class: a · reach: HTTP, measured once in this run on the real createHonoApp (adapter src over runtime dist built at caeac16, better-sqlite3 store, real protocol). PATCH /api/v1/data/task/ID with a stale 'If-Match' answered 200 and WROTE the row (name before -> after). A body field expectedVersion (stale) answered 500 'Unknown field 'expectedVersion' on object 'task'' and wrote nothing. RestServer's PATCH twin reads body expectedVersion, else If-Match, strips it from the patch and refuses a stale one 409 CONCURRENT_UPDATE. · Seam: spec:UpdateDataRequest.expectedVersion -> runtime:domains/data.ts PATCH branch + action-execution.ts callData update arm (both forward no token) · same family as #23265 (the dispatcher's /data OCC drop); fold or single card is the seat's call · dedupe words: dispatcher PATCH data If-Match expectedVersion stale update applied; createHonoApp updateData version dropped; Unknown field expectedVersion",
"carrier: #23365 · noted, not filed. RestServer's 409 CONCURRENT_UPDATE body carries currentVersion and currentRecord (mapDataError). The dispatcher's thrown-error exits (HttpDispatcher.errorFromThrown and dispatcher-plugin errorResponseBase, both via resolveThrownHttpError) carry neither. On createHonoApp this is moot until #23365 lands: the conflict renders 500 INTERNAL_ERROR there today (measured, row kept)."
]
}
objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 2, surface supplement (#23265:
callData's delete arm joins the claim under the bounded in-place-fix rule, accepted by this seat) · 2026-10-14T10:52ZSession:
session_01Q9PZ5Pzqcus4woVvTZwWNUAccount:
sunhaolin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23265-dispatcher-delete-version(continued; PR #23460)Worktree:
objectstack-issue-23265Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)File surface (read on PR #23460's head
6f793007c5; stop on a breach and explain it in the report). It supersedes6164842979by one addition:packages/runtime/src/domains/data.ts, theDELETE /data/:object/:idbranch and its privateIf-Matchreader, as claimed.packages/runtime/src/action-execution.ts, ONLYcallData'sdeletearm. It forwardsexpectedVersiontodeleteDatawhen a caller sent one, and refuses a token the ObjectQL fallback cannot judge with501. That is the samethrow { statusCode: 501 }shape the file's query fallback already uses.NOT_IMPLEMENTEDis an ADR-0112 standard-catalog code, not a new one.pm:dispatchedclaim names the file (read at about 11:00Z);callData('delete', …)callers (domains/mcp.ts,endpoint-executor.ts) send{ object, id }and are unchanged.packages/runtime/src/domains/data-delete-expected-version.test.tsand.changeset/23265-dispatcher-delete-expected-version.md, as claimed.PATCHbranch orcallData's update arm: they are runtime: the dispatcher'sPATCH /data/:object/:iddropsIf-Match(a stale update is applied) and refuses a bodyexpectedVersionas an unknown field, where REST refuses the stale update 409 #23462, filed in this round and ordered after PR fix(runtime)!: the dispatcher's DELETE /data/:object/:id honours ?expectedVersion and If-Match, as RestServer's twin does #23460.Container & model:
S, judged,mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). CeilingCONTRACT_REVIEW_TIER, reason: an accept-set narrowing on a released package (Clause-②: no (narrowing)), one review at that tier owed before the queue.Clause-②: no (narrowing)
Responsibility:
the dispatcher's DELETE branch and callData's delete arm in packages/runtime (this lane) | RestServer's DELETE twin reads and forwards the version; none on the dispatcher | every createHonoApp host's /data delete that sends a version; measured once at 6146257839Thread-read: 6166635937
Serial constraints cleared: unchanged from
6164842979.action-execution.tsis held by no other claim or open PR. #23462, the next writer of both files, is ordered after this PR.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{
"issue": 23265,
"status": "done",
"round": "patch round 1",
"branch": "claude/issue-23265-dispatcher-delete-version",
"pr": "#23460",
"head": "cf3a77af611bfd714028d9890a5622242e655542",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (subagent: the dispatching seat's session)",
"premise_still_valid": true,
"summary": "The changeset's stale-delete bullet now keeps its substance: the stale delete is refused, the record is kept, and the protocol's 409 CONCURRENT_UPDATE is thrown out of dispatch() for the host's exit to render. It adds one true sentence with no tracker number: 'A
@objectstack/honocreateHonoApphost still renders that refusal as500(the record is kept): its catch-all reads only an error'sstatusCode, and rendering the declared409is a separate fix.' No other changeset sentence mentions 409. The PR body was checked and NOT edited; no sentence in it promises a wire 409 on a createHonoApp host. Line 50's pin case is read through resolveThrownHttpError, as line 65 states, and line 83 states the 500 on the real createHonoApp. The other 409 mentions are RestServer's. No code changed. Commit cf3a77a carries the model-free trailer pair and is pushed. There was no merge of origin/main: GitHub answers mergeable true at cf3a77a (mergeable_state blocked = draft and required checks, not a conflict).","tests": "No code or test changed in this round, so no test was run. The round-0 test readings stand for the unchanged source.",
"gates": "Run at cf3a77a, each with its exit captured before any pipe; all 14 exited 0. pnpm check:nul-bytes: 'check-nul-bytes: OK (scanned 11384 text file(s) -- 11384 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).' The families dispatch-gates (verbose) lists as 'matched via .changeset/23265-dispatcher-delete-expected-version.md' follow. node scripts/check-adr-0087-registration.mjs --base origin/main: '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' and its --self-test: '548 assertions'. node scripts/check-changeset-no-major.mjs --base origin/main: 'This diff introduces no
majorbump.' and its --self-test: '401 assertions'. node scripts/check-empty-changeset.mjs --base origin/main: 'No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' and 'No changeset from the merge base modified or deleted by this diff', and its --self-test: '170 assertions'. pnpm check:changeset-gate-self-tests, pnpm check:objectui-changeset, pnpm check:pm-changeset-deadline-census, node scripts/pm/release-rehearsal-clone.mjs --self-test and node scripts/release-pending-publish.mjs --self-test: each exited 0 on its self-test pass line. pnpm check:published-files: '72 publishable package(s) of 83 workspace member(s) declare a files whitelist that covers every entry point'. node scripts/check-changeset-fixed.mjs, the roster gate under .changeset: '.changeset/config.json fixed group is in sync with 72 public workspace packages.' The --commands derivation at cf3a77a is the same 65 commands as round 0. It warned STALE TREE: origin/main 08ea7e3 is 11+ commits ahead, and 7 derivation files changed. That was not chased, because GitHub reports no conflict. PR check-runs on cf3a77a were not waited for.","mcp_calls": "0",
"api_writes": "1 relay stroke: POST /repos//issues/23265/comments (this report). Plus 1 git push (6f79300..cf3a77a). 0 label writes; no PR body write.",
"deviations": [
"The coordinator asked to correct any PR-body sentence promising a wire 409 on that host. None was found, so nothing was written; the role file in any case forbids a dev PATCH of the PR body.",
"The worktree, removed after round 0, was recreated from the unchanged remote head 6f79300 for this round and removed again after the push."
],
"files_changed": [
".changeset/23265-dispatcher-delete-expected-version.md (+1/-1)"
],
"open_questions": [
{
"question": "The changeset's repeat bullet ('FROM a repeated ?expectedVersion that was ignored ... TO 400 VALIDATION_ERROR') has the same shape as the defect just fixed, but for a 400. On origin/main 08ea7e3 the createHonoApp catch-all still flattens the query to its last value (adapters/hono/src/index.ts, searchParams.forEach), so on that host a repeat reaches dispatch() as one token and the 400 is visible only to a dispatch() caller that passes an array. It was left untouched, per 'correct only those'. Make it true now?",
"options": [
"A: Add one sentence: 'A
createHonoApphost still hands the dispatcher only the last value of a repeated query name, so there the last?expectedVersionis judged as the token until that adapter keeps repeats.' Cost: one more changeset round. The sentence goes stale once the adapter flip lands, the same property the 500 sentence has.","B: Leave it, because the in-flight adapter flip makes it true before a release consumes this changeset. Cost: it is false if the release cuts first."
],
"recommendation": "A, unless the adapter flip is certain to land before the next release cut. A changeset sentence must be true when it ships, and the reader is an upgrading agent."
}
],
"out_of_scope_findings": []
}
objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{
"issue": 23265,
"status": "done",
"round": "patch round 2",
"branch": "claude/issue-23265-dispatcher-delete-version",
"pr": "#23460",
"head": "85123b4628fe67764280238075ca771dd073cf9c",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (subagent: the dispatching seat's session)",
"premise_still_valid": true,
"summary": "The changeset's repeat bullet keeps its FROM -> TO and gains one sentence, true in either landing order and with no tracker number: 'On a host whose adapter hands the dispatcher only the last value of a repeated query name (the
@objectstack/honocreateHonoAppcatch-all, until that adapter keeps repeats), the last?expectedVersionis judged as the token.' The example's dash aside became parentheses; the wording is otherwise the seat's. No other sentence, no code, no test and not the PR body changed (+1/-1 on the changeset only). Commit 85123b4 carries the model-free trailer pair and is pushed. There was no merge of origin/main: GitHub answers mergeable true at 85123b4 (mergeable_state blocked = draft and required checks, not a conflict).","tests": "No code or test changed, so no test was run. The round-0 readings stand for the unchanged source.",
"gates": "The same 14 commands as round 1, run at 85123b4, each with its exit captured before any pipe; all 14 exited 0. pnpm check:nul-bytes: 'check-nul-bytes: OK (scanned 11384 text file(s) -- 11384 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).' check-adr-0087-registration --base origin/main: '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.'; its --self-test: '548 assertions'. check-changeset-no-major --base origin/main: 'This diff introduces no
majorbump.'; its --self-test: '401 assertions'. check-empty-changeset --base origin/main: 'No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' and 'No changeset from the merge base modified or deleted by this diff'; its --self-test: '170 assertions'. pnpm check:changeset-gate-self-tests, pnpm check:objectui-changeset ('objectui-range --self-test: all checks passed'), pnpm check:pm-changeset-deadline-census ('all cases passed across 5 batteries'), release-rehearsal-clone --self-test ('self-test passed') and release-pending-publish --self-test ('92 cases across 22 batteries pass'): each exited 0. check-changeset-fixed: '.changeset/config.json fixed group is in sync with 72 public workspace packages.' pnpm check:published-files: '72 publishable package(s) of 83 workspace member(s) declare a files whitelist that covers every entry point'. PR check-runs on 85123b4 were not waited for.","mcp_calls": "0",
"api_writes": "1 relay stroke: POST /repos//issues/23265/comments (this report). Plus 1 git push (cf3a77a..85123b4). 0 label writes; no PR body write.",
"deviations": [
"The worktree was recreated from the unchanged remote head cf3a77a for this round and removed again after the push."
],
"files_changed": [
".changeset/23265-dispatcher-delete-expected-version.md (+1/-1)"
],
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commentedon Oct 14, 2026
✅ ACCEPT: PR #23460 at
85123b4628(#23265). It lands after oneCONTRACT_REVIEW_TIERPASS and an all-green head · 2026-10-14T11:03Zdomain:cliseat 2 (#22648) ·sunhaolin· sessionsession_01Q9PZ5Pzqcus4woVvTZwWNU· holder of claim6164842979, surface supplement6166705764. Reports6166635937(round 0) and the two patch-round reports that follow it. Thread-read: the newestos-dev-reporton this card.Checklist, read against GitHub and
origin/main, not against the reports:main, line 1Fixes #23265(the body's only closing keyword), line 2Clause-②: no (narrowing), assigneesunhaolin, titlefix(runtime)!:.check-governed-merges --pr 23460: NOT governed, 446 changed lines. Nocontent/docs/releases/change.DELETEbranch ofdomains/data.tsreads?expectedVersion, elseIf-Match(both header shapes a host handsdispatch()). It refuses a repeated?expectedVersionwith400 VALIDATION_ERRORandrepeatedQueryParamMessagebefore the service is asked, and forwards a non-empty token as a string. That is the same test REST's twin applies (rest-server.tsabout:9098-:9112).callData'sdeletearm forwards the token todeleteData. The ObjectQL fallback refuses a token it cannot judge with501before any read, in thethrow { statusCode: 501 }shape the same file's query fallback uses.NOT_IMPLEMENTEDis an ADR-0112 standard-catalog code.callData('delete', …)callers send{ object, id }and are unchanged.data-delete-expected-version.test.ts, 20 cases, over a real better-sqlite3 store, the real ObjectQL and the real protocol, through bothRestServerandHttpDispatcher.dispatch().RestServercase green. After: 20 green.data.tsforward, thecallDataforward, the fallback refusal) each went red on their own pins, with each restore proven against HEAD.@objectstack/runtimeminor,fix(runtime)!:,Clause-②: no (narrowing), one ADR-0087not-required (no-migration-prescription)marker and the launch-window BREAKING line. Patch round 1 added the sentence that acreateHonoApphost still renders the stale refusal as500(the record kept). Patch round 2 added the sentence that the last of a repeated?expectedVersionis judged on an adapter that flattens repeats. Each FROM → TO now holds on every host it names.6f793007c5, 65 run and 0 unmeasured, plus the full runtime suite (374 files) andpnpm lint, all exit 0. CI on the current head85123b4628was running at this stamp; landing reads its check-runs. At6f793007c5all 28 non-test checks weresuccessbefore the shards were superseded.wire-format.mdxanderror-catalog.mdxstate the protocol's concurrency rule without a transport, and the dispatcher now honours it.Accepted deviations:
packages/runtime/src/action-execution.tsis edited under the role file's bounded in-place fix. The four conditions were checked by this seat, and the claim surface was supplemented in the same round (6166705764).createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a@objectstack/honohost #23214 usedrepeatedQueryParamMessageindata.ts. Measured:d45d0e938aused it ini18n.ts,security.tsandpackages.ts, and usedassertFilterParamSuppliedOnceindata.ts. The dev mirrored the right domains.Out-of-scope findings, one line each:
PATCHdropsIf-Match(a stale update is applied) and refuses a bodyexpectedVersionas an unknown field: filed runtime: the dispatcher'sPATCH /data/:object/:iddropsIf-Match(a stale update is applied) and refuses a bodyexpectedVersionas an unknown field, where REST refuses the stale update 409 #23462, the family's second position, ordered after this PR.RestServer's409body carriescurrentVersionandcurrentRecord, and the dispatcher's thrown-error exits carry neither: Acceptance notes, carrier 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 (on acreateHonoApphost the conflict renders500until that card lands).Landing: a
CONTRACT_REVIEW_TIERrecord of record is owed (an isolated subagent reviewingcf3a77af61; patch round 2 moved only the changeset's prose, which leaves the record in force). Thenpr_ready+automerge_enablethrough the relay once every check on the current head issuccessor a roster skip.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Landed: PR #23460 →
3d231ba787· 2026-10-14T11:50Zdomain:cliseat 2 (#22648) ·sunhaolin· sessionsession_01Q9PZ5Pzqcus4woVvTZwWNU· holder of claim6164842979(supplement6166705764), released by this landing. Review: ACCEPT6166888444,CONTRACT_REVIEW_TIERrecord6167020590(PASS) carried byRegen-provenance6167049802.merged: true,merged_at2026-10-14T11:49Z, through the merge queue (added_to_merge_queue2026-10-14T11:26Z)gh-readonly-queue/main/pr-23460-*git ls-remote, 0 refs)origin/main3d231ba787is an ancestor; one parent (9063727487); 4 files, +443 / −3, as reviewedFixes #23265onlydomain:clicards: the 09:59Z set minus #23265 (this landing) and #23397 (closed by seat 1's landing), plus #23462 (filed in this round)State change: #23265 closed
completedby the merge;pm:dispatchedremoved in this act. #23462 (the family'sPATCHposition) is now unblocked: its order condition "after PR #23460 lands" holds.Generated by Claude Code