Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/liveness-symbol-anchors-action-object.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
---
"@objectstack/spec": patch
---

chore(spec): re-anchor the `action` and `object` liveness ledgers to consuming symbols (#13003)

Adoption batch 1 of the symbol-anchor citation grammar landed by #12516. The
`liveness/` ledgers ship inside this package's npm tarball (they are named in
`files`), so this is a published-data change even though no runtime behaviour
moves and no schema key changes.

Twenty `path:NNN` evidence citations across `liveness/action.json` and
`liveness/object.json` are now written `path#symbol`, each re-closed by reading
the code on the current tree rather than by shifting a line number. A symbol
moves with its consumer, so the pointer survives the in-file drift that rots a
line, and goes red when the consumer is renamed or deleted — a direction a
stale line can never produce.

What the re-closure found, which is the reason the migration is not mechanical:
fourteen of the fifteen `object.json` citations were pointing at the wrong place
already, every one of them IN RANGE and so invisible to all three existing
checks. They had drifted onto a docblock about aggregate-function lowering, a
job-scheduling block, a neighbouring `const`, and — for `object.enable.clone` —
a sort-node normalizer roughly 6,700 lines from its actual reader. Three had
additionally moved package: the `ownership`, `managedBy` and `tenancy.enabled`
readers now live in `@objectstack/spec` itself, and one of those citations
carried a parenthetical (`applySystemFields reads schema.ownership`) that the
re-read falsified outright — that function's nine `ownership` occurrences are
all comments about a decision it delegates.
12 changes: 6 additions & 6 deletions packages/spec/liveness/action.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,9 +37,9 @@
},
"type": {
"status": "live",
"verifiedAt": "2026-08-26",
"evidence": "packages/runtime/src/sandbox/body-runner.ts:385 (the #4352 gate — `const type = action.type ?? 'script'` decides whether a `body` binds an executable handler at all); packages/runtime/src/action-execution.ts:1309 (`action.type === 'flow'` routes to dispatchFlowAction), :524 (isHeadlessInvokableAction), :552 (headlessActionTypeError names the type it refuses), :906 (MCP action summary projects it); packages/runtime/src/domains/actions.ts:591 (the REST route's own `actionType` resolution); objectui @732b1bf core ActionRunner.ts:513-541 switches every variant to its own executor (executeScript/Url/Modal/Flow/API/Form/Navigation)",
"note": "api/script/flow wired; url thinner; modal PARTIAL (maps to serverActionHandler, not a real modal); form LIVE via objectui ActionRunner.executeForm (routes a type:'form' action to the FormView at /forms/:target, forwarding the current record id) — the 2026-06 audit mis-classified as dead (objectui renderer not re-verified; fixed the 'Log Time does nothing' report). Build-time lint-view-refs.ts validates the form target resolves to a form view. RE-VERIFIED 2026-07 (#3714 follow-up): `api` -> executeAPI (:974, string-or-ApiConfig endpoint, method/headers/queryParams/responseType) and `form` -> executeForm (:920) both resolve; content/docs/ui/actions.mdx had been telling authors the opposite (\"no runtime executor / renderer today\") and was corrected in the same pass. 2026-08-26: REPOINTED (framework half only) — the evidence led with packages/runtime/src/http-dispatcher.ts, which reads this key nowhere. That file's 9 word-`type` occurrences are ALL something else: four are the TypeScript `import type` keyword (:9, :13, :14, :21) and the rest are other domains' prose or data (`error.type` :862, a field-type→JSON-Schema mapper :926, `details.type` :1156, a metadata type list :1634, an inbox query param :1765). The action consumer was extracted into domains/actions.ts + action-execution.ts (+ the sandbox body-runner, which is where the type gate has always lived) and http-dispatcher.ts now only delegates (handleActions → handleActionsRequest at :1969-1970). WHY THE GATE COULD NOT SEE THE ROT — and this is the part that differs from the `target`/`requiredPermissions` siblings repointed the day before: those were caught because their cited file had 0 occurrences of the key, whereas `type` is a common English word AND a TypeScript keyword, so the word-bounded key-mention check anchors on the coincidence and PASSES. This entry was repaired by a hand call-graph read, not by tooling; the check's designed, honest limit is what left it standing. The objectui half is unchanged and stays as measured @732b1bf (2026-07-28) — it was not re-graded here."
"verifiedAt": "2026-08-28",
"evidence": "packages/runtime/src/sandbox/body-runner.ts#actionBodyRunnerFactory (the #4352 gate — `const type = action.type ?? 'script'` decides whether a `body` binds an executable handler at all); packages/runtime/src/action-execution.ts#invokeBusinessAction (`action.type === 'flow'` routes to dispatchFlowAction); packages/runtime/src/action-execution.ts#isHeadlessInvokableAction (gates headless dispatch on the type); packages/runtime/src/action-execution.ts#headlessActionTypeError (names the type it refuses); packages/runtime/src/action-execution.ts#summarizeAction (MCP action summary projects it); packages/runtime/src/domains/actions.ts#handleActionsRequest (the REST route's own `actionType` resolution, read again at the flow branch); objectui @732b1bf core ActionRunner.ts:513-541 switches every variant to its own executor (executeScript/Url/Modal/Flow/API/Form/Navigation)",
"note": "api/script/flow wired; url thinner; modal PARTIAL (maps to serverActionHandler, not a real modal); form LIVE via objectui ActionRunner.executeForm (routes a type:'form' action to the FormView at /forms/:target, forwarding the current record id) — the 2026-06 audit mis-classified as dead (objectui renderer not re-verified; fixed the 'Log Time does nothing' report). Build-time lint-view-refs.ts validates the form target resolves to a form view. RE-VERIFIED 2026-07 (#3714 follow-up): `api` -> executeAPI (:974, string-or-ApiConfig endpoint, method/headers/queryParams/responseType) and `form` -> executeForm (:920) both resolve; content/docs/ui/actions.mdx had been telling authors the opposite (\"no runtime executor / renderer today\") and was corrected in the same pass. 2026-08-26: REPOINTED (framework half only) — the evidence led with packages/runtime/src/http-dispatcher.ts, which reads this key nowhere. That file's 9 word-`type` occurrences are ALL something else: four are the TypeScript `import type` keyword (:9, :13, :14, :21) and the rest are other domains' prose or data (`error.type` :862, a field-type→JSON-Schema mapper :926, `details.type` :1156, a metadata type list :1634, an inbox query param :1765). The action consumer was extracted into domains/actions.ts + action-execution.ts (+ the sandbox body-runner, which is where the type gate has always lived) and http-dispatcher.ts now only delegates (handleActions → handleActionsRequest at :1969-1970). WHY THE GATE COULD NOT SEE THE ROT — and this is the part that differs from the `target`/`requiredPermissions` siblings repointed the day before: those were caught because their cited file had 0 occurrences of the key, whereas `type` is a common English word AND a TypeScript keyword, so the word-bounded key-mention check anchors on the coincidence and PASSES. This entry was repaired by a hand call-graph read, not by tooling; the check's designed, honest limit is what left it standing. The objectui half is unchanged and stays as measured @732b1bf (2026-07-28) — it was not re-graded here. 2026-08-28: RE-ANCHORED (#13003, adoption of the #12516 grammar) — re-closed by hand against c459da6bc. All six framework consumers stand and none had left its file, so this is a grammar migration rather than a repair; what it DOES repair is a second, quieter gap: `:524`/`:552`/`:906` were written as bare line suffixes with no path in front of them, which the evidence scanner cannot parse as citations at all (`PATH_RE` needs a repo-rooted token), so three of this entry's six consumers were unfalsifiable prose that no check has ever resolved. They are now anchors of their own."
},
"target": {
"status": "live",
Expand All@@ -49,9 +49,9 @@
},
"body": {
"status": "live",
"verifiedAt": "2026-08-26",
"evidence": "packages/runtime/src/sandbox/body-runner.ts:356 (`const raw = action.body` in actionBodyRunnerFactory, whose own header at :342 calls it \"the ONE choke point where an `action.body` becomes an executable handler\" — both bind paths reach it: AppPlugin's collectBundleActions walk and engine.setDefaultActionRunner); packages/runtime/src/action-execution.ts:525 (isHeadlessInvokableAction gates headless dispatch on `action?.target || action?.body`)",
"note": "server script (L1/L2) via engine.executeAction→body-runner. 2026-08-26: REPOINTED — the evidence cited packages/runtime/src/http-dispatcher.ts, which reads this key nowhere. Its 68 word-`body` occurrences are the inbound HTTP request body — the `body: any` parameter threaded through the handleX delegates (:890, :961, :1732, :1969 …) and the result envelope at :728 — plus the file's own \"Thin delegate — body extracted to ./domains/…\" extraction comments, where `body` means a function body. The consumer this note already NAMED in prose (body-runner) is where the read has always been; the surrounding action path was extracted into domains/actions.ts + action-execution.ts, and http-dispatcher.ts now only delegates (handleActions → handleActionsRequest at :1969-1970). WHY THE GATE COULD NOT SEE THE ROT: `body` is both a common English word and the name of the HTTP request member this dispatcher is built around, so the word-bounded key-mention check added in #11457 anchors on 68 unrelated hits and passes — the designed, honest limit of that signal, which is why this needed a hand call-graph read. Note the shape the repoint exposes: the NOTE's pointer (\"via engine.executeAction→body-runner\") stayed true the whole time while the EVIDENCE pointer rotted — the same split recorded on `requiredPermissions`, one layer over."
"verifiedAt": "2026-08-28",
"evidence": "packages/runtime/src/sandbox/body-runner.ts#actionBodyRunnerFactory (`const raw = action.body`, in the factory whose own header calls it \"the ONE choke point where an `action.body` becomes an executable handler\" — both bind paths reach it: AppPlugin's collectBundleActions walk and engine.setDefaultActionRunner); packages/runtime/src/action-execution.ts#isHeadlessInvokableAction (gates headless dispatch on `action?.target || action?.body`)",
"note": "server script (L1/L2) via engine.executeAction→body-runner. 2026-08-26: REPOINTED — the evidence cited packages/runtime/src/http-dispatcher.ts, which reads this key nowhere. Its 68 word-`body` occurrences are the inbound HTTP request body — the `body: any` parameter threaded through the handleX delegates (:890, :961, :1732, :1969 …) and the result envelope at :728 — plus the file's own \"Thin delegate — body extracted to ./domains/…\" extraction comments, where `body` means a function body. The consumer this note already NAMED in prose (body-runner) is where the read has always been; the surrounding action path was extracted into domains/actions.ts + action-execution.ts, and http-dispatcher.ts now only delegates (handleActions → handleActionsRequest at :1969-1970). WHY THE GATE COULD NOT SEE THE ROT: `body` is both a common English word and the name of the HTTP request member this dispatcher is built around, so the word-bounded key-mention check added in #11457 anchors on 68 unrelated hits and passes — the designed, honest limit of that signal, which is why this needed a hand call-graph read. Note the shape the repoint exposes: the NOTE's pointer (\"via engine.executeAction→body-runner\") stayed true the whole time while the EVIDENCE pointer rotted — the same split recorded on `requiredPermissions`, one layer over. 2026-08-28: RE-ANCHORED (#13003) — re-closed by hand against c459da6bc; both consumers stand where the 08-26 repoint put them, so the lines had not yet rotted and this is the grammar migration, not a second repair. The anchor is what makes that claim re-testable next time: `actionBodyRunnerFactory` is the symbol the header already calls the ONE choke point, so a consumer that moves inside this 798-line file keeps the pointer true and one that is deleted turns it red."
},
"execute": {
"status": "live",
Expand Down
Loading
Loading