Skip to content

Commit 6530e04

Browse files
os-warrenclaude
andauthored
fix(approvals): give a restored approval suspension an issuer, so it can be decided and not only cancelled (#15966)
* fix(approvals): give a restored approval suspension an issuer, so it can be decided and not only cancelled `AutomationEngine.restoreConsumedSuspension` re-arms the pause of a run that stranded mid-resume and tells the operator to re-issue the continuation. For an `approval` suspension nobody could. Every approvals door that stamps the resume marker — `decide`, `recall`, `sendBack`, `resubmit` — guards on a `pending` request, and the row is terminal, written by the very call that stranded the run; the generic engine door refuses an `approval` pause outright because that node declares `resumeAuthority: 'service'`. The only verb left was `cancelRun`, which discards the branch's downstream work, so the advertised repair produced a run that looked resumable and was not decidable. Measured against the real engine and the real decision door: the restored suspension lacks nothing. A `resumeAuthority`-marked resume walks the restored pause to completion. What was missing is an ISSUER on the approvals side, which is what this adds — `ApprovalService.continueRestoredRun(requestId, options?)`. The failing door now journals the signal it was carrying, on the repairable exit only (the engine's own `status: 'stranded'` discriminator, the one exit that journals a repair snapshot), under `__strandedContinuation` in the request's `node_config_json` — beside the `__decisionOutputs` side-channel already there, so no column and no authored config moves. It is awaited but can never replace the `RESUME_FAILED` throw the decision's caller is owed. Runs stranded before this shipped are served too: with no journal, the signal is rebuilt from the recorded outcome, which is exact for each accepted status because each is written by exactly one door (`approved` → `approve`, `rejected` → `reject`, `returned` → `revise`, `recalled` → `reject`). The one shape it cannot rebuild — a `rejected` row also carrying a `revise` action, possibly ADR-0044's revision-limit auto-rejection whose resume carried `autoRejected` — is refused rather than guessed. The verb replays a decision and rewrites nothing: all four `pending` guards are untouched, no status, mirror field or audit row is written, `resumeAuthority` stays as it is, and `ApprovalDecisionResult` is not touched. Like the engine verb it completes, it is an in-process operator repair with no REST route and no entry in the spec `ApprovalService` contract. Part of #15389 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * test(approvals): pin the engine-side measurement directly, and fix the fake engine's insert arity The raw `resumeAuthority`-marked resume against a restored suspension is the measurement that places this card's fix on the approvals side rather than in `engine.ts`, so it is pinned in its own right and not only through the new verb. Driven on a second stranded-and-restored run so the cancel measurement above it is undisturbed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * chore(gates): teach three ledgers about this change's new double, new write site and shifted lines Each repair is the one the failing gate itself prescribes, and each grows or re-anchors a ledger rather than loosening one. A base control at the merge-base (924f0fe) confirms all three are caused by this branch and were green before it. - `engine-double-contract.pinned.json` learns the two doubles (delete, update) in the new pin file. +10 lines, nothing removed, no baseline lowered: 791 pinned / 133 debt / 3 exempt, up from 789 at base. - `content/docs/permissions/system-context.mdx` re-anchored by the census's own `--fix`: inserting methods into `approval-service.ts` shifted eight cited line numbers. Pure line rot, no elevation behaviour added or removed — this branch introduces no `context.isSystem` read. - The tenant-audit census re-measured: `journalStrandedContinuation` adds one write call site, so 221 -> 222, 147 -> 148 decidable, 103 -> 104 decidably elevated. `--write` regenerates the tables; the eight prose figures the gate names as hand-written are corrected to match, each verified against the regenerated table rather than arithmetic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * chore(gates): re-anchor the system-context census against the merged tree Merging origin/main auto-merged content/docs/permissions/system-context.mdx textually clean and semantically stale: the census is green at origin/main (904e707, measured) and green on this branch before the merge, but red on the merge product with 26 problems, every one an anchor into plugin-security or rest — files this branch never touches and the merge brought in. Repaired with the census's own --fix, which re-anchors against the merged code. 15 anchors rewritten; the result reports 105 elevation read sites across 44 files and 140 resolving anchors, identical to what origin/main reports, so this restores the upstream reading rather than inventing one. No elevation behaviour is added or removed anywhere on this branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * fix(approvals): tie the restored continuation to this request's own pause, and refuse the rebuilds that cannot be proved Two defects found by contract review of the first revision, both measured at the real door, and both making the repair verb advance a flow with no decision behind it - strictly worse than the dead end it exists to open. GUARDS. `hasSuspendedRun(runId)` answers a boolean and cannot tell a re-armed pause from any other live pause on the same run. Four shapes were measured advancing something they should not: a terminal request driving a LATER approval node's pending pause with no decision recorded on it; a plain RETRY of the verb doing the same after the run re-suspended; a next-round `returned` row orphaning round two; and a `recalled` request resurrected into a new pending round from a pause it never gated. The retry is the one that settles the severity: repeating a repair verb is the ordinary shape of using it, not a hostile construction. Three guards now stand, each with its own reverse-control pin: `assertLatestForRun` (already in this file, used by recall and resubmit for the same reason) refuses a superseded row; the strict `hasSuspendedRun` refuses a run with no pause and throws rather than read a store outage as "not suspended"; and a node-identity check refuses a pause parked anywhere but this request's own node. The last is fail-closed in every direction - no reader, no listing, no entry, or an entry at another node all refuse - because a false refusal costs a retry while a false admission advances a step nobody decided. It needs NO new automation-engine surface. `listSuspendedRunsDurable` is already public on AutomationEngine and already names the parked node; the approvals-side resume interface simply declares the method it already has. REBUILD. "Each status is written by exactly one door" was false, and it was the load-bearing premise of the no-journal path that serves runs stranded before this ships. `returned` has one writer but two issuers, so a stranded resubmit was rebuilt as a send-back and walked the `revise` edge with `{decision:'revise'}` where `{resubmitted:true}` was owed - proceeding, rather than failing loudly, through the engine's unmatched-label fallback. It is now discriminated by the `resubmit` action row, whose sole writer in this file is `resubmit`. `recalled` has two writers across three behaviours, two issuing no continuation at all, and nothing on the row tells them apart - so it is REFUSED on the rebuild path, with a message naming what an operator can do instead. Both stay journal-recoverable. HARNESS. The test double ignored `orderBy`, so it returned the OLDEST row where `assertLatestForRun` asks for the newest - the guard passed on every input and a pin naming it would have measured nothing. The double now honours it, and guard 1 is additionally driven in isolation on a row whose `created_at` is controlled, so the ordering is what is under test rather than the clock's resolution. PIN 3 previously asserted `decide` alone while claiming all four `pending` guards; it now asserts all four by name. The system-context census is regenerated from the merged tree with `pnpm gen:system-context-census` (9 anchors), discharging the deferral the merge commit took; it reports the same 105 sites / 44 files / 140 anchors as before. Part of #15389 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * fix(approvals): make guard 3 ask where the signal was ISSUED FROM, and replace a phantom pin Two things this PR claimed were not what it said they were. Both were in the safe direction — nothing escalated — but a false claim is not shippable. 1. Guard 3 compared the run's parked node against the request row's own `flow_node_id`. That is right for `approve`, `reject`, `revise` and `recall`, all of which are issued at the request's own approval node. It is WRONG for `resubmit`: a resubmit is only reachable after a send-back moved the run to the revise window, and it resumes THAT pause down the `resubmit` back-edge. So a stranded resubmit re-arms at the revise window while the row still records the approval node, and guard 3 refused it — on both the journal and the rebuild paths — with a message telling the operator the pause was not this request's when it was exactly this request's. The expected node is now signal-aware (`ApprovalService.expectedPauseNode`), derived from the flow definition the same way `assertReviseEdge` already derives the revise window, fail-closed in every direction, and needing no automation surface that method did not already use. The refusal message now names the node the signal was issued from rather than "this request's own node". ⚠️ It widens what guard 3 admits for exactly one signal. Every other decision resolves byte-identically to the row's own node, so the recall-in-revise-window shape stays refused: its journalled signal is `recall`, not `resubmit`. 2. PIN 7(b) could not reach the code it named. It set the reject-branch throw for a flow with no `mark_rejected` node, never decided anything, restored a suspension that had never been consumed, and asserted a refusal that was only `request is pending` — then discarded the first call with `void`, which is what hid it. It passed on every input, and P5 therefore had no pin behind it. Replaced with the real construction: strand the first approval's `approve` continuation by failing the next round's request insert once, restore, ASSERT the first call resumes and parks the run at the second approval node, then retry and assert guard 1 refuses by exact message with the second node still pending and no branch run. The test double gains a one-shot insert failure lever, which is what makes a real strand reachable from a test; every leg that uses it asserts the lever was consumed, so a fixture that silently stopped firing reads as a failure rather than as a pass. 3. PIN 8 gained two legs through `continueRestoredRun` end to end — the resubmit replay on the journal path and on the rebuild path — because its discriminator was green over code the verb never reached. PIN 7(a)'s population comment is corrected: the row is `returned`, not `recalled` (a `recalled` row is refused before any guard runs). 4. The changeset's "every one of these is journal-recoverable" is replaced by a measured, named set, with the two by-design refusals named and the two shapes no pin covers stated as not claimed. Part of #15389 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y * fix(approvals): refuse a journalled continuation the row's status can no longer have issued The stranded-continuation journal was returned before `raw.status` was read, and nothing clears it when a later door moves the row on — so a signal could outlive the state that issued it and be replayed onto a re-armed pause. Measured, with no injected failure beyond the strand itself: a `resubmit` strands and journals `resubmit`; the submitter then recalls, a real `cancelRun` on an already-stranded run answers `false`, so the row is marked `recalled` and the run stays parked; `restoreConsumedSuspension` re-arms the pause; and `continueRestoredRun` replayed the stale `resubmit`, opening a fresh `pending` round on a request somebody deliberately withdrew — statuses `["recalled","pending"]`. Every step an ordinary action answering ordinarily. The `revise` sibling (a stranded send-back, then a recall) did the same. A row is now replayable only for a continuation its own status can have issued (`approved`→`approve`, `rejected`→`reject`, `returned`→`revise`|`resubmit`, `recalled`→`recall`), and nothing at all for a status nobody has enumerated. ⛔ Clearing the journal after a successful replay was measured NOT to close this: the offending replay is the FIRST replay of that journal, so a clear that fires afterwards can never run before the advance it would prevent. With such a clear in place both limbs still reached `resumed: true`. PIN 9 pins both limbs end to end, plus the compatible control (a stranded recall journalling `recall` on a `recalled` row still replays, and its reject branch runs) so the check reads as a gate rather than a blanket refusal. Also in this commit: - Regenerated `content/docs/permissions/system-context.mdx` with the census gate's own `--fix` (8 anchors re-anchored) and proved the result a fixpoint: a second `--fix` rewrites 0 and leaves the blob byte-identical. `pnpm check:system-context-census` exit 0. - Corrected three prose claims: "checks that the pause is genuinely re-armed" (no guard checks that — a never-consumed pause is admitted); the retracted "its journalled signal is `recall`, not `resubmit`" (a recall inside the revise window journals nothing, so the journal there is whatever an earlier strand left); and the journal-recoverable named set, which now includes the pinned `recall` journal path. - Guard 3's refusal no longer presumes a re-arm and a refusal its own PIN 7(a) population does not contain: it names the pause it was ASKED to continue against the node the outcome was ISSUED AT. Refs #15389 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent dcad825 commit 6530e04

8 files changed

Lines changed: 1614 additions & 24 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
"@objectstack/plugin-approvals": minor
3+
---
4+
5+
A restored approval suspension can now be decided again, not only cancelled.
6+
7+
`AutomationEngine.restoreConsumedSuspension` re-arms the pause of a run that stranded mid-resume and tells the operator to *re-issue the continuation*. For an `approval` suspension nobody could: every approvals door that stamps the resume marker — `decide`, `recall`, `sendBack`, `resubmit` — guards on a `pending` request, and the row is terminal, written by the very call that stranded the run; and the generic engine door refuses an `approval` pause outright, because that node declares `resumeAuthority: 'service'`. The only remaining verb was `cancelRun`, which discards the branch's downstream work — so the advertised repair produced a run that looked resumable and was not decidable.
8+
9+
Measured against the real engine and the real decision door: the restored suspension lacks nothing. A `resumeAuthority`-marked resume walks the restored pause to completion. What was missing was an **issuer** on the approvals side, and that is what this adds.
10+
11+
- **`ApprovalService.continueRestoredRun(requestId, options?)`** re-issues the continuation the recorded outcome already produced once, against a pause an operator has re-armed. It reports which outcome it replayed, which edge it walked, and whether the signal was replayed exactly or rebuilt (`source: 'journal' | 'reconstructed'`).
12+
- **The failing door now journals the signal it was carrying** on the repairable exit — the engine's own `status: 'stranded'` discriminator, the one exit that journals a repair snapshot — under `__strandedContinuation` in the request's `node_config_json`, beside the `__decisionOutputs` side-channel that was already there. Best-effort: it is awaited but can never replace the `RESUME_FAILED` throw the decision's caller is owed.
13+
- **The continuation is tied to this request's own pause, by three guards.** A boolean "is this run suspended" is not enough: a run outlives any one request, so a terminal row's continuation could be issued against whatever pause the run happened to be sitting on. It now requires that the request is still the newest on its run, that a pause exists (strictly — an unreadable store throws rather than reading as "not suspended"), and that the pause is parked **where this request's recorded outcome was issued from**. That node is signal-aware, not simply the row's own: `approve`, `reject`, `revise` and `recall` are all issued at the request's own approval node, but a `resubmit` is only ever issued from the revise window the request's `revise` edge leads to, so its pause is re-armed there while the row still records the approval node. Comparing against the row's own node refused exactly that case, and told the operator the pause was not this request's when it was. The node check is fail-closed in every direction, including an engine that cannot report where a run is parked and a revise window this service cannot derive from the flow definition. This needs no new automation-engine surface: `listSuspendedRunsDurable` is already public, and the approvals-side resume interface simply declares it.
14+
- **Runs stranded before this shipped are served too**, and where the signal cannot be proved the verb **refuses instead of guessing**. A status is not the same thing as a continuation, and three of the four terminal statuses have more than one writer or issuer: `approved` is unambiguous; `rejected` has two writers, discriminated by the `revise` action row that only ADR-0044's revision-limit auto-rejection leaves behind; `returned` has one writer but **two** issuers, discriminated by the `resubmit` action row whose sole writer is `resubmit` — without it a stranded resubmit was rebuilt as a send-back and walked the wrong edge, proceeding only through the engine's unmatched-label fallback with the wrong output; and `recalled` has two writers across **three** behaviours, two of which issue no continuation at all, so it is **refused on the rebuild path** with a message naming what an operator can do instead. Journal-recoverable is a **measured, named set** rather than a blanket claim: `approve`, `reject`, `resubmit` and `recall` continuations replay end to end through the verb, and `reject` and `resubmit` do so on the rebuild path as well. Two shapes are refused by design and stay refused — a `rejected` row that also carries a `revise` action, and a `recalled` row with no journal. NOT covered by a pin, and so not claimed: the `approve` rebuild path.
15+
16+
- **A journalled signal is checked against what the row's status can have issued, before it is replayed.** The journal records what the last FAILED resume was carrying, and nothing rewrites it when a later door moves the row on — so a signal can outlive the state that issued it. Measured, with no injected failure beyond the strand: a `resubmit` strands and journals `resubmit`; the submitter then recalls, a real `cancelRun` on an already-stranded run answers `false`, the row is marked `recalled` and the run stays parked; the restore re-arms the pause; and the stale `resubmit` was replayed, opening a fresh `pending` round on a request somebody deliberately withdrew. Every step an ordinary action answering ordinarily. A row is now replayable only for a continuation its own status can have issued — `approved`→`approve`, `rejected`→`reject`, `returned`→`revise` or `resubmit`, `recalled`→`recall`, and nothing at all for a status nobody has enumerated. ⛔ Clearing the journal after a successful replay does not close this and was measured not to: the offending replay is the FIRST replay of that journal, so a clear that fires afterwards can never run before the advance it would prevent.
17+
18+
⛔ What this deliberately does not do, each pinned: it does not re-open or rewrite the request row — all four `pending` guards are untouched and no status, mirror field or audit row is written, so a decided request still cannot be decided again through the front door; it does not relax `resumeAuthority: 'service'`, since the resume still goes through the one call site that stamps the marker; and it does not change `ApprovalDecisionResult`, whose shape is the subject of an open ruling. It also grants no capability in-process code did not already have — `RESUME_AUTHORITY_SERVICE` is importable by any host — what it adds is the guarded form, and the guards are stated as what they actually check: that this request is still the newest on its run, that a pause exists at all, that it is parked where this outcome was issued from, and that the recorded signal is one the row's present status can have issued. ⛔ None of them checks that the pause was consumed and genuinely re-armed, and an earlier wording of this entry claimed one did: a `returned` row with a resubmit action row and a pause that was never consumed is admitted, with `restoreConsumedSuspension` itself answering *"already resumable — nothing to restore"*. That shape is benign — the recorded action is the submitter's own resubmit, so the step it walks was decided — but it is not what any guard tests. Like the engine verb it completes, it is an in-process operator repair: no REST route, and no entry in the spec `ApprovalService` contract.

content/docs/permissions/system-context.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ The largest single consumer — **17 of the 105 sites**.
145145
|:--|:---|:---|:---|:---|
146146
| 40 | **Approval record lock released** — a locked record is writable | plugin-approvals | Get: engine self-writes (the status mirror) pass. Lose: the lock that stops edits while an approval is live. Note there is deliberately **no admin exemption** here — only `isSystem` | `lifecycle-hooks.ts:347` |
147147
| 41 | Delegation write guard bypassed | plugin-approvals | Get: service / seed / import may write delegation rows naming another delegator | `lifecycle-hooks.ts:570` |
148-
| 42 | Approval actor / submitter / pending-approver checks bypassed (8 sites) | plugin-approvals | Get: approve, reject, recall, reassign without being a pending approver or the submitter | `plugin-approvals/src/approval-service.ts:963`, `:1072`, `:3305`, `:3453`, `:3621`, `:3692`, `:3881`, `:3921` |
148+
| 42 | Approval actor / submitter / pending-approver checks bypassed (8 sites) | plugin-approvals | Get: approve, reject, recall, reassign without being a pending approver or the submitter | `plugin-approvals/src/approval-service.ts:1093`, `:1202`, `:3458`, `:3606`, `:3774`, `:3845`, `:4034`, `:4074` |
149149
| 43 | Saved-report ownership is **assignable**, and an update may reassign it | plugin-reports | Get: `ownerId` from input is honoured. A non-system caller always owns what it creates and can never reassign | `plugin-reports/src/report-service.ts:404`, `:425` |
150150
| 44 | Saved-report access / export / mutation gates bypassed | plugin-reports | Get: read, bulk-export and overwrite any report | `plugin-reports/src/report-service.ts:343`, `:372`, `:447`, `:684` |
151151
| 45 | Attachment access hooks return early (insert + update + delete, and the read AST) | service-storage | Lose: attachment visibility scoping | `attachment-access-hooks.ts:300`, `:349`, `:448`, `:524` |

content/docs/permissions/tenant-audit-census.mdx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ are reported as `undecidable` rather than assumed either way.
9898

9999
The same holds twice over for the context. An options argument spelled as a
100100
literal can be read; one spelled `options`, `{ ...opts }`, or handed through a
101-
forwarding shim cannot, and **67 of the 221 sites are spelled that way**. A
101+
forwarding shim cannot, and **67 of the 222 sites are spelled that way**. A
102102
context resolved from an inline literal or a local `const` can be tested for
103103
`isSystem`; one arriving from a helper call cannot.
104104

@@ -147,10 +147,10 @@ reproduce them. Where it disagrees, it disagrees on the page:
147147

148148
| carried figure | where it survives | this census |
149149
| :--- | :--- | ---: |
150-
| 175 write call sites | quoted in the merged changeset | **221** |
150+
| 175 write call sites | quoted in the merged changeset | **222** |
151151
| 24 carrying no tenant context | quoted in the merged changeset | **9** provable and tenancy-enabled; **32** more whose options argument is unreadable |
152-
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card | **147 of 221** decidable, **74** undecidable |
153-
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration** | **not reproduced**: 103 decidably elevated, 0 decidably not, 101 undecidable |
152+
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card | **148 of 222** decidable, **74** undecidable |
153+
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration** | **not reproduced**: 104 decidably elevated, 0 decidably not, 101 undecidable |
154154
| 141 and 132, two independent re-derivations | the card that filed this work ||
155155

156156
**The differences are not reconciled, and deliberately so.** The old census's
@@ -167,11 +167,11 @@ would report a smaller number and would not say so.
167167

168168
The fourth row is the one worth flagging to anyone citing it. **The 135 / 77%
169169
figure has no surviving corroboration anywhere in the tree.** This census reads
170-
103 of 221 (47%) as decidably elevated, with 101 more whose elevation is a
170+
104 of 222 (47%) as decidably elevated, with 101 more whose elevation is a
171171
run-time fact — so the claim is neither confirmed nor refuted, and the honest
172172
answer is that a static reading cannot settle it.
173173

174-
**Cite `9 / 221`, and say what it is**: the sites whose options argument was
174+
**Cite `9 / 222`, and say what it is**: the sites whose options argument was
175175
READ and holds no tenant context, against a decidably tenancy-enabled object.
176176
That is the control's provable yield surface. ⛔ Do not cite it as "the sites
177177
without tenant context" — **32 further sites** have an options argument this
@@ -183,28 +183,28 @@ cannot read, and they are neither in nor out.
183183

184184
| what | count |
185185
| :--- | ---: |
186-
| write call sites on the application surface | **221** |
187-
| …whose object name is statically decidable | 147 |
186+
| write call sites on the application surface | **222** |
187+
| …whose object name is statically decidable | 148 |
188188
| …whose object name is chosen at run time | 74 |
189-
| …against an object with tenancy ENABLED | 147 |
189+
| …against an object with tenancy ENABLED | 148 |
190190
| …against an object that declares tenancy off | 0 |
191-
| threading a tenant context | 137 |
191+
| threading a tenant context | 138 |
192192
| PROVABLY carrying none (options read, no context key) | **17** |
193193
| …of those, against a decidably tenancy-enabled object | **9** |
194194
| options argument UNREADABLE — may or may not carry one | 67 |
195195
| …of those, against a decidably tenancy-enabled object | 32 |
196-
| threading a decidably ELEVATED (`isSystem`) context | 103 |
196+
| threading a decidably ELEVATED (`isSystem`) context | 104 |
197197
| threading a context that is decidably NOT elevated | 0 |
198198
| threading a context whose elevation is a run-time fact | 101 |
199199

200200
| how the instrument reached the site | count |
201201
| :--- | ---: |
202-
| receiver carried a readable engine type | 176 |
202+
| receiver carried a readable engine type | 177 |
203203
| receiver erased, placed by the object NAME | 19 |
204204
| receiver erased, placed by an `object: string` PARAMETER | 15 |
205205
| receiver erased, placed by an `UNTYPED_RECEIVERS` row | 11 |
206206

207-
| object name spelled inline | 108 |
207+
| object name spelled inline | 109 |
208208
| object name spelled through a `const` | 39 |
209209
| object name is an `object: string` parameter | 19 |
210210
| object name is some other run-time expression | 55 |
@@ -224,11 +224,11 @@ holds still. They are required to be HERE and to say WHEN they were true;
224224
their values are not compared. The reasoning, and the measurement behind it,
225225
are in `scripts/check-tenant-audit-census.mjs`.
226226

227-
Measured on 2026-09-05 at `8a7446de8`.
227+
Measured on 2026-09-05 at `63a1a410e`.
228228

229229
| corpus scale (not enforced) | count |
230230
| :--- | ---: |
231-
| tracked non-test sources scanned | 547 |
231+
| tracked non-test sources scanned | 548 |
232232
| engine-shaped types recognised | 58 |
233233
| declared objects in the registry | 298 |
234234
| same-named calls subtracted as non-engine | 134 |

docs/audits/2026-08-tenant-audit-write-call-sites.counts.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ silent, and `node scripts/tenant-audit-census.mjs --write` is the resolution.
2929

3030
| Measure | Value |
3131
|---|---:|
32-
| Write call sites | 221 |
33-
| Object name statically decidable | 147 |
32+
| Write call sites | 222 |
33+
| Object name statically decidable | 148 |
3434
| Object name chosen at run time | 74 |
35-
| Against a tenancy-enabled object | 147 |
35+
| Against a tenancy-enabled object | 148 |
3636
| Against an object declaring tenancy off | 0 |
37-
| Threading a tenant context | 137 |
37+
| Threading a tenant context | 138 |
3838
| Provably carrying none | 17 |
3939
| …and decidably tenancy-enabled | 9 |
4040
| Options argument unreadable | 67 |
4141
| …and decidably tenancy-enabled | 32 |
42-
| Threading a decidably elevated context | 103 |
42+
| Threading a decidably elevated context | 104 |
4343
| Threading a decidably non-elevated context | 0 |
4444
| Threading a context of undecidable elevation | 101 |
4545

@@ -52,11 +52,11 @@ holds still. They are required to be HERE and to say WHEN they were true;
5252
their values are not compared. The reasoning, and the measurement behind it,
5353
are in `scripts/check-tenant-audit-census.mjs`.
5454

55-
Measured on 2026-09-05 at `8a7446de8`.
55+
Measured on 2026-09-05 at `63a1a410e`.
5656

5757
| corpus scale (not enforced) | count |
5858
| :--- | ---: |
59-
| tracked non-test sources scanned | 547 |
59+
| tracked non-test sources scanned | 548 |
6060
| engine-shaped types recognised | 58 |
6161
| declared objects in the registry | 298 |
6262
| same-named calls subtracted as non-engine | 134 |
@@ -70,7 +70,7 @@ Measured on 2026-09-05 at `8a7446de8`.
7070
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `delete` | `sys_approval_approver` | enabled | elevated | 2 |
7171
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `insert` | `sys_approval_approver` | enabled | elevated | 2 |
7272
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `insert` | `sys_approval_request` | enabled | elevated | 1 |
73-
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `update` | `sys_approval_request` | enabled | elevated | 9 |
73+
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `update` | `sys_approval_request` | enabled | elevated | 10 |
7474
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `insert` | `sys_approval_token` | enabled | elevated | 1 |
7575
| `packages/plugins/plugin-approvals/src/approval-service.ts` | `update` | `sys_approval_token` | enabled | elevated | 1 |
7676
| `packages/plugins/plugin-approvals/src/backfill-platform-row-organizations.ts` | `update` | `objectPlan.object` | undecidable | context, elevation undecidable | 1 |

0 commit comments

Comments
 (0)