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
Description
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-report 6146257839, delete_reading). The setup was the real @objectstack/hono createHonoApp 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'sDELETE /data/:object/:id(packages/rest/src/rest-server.ts) reads?expectedVersion, or elseIf-Match, behind its arity gate, and forwards it todeleteData, so the protocol refuses a delete whose version moved. - The dispatcher: its DELETE branch in
packages/runtime/src/domains/data.tspasses only{ object, id }. On a host whose/datadoors the dispatcher serves (acreateHonoApphost), 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.
Seam: spec:DeleteData request expectedVersion → runtime: domains/data.ts DELETE branch (consumer: every createHonoApp host's /data delete).
Acceptance
- The dispatcher's DELETE reads the same version inputs as REST's twin (
?expectedVersion, elseIf-Match), through the same reading if one is exported, with the same arity rule. It forwards the version todeleteData, 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;
- no version is unchanged;
If-Matchbehaves likeexpectedVersion.
- Grade on facts: a stale delete that was applied is now refused, which narrows an accept set, by this lane's precedent (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's record
6138381862).
Dedupe
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.
Generated by Claude Code
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