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
docs(adr): ADR-0104 D2 still says every action that declares no params keeps the pass-through — since #24268 a declarative operation: 'update' action's empty list is an empty accept set (Tier H, the maintainer's hand) #24393
Filing gate ①: a named location whose sentence the landing work makes untrue. This is a follow-up owed by PR #24392 (#24268, in review). reach: is the record itself, the decision every action-door change is read against (Prime Directive #13).
Filed by domain:cli seat 1 (seat post #6024) · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T, from the dev's open_questions[0] in report 6254612136 on #24268. Checked on origin/maincf16a0d18. ⛔ Not a claim. Lane, type and grade are triage's.
docs/adr/0104-field-runtime-value-shape-contract.md states D2's boundary twice:
Lines 195–196: "Actions declaring no params keep today's pass-through (there is nothing to check against), so existing param-less actions are untouched."
Lines 941–942: "Actions declaring no params keep their pass-through — that boundary is unchanged from D2's landing."
Both were written before the declarative operation: 'update' action existed (#14092, executed by #15079). Such an action has no handler. Its bag is merged over patch and written to the row, so the reason the pass-through gives ("there is nothing to check against") does not hold for it.
PR #24392 makes enforceActionParams (packages/runtime/src/action-execution.ts) judge a param-less declarative update's bag against an empty declared list. Every undeclared wire key is refused through the existing D2 path: the same 400, the same OS_ALLOW_LAX_ACTION_PARAMS hatch. Handler actions keep the pass-through. The published liveness row liveness/action.json/props/patch already says the wire cannot widen the bag ("an undeclared param is refused by ADR-0104 D2").
What the record lacks
A dated addendum (or an amended status line) of about one paragraph:
the param-less pass-through is scoped to actions with a handler;
The record is written once PR #24392 has landed, so it never states an enforcement the code does not yet hold. The precedent is #23483, the ADR-0069 note that waited on its seams.
Reader who acts
This is one Tier H edit to docs/adr/ alone, with a PR of its own. It is a governed surface, landed only on the maintainer's approval (precedents #23483 and #23006). The seat that lands #24268 can carry it once the blocker closes, if triage routes it there.
Dedupe
MCP search_issues on objectstack, "ADR-0104 D2 addendum param-less pass-through declarative update": 41 hits, first 10 read. None is this record.
Lane domain:spec, the owner of the ADR's contract face. The ADR path is Tier H governed, so the PR needs the maintainer's approval. It is written as an amendment beside D2, not a rewrite.
The record is now false on origin/main.docs/adr/0104-field-runtime-value-shape-contract.md:195 and :941 still say that an action declaring no params keeps the pass-through.
Grade unchanged: p2, documentation, domain:spec. The ADR edit is the maintainer's to land by hand (6255294986), so the spec lane's PR waits for that merge rather than for the queue. The Blocked-by: line stays as history.
Claim: PM loop round 1 · 2026-10-19T00:36Z · ADR-0104 D2: a dated note that scopes the param-less pass-through to actions with a handler; an operation: 'update' action's empty or absent params list is an empty accept set (PR #24392, d79dc4883c) (Tier H: the maintainer merges)
Session: session_019oFY6Hi35J7atvgVFnmoaK (the seat; the dev is an os-dev subagent of this session)
Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-24393-adr-0104-d2-declarative-update
Worktree: objectstack-issue-24393
Domain: domain:spec
Seat: domain:spec#2 (seat post #18549)
File surface (paths on origin/mainee882b845 or later; stop on a breach and explain it in the report):
docs/adr/0104-field-runtime-value-shape-contract.md: one dated note (or an amended status line plus the note) that re-trues the two pass-through sentences (:195–:196, :941–:942);
any ADR index or anchor file the ADR gates demand for that edit, by its own tool.
{
"issue": 24393,
"status": "done",
"branch": "claude/issue-24393-adr-0104-d2-declarative-update",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24416",
"session": "session_019oFY6Hi35J7atvgVFnmoaK — subagent, so the parent seat's id; the relay resolved the same id from the container's CLAUDE_CODE_REMOTE_SESSION_ID",
"premise_still_valid": true,
"summary": "ADR-0104 now carries a dated note under D2 item 1, a status-line entry and a one-line pointer at the 2026-07-30 addendum's restatement. Each says what origin/main holds after d79dc4883c: the param-less pass-through is scoped to actions with a handler; a declarative operation 'update' action's empty or absent params list is an empty accept set, refused through D2's path (same 400, same OS_ALLOW_LAX_ACTION_PARAMS hatch); and at the MCP run_action door a D2 refusal carries code + status, the ADR-0112 envelope, as at the REST door. P1-P3 were all measured true on ee882b845. Two precisions are recorded in the PR body: the pass-through discriminator is operation alone, so 'handler' covers script handlers, inline bodies, flow targets and undeclared actions; and P3 is held by two pins that meet at one seam (the runtime throw in action-declarative-update.test.ts, run_action's renderer in mcp-coded-refusal.test.ts), with no committed end-to-end pin, so the note cites both halves rather than claiming one. Placement (G2): one note at D2 item 1 plus a pointer at the second sentence; neither original sentence is rewritten. files_changed: docs/adr/0104-field-runtime-value-shape-contract.md (+41/-1). deviations: none. Notes: (1) no labels were written, because the dispatch named none (the definition's 'forbidden or empty set: zero writes and report' branch). Check Changeset will read red until the seat applies skip-changeset; the diff publishes nothing. (2) The harness asked for a model-named Co-Authored-By trailer and a different PR footer; the commits carry AGENTS.md's model-free pair and the body carries AGENTS.md's session-URL footer. (3) Acceptance note 1 records that D2 item 1 spells the code VALIDATION_FAILED while both doors answer VALIDATION_ERROR. It is documentation drift with no carrier, so it is left unedited and not filed.",
"tests": "No package touched (docs/adr only), so no build closure, no package test or typecheck, and no ablation is owed (docs are not ablated). test:repo not run locally: the diff edits none of its tests, so it is declared to CI. Union run after the final commit, at head 5823853f3 (git rev-parse --short HEAD after the merge of origin/main 97cb5c5a8): 19 of 19 derived commands exit 0. One build ran under the verify lock to satisfy a gate prerequisite: OS_VERIFY_LOCK_SLOT=issue-24393-dev os-verify-lock.sh -c 'pnpm exec turbo run build --concurrency=2 --filter=@objectstack/formula --filter=@objectstack/lint' gave 'Tasks: 4 successful, 4 total' and 'VERDICT command-exit 0', held 102s, waited 0s. Anchor proof: scripts/symbol-anchors.mjs extractAnchors reads all 8 new anchors (5 symbol, 3 file-level) out of the edited file, so check-adr-symbol-anchors' green judged them.",
"gates": "Local subset, each exit code captured before any pipe, at 5823853f3: node scripts/check-adr-links.mjs 0 ('✅ check-adr-links: 770 relative link destination(s) under docs/adr/ resolve'); --self-test 0; node scripts/check-adr-symbol-anchors.mjs 0 ('✅ check-adr-symbol-anchors: 2365 anchors across 142 records resolve ... 0 line anchors survive'); --self-test 0; check-ci-filter-parity 0; check-closing-keyword-parity 0 and --self-test 0; check-comment-mask-corpus 0; pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 (on pre-merge head 4ca7af7f9 it first exited 3, PREREQUISITE NOT MET, unbuilt formula and lint; 0 after the locked build); check:adr-anchors 0 ('check-adr-anchors: OK (75 anchored file(s) ...)'); check:cross-package-test-inputs 0; check:doc-authoring 0; check:driver-memory-census 0; check:gitlink-declared 0; check:nul-bytes 0; check:pm-governed-merges 0 (self-test only); check:pm-prior-rulings 0 (self-test only); check:refd-timer-probe 0; check:watch-hint-literal 0. Derivation: dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, change set from merge base ee882b845 = 1 path, 42 changed lines, 19 commands, the same set before and after the merge. Reconciliation: 'Run reconciliation — 19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 19 derived famil(ies) accounted for'. Control-byte self-scan of the ADR and PR body: zero hits. PR check-runs on 5823853f3 at report time, read once and not polled (22 runs): 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; in_progress: Governed Surface Queue Guard, Type Check · workspace / debt ledger / source gates / consumer gates, Lint · ESLint / PM dispatch-gates / docs & code guards / repo & tooling gates / CI, release & engine gates / scoped · tooling self-tests / scoped · query-options erasure / scoped · slot lookup & corpus, Check Changeset (expected red: no skip-changeset label, see summary), Check PR Size, Check Documentation Links, filter, and the claim-path, card-claims-branch and Part-of guards. Test Core and Build Core were not yet listed.",
"mcp_calls": "0 — none",
"api_writes": "3 endpoint writes in 2 fleet-write relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches from this container, executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24416/assignees (zhuangjianguo), run 39183166492, read-back 10670 bytes sent = stored; (2) comment = POST /repos/objectstack-ai/objectstack/issues/24393/comments (this report). git push x3 of the branch is not a REST write. Reads were single-card REST GETs.",
"open_questions": [],
"out_of_scope_findings": []
}
{
"issue": 24393,
"status": "done",
"branch": "claude/issue-24393-adr-0104-d2-declarative-update",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24416",
"session": "session_019oFY6Hi35J7atvgVFnmoaK — subagent, so the parent seat's id",
"premise_still_valid": true,
"summary": "Patch round 1 (REWORK 6257559771, from contract review 6257540342 ③ 2). The dated note under D2 item 1 now says what a D2 refusal is on main, at both doors and for every action: one 400 VALIDATION_ERROR whose message (Invalid action params: …) joins one issue per offending param, each naming it, with no fields[]. The REST door passes no code, so buildApiError derives it through standardErrorCodeForHttpStatus(400), and the MCP throw stamps that function's answer. The note also says that item 1's 'standard 400 VALIDATION_FAILED per-field envelope' names the wrong code and the wrong shape: VALIDATION_FAILED is the ADR-0112 ledger's record-level fields[] member, which the D2 gate never builds. The pin parenthetical names the four pinned assertions, and the closing line now reads 'No other decision in D2 changes: the code and shape above correct the record, they add no rule.' The original sentence stays verbatim. Every cited line was re-measured on the merged tree 485dc4362: action-declarative-update.test.ts:326 (REST, the declared-params triage_case), :359 and :376 (REST, the param-less close_case), :390-:391 (the MCP throw's code and status); action-execution.ts:1689-:1691 and :2542; domains/actions.ts:678; error-envelope.ts:120; errors.zod.ts:184; error-code-ledger.zod.ts:302. files_changed: docs/adr/0104-field-runtime-value-shape-contract.md (this round +24/-5 in fce8981f8; +60/-1 against merge base 71fa166e9). deviations: (1) the status-line entry stated no code, so the instruction did not require a change there. I added one sentence anyway ('A D2 refusal is one 400 VALIDATION_ERROR naming every offending param, with no fields[], at both doors, which corrects item 1's \"400 VALIDATION_FAILED per-field envelope\"'), so the status line announces the correction; dropping that sentence reverts it. (2) A container restart killed the first gate run (exit 137); all 19 gates were re-run on 485dc4362 after the restart, with check-comment-mask-corpus in its own call and the same command text in ran.list. pr_body_edits: 10 pairs. Each old_str occurs exactly once in the live body, which was re-read over REST at 485dc4362 and is byte-identical to the round-0 body (10670 bytes). Applied in order, they give a 12828-byte body: no less-than character, the only closing keyword is line 1's, and no control bytes. The body was not PATCHed.",
"tests": "No package touched (docs/adr only), so no build closure, no package test or typecheck, and no ablation is owed (docs are not ablated). test:repo is declared to CI. Gates were re-run on the final head 485dc4362 (git rev-parse --short HEAD after the merge of origin/main 71fa166e9), after the container restart: 19 of 19 exit 0. The gate prerequisite build ran under the verify lock: OS_VERIFY_LOCK_SLOT=issue-24393-dev os-verify-lock.sh -c 'pnpm exec turbo run build --concurrency=2 --filter=@objectstack/formula --filter=@objectstack/lint' gave 'Tasks: 4 successful, 4 total', 'Cached: 4 cached, 4 total' and 'VERDICT command-exit 0'. Anchor proof: scripts/symbol-anchors.mjs extractAnchors reads 11 anchors out of the note (7 symbol, 4 file-level; new this round: error-envelope.ts#buildApiError, errors.zod.ts#standardErrorCodeForHttpStatus, packages/types/src/validation-failure.ts). check-adr-symbol-anchors' total moved 2365 to 2368.",
"gates": "Local, at 485dc4362, each exit code captured before any pipe: check-adr-links 0 ('✅ check-adr-links: 770 relative link destination(s) under docs/adr/ resolve') and --self-test 0; check-adr-symbol-anchors 0 ('✅ check-adr-symbol-anchors: 2368 anchors across 142 records resolve … 0 line anchors survive') and --self-test 0; check:adr-anchors 0 ('check-adr-anchors: OK (75 anchored file(s) …)'); check:doc-authoring 0 ('✓ doc authoring guard: 423 files clean'); check-ci-filter-parity 0; check-closing-keyword-parity 0 and --self-test 0; check-comment-mask-corpus 0 ('9516 files, 0 disagree'); check:doc-formula-expressions 0; check:cross-package-test-inputs 0; check:driver-memory-census 0; check:gitlink-declared 0; check:nul-bytes 0; check:pm-governed-merges 0 (self-test only); check:pm-prior-rulings 0 (self-test only); check:refd-timer-probe 0; check:watch-hint-literal 0. Derivation: dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, at commit 485dc4362; change set 1 path vs merge base 71fa166e9, 61 changed lines; 19 commands, the same set as round 0. Reconciliation: 'Run reconciliation — 19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 19 derived famil(ies) accounted for'. CI on 485dc4362, read once at 2026-10-19T01:27:07Z (36 runs): 9 success (Auto Label, Dogfood Regression Gate, filter, the card-claims-branch, Part-of, single-writer-path and same-issue guards, Check PR Size, Check Documentation Links); 8 skipped (Build Core, Check Changeset (skip-changeset label present), Dogfood Verify CLI, Dogfood Regression Gate shards, Build Docs, Temporal Conformance, Console Pin Gate, Packed-tarball smoke); 19 in_progress (Test Core 1-6/6, Type Check · workspace / debt ledger / source gates / consumer gates, every Lint · job including docs & code guards and ESLint, Governed Surface Queue Guard). None failed.",
"mcp_calls": "0 — none",
"api_writes": "1 endpoint write this round, in 1 fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches from this container, executed as objectstack-fleet[bot]): comment = POST /repos/objectstack-ai/objectstack/issues/24393/comments (this report). Plus 1 git push of the branch (5823853f3..485dc4362), which is not a REST write. The PR body was not written; reads were single-resource REST GETs.",
"open_questions": [],
"out_of_scope_findings": [],
"pr_body_edits": [
{
"old_str": "Head: `5823853f3`.",
"new_str": "Patch round 1 (REWORK `6257559771`, from contract review `6257540342` ③ 2) added the correction commit `fce8981f8`, then merged `origin/main` `71fa166e9` (PR #24413, which touches no file the note cites). Head: `485dc4362`."
},
{
"old_str": "One file, `docs/adr/0104-field-runtime-value-shape-contract.md`, +41 / −1.",
"new_str": "One file, `docs/adr/0104-field-runtime-value-shape-contract.md`, +60 / −1."
},
{
"old_str": "**Placement (G2).**",
"new_str": "4. **Patch round 1: what a D2 refusal is on `main`.** D2 item 1's sentence just above the note says a refusal is \"the standard `400 VALIDATION_FAILED` per-field envelope\", and the note said \"the same 400\" right under it. The note now says what a D2 refusal is, at both doors and for every action: one `400 VALIDATION_ERROR` whose message (`Invalid action params: …`) joins one issue per offending param, each naming it, with no `fields[]`. The REST door's `deps.error(paramError, 400)` passes no code, so `buildApiError` (`packages/runtime/src/error-envelope.ts`) derives it through `standardErrorCodeForHttpStatus(400)`; the MCP throw stamps that function's answer. `VALIDATION_FAILED` is the ADR-0112 ledger's record-level validation member, which carries `fields[]` (`packages/types/src/validation-failure.ts`), and the D2 gate never builds it. Pins, re-measured on `485dc4362`: `packages/runtime/src/action-declarative-update.test.ts:326` (REST, a declared-params action), `:359` and `:376` (REST, the param-less `close_case`), `:390`–`:391` (the MCP throw's `code` and `status`). The status-line entry carries the same correction in one sentence. The original sentence stays verbatim (Prime Directive #13), and the note's closing line now reads \"No other decision in D2 changes: the code and shape above correct the record, they add no rule.\"\n\n**Placement (G2).**"
},
{
"old_str": "## Local gates, at head `5823853f3`",
"new_str": "## Local gates, at head `485dc4362`"
},
{
"old_str": "Its change set, taken from merge base `ee882b845`, is this one file with 42 changed lines. It derived 19 commands, the same set before and after the merge. All 19 exited 0 on the final head:",
"new_str": "Its change set, taken from merge base `71fa166e9`, is this one file with 61 changed lines. It derived 19 commands, the same set as in round 0. All 19 exited 0 on the patch-round-1 head, re-run after a container restart:"
},
{
"old_str": "`✅ check-adr-symbol-anchors: 2365 anchors across 142 records resolve … 0 line anchors survive …`",
"new_str": "`✅ check-adr-symbol-anchors: 2368 anchors across 142 records resolve … 0 line anchors survive …`"
},
{
"old_str": "reads the 8 new anchors (5 symbol, 3 file-level) out of the edited file",
"new_str": "reads the 11 new anchors (7 symbol, 4 file-level) out of the edited file"
},
{
"old_str": "- On the pre-merge head `4ca7af7f9`, `check:doc-formula-expressions` first exited 3",
"new_str": "- In patch round 1, `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` ran under the verify lock first (4 of 4 cached, `VERDICT command-exit 0`), and `check:doc-formula-expressions` then exited 0. In round 0, on the pre-merge head `4ca7af7f9`, `check:doc-formula-expressions` first exited 3"
},
{
"old_str": "1. **A code spelling D2 item 1 already gets wrong.** It says failures return \"the standard `400 VALIDATION_FAILED` per-field envelope\". The code both doors answer for a D2 refusal is `VALIDATION_ERROR`: `HttpStatusErrorCodeMap[400]`, which `standardErrorCodeForHttpStatus(400)` returns, as pinned in `action-declarative-update.test.ts`. That sentence predates this card and is not edited here. The note does not spell the code, so it adds no second, contradicting literal. Observation only (documentation drift), no carrier.",
"new_str": "1. **A code spelling D2 item 1 gets wrong, now corrected by the note (patch round 1, What changed item 4).** It says failures return \"the standard `400 VALIDATION_FAILED` per-field envelope\". A D2 refusal is one `400 VALIDATION_ERROR` naming every offending param, with no `fields[]`, at both doors. That sentence predates this card and stays verbatim (Prime Directive #13); the note and the status-line entry state what a D2 refusal is on `main`."
},
{
"old_str": "2026-07-30 附录里重复这条边界的那句话后面,加了一行指向这段说明的指引。原文一句都没删、没改。",
"new_str": "2026-07-30 附录里重复这条边界的那句话后面,加了一行指向这段说明的指引。第 1 轮修订在这段说明里又补了一处更正:D2 第 1 条原文说拒收时返回\"`400 VALIDATION_FAILED` 逐字段信封\",但实际上 REST 和 MCP 两个入口返回的都是 `400 VALIDATION_ERROR`,一条消息里点出每个出错的参数,没有 `fields[]`。`VALIDATION_FAILED` 是记录级校验用的错误码,带 `fields[]`,D2 从来不会返回这种形状。状态行也写上了这条更正。原文一句都没删、没改。"
}
]
}
approved by os-zhuang 2026-10-19T03:55Z on head 485dc43620. os-zhuang also marked it ready and enqueued it, so the seat's pr_ready + automerge_enable stroke (run 39197098282, note on the PR) found both already done. Merged 2026-10-19T04:40Z out of the merge queue, as merge commit 7e1ce9d945fcc941d781cbc72aade2ee08f360b4 on parent 2be3d7a5e6.
git diff 2be3d7a5e6 7e1ce9d945 and the reviewed head 485dc43620 over its merge base 71fa166e96 share git patch-id --stable8a0725a5. One file, docs/adr/0104-field-runtime-value-shape-contract.md, +60 / −1. Record chain: contract review PASS in two rounds, 6257540342 and 6257846085; seat record (maintainer brief and seat check), 6257859051.
queue branch
no gh-readonly-queue/main/pr-24416-* ref remains
mis-close scan
The body's only closing keyword is Fixes #24393. One other issue closed in the same second, #24306; GitHub names PR #24440 (another lane's) as what closed it, not this PR.
What now holds on main: ADR-0104 carries a dated note under D2 item 1:
the param-less pass-through is scoped to actions with a handler;
a D2 refusal is one 400 VALIDATION_ERROR naming every offending param, with no fields[], at both the REST and the MCP run_action door. This corrects item 1's "400 VALIDATION_FAILED per-field envelope", which stays verbatim.
The ADR's status line records the amendment, and the 2026-07-30 addendum's restatement points to the note.
Downstream: no open card carries Blocked-by: #24393.
State: the merge closed this card completed. pm:dispatched is removed in this act. Claim 6257175272 ends here.
Activity
objectstack-fleet commentedon Oct 18, 2026
Triage: p2 ·
documentation·domain:spec·pm:blockedon #24268 · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T20:54ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
:195–:196,:941–:942) is false for a declarativeoperation: 'update'action.domain:spec, the owner of the ADR's contract face. The ADR path is Tier H governed, so the PR needs the maintainer's approval. It is written as an amendment beside D2, not a rewrite.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Unblocked: #24268 landed as
d79dc4883c·pm:blocked→pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T22:05ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
d79dc4883c(landing note6255674746on runtime: an operation: 'update' action with no declared params writes any wire key into the row — the ledger says an undeclared param is refused (ADR-0104 D2); the console posts the patch as params, so both halves change together #24268). That commit is onorigin/main.origin/main.docs/adr/0104-field-runtime-value-shape-contract.md:195and:941still say that an action declaring noparamskeeps the pass-through.documentation,domain:spec. The ADR edit is the maintainer's to land by hand (6255294986), so the spec lane's PR waits for that merge rather than for the queue. TheBlocked-by:line stays as history.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 1 · 2026-10-19T00:36Z · ADR-0104 D2: a dated note that scopes the param-less pass-through to actions with a handler; an
operation: 'update'action's empty or absentparamslist is an empty accept set (PR #24392,d79dc4883c) (Tier H: the maintainer merges)Session:
session_019oFY6Hi35J7atvgVFnmoaK(the seat; the dev is anos-devsubagent of this session)Account:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24393-adr-0104-d2-declarative-updateWorktree:
objectstack-issue-24393Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface (paths on
origin/mainee882b845or later; stop on a breach and explain it in the report):docs/adr/0104-field-runtime-value-shape-contract.md: one dated note (or an amended status line plus the note) that re-trues the two pass-through sentences (:195–:196,:941–:942);Container & model:
S,mode:subagent,model: default tierfor the draft (dispatch-gates --tier: no path-derived mandate). The review before the maintainer reads it runs atCONTRACT_REVIEW_TIER(the rules face). Tier H: the PR stays draft until an authorised approval. The seat runs atbatch 3(「并发加到3」): this dev is the third slot, after spec(stack):forEachPageComponentnever visits apage:tabsitem'schildren, sodefineStack's page cross-reference checks (inline action targets, inline grid columns) do not judge anything placed inside tabs #24349's dev and Hook ctx.title is documented as part of every hook context, but @objectstack/verify bootStack runs app hooks without it: "ctx.title is not a function" under the test harness, while objectstack dev serves it #24289's cloud session.Clause-②: no
Responsibility: n/a — not a defect card (a record the landing work made untrue)
Thread-read: 6255723589
Serial constraints cleared:
docs/adr/0104-*. PR docs(adr): ADR-0095 D1 note — Layer 0 is inert on 22 of the 23 platform identity objects; Layer 1 silence is to mean the object's declared default row scope (#24164 ruling E′) #24408 edits ADR-0095 and ADR-0090 only.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24393, "status": "done", "branch": "claude/issue-24393-adr-0104-d2-declarative-update", "pr": "https://github.com/objectstack-ai/objectstack/pull/24416", "session": "session_019oFY6Hi35J7atvgVFnmoaK — subagent, so the parent seat's id; the relay resolved the same id from the container's CLAUDE_CODE_REMOTE_SESSION_ID", "premise_still_valid": true, "summary": "ADR-0104 now carries a dated note under D2 item 1, a status-line entry and a one-line pointer at the 2026-07-30 addendum's restatement. Each says what origin/main holds after d79dc4883c: the param-less pass-through is scoped to actions with a handler; a declarative operation 'update' action's empty or absent params list is an empty accept set, refused through D2's path (same 400, same OS_ALLOW_LAX_ACTION_PARAMS hatch); and at the MCP run_action door a D2 refusal carries code + status, the ADR-0112 envelope, as at the REST door. P1-P3 were all measured true on ee882b845. Two precisions are recorded in the PR body: the pass-through discriminator is operation alone, so 'handler' covers script handlers, inline bodies, flow targets and undeclared actions; and P3 is held by two pins that meet at one seam (the runtime throw in action-declarative-update.test.ts, run_action's renderer in mcp-coded-refusal.test.ts), with no committed end-to-end pin, so the note cites both halves rather than claiming one. Placement (G2): one note at D2 item 1 plus a pointer at the second sentence; neither original sentence is rewritten. files_changed: docs/adr/0104-field-runtime-value-shape-contract.md (+41/-1). deviations: none. Notes: (1) no labels were written, because the dispatch named none (the definition's 'forbidden or empty set: zero writes and report' branch). Check Changeset will read red until the seat applies skip-changeset; the diff publishes nothing. (2) The harness asked for a model-named Co-Authored-By trailer and a different PR footer; the commits carry AGENTS.md's model-free pair and the body carries AGENTS.md's session-URL footer. (3) Acceptance note 1 records that D2 item 1 spells the code VALIDATION_FAILED while both doors answer VALIDATION_ERROR. It is documentation drift with no carrier, so it is left unedited and not filed.", "tests": "No package touched (docs/adr only), so no build closure, no package test or typecheck, and no ablation is owed (docs are not ablated). test:repo not run locally: the diff edits none of its tests, so it is declared to CI. Union run after the final commit, at head 5823853f3 (git rev-parse --short HEAD after the merge of origin/main 97cb5c5a8): 19 of 19 derived commands exit 0. One build ran under the verify lock to satisfy a gate prerequisite: OS_VERIFY_LOCK_SLOT=issue-24393-dev os-verify-lock.sh -c 'pnpm exec turbo run build --concurrency=2 --filter=@objectstack/formula --filter=@objectstack/lint' gave 'Tasks: 4 successful, 4 total' and 'VERDICT command-exit 0', held 102s, waited 0s. Anchor proof: scripts/symbol-anchors.mjs extractAnchors reads all 8 new anchors (5 symbol, 3 file-level) out of the edited file, so check-adr-symbol-anchors' green judged them.", "gates": "Local subset, each exit code captured before any pipe, at 5823853f3: node scripts/check-adr-links.mjs 0 ('✅ check-adr-links: 770 relative link destination(s) under docs/adr/ resolve'); --self-test 0; node scripts/check-adr-symbol-anchors.mjs 0 ('✅ check-adr-symbol-anchors: 2365 anchors across 142 records resolve ... 0 line anchors survive'); --self-test 0; check-ci-filter-parity 0; check-closing-keyword-parity 0 and --self-test 0; check-comment-mask-corpus 0; pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 (on pre-merge head 4ca7af7f9 it first exited 3, PREREQUISITE NOT MET, unbuilt formula and lint; 0 after the locked build); check:adr-anchors 0 ('check-adr-anchors: OK (75 anchored file(s) ...)'); check:cross-package-test-inputs 0; check:doc-authoring 0; check:driver-memory-census 0; check:gitlink-declared 0; check:nul-bytes 0; check:pm-governed-merges 0 (self-test only); check:pm-prior-rulings 0 (self-test only); check:refd-timer-probe 0; check:watch-hint-literal 0. Derivation: dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, change set from merge base ee882b845 = 1 path, 42 changed lines, 19 commands, the same set before and after the merge. Reconciliation: 'Run reconciliation — 19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 19 derived famil(ies) accounted for'. Control-byte self-scan of the ADR and PR body: zero hits. PR check-runs on 5823853f3 at report time, read once and not polled (22 runs): 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; in_progress: Governed Surface Queue Guard, Type Check · workspace / debt ledger / source gates / consumer gates, Lint · ESLint / PM dispatch-gates / docs & code guards / repo & tooling gates / CI, release & engine gates / scoped · tooling self-tests / scoped · query-options erasure / scoped · slot lookup & corpus, Check Changeset (expected red: no skip-changeset label, see summary), Check PR Size, Check Documentation Links, filter, and the claim-path, card-claims-branch and Part-of guards. Test Core and Build Core were not yet listed.", "mcp_calls": "0 — none", "api_writes": "3 endpoint writes in 2 fleet-write relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches from this container, executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24416/assignees (zhuangjianguo), run 39183166492, read-back 10670 bytes sent = stored; (2) comment = POST /repos/objectstack-ai/objectstack/issues/24393/comments (this report). git push x3 of the branch is not a REST write. Reads were single-card REST GETs.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 24393, "status": "done", "branch": "claude/issue-24393-adr-0104-d2-declarative-update", "pr": "https://github.com/objectstack-ai/objectstack/pull/24416", "session": "session_019oFY6Hi35J7atvgVFnmoaK — subagent, so the parent seat's id", "premise_still_valid": true, "summary": "Patch round 1 (REWORK 6257559771, from contract review 6257540342 ③ 2). The dated note under D2 item 1 now says what a D2 refusal is on main, at both doors and for every action: one 400 VALIDATION_ERROR whose message (Invalid action params: …) joins one issue per offending param, each naming it, with no fields[]. The REST door passes no code, so buildApiError derives it through standardErrorCodeForHttpStatus(400), and the MCP throw stamps that function's answer. The note also says that item 1's 'standard 400 VALIDATION_FAILED per-field envelope' names the wrong code and the wrong shape: VALIDATION_FAILED is the ADR-0112 ledger's record-level fields[] member, which the D2 gate never builds. The pin parenthetical names the four pinned assertions, and the closing line now reads 'No other decision in D2 changes: the code and shape above correct the record, they add no rule.' The original sentence stays verbatim. Every cited line was re-measured on the merged tree 485dc4362: action-declarative-update.test.ts:326 (REST, the declared-params triage_case), :359 and :376 (REST, the param-less close_case), :390-:391 (the MCP throw's code and status); action-execution.ts:1689-:1691 and :2542; domains/actions.ts:678; error-envelope.ts:120; errors.zod.ts:184; error-code-ledger.zod.ts:302. files_changed: docs/adr/0104-field-runtime-value-shape-contract.md (this round +24/-5 in fce8981f8; +60/-1 against merge base 71fa166e9). deviations: (1) the status-line entry stated no code, so the instruction did not require a change there. I added one sentence anyway ('A D2 refusal is one 400 VALIDATION_ERROR naming every offending param, with no fields[], at both doors, which corrects item 1's \"400 VALIDATION_FAILED per-field envelope\"'), so the status line announces the correction; dropping that sentence reverts it. (2) A container restart killed the first gate run (exit 137); all 19 gates were re-run on 485dc4362 after the restart, with check-comment-mask-corpus in its own call and the same command text in ran.list. pr_body_edits: 10 pairs. Each old_str occurs exactly once in the live body, which was re-read over REST at 485dc4362 and is byte-identical to the round-0 body (10670 bytes). Applied in order, they give a 12828-byte body: no less-than character, the only closing keyword is line 1's, and no control bytes. The body was not PATCHed.", "tests": "No package touched (docs/adr only), so no build closure, no package test or typecheck, and no ablation is owed (docs are not ablated). test:repo is declared to CI. Gates were re-run on the final head 485dc4362 (git rev-parse --short HEAD after the merge of origin/main 71fa166e9), after the container restart: 19 of 19 exit 0. The gate prerequisite build ran under the verify lock: OS_VERIFY_LOCK_SLOT=issue-24393-dev os-verify-lock.sh -c 'pnpm exec turbo run build --concurrency=2 --filter=@objectstack/formula --filter=@objectstack/lint' gave 'Tasks: 4 successful, 4 total', 'Cached: 4 cached, 4 total' and 'VERDICT command-exit 0'. Anchor proof: scripts/symbol-anchors.mjs extractAnchors reads 11 anchors out of the note (7 symbol, 4 file-level; new this round: error-envelope.ts#buildApiError, errors.zod.ts#standardErrorCodeForHttpStatus, packages/types/src/validation-failure.ts). check-adr-symbol-anchors' total moved 2365 to 2368.", "gates": "Local, at 485dc4362, each exit code captured before any pipe: check-adr-links 0 ('✅ check-adr-links: 770 relative link destination(s) under docs/adr/ resolve') and --self-test 0; check-adr-symbol-anchors 0 ('✅ check-adr-symbol-anchors: 2368 anchors across 142 records resolve … 0 line anchors survive') and --self-test 0; check:adr-anchors 0 ('check-adr-anchors: OK (75 anchored file(s) …)'); check:doc-authoring 0 ('✓ doc authoring guard: 423 files clean'); check-ci-filter-parity 0; check-closing-keyword-parity 0 and --self-test 0; check-comment-mask-corpus 0 ('9516 files, 0 disagree'); check:doc-formula-expressions 0; check:cross-package-test-inputs 0; check:driver-memory-census 0; check:gitlink-declared 0; check:nul-bytes 0; check:pm-governed-merges 0 (self-test only); check:pm-prior-rulings 0 (self-test only); check:refd-timer-probe 0; check:watch-hint-literal 0. Derivation: dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, at commit 485dc4362; change set 1 path vs merge base 71fa166e9, 61 changed lines; 19 commands, the same set as round 0. Reconciliation: 'Run reconciliation — 19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 19 derived famil(ies) accounted for'. CI on 485dc4362, read once at 2026-10-19T01:27:07Z (36 runs): 9 success (Auto Label, Dogfood Regression Gate, filter, the card-claims-branch, Part-of, single-writer-path and same-issue guards, Check PR Size, Check Documentation Links); 8 skipped (Build Core, Check Changeset (skip-changeset label present), Dogfood Verify CLI, Dogfood Regression Gate shards, Build Docs, Temporal Conformance, Console Pin Gate, Packed-tarball smoke); 19 in_progress (Test Core 1-6/6, Type Check · workspace / debt ledger / source gates / consumer gates, every Lint · job including docs & code guards and ESLint, Governed Surface Queue Guard). None failed.", "mcp_calls": "0 — none", "api_writes": "1 endpoint write this round, in 1 fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches from this container, executed as objectstack-fleet[bot]): comment = POST /repos/objectstack-ai/objectstack/issues/24393/comments (this report). Plus 1 git push of the branch (5823853f3..485dc4362), which is not a REST write. The PR body was not written; reads were single-resource REST GETs.", "open_questions": [], "out_of_scope_findings": [], "pr_body_edits": [ { "old_str": "Head: `5823853f3`.", "new_str": "Patch round 1 (REWORK `6257559771`, from contract review `6257540342` ③ 2) added the correction commit `fce8981f8`, then merged `origin/main` `71fa166e9` (PR #24413, which touches no file the note cites). Head: `485dc4362`." }, { "old_str": "One file, `docs/adr/0104-field-runtime-value-shape-contract.md`, +41 / −1.", "new_str": "One file, `docs/adr/0104-field-runtime-value-shape-contract.md`, +60 / −1." }, { "old_str": "**Placement (G2).**", "new_str": "4. **Patch round 1: what a D2 refusal is on `main`.** D2 item 1's sentence just above the note says a refusal is \"the standard `400 VALIDATION_FAILED` per-field envelope\", and the note said \"the same 400\" right under it. The note now says what a D2 refusal is, at both doors and for every action: one `400 VALIDATION_ERROR` whose message (`Invalid action params: …`) joins one issue per offending param, each naming it, with no `fields[]`. The REST door's `deps.error(paramError, 400)` passes no code, so `buildApiError` (`packages/runtime/src/error-envelope.ts`) derives it through `standardErrorCodeForHttpStatus(400)`; the MCP throw stamps that function's answer. `VALIDATION_FAILED` is the ADR-0112 ledger's record-level validation member, which carries `fields[]` (`packages/types/src/validation-failure.ts`), and the D2 gate never builds it. Pins, re-measured on `485dc4362`: `packages/runtime/src/action-declarative-update.test.ts:326` (REST, a declared-params action), `:359` and `:376` (REST, the param-less `close_case`), `:390`–`:391` (the MCP throw's `code` and `status`). The status-line entry carries the same correction in one sentence. The original sentence stays verbatim (Prime Directive #13), and the note's closing line now reads \"No other decision in D2 changes: the code and shape above correct the record, they add no rule.\"\n\n**Placement (G2).**" }, { "old_str": "## Local gates, at head `5823853f3`", "new_str": "## Local gates, at head `485dc4362`" }, { "old_str": "Its change set, taken from merge base `ee882b845`, is this one file with 42 changed lines. It derived 19 commands, the same set before and after the merge. All 19 exited 0 on the final head:", "new_str": "Its change set, taken from merge base `71fa166e9`, is this one file with 61 changed lines. It derived 19 commands, the same set as in round 0. All 19 exited 0 on the patch-round-1 head, re-run after a container restart:" }, { "old_str": "`✅ check-adr-symbol-anchors: 2365 anchors across 142 records resolve … 0 line anchors survive …`", "new_str": "`✅ check-adr-symbol-anchors: 2368 anchors across 142 records resolve … 0 line anchors survive …`" }, { "old_str": "reads the 8 new anchors (5 symbol, 3 file-level) out of the edited file", "new_str": "reads the 11 new anchors (7 symbol, 4 file-level) out of the edited file" }, { "old_str": "- On the pre-merge head `4ca7af7f9`, `check:doc-formula-expressions` first exited 3", "new_str": "- In patch round 1, `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` ran under the verify lock first (4 of 4 cached, `VERDICT command-exit 0`), and `check:doc-formula-expressions` then exited 0. In round 0, on the pre-merge head `4ca7af7f9`, `check:doc-formula-expressions` first exited 3" }, { "old_str": "1. **A code spelling D2 item 1 already gets wrong.** It says failures return \"the standard `400 VALIDATION_FAILED` per-field envelope\". The code both doors answer for a D2 refusal is `VALIDATION_ERROR`: `HttpStatusErrorCodeMap[400]`, which `standardErrorCodeForHttpStatus(400)` returns, as pinned in `action-declarative-update.test.ts`. That sentence predates this card and is not edited here. The note does not spell the code, so it adds no second, contradicting literal. Observation only (documentation drift), no carrier.", "new_str": "1. **A code spelling D2 item 1 gets wrong, now corrected by the note (patch round 1, What changed item 4).** It says failures return \"the standard `400 VALIDATION_FAILED` per-field envelope\". A D2 refusal is one `400 VALIDATION_ERROR` naming every offending param, with no `fields[]`, at both doors. That sentence predates this card and stays verbatim (Prime Directive #13); the note and the status-line entry state what a D2 refusal is on `main`." }, { "old_str": "2026-07-30 附录里重复这条边界的那句话后面,加了一行指向这段说明的指引。原文一句都没删、没改。", "new_str": "2026-07-30 附录里重复这条边界的那句话后面,加了一行指向这段说明的指引。第 1 轮修订在这段说明里又补了一处更正:D2 第 1 条原文说拒收时返回\"`400 VALIDATION_FAILED` 逐字段信封\",但实际上 REST 和 MCP 两个入口返回的都是 `400 VALIDATION_ERROR`,一条消息里点出每个出错的参数,没有 `fields[]`。`VALIDATION_FAILED` 是记录级校验用的错误码,带 `fields[]`,D2 从来不会返回这种形状。状态行也写上了这条更正。原文一句都没删、没改。" } ] }Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
ACCEPT → PR #24416 comment 6257859051 ·
domain:specseat 2 · sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-19T01:43Z · contract review PASS in two rounds (6257540342,6257846085) · Tier H: awaiting an approval byos-zhuangorhotlong(both requested), or the maintainer's merge.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Landed · PR #24416 →
7e1ce9d945·domain:specseat 2 ·zhuangjianguo· sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-19T04:41Zos-zhuang2026-10-19T03:55Z on head485dc43620.os-zhuangalso marked it ready and enqueued it, so the seat'spr_ready+automerge_enablestroke (run 39197098282, note on the PR) found both already done. Merged 2026-10-19T04:40Z out of the merge queue, as merge commit7e1ce9d945fcc941d781cbc72aade2ee08f360b4on parent2be3d7a5e6.origin/maingit merge-base --is-ancestor 7e1ce9d945 origin/main→ exit 0git diff 2be3d7a5e6 7e1ce9d945and the reviewed head485dc43620over its merge base71fa166e96sharegit patch-id --stable8a0725a5. One file,docs/adr/0104-field-runtime-value-shape-contract.md, +60 / −1. Record chain: contract review PASS in two rounds,6257540342and6257846085; seat record (maintainer brief and seat check),6257859051.gh-readonly-queue/main/pr-24416-*ref remainsFixes #24393. One other issue closed in the same second, #24306; GitHub names PR #24440 (another lane's) as what closed it, not this PR.What now holds on
main: ADR-0104 carries a dated note under D2 item 1:operation: 'update'action's empty or absentparamslist is an empty accept set (runtime: an operation: 'update' action with no declared params writes any wire key into the row — the ledger says an undeclared param is refused (ADR-0104 D2); the console posts the patch as params, so both halves change together #24268, PR fix(runtime)!: a param-less declarative update refuses undeclared wire params at both action doors #24392);400 VALIDATION_ERRORnaming every offending param, with nofields[], at both the REST and the MCPrun_actiondoor. This corrects item 1's "400 VALIDATION_FAILED per-field envelope", which stays verbatim.The ADR's status line records the amendment, and the 2026-07-30 addendum's restatement points to the note.
Downstream: no open card carries
Blocked-by: #24393.State: the merge closed this card
completed.pm:dispatchedis removed in this act. Claim6257175272ends here.Generated by Claude Code