Skip to content

chore(spec): re-classify the action.execute liveness entry dead (#13036) - #13045

Merged
os-trump merged 1 commit into
mainfrom
claude/issue-13036-action-execute-dead
Aug 29, 2026
Merged

chore(spec): re-classify the action.execute liveness entry dead (#13036)#13045
os-trump merged 1 commit into
mainfrom
claude/issue-13036-action-execute-dead

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13036

The action.execute liveness entry was ledgered live on a .transform that protocol 17 removed. This flips the verdict to dead with the row STAYING, re-points the pointer at the tombstone, preserves the #3713 history the card asked to keep, and stamps verifiedAt. Data only — no schema, no runtime, no authoring surface moves: authoring execute already failed tsc and the parse before this change, and still does.

Premise, re-measured on the base 8cb96ec41

The card's claim holds in every particular.

the entry claimedthe tree says
"the .transform lowers execute → target and DROPS the alias"packages/spec/src/ui/action.zod.ts has exactly two.transform calls, :526 and :1668, and both are lowerRequiresFeature. No transform touches the key.
the key is liveaction.zod.ts:1040 declares execute: retiredKey('...was removed in @objectstack/spec 17 — use target...'), and its docblock reads [REMOVED in protocol 17 — #3855] The deprecated alias of target. It types the key never.
a consumer reads itpackages/cli/src/utils/lower-callables.ts#lowerActionCallabledeclines on purpose: "Only a callable target is lowered. A function on the removed execute alias is deliberately left in place so the parse rejects it by name." packages/runtime/src/action-execution.ts:525 gates on action?.target || action?.body.

The tombstone landed 2026-07-28 in acbf364a0, "feat(spec)!: retire the last three deprecated authorable aliases (#3855)" (PR #3883) — dated from git, not recalled.

:581 is in range in an 1802-line file that names the key, which is why existence, line bound and key-mention were all green on it. What :581 holds today is a comment about the global_nav enum-VALUE retirement's declaration style.

The disposition, and the precedent it follows

.claude/skills/spec-property-retirement/SKILL.md §2 splits the ledger discipline by removal route, and the two directions fail in opposite ways: deleting a tombstone key's row reports UNCLASSIFIED; keeping a strict-removed key's row reports ORPHAN. execute is the retiredKey() route — retiredKey is z.never().optional(), still a property in the walked shape — so the row stays and the verdict changes. That is the rls.priority precedent both same-file siblings already cite.

Before

"execute": {
"status": "live",
"evidence": "packages/spec/src/ui/action.zod.ts:581 — the .transform lowers execute -> target and DROPS the alias, ...",
"note": "RE-VERIFIED 2026-07 (#3686 preview-claim sweep): ... DIVERGENCE RESOLVED in #3713: ..."
}

Afterstatus, verifiedAt, note, and no evidence key.

Why no evidence key: the sibling style, measured rather than assumed

The dispatch asked me to re-point the evidence "in the sibling rows' style (read how shortcut/bulkEnabled cite theirs and match it)". Read, and they carry no evidence field at all — the tombstone pointer lives in the note prose. That is the house style, not a two-row accident: across all 33 dead entries in the 31 ledgers, 30 carry no evidence, and the 3 that do carry bare prose ("no reader either layer"), never a path citation.

It is also the correct shape for a second reason the counters below make checkable: the walk reads evidenceonly when status === 'live' (check-liveness.mts:588). An evidence pointer on a dead row is scanned by nothing — no existence check, no line bound, no anchor resolution, no key-mention. Writing a path#symbol anchor there would have planted a citation that no gate can ever falsify, which is the exact failure class this card is about. So the pointer is written in the note, in path#symbol grammar, beside prose that says what it is:

  • packages/spec/src/ui/action.zod.ts#execute — the tombstone
  • packages/cli/src/utils/lower-callables.ts#lowerActionCallable — the deliberate refusal

Both resolve as identifier-bounded words in their files (5 and 3 occurrences). This is a declared deviation from the dispatch's literal wording, resolved in favour of its own instruction to read the siblings and match them. Flagged here rather than chosen quietly.

The #3713 history is preserved verbatim in substance — three readers disagreeing in two directions, the parse keeping target, objectui ActionRunner doing execute || target, the CLI preferring a function on execute — because, as the card says, it is the argument for target being the single handler slot.

The counter: what moved, and what the number actually reads

The dispatch predicted 2 → 1. The observable reading is 82 → 81, and the reason is benign: PR #13044 (batch 4, the run that takes the counter to 2) has NOT landed. It is still a draft, mergeable_state: blocked; origin/main is 8cb96ec41, re-fetched at the start of this run and again before pushing. The 2 → 1 arithmetic is the post-batch-4 statement of the same one-citation movement, not a second fact.

Quoted from the gate itself, exit code captured by redirect-then-read before any pipe:

base 8cb96ec41 line citations: 82 pointer(s) written `path:NNN`, 82 inside the cited file.
head cc4c52948 line citations: 81 pointer(s) written `path:NNN`, 81 inside the cited file.

The one that left is proven by identity, not by subtraction — the gate's own scanEvidence run over both revisions of this entry:

--- BASE 8cb96ec41 ---
status: "live" has evidence key: true
citations scanned from evidence: [ 'packages/spec/src/ui/action.zod.ts:581' ]
--- HEAD ---
status: "dead" verifiedAt: "2026-08-29" has evidence key: false
citations scanned from evidence: (none — no evidence field)

That singleton is exactly the action.json entry PR #13044 lists as HELD. With batch 4 landed, the remaining line citation is field/conditionalRequired and the counter reads 1 — which is #13043's to retire, not this branch's.

The same probe measures the "notes are not scanned" claim instead of asserting it. The new note contains 2 line-shaped pointers and 2 anchor-shaped pointers; if the walk read notes, the counters would have gone up:

 [note text, NOT read by the walk] would-be citations: [ 'packages/spec/src/ui/action.zod.ts:581',
'packages/runtime/src/action-execution.ts:525' ]
[note text, NOT read by the walk] would-be anchors : [ 'packages/spec/src/ui/action.zod.ts#execute',
'packages/cli/src/utils/lower-callables.ts#lowerActionCallable' ]

Observed: citations 82 → 81, symbol anchors 383 → 383 (unchanged). The walk reads evidence and producer only, measured.

The other counters move consistently, all from this one entry leaving the live population: evidence paths 464 → 463, key-mention pairs asked 464 → 463 / anchored 463 → 462 / exempt 1 → 1. No baseline row is orphaned: key-mention.baseline.json holds one exemption, email_template/fromOverride, untouched.

The floor files are untouched, by design

packages/spec/scripts/ has zero diff against the merge base — verified as a command, not as an intention:

$ git diff --stat 8cb96ec41...HEAD -- packages/spec/scripts/
(no output)

Both floors sit at toBeGreaterThan(0) per ruling comment 5458356183, and both guard comments instruct that the assertion is deleted in the same PR that retires the last line citation. At 81 — and at 1 after batch 4 — the population is not zero, so the floors stay and this branch does not touch them. Their suite is green at this head: pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 scripts/liveness/10 test files, 254 tests passed, VERDICT command-exit 0 from the shared verify lock.

This body deliberately carries no closing keyword for the #13003 worklist; that card stays open, and machine-scanning for one is part of the checklist below.

File surface: 4 files, and why it is not 2

The claim declared action.json plus a changeset. Two more files came along, both as consequences of the flip rather than as scope:

  1. packages/spec/liveness/state-counts.md — a generated artifact the gate demands.check:liveness went EXIT=1 on the edit alone: "packages/spec/liveness/state-counts.md is STALE ... The count columns are GENERATED (spec(liveness): 9 rows of the README state table have count columns that disagree with the gate's --json report #7377). Regenerate them, wholesale: pnpm --filter @objectstack/spec gen:liveness-counts". Regenerated with that command, never by hand. The whole diff is the movement predicted: action row 42 0 2 2 4641 0 3 2 46, total 821 → 820 live and 78 → 79 dead. One property changed verdict; none entered or left the walked shape.

  2. packages/spec/liveness/README.md — a sentence this change makes false. The same gate output orders the re-read: "A count that moved means a property entered or left the walked shape, or a ledger verdict changed — and the Notes cell beside that row in README.md may now describe a set it no longer has." It did. The action notes cell ended shortcut + bulkEnabled ... and they are still the whole dead set — true until this commit, false after it. That is the spec(liveness): 9 rows of the README state table have count columns that disagree with the gate's --json report #7377 shape the guidance names in its own next sentence. Corrected to record the third member and why the row stays. A second, smaller touch: the 2026-07 [P2] AI scope keys declared but unenforced: skill.permissions + agent.knowledge (enforce-or-prune) #3686 tally lists action.execute under "live, evidence corrected to the real reader"; that tally is a dated historical record and stays as written, with a footnote marker noting the key did not survive — the same treatment the file already gives skill.permissions one line below.

Nothing else: no other ledger .json, no packages/spec/src/**, no check-liveness.mts, no test file, no ADR-0087 registry (the conversion action-execute-to-target and its migration row have been in place since protocol 17 — this is a ledger correction, not a retirement).

.changeset/action-execute-ledger-dead.md is a patch on @objectstack/spec: liveness/ is named in that package's files array, so these ledgers ship in the npm tarball and this is published data.

Gates

Family derived at the committed head, node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, no paths passed — 4 paths vs merge base 8cb96ec41, 25 families matched, no convention trigger fired (this diff adds and edits no test file, no gate script, no root-program TypeScript, no error code, and touches no skills/**). Exit codes captured by redirect-then-read, before any pipe.

23 green:check:changeset-gate-self-tests · @objectstack/spec check:empty-state · @objectstack/spec check:liveness · check:merge-driver · check:objectui-changeset · check:page-declaration-shape · check:pm-half-states · check:published-files · check:slot-lookup · @objectstack/spec check:strictness-ledger · check:test-source-alias · check:type-source-resolution · @objectstack/spec check:variant-docs · check-adr-0087-registration.mjs · check-changeset-no-major.mjs · check-comment-mask-adoption.mjs · check-empty-changeset.mjs · check-keyed-text-bounds.mjs · check-plugin-teardown-shape.mjs · check-undeclared-dep-imports.mjs · docs-audit/check-affected-docs.mjs · docs-audit/check-drift-comment.mjs · pm/release-rehearsal-clone.mjs --self-test. Plus pnpm check:nul-bytes on top of the derivation.

check:liveness verdict line at head:

✓ every governed-type property at the walk's one-level granularity is classified, ... every `path:NNN`
citation names a line that file actually has, every `path#symbol` anchor names a symbol its file contains, ...
✓ packages/spec/liveness/state-counts.md is current — the same 31 row(s), no count column left in the README.

check:nul-bytes verdict line:

check-nul-bytes: OK (scanned 7246 text file(s) -- 7246 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).

2 NOT MEASURED, neither of them a red gate:

  • node scripts/check-dev-prereqs.mjs, EXIT=1 — a container prerequisite on an unbuilt worktree, and it says so: "Fix: pnpm build". Deliberately not converted into a reading here: this diff contains zero TypeScript, so a 70-package build would spend the fleet's scarcest resource (the shared verify lock, ~6m30s when a sibling ran it 30 minutes ago) measuring the container rather than this change. A declared narrowing, not an omission.
  • node scripts/pm/check-half-states.mjs, EXIT=3PREREQUISITE NOT MET, no GitHub credential in this container. The gate's own words: "It is not a clean board and it is not a dirty one — it is no reading at all." Standing across this seat's whole run.

No typecheck leg is owed: the diff is one JSON ledger, one generated Markdown table, one Markdown README and one changeset. No ablation is owed either, and the reason is measured rather than claimed — a status flip adds no new anchor to any scanned field, and the probe above shows the two path#symbol spellings in the note sit outside the scanned population entirely, so there is no anchor mechanism whose failure could be demonstrated.

All gates above ran at cc4c52948 with git diff HEAD empty and git status --porcelain silent, so the tree measured is the tree committed and pushed.

Checklist

Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4


Generated by Claude Code

The entry was ledgered `live` on a `.transform` that lowers `execute` -> `target`
and drops the alias. No such transform exists: action.zod.ts has exactly two
`.transform` calls and both are `lowerRequiresFeature`. The alias and its lowering
were removed together in protocol 17 (#3855); the key is a `retiredKey` tombstone
with no consumer on any layer.
Status flips to `dead` with the ROW STAYING - the rls.priority precedent, a
tombstone keeps the key in the walked shape - matching the two already-dead
siblings in the same ledger. The falsified `evidence` goes, the #3713
three-readers history is preserved in the note.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️3 changed file(s) yielded no anchor (packages/spec/liveness/README.md, packages/spec/liveness/action.json, packages/spec/liveness/state-counts.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/liveness/README.md, packages/spec/liveness/action.json, packages/spec/liveness/state-counts.md) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json cf71d73f8428424244b15674942e04ea34feb0a9packageMentionDocs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/stooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] action.execute is ledgered live citing a .transform that protocol 17 removed — the key is a retiredKey tombstone with no consumer anywhere

2 participants

@os-trump@claude