diff --git a/.changeset/liveness-action-dispatch-anchors.md b/.changeset/liveness-action-dispatch-anchors.md new file mode 100644 index 0000000000..6183968056 --- /dev/null +++ b/.changeset/liveness-action-dispatch-anchors.md @@ -0,0 +1,43 @@ +--- +'@objectstack/spec': patch +--- + +liveness ledger: repoint the `action` `type` / `body` / `method` anchors at their real consumers + +All three cited `packages/runtime/src/http-dispatcher.ts`, and none of them is read +there. The actions domain was extracted out of that file — it now only delegates +(`handleActions` → `handleActionsRequest` at :1969-1970) — and the reads live in +`action-execution.ts`, `domains/actions.ts`, `sandbox/body-runner.ts` and, for the +client-dispatched `method`, in the renderer repo. + +This is the residue of the same extraction that rotted `action.target` and +`action.requiredPermissions`, but it survived the sweep that repaired those, and the +reason is the interesting part. The key-mention check added with that sweep asks +whether the cited file names the key at all — which caught `target` and +`requiredPermissions` because `http-dispatcher.ts` contains **0** occurrences of +either. It cannot catch these three, because they are common English and HTTP words +that the file is full of for unrelated reasons: + +- `type` — 9 occurrences, four of them the TypeScript `import type` keyword, the rest + other domains' data (`error.type`, `details.type`, a field-type→JSON-Schema mapper). +- `body` — 68 occurrences: the inbound HTTP request body threaded through every + domain delegate, plus the file's own "body extracted to ./domains/…" comments, + where `body` means a *function* body. +- `method` — 41 occurrences: the inbound HTTP verb (15 `method: string` parameter + declarations, route matching such as `method === 'GET'`) and the ordinary + object-oriented sense in prose. + +So the word-bounded check anchors on the coincidence and passes. That is the designed, +honest limit of the signal rather than a defect in it — the census that shipped it said +so — and it means this class is invisible to tooling and only a hand call-graph read +can settle it. Each repointed entry now records which spelling misled the gate, so the +next reader does not have to re-derive it. + +`method` additionally CHANGES REALM, joining its siblings `bodyShape` and `bodyExtra`: +`type: 'api'` actions are client-dispatched by design, so the server never read the +verb. Its one in-repo appearance is a diagnostic that interpolates the verb into the +refusal explaining the server does *not* dispatch it — evidence of non-consumption, +and deliberately not cited as a consumer. + +No verdict was re-graded: all three were `live` and remain `live`, with the consumer +proven rather than asserted. Citation repair only. diff --git a/packages/spec/liveness/action.json b/packages/spec/liveness/action.json index 97e2cacb53..f3f0741b22 100644 --- a/packages/spec/liveness/action.json +++ b/packages/spec/liveness/action.json @@ -37,9 +37,9 @@ }, "type": { "status": "live", - "verifiedAt": "2026-07-28", - "evidence": "packages/runtime/src/http-dispatcher.ts; 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." + "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." }, "target": { "status": "live", @@ -49,8 +49,9 @@ }, "body": { "status": "live", - "evidence": "packages/runtime/src/http-dispatcher.ts", - "note": "server script (L1/L2) via engine.executeAction→body-runner." + "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." }, "execute": { "status": "live", @@ -139,7 +140,10 @@ }, "method": { "status": "live", - "evidence": "packages/runtime/src/http-dispatcher.ts" + "verifiedAt": "2026-08-26", + "evidenceScope": "cross-repo", + "evidence": "objectui @7b6f7f889: packages/core/src/actions/ActionRunner.ts:1774 (`method = action.method || 'POST'` on the string-endpoint branch of executeAPI) and :1780 (`config.method || action.method` on the ApiConfig branch), both landing on the real request at :1800 `const fetchInit: RequestInit = { method, headers }` and gating payload attachment at :1795/:1801", + "note": "The HTTP verb of an inline `type:'api'` action. 2026-08-26: REPOINTED, and the repoint CHANGED THE REALM for exactly the reason its siblings `bodyShape`/`bodyExtra` did — `type:'api'` actions are client-dispatched by design, and action-execution.ts:557-563 (the `api` branch of headlessActionTypeError, :551-568) refuses them server-side with the message \"it dispatches on `target`, not through the action registry\". So packages/runtime/src/http-dispatcher.ts never read this key at any point: its 41 word-`method` occurrences are the inbound HTTP verb (15 `method: string` parameter declarations on the domain delegates, plus route matching such as `method === 'GET'` at :2067 and the log line at :2178) and the ordinary object-oriented sense in prose (\"this method\", :837/:2047/:2108/:2135). The ONE in-repo occurrence of `action.method` — action-execution.ts:561 — is a DIAGNOSTIC that interpolates the verb into the very refusal explaining the server does NOT dispatch it, so it is evidence of non-consumption and deliberately not cited above as a consumer. WHY THE GATE COULD NOT SEE THE ROT: `method` is this dispatcher's most-used noun, so the word-bounded key-mention check anchors incidentally and passes — the designed, honest limit of that signal." }, "bodyExtra": { "status": "live",