Skip to content

Commit 8b117c0

Browse files
fix(service-queue): reclaim a running job whose lease ran out, and renew a live handler's lease (#30277)
<!-- claude-projects-attribution: {"github_login":"os-bill"} --> _Requested by **Bill** · [project thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgC5DLD798CXXfMJSTLB6VJi)_ domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb ## 维护者速读 - 改了什么:数据库队列在处理器还没跑完时进程挂了(崩溃、OOM、重新部署),那条任务现在会在租约到期后被下一次轮询收回:记一次失败尝试,按退避重排,次数用尽进死信;活着的慢处理器会定期续租,不会被别的节点抢走重跑。 - 为什么改:`leaseMs` 文档写着"到期后可由其他 worker 回收",但认领只读 `pending`,挂掉的任务永远停在 `running`,既不重试也不进死信,运维看不到。 - 风险与回滚:多节点(未开 soleWriter)时每个租约窗口(默认 30 秒)多一条带索引的读,空闲一小时从 125 条变 246 条;单写者(soleWriter,云端租户内核)只在开机多读一次,空闲一小时不再多读。处理器若同步阻塞事件循环超过大半个租约,会被当成死掉而重跑。回滚即撤本 PR,无数据迁移。 - 先看:`packages/services/service-queue/src/db-queue-adapter.ts` 的 `reclaimRow`(CAS 谓词)、`renewLease`(续租)、`reclaimExpired`(读的节流界)与 `nextDueAt`(并入最早活租约)。 - 两分钟验证:`pnpm --filter @objectstack/service-queue exec vitest run src/db-queue-idle-backoff.test.ts -t 30228`,预期 9 条全绿;其中"CRASHED-WORKER FIXTURE"一条在修复前的代码上是红的。 - 需要你做的:无,席位落地。 Fixes #30228 Clause-②: no — restores the documented `leaseMs` reclaim; no accept set or export widens Metadata-protocol: none ## What changes The card's acceptance, quoted: > 1. A `running` row whose `locked_until` has passed is claimable again (or moved to the retry path with its attempt counted), within one idle tick of the lease running out. > 2. A live worker that is still inside its lease is never double-claimed. > 3. Pins: a crashed-worker fixture (row left `running` with an expired lease) is reclaimed; a row inside its lease is not. The `nextDueAt` read #29919 adds (PR #30225) also counts the earliest lease expiry, so a sole-writer loop wakes for it. **Reclaim (item 1).** `pollOnce()` now starts with `reclaimExpired()`: one read of the table's `status = 'running'` rows ordered by `locked_until`, every queue's (reclaiming runs nothing, so a row of a queue this node does not subscribe to just goes back to `pending` for whoever does; the predicate is the claim read's plain `status` equality, served by the declared `status` index, with no operator a stand-in engine might not answer), and each row whose lease has passed (or that has no lease at all) goes to the retry path. Of the card's two options this takes the second, retry path with the attempt counted, because `attempts` is written only after a handler settles: a worker that died left the row one attempt short, so reclaiming without counting would retry forever a message that kills its worker every time (an OOM on a large payload). The reclaim writes that attempt down and takes the branch a thrown handler takes: `pending` on its backoff, or `dlq` once `max_attempts` is spent, with `last_error` naming the dead worker. A reclaimed row counts in the tick's result, so the loop snaps back to `pollIntervalMs` and claims it on its backoff rather than after the idle ceiling. **The reclaim write is a compare-and-set (item 2).** Its predicate re-states the expiry: `id`, `status: 'running'`, the `locked_by` it read, and `locked_until` null or at most now. A holder that renewed between the read and the write matches nothing and keeps its run. It is a `multi: true` write on purpose: the by-id path binds only the primary key and drops the rest of the predicate (the `outbox-dispatcher-scope.ts` precedent). Two workers reclaiming the same row write the same values from the same read and neither runs it, so that race costs nothing. `sys_job_queue` carries no tenant column (ADR-0131 D7), so the predicate writes need no tenant-audit warrant. **A live handler renews its lease (item 2).** This is the risk the reclaim itself introduces. Before, nothing reclaimed, so a handler slower than `leaseMs` was safe; with a reclaim and no renewal it would be reclaimed mid-run and run twice. `dispatch()` now moves its row's `locked_until` a full window ahead every third of `leaseMs` while the handler runs: a `multi: true` compare-and-set on `locked_by = workerId`, logged when it matches nothing (the lease was lost underneath). A lease therefore passes only when its holder stopped renewing: the process died, or its event loop was blocked for most of a window. That is how a dead worker is told apart from a slow one, and the `leaseMs` doc now says the one thing an author must know: keep synchronous stretches well under the lease. **The read is bounded, not per tick.** The read also returns the earliest lease still running. No lease can run out before the earlier of that lease and `now + leaseMs` (every running row was in the read, a row claimed after it holds at least `leaseMs` from its claim, and renewal only moves a lease later), so the next read waits until then. A full page means more may be hidden, so the next tick reads again. Cost without `soleWriter`: a busy 1 s poll pays one reclaim read per lease window; an idle loop at the 30 s ceiling pays one per tick, and an idle hour at the defaults goes from 125 to 246 reads (pinned). **A sole writer reads once per boot.** With `soleWriter` no other process claims rows, so the window term is dropped. The first read after `start` finds what a crashed previous process left behind; after it, the only leases that can run out are the live ones it saw and this process's own claims whose settling write (completed, retry, dead letter, release) failed and left the row `running`. The adapter keeps those unsettled claims' leases (renewal moves them, a settling write drops them, the next read replaces them all), reads again only when one runs out, and folds them into `nextDueAt`. An idle sole writer's hour carries one reclaim read, the boot's, against 120 for the same hour without the flag (pinned); its fresh first minute goes from 2 to 3 reads. **`nextDueAt` counts the earliest live lease (item 3).** Built on #30225's `DbQueueAdapter.nextDueAt` after it merged (771b2db). The reclaim read already returns the earliest lease still running; the adapter keeps it, and `nextDueAt` answers the earliest of that, a sole writer's unsettled claims and the earliest deferred `scheduled_for`, at no read of its own. That read ran on the empty tick the answer follows, or the bound it set has not passed. A `soleWriter` loop, which otherwise sleeps to its ceiling, now wakes when a row its own previous process left `running` across a restart runs out of lease. The window term (`now + leaseMs`) is deliberately not in the answer: it only bounds the next read, and folding it in would wake a sole-writer loop every lease window for nothing. **Docs.** Read sentence by sentence, none made false by this PR, left unchanged: `content/docs/plugins/packages.mdx:287` ("multi-node claim via a lease on the `db` adapter", note 6648413565) is now true as written; `content/docs/automation/webhooks.mdx`, the `dispatchSoleWriter` / `db.soleWriter` paragraph and the idle-cost sentences, describe the HTTP and notification dispatchers' reads and the flag's meaning, both still exact (the queue's sole-writer loop still sleeps to its next due time or the ceiling; a lease is one more due time); `content/docs/kernel/runtime-services/audit-service.mdx:71`'s `locked_until` is the account lock's field on an auth audit event, not the queue's; the package README's `leaseMs` line is now true. ## Tests `packages/services/service-queue/src/db-queue-idle-backoff.test.ts`, new `#30228` block, on that file's counting engine (its `update` already opens with `assertEngineUpdateDispatch`; it now applies a `multi` dispatch to the rows its `where` matches and reports the count): - **CRASHED-WORKER FIXTURE**: a row written `running`, `locked_by: 'worker_dead'`, lease 1 ms past: one tick returns it to `pending`, `attempts: 1`, `scheduled_for` = now + 1 s, `last_error` naming the worker; on its backoff it runs as attempt 2 and completes. - A lease that runs out while the loop is deep in its idle backoff (another node's crash, nothing tells this process) is reclaimed within one idle interval of expiry and run. - The lost run counts: a fixture at `attempts: 2` of 3 goes to `dlq`, and `listFailed` returns it. - A row inside its lease is not touched (no write at all). - CAS: a renewal landing between the reclaim read and its write leaves the row `running` under its holder. - A live handler three leases long, with a second adapter polling the same table: one run, `completed`, `attempts: 1`. - In #29919's block: a sole-writer adapter booting over a row its previous process left `running`, lease 40 s from expiry, ceiling 5 min: the row runs at exactly 41 s (reclaimed at the lease's expiry, then its 1 s backoff). - A sole writer's idle hour, with one job run at 10 min, makes exactly 1 reclaim read; the same hour without `soleWriter` makes 120 (the engine counts the reclaim read on its own). - A sole writer whose `completed` write fails once (the row stays `running` under itself) reclaims it when its 3 s lease runs out and runs it again: 2 runs, `completed`, `attempts: 2`, 2 reclaim reads (boot, then the expired lease). Moved pins in the same file, each with its reason in the comment: the idle hour (125 → 125 + 121), the flat-poll negative control (3602 → 3602 + 121), the per-tick cost (n claim reads + 1 reclaim read, then n on a tick inside the same window), the sole-writer fresh minute (2 → 3) and its ramping control (6 → 6 + 2). <details><summary>Crashed-worker fixture, before / after</summary> The six reclaim legs, run against the pre-fix adapter before the `origin/main` merge (`db-queue-adapter.ts` restored from the branch base `6f095b9c`, blob `67be673a`, then restored to HEAD and proved byte-identical). The seventh leg (sole-writer restart) needs #30225's loop; its before-reading is ablation A4 below. ``` before × CRASHED-WORKER FIXTURE: an expired lease goes back to the retry path … AssertionError: expected +0 to be 1 × a lease that runs out while the loop is deep in its idle backoff … row still { status: 'running', attempts: 0 } × the lost run counts: … dead-lettered, not retried forever AssertionError: expected +0 to be 1 ✓ a row INSIDE its lease is never reclaimed (trivially: nothing reclaimed anything) ✓ the reclaim is a compare-and-set … (trivially) ✓ a LIVE handler three leases long is never double-claimed … (trivially) after ✓ all of them, and the whole package: Test Files 5 passed (5), Tests 91 passed (91) ``` </details> <details><summary>Ablations (each through scripts/ablation-replace.mjs, restored to HEAD blob, git diff HEAD empty)</summary> | Leg | Mutation | Expected | Observed | |---|---|---|---| | A1 | `renewLease` replaced by a no-op | live handler reclaimed and run twice | red: `expected [ 'a', 'b' ] to deeply equal [ 'a' ]` | | A2 | the expired-lease term deleted from the reclaim predicate | CAS leg red | red: `expected 1 to be +0` (a renewed lease was taken back) | | A3 | the `now + leaseMs` window bound dropped | a lease another node takes after the read is never re-read | red: idle-reclaim leg, plus both cost pins (measured before the merge: 3602 and 125 reads against the then pins 3722 and 245) | | A4 | the live lease dropped from `nextDueAt` | sole-writer restart leg red | red: `expected [] to have a length of 1` (no run inside the minute) | | A5 | a sole writer's claims not tracked as unsettled | failed-settle leg red | red: `expected [ … ] to have a length of 2 but got 1` (the row stays `running`) | | A6 | the window term kept for a sole writer | sole-writer idle-hour pin red | red: `expected 120 to be 1` | A1–A6 were re-run on head `8d430e0c` (A3 now also reds the sole-writer and ramp-control pins, 5 legs in all). A0 (the whole pre-fix adapter) is the before/after above. Each leg's on-disk landing and restore are printed by the tool (`anchor 1 -> 0`, `blob == HEAD`, `git diff HEAD` empty). </details> ## Acceptance notes - The pending → running claim is still the by-id write it was (the comment above it says so: concurrent claimers may both take a `pending` row, and delivery is at-least-once). This PR does not change that; it adds no new way to double-claim, and the reclaim never runs anything itself. - A worker whose lease was reclaimed while it was stalled and then finishes still writes its terminal state by id. The renewal logs the lost lease; making the terminal writes a compare-and-set on `locked_by` too would close that last window and is a separate change. - Deviation: `census +2 write sites (reclaimRow, renewLease), surface widened by seat 03:38Z`. `check:tenant-audit-census` counts every engine write call site, so `content/docs/permissions/tenant-audit-census.mdx` and `docs/audits/2026-08-tenant-audit-write-call-sites.counts.md` are regenerated (`node scripts/tenant-audit-census.mjs --write`) and the page's hand-written figures moved with them (259 → 261 sites, 182 → 184 decidable, 107 → 109 undecidable, 66 → 68 const-named, 56% → 55%). - Leases assume every node runs the same `leaseMs`, as they do when the nodes share one `QueueServicePlugin` configuration. A node with a shorter lease than this one's is still reclaimed, at most one lease window late. ## Verification All readings on head `8d430e0c` (code identical to `87fd3a2e`; the last commit edits only the changeset). - `pnpm --filter @objectstack/service-queue exec vitest run --maxWorkers=2`: Test Files 5 passed (5), Tests 91 passed (91). - `pnpm --filter @objectstack/plugin-email exec vitest run --maxWorkers=2` (after building its closure): Test Files 48 passed (48), Tests 785 passed (785). On `9a2d7712` its suites were red in CI (Test Core 7/8, "fakeEngine: unsupported operator $in"): the reclaim read then filtered `queue: { $in: … }`, which that package's stand-in engine refuses; the read now carries only `status` equality. - `pnpm --filter @objectstack/service-queue typecheck` (`tsc --noEmit`, whose program lists `db-queue-idle-backoff.test.ts`): exit 0. - `eslint --no-inline-config` on the two changed source files: exit 0. `pnpm lint`: exit 0. <details><summary>Derived gates: 90 families, 87 green, 3 NOT MEASURED</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` against merge base `771b2db20`, each command's exit code recorded before any pipe, reconciled with `--ran`: `90 derived famil(ies) accounted for — 87 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)`. - NOT MEASURED, exit 3 (PREREQUISITE NOT MET), left to CI: `node scripts/check-plugin-teardown-shape.mjs --self-test` (its positive control is a pinned commit a shallow clone cannot reach), `pnpm --filter @objectstack/spec run check:skill-examples` (needs the client packages built), `pnpm check:dual-build-cjs-loads` (needs every package's `dist/`; this worktree built only the queue and email closures). - `pnpm check:tenant-audit-census` and its `--self-test`: exit 0 after the census regeneration listed under Acceptance notes (red before it, 259 vs 261). </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01Utp6CaNicTDZwYQgDRwD7a)_ <!-- signed: domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a26abbc commit 8b117c0

5 files changed

Lines changed: 630 additions & 56 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/service-queue': patch
3+
---
4+
5+
fix(service-queue): a job whose worker died mid-handler is reclaimed once its lease runs out, instead of staying `running` forever
6+
7+
Clause-②: no — restores the documented `leaseMs` reclaim; no accept set or export widens
8+
Metadata-protocol: none
9+
10+
`DbQueueAdapterOptions.leaseMs` documented a lease "before another worker may reclaim", but the claim read only `pending` rows, so a worker that crashed, was OOM-killed or was redeployed mid-handler left its `sys_job_queue` row `running` for good: never retried, never dead-lettered.
11+
12+
- **Reclaim.** A poll tick reads the table's `running` rows (one read for every queue, at most once per lease window) and returns each whose `locked_until` has passed to the retry path. The lost run counts as an attempt: the row goes back to `pending` on its backoff, or to `dlq` once `maxAttempts` is spent, with `last_error` saying the lease expired. The write is a compare-and-set on the expired lease, so a holder that renewed in the meantime keeps its run.
13+
- **Renewal.** While a handler runs, its worker moves its lease a full window ahead every third of `leaseMs`, so a slow but live handler is never reclaimed and run twice. A handler must not block the event loop for most of a lease window.
14+
- **Sole writer.** With `soleWriter` no other process claims rows, so the reclaim reads once per boot (finding what a crashed previous process left behind) and again only when one of this process's own claims was left unsettled by a failed write and its lease runs out. `nextDueAt` folds in those leases and the earliest live lease the last read saw, at no read of its own, so the loop wakes for them instead of at the idle ceiling.
15+
- **Cost.** Without `soleWriter`, an idle queue pays one more indexed read per lease window (default 30 s): an idle hour at the defaults goes from 125 to 246 reads. With `soleWriter`, one more read per boot: a fresh kernel's first minute goes from 2 to 3 reads, and an idle hour carries no other.

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

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

133133
The same holds twice over for the context. An options argument spelled as a
134134
literal can be read; one spelled `options`, `{ ...opts }`, or handed through a
135-
forwarding shim cannot, and **55 of the 259 sites are spelled that way**. A
135+
forwarding shim cannot, and **55 of the 261 sites are spelled that way**. A
136136
context resolved from an inline literal or a local `const` can be tested for
137137
`isSystem`; one arriving from a helper call cannot.
138138

@@ -197,10 +197,10 @@ reproduce them. Where it disagrees, it disagrees on the page:
197197

198198
| carried figure | where it survives | this census |
199199
| :--- | :--- | ---: |
200-
| 175 write call sites | quoted in the merged changeset | **259** |
200+
| 175 write call sites | quoted in the merged changeset | **261** |
201201
| 24 carrying no tenant context | quoted in the merged changeset | **2** provable and tenancy-enabled; **29** more whose options argument is unreadable |
202-
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card | **182 of 259** decidable, **77** undecidable |
203-
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration** | **not reproduced**: 144 decidably elevated, 0 decidably not, 107 undecidable |
202+
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card | **184 of 261** decidable, **77** undecidable |
203+
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration** | **not reproduced**: 144 decidably elevated, 0 decidably not, 109 undecidable |
204204
| 141 and 132, two independent re-derivations | the card that filed this work | — |
205205

206206
**The differences are not reconciled, and deliberately so.** The old census's
@@ -211,17 +211,17 @@ at any commit.
211211

212212
Two structural facts do plausibly widen this reading against any hand or regex
213213
one, and both are counted in the generated tables below: the 62 sites reached
214-
through an erased (`any`) receiver, and the 66 that name their object through a
214+
through an erased (`any`) receiver, and the 68 that name their object through a
215215
`const` rather than inline. An instrument that read either the way a person does
216216
would report a smaller number and would not say so.
217217

218218
The fourth row is the one worth flagging to anyone citing it. **The 135 / 77%
219219
figure has no surviving corroboration anywhere in the tree.** This census reads
220-
144 of 259 (56%) as decidably elevated, with 107 more whose elevation is a
220+
144 of 261 (55%) as decidably elevated, with 109 more whose elevation is a
221221
run-time fact — so the claim is neither confirmed nor refuted, and the honest
222222
answer is that a static reading cannot settle it.
223223

224-
⇒ **Cite `2 / 259`, and say what it is**: the sites whose options argument was
224+
⇒ **Cite `2 / 261`, and say what it is**: the sites whose options argument was
225225
READ and holds no tenant context, against a decidably tenancy-enabled object.
226226
That is the control's provable yield surface. ⛔ Do not cite it as "the sites
227227
without tenant context" — **29 further sites** have an options argument this
@@ -233,29 +233,29 @@ cannot read, and they are neither in nor out.
233233

234234
| what | count |
235235
| :--- | ---: |
236-
| write call sites on the application surface | **259** |
237-
| …whose object name is statically decidable | 182 |
236+
| write call sites on the application surface | **261** |
237+
| …whose object name is statically decidable | 184 |
238238
| …whose object name is chosen at run time | 77 |
239-
| …against an object with tenancy ENABLED | 176 |
239+
| …against an object with tenancy ENABLED | 178 |
240240
| …against an object that declares tenancy off | 6 |
241-
| threading a tenant context | 196 |
241+
| threading a tenant context | 198 |
242242
| PROVABLY carrying none (options read, no context key) | **8** |
243243
| …of those, against a decidably tenancy-enabled object | **2** |
244244
| options argument UNREADABLE — may or may not carry one | 55 |
245245
| …of those, against a decidably tenancy-enabled object | 29 |
246246
| threading a decidably ELEVATED (`isSystem`) context | 144 |
247247
| threading a context that is decidably NOT elevated | 0 |
248-
| threading a context whose elevation is a run-time fact | 107 |
248+
| threading a context whose elevation is a run-time fact | 109 |
249249

250250
| how the instrument reached the site | count |
251251
| :--- | ---: |
252-
| receiver carried a readable engine type | 197 |
252+
| receiver carried a readable engine type | 199 |
253253
| receiver erased, placed by the object NAME | 52 |
254254
| receiver erased, placed by an `object: string` PARAMETER | 5 |
255255
| receiver erased, placed by an `UNTYPED_RECEIVERS` row | 5 |
256256

257257
| object name spelled inline | 116 |
258-
| object name spelled through a `const` | 66 |
258+
| object name spelled through a `const` | 68 |
259259
| object name is an `object: string` parameter | 7 |
260260
| object name is some other run-time expression | 70 |
261261

@@ -307,13 +307,13 @@ holds still. They are required to be HERE and to say WHEN they were true;
307307
their values are not compared. The reasoning, and the measurement behind it,
308308
are in `scripts/check-tenant-audit-census.mjs`.
309309

310-
Measured on 2026-11-03 at `1c86b45c5`.
310+
Measured on 2026-11-05 at `e40e04854`.
311311

312312
| corpus scale (not enforced) | count |
313313
| :--- | ---: |
314-
| tracked non-test sources scanned | 856 |
315-
| engine-shaped types recognised | 103 |
316-
| declared objects in the registry | 126 |
317-
| same-named calls subtracted as non-engine | 226 |
314+
| tracked non-test sources scanned | 871 |
315+
| engine-shaped types recognised | 105 |
316+
| declared objects in the registry | 127 |
317+
| same-named calls subtracted as non-engine | 229 |
318318

319319
{/* END GENERATED: tenant-audit-census */}

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@ silent, and `node scripts/tenant-audit-census.mjs --write` is the resolution.
3333

3434
| Measure | Value |
3535
|---|---:|
36-
| Write call sites | 259 |
37-
| Object name statically decidable | 182 |
36+
| Write call sites | 261 |
37+
| Object name statically decidable | 184 |
3838
| Object name chosen at run time | 77 |
39-
| Against a tenancy-enabled object | 176 |
39+
| Against a tenancy-enabled object | 178 |
4040
| Against an object declaring tenancy off | 6 |
41-
| Threading a tenant context | 196 |
41+
| Threading a tenant context | 198 |
4242
| Provably carrying none | 8 |
4343
| …and decidably tenancy-enabled | 2 |
4444
| Options argument unreadable | 55 |
4545
| …and decidably tenancy-enabled | 29 |
4646
| Threading a decidably elevated context | 144 |
4747
| Threading a decidably non-elevated context | 0 |
48-
| Threading a context of undecidable elevation | 107 |
48+
| Threading a context of undecidable elevation | 109 |
4949

5050
## Subtractions the census could NOT defend — enforced
5151

@@ -90,14 +90,14 @@ holds still. They are required to be HERE and to say WHEN they were true;
9090
their values are not compared. The reasoning, and the measurement behind it,
9191
are in `scripts/check-tenant-audit-census.mjs`.
9292

93-
Measured on 2026-11-03 at `1c86b45c5`.
93+
Measured on 2026-11-05 at `e40e04854`.
9494

9595
| corpus scale (not enforced) | count |
9696
| :--- | ---: |
97-
| tracked non-test sources scanned | 856 |
98-
| engine-shaped types recognised | 103 |
99-
| declared objects in the registry | 126 |
100-
| same-named calls subtracted as non-engine | 226 |
97+
| tracked non-test sources scanned | 871 |
98+
| engine-shaped types recognised | 105 |
99+
| declared objects in the registry | 127 |
100+
| same-named calls subtracted as non-engine | 229 |
101101

102102
## Every site
103103

@@ -248,7 +248,7 @@ Measured on 2026-11-03 at `1c86b45c5`.
248248
| `packages/services/service-messaging/src/sql-outbox.ts` | `update` | `this.objectName` | undecidable | options unreadable | 1 |
249249
| `packages/services/service-queue/src/db-queue-adapter.ts` | `delete` | `sys_job_queue` | enabled | context, elevation undecidable | 2 |
250250
| `packages/services/service-queue/src/db-queue-adapter.ts` | `insert` | `sys_job_queue` | enabled | context, elevation undecidable | 1 |
251-
| `packages/services/service-queue/src/db-queue-adapter.ts` | `update` | `sys_job_queue` | enabled | context, elevation undecidable | 6 |
251+
| `packages/services/service-queue/src/db-queue-adapter.ts` | `update` | `sys_job_queue` | enabled | context, elevation undecidable | 8 |
252252
| `packages/services/service-settings/src/config-change-audit.ts` | `insert` | `sys_audit_log` | enabled | context, elevation undecidable | 1 |
253253
| `packages/services/service-settings/src/settings-service-plugin.ts` | `insert` | `objectName` | undecidable | options unreadable | 1 |
254254
| `packages/services/service-settings/src/settings-service-plugin.ts` | `update` | `objectName` | undecidable | options unreadable | 2 |

0 commit comments

Comments
 (0)