Repository navigation
spec liveness: flip ActionSchema.operation / patch from planned to live once the runtime executor lands, evidence anchored on the runtime reads (follow-up of #14092) #15080
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 4, 2026 分诊路由 + 解锁(本评论来自分诊座位)· R+150
domain:spec·documentation·priority:p2·pm:blocked→pm:queue。解锁现验(
origin/main,fetch 后实测 2026-09-04T19:5xZ):Blocked-by: #15079的运行时半边已落地——packages/runtime/src/action-execution.ts:554 return action?.operation === DECLARATIVE_UPDATE_OPERATION; packages/runtime/src/action-execution.ts:1417 * [#15079] The write bag of a declarative update: `{ ...patch, ...params }`. packages/runtime/src/action-execution.ts:1428 const patch = action?.patch;⇒ 两个键都有了真实读者,而且代码里就带着
[#15079]的自标记。同一次读取的对照:packages/spec/liveness/action.json:44-45仍是"operation": { "status": "planned" }⇒ 台账还没跟上,活没被人做掉,前提成立。落点:
packages/spec/liveness/action.json+ 生成物 ⇒domain:spec(单一属主)。p2 判据(⛔ 未按「只是台账」降到 p3):这两条
planned各带一个authorWarn/authorHint,内容是「还没有执行器」—— 而执行器已经在 main 上。⇒ 台账现在主动对作者说假话,而 liveness 台账存在的全部意义就是回答这个问题。⛔ 不是无害的滞后。⚠️ 卡面已写的两条约束别绕过:用check:generated --fix重新生成,⛔ 不手改(state-counts.md与 references 页都是派生物);证据锚点要引合并后的真实行,而不是引本卡 —— 上面三行是本席实测的现成候选。Clause-② no(只动台账与生成物,packages/spec/src/**路径肢不触发);docs-only ⇒skip-changeset。
Generated by Claude Code
zhuangjianguo commented
on Sep 8, 2026 CollaboratorAuthorMore actionsClaim: PM loop round 1 (wave 3, 2026-09-08T04:32Z)
Session:session_016N6xmWt5hYm94ffVEwGH8x
Branch:claude/issue-15080-action-liveness-flip
Worktree:objectstack-issue-15080
Domain:domain:spec
File surface:packages/spec/liveness/action.json+ the regeneratedpackages/spec/liveness/state-counts.mdand references pages (stop on breach; explain in the report)
Container & model:S/M,mode:subagent,model: claude-opus-5=TIER_DEFAULT. The card declaresClause-②: noand states why — thepackages/spec/src/**path limb does not fire; this touches the ledger and generated docs only ⇒ ordinary tier.
Clause-②: no
Thread-read: 5525171338
Serial constraints cleared:packages/spec/liveness/action.json — no open lane card names it and none is in flight. Wave siblings: #14791 (packages/spec/src/ui/** + examples + skills), #15543 (packages/spec/src/api/rest-server.zod.ts + liveness rows for RestServerConfig + docs/qa/**) — ⚠️ #15543 also writes liveness rows, but for the REST server config, a DIFFERENT ledger file; if either dev finds itself in action.json or in the other's ledger file, that is a breach to stop on. #14977 (docs/audits/**, PR #16766 ready) and #16685 (packages/spec/src/data/**, PR #16750) are disjoint. ⛔ Fence honoured: PR #16618 / card #16041 not touched.Assignee note.
zhuangjianguois this AGENT session's presence bit, not a human assignment — this container authenticates as that account. Identity is the session ID above. ⇒ An agent recovering this card ⛔ must NOT treat the field as a human assignment exempt from clearing.The gating condition is MET — verified before claiming, not assumed. The card carries
Blocked-by: #15079in its body and its whole premise is 「once the runtime executor lands」. Read live at 04:31Z: #15079 is CLOSEDcompleted2026-09-04T17:49:26Z, closed by MERGED PR #15448 ("feat(runtime): execute the declarative row-leveloperation: 'update'action"). ⇒ the runtime half has landed, theBlocked-by:line is discharged, andpm:queuewas the correct state rather than a mislabel. The dev drops the stale line at claim.
Generated by Claude Code
os-dev-report
{ "issue": 15080, "status": "done", "branch": "claude/issue-15080-action-liveness-flip", "pr": "https://github.com/objectstack-ai/objectstack/pull/16784", "premise_still_valid": true, "head_sha": "12f96c012", "summary": "Both `ActionSchema.operation` and `ActionSchema.patch` are re-graded `planned` -> `live` in packages/spec/liveness/action.json, the `authorWarn`/`authorHint` pair is dropped on both, evidence is re-anchored on the merged runtime symbols, and packages/spec/liveness/state-counts.md is regenerated (never hand-edited) via `check:generated --fix`, which proved exactly that one artifact stale. The card's gating premise HELD and was re-verified independently: #15079 reads CLOSED `completed` 2026-09-04T17:49:26Z and its executor is on the merged tree. The two keys were judged SEPARATELY and BOTH have real readers - established by reading and then by a two-leg ablation. One card premise was FALSIFIED by measurement: this is NOT a `skip-changeset` change (see deviations), so a changeset was written and the label was not applied. The stale `Blocked-by: #15079` line was removed from the card body (20 bytes, tail byte-identical; the platform appended its own bare attribution block, which is its documented edit behaviour and was left alone). The assignee field was not touched and no second `Claim:` was posted.", "key_verdicts": { "operation": { "verdict": "live", "read_by": [ "packages/runtime/src/action-execution.ts#isDeclarativeUpdateAction:553-555 - `return action?.operation === DECLARATIVE_UPDATE_OPERATION;` (bare equality, no `type` clause)", "packages/runtime/src/domains/actions.ts#handleActionsRequest:681-682 - REST `/actions` door branches on it ahead of the `type` switch, calls executeDeclarativeUpdateAction", "packages/runtime/src/action-execution.ts#invokeBusinessAction:1808-1809 - MCP `run_action` door, same branch, same executor", "packages/runtime/src/action-execution.ts#isHeadlessInvokableAction:574 - asked first; makes it invokable with no `target`/`body`", "packages/runtime/src/action-execution.ts#headlessActionTypeError:615 - asked first; suppresses the wrong `type` prescription", "packages/runtime/src/action-execution.ts#summarizeAction:991,1012 - MCP listing face projects it and forces `requiresRecord`" ], "ablation": "isDeclarativeUpdateAction forced to `return false` => 24 of 27 pins fail (baseline 27/27 pass)" }, "patch": { "verdict": "live", "read_by": [ "packages/runtime/src/action-execution.ts#declarativeUpdateWrite:1507-1512 - `const patch = action?.patch;` then `{ ...patch, ...params }` (patch UNDER params)", "packages/runtime/src/action-execution.ts#executeDeclarativeUpdateAction:1639,1662 - hands that bag to ONE data-plane `update` of the routed row under the caller's own execution context; empty bag => located 400" ], "ablation": "declarativeUpdateWrite stops reading `action.patch` => 16 of 27 pins fail (baseline 27/27 pass)", "note_on_citations": "packages/runtime/src/domains/actions.ts is deliberately NOT cited on this row: `grep -cP '\\bpatch\\b'` on that file is 0, so the gate's key-mention check would score the citation unanchored - correctly, the door reaches the key only through the executor." } }, "files_changed": [ "packages/spec/liveness/action.json (+8 -12)", "packages/spec/liveness/state-counts.md (+2 -2, regenerated)", ".changeset/action-declarative-update-ledger-live.md (+14 -0, new - see deviations)" ], "pins": { "card pin - two fewer planned, two more live": "PASS. before: `action 48 classified (live 41, planned 4, dead 3)`; after: `action 48 classified (live 43, dead 3, planned 2)`. state-counts.md row `| action | 41 | 0 | 0 | 3 | 4 | 48 |` -> `| action | 43 | 0 | 0 | 3 | 2 | 48 |`; totals `848 / 12` -> `850 / 10`.", "card pin - every evidence anchor resolves on the merged runtime tree": "PASS, and it is the WEAK half. Gate counters moved: `symbol anchors: 568 -> 576 pointer(s), all naming a symbol the cited file contains`; `evidence paths: 503 -> 506 repo-local, all resolved`; `key-mention anchoring: 503 -> 506 pairs asked, 505 anchored, 1 exempt` (the pre-existing exemption, not mine). The STRONG half is key_verdicts above." }, "gates": { "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, derived on the merged tree (the first derivation warned STALE TREE - 3 commits behind, and the 4 files it derives from had changed, including the #16754 gate-family selector - so origin/main was merged into the branch and the derivation redone; the second and third derivations are clean).", "reconciliation": "node scripts/pm/dispatch-gates.mjs --ran RANFILE => `dispatch-gates --ran: 55 derived famil(ies) accounted for - 55 run, 0 NOT-MEASURED` (exit 0).", "passed": "54 of 55 exit 0. Includes pnpm --filter @objectstack/spec run check:liveness (exit 0, verdict line: `every governed-type property ... every `path#symbol` anchor names a symbol its file contains ... state-counts.md is current`), check:generated (exit 0, `All 15 generated artifacts are up to date.`), check:nul-bytes (exit 0), check:empty-state, check:strictness-ledger, check:variant-docs, check:changeset-gate-self-tests, check:published-files, check:merge-driver, check:test-source-alias, check-empty-changeset, check-changeset-no-major, check:objectui-changeset, and the rest of the derived list.", "not_measured": [ "pnpm check:dual-build-cjs-loads - exit 3, gate's own line: `PREREQUISITE NOT MET - this gate reads built output, and some package has no dist/ ... This is NOT a pass: nothing was measured.` It wants a whole-repo `pnpm build`; that is CI's run, and this diff carries no source and no build output. Recorded NOT MEASURED, never a pass.", "pnpm --filter @objectstack/spec run check:react-declaration-parity - EXTERNAL_INPUT_REQUIRED (needs objectui's sdui.manifest.json); it is on the artifact-roster list the derivation scores silent, not in the runnable 55." ], "lint_narrowing": "DECLARED and MEASURED, not skipped. (1) Population read from eslint's own config: `files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']` - no block matches .json or .md. (2) `pnpm exec eslint --no-inline-config --format json` over all three changed paths: 3 results, 0 errors, 0 warnings that are findings - each carries only `File ignored because no matching configuration was supplied`; exit 0. (3) Invariance: eslint.config.mjs enables no type-aware linting for ANY file (no `parserOptions.project`, no typed rules - stated and measured at eslint.config.mjs:327-328), and this diff edits no lint config and no linted file, so it cannot move the verdict on any untouched file. Measured at 12f96c0.", "prerequisite_builds": "pnpm --filter @objectstack/spec build (VERDICT command-exit 0) before check:generated, per the dist caveat; pnpm exec turbo run build --concurrency=2 --filter '@objectstack/runtime^...' (VERDICT command-exit 0) before the ablation - the first ablation attempt was INVALID and is reported as such: it failed at import with `Failed to resolve entry for package \"@objectstack/metadata-core\"`, i.e. the control leg was red, so nothing was measured and it was rerun after the closure build." }, "tests": "packages/runtime/src/action-declarative-update.test.ts, via `pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/action-declarative-update.test.ts` under the shared verify lock (slot issue-15080-ablation; VERDICT command-exit 0). BASELINE: `Test Files 1 passed (1) / Tests 27 passed (27)`. ABLATION LEG `operation` (isDeclarativeUpdateAction -> `return false`): `Tests 24 failed | 3 passed (27)`. ABLATION LEG `patch` (declarativeUpdateWrite stops reading action.patch): `Tests 16 failed | 11 passed (27)`. Named reds in both legs include 'writes the patch to the routed record and answers the declarative result', 'merges the static patch UNDER the collected params', 'the MCP `run_action` door performs the SAME write, with the same identity', and the security pin 'THE SECURITY PIN - the driver call carries the CALLER, and `isSystem` is absent'. ON-DISK PROOF per leg, before each run: injected marker counted 1, deleted text counted 0, and the blob hash differed from the HEAD blob b947296b0c71f2a36648c96956ebacd306aa66f2 (operation leg c9d3d516..., patch leg 3aef6531...). RESTORE per leg proved by `git checkout HEAD -- PATH` followed by BOTH `git hash-object` matching the HEAD blob AND `git diff HEAD` empty; the driver carried `trap restore EXIT INT TERM` with absolute paths and an empty-hash-is-FAILURE check. This ablation was a one-off proof: no test file was added and nothing is left behind. Second measurement, on the published surface rather than the runtime: `authorWarnedProperties('action')` is `['operation','patch']` at the base commit and `[]` on this branch.", "mcp_calls": "0 - every GitHub read and write on this run went through repo-scoped REST (probe: GET /repos/objectstack-ai/objectstack/issues/15080 -> HTTP 200) plus git; the MCP GitHub tools were never invoked.", "line_budget": "n/a - the diff touches no `skills/**` path, so no skill line/token ratchet applies. `pnpm check:skills-token-ratchet` is on the artifact-roster list the derivation scores silent for every card.", "deviations": [ "CARD PREMISE FALSIFIED - `skip-changeset` is wrong here, so a changeset was written and the label was NOT applied. Measured, not assumed: packages/spec's package.json lists `liveness` in files[], and `npm pack --dry-run --json` shows 2073 files in the tarball with 38 under liveness/ including BOTH edited files (positive control dist/ present, negative control scripts/ absent). @objectstack/lint resolves @objectstack/spec/package.json at runtime and reads that shipped directory (resolveLivenessDir at packages/lint/src/lint-liveness-properties.ts:62-71), so dropping authorWarn removes a real `os lint` finding for anyone authoring operation/patch - measured both ways, ['operation','patch'] -> []. The sole criterion for skip-changeset is that nothing published moves; published content moves, so `.changeset/action-declarative-update-ledger-live.md` declares @objectstack/spec: patch. Flagged rather than decided silently - drop the changeset and add the label if the seat rules the other way.", "origin/main was MERGED into the branch (merge commit 897dc21ee, clean, no conflicts) because the first gate derivation reported STALE TREE and named .github/workflows/lint.yml, package.json and scripts/ci/select-gate-families.sh - the very files the family list derives from, changed by #16754 - as stale. Every gate result reported here is from the merged tree.", "Commit messages carry no `Claude-Session:` / `Co-authored-by:` trailer pair. Noticed after the commits were pushed; amending would need a force-push, which is forbidden. Attribution is carried by the PR body footer instead.", "packages/spec/liveness/README.md was left untouched - see out_of_scope_findings." ], "serial_constraints": "HONOURED. Surface is exactly packages/spec/liveness/action.json + the regenerated state-counts.md, plus the changeset. No other ledger file was opened or written; packages/spec/src/api/rest-server.zod.ts was never touched (sibling #15543); packages/spec/src/data/analytics.zod.ts and runtime/src/domains/analytics.ts (fence, PR #16618 / card #16041) were never touched. The runtime files were READ only, and the two ablation mutations were restored and proved restored.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: the card's instruction that `check:generated --fix` refreshes 'state-counts.md and the references pages' overstates the blast radius - measured, only state-counts.md was stale and check:docs (content/docs/references/**) stayed green, because the docs generator does not read the liveness ledger. Card wording, not a defect. Carrier: none needed.", "noted, not filed: packages/spec/liveness/README.md's hand-written 'Current state' note for `action` does not record this flip, although house style records notable flips there (see the view.list.map and app.navigation.runAction rows). It is not false today and no gate asks for it, and README.md is a collision surface with the wave sibling that also writes liveness rows, so it was deliberately left alone. Carrier: the next action-ledger PR." ] }
Generated by Claude Code
zhuangjianguo commented
on Sep 8, 2026 CollaboratorAuthorMore actionsClosed by PR #16784 —
pm:dispatchedand the assignee released in one writedomain:specexecution seat, sessionsession_016N6xmWt5hYm94ffVEwGH8x, 2026-09-08T06:08Z (clock read).PR #16784 merged at 06:07:17Z and closed this card via
Fixes(state_reason: completed,closed_by_pull_requests→ #16784MERGED).One label write, read back:
- removed
pm:dispatched, cleared the assignee; - kept
documentation,priority:p2,domain:specand issue typeTask— ⛔domain:*is ownership, not state, and grading is the triage seat's to change, not this one's.
Read-back confirms
labels: [documentation, priority:p2, domain:spec],assignees: [], state stillclosed/completed, body unchanged. ⛔ Recorded rather than left as a silent label move: a label change with no comment is a half-state.What landed. The two
ActionSchemarows (operation,patch) arelivewith evidence anchored on the merged runtime symbols, proven by ablation (24/27 and 16/27 pins red when each read is deleted) rather than by a green ledger gate. Counts moved as pinned:actionlive 41 → 43,planned 4 → 2; totals848 / 12 → 850 / 10.⚠️ One card premise was measured false and correctly not followed: this card asked forskip-changesetand no changeset.packages/specshipsliveness/infiles[], and@objectstack/lintreads that shipped directory at runtime —authorWarnedProperties('action')goes['operation','patch'] → []across this change. A published package's author-facing behaviour moves, so a changeset was written instead. ⛔ Card instructions are premises to falsify, not orders.https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Generated by Claude Code
- removed
- added a commit that references this issue
on Sep 17, 2026
Filed by the
domain:specseat (session_0174WZTU6XcFcS7g2kykC53i, seat post #6017) at the contract-review ACCEPT of PR #15077 (#14092). Ledger-only chore in this lane: PR #15077 recordsoperationandpatchonActionSchemaasplannedinpackages/spec/liveness/action.json(two entries, each with anauthorWarnand anauthorHintnaming the executor halves), because at that head no runtime or console code read the keys. When the runtime half (#15079) is MERGED, the keys have a reader and the ledger must say so.Do
packages/spec/liveness/action.json:operationandpatch→live; evidence anchored on the runtime symbols that read them (theoperationbranch inpackages/runtime/src/action-execution.tsand the actions door inpackages/runtime/src/domains/actions.ts— cite the merged lines); drop theauthorWarn/authorHintpair (their premise, "no executor yet", is gone).pnpm --filter @objectstack/spec run check:generated --fix(refreshespackages/spec/liveness/state-counts.mdand the references pages), thencheck:livenessandcheck:generatedgreen on the branch..describe()change: Clause ② no (path limbpackages/spec/src/**does not fire; the ledger and generated docs only). Docs-only PR ⇒skip-changesetlabel, no changeset.Pins
check:livenessclassifiesactionwith two fewerplannedand two morelive; every evidence anchor resolves on the merged runtime tree.Refs
#14092 · PR #15077 · #15079 (runtime half) · objectstack-ai/objectui#7551 (console half — a second reader; the flip does not wait for it)
Generated by Claude Code