Skip to content

Commit 3d8960c

Browse files
feat(cli,plugin-audit): os migrate audit-mail-bodies masks the archive datasource too (#24934)
Fixes #24899 Clause-②: yes (widening) `os migrate audit-mail-bodies` now masks the mail bodies in the `sys_audit_log` copies the lifecycle Archiver already moved to the `archive` datasource. Hot copies masked by the run are archived masked later, so the same run covers those too. The command keeps its dry-run / `--apply` / `--yes` / `--json` shape, and its report is still counts only. > The claim expected `Clause-②: no`. From the diff it is `yes (widening)`: the `--json` document gains an `archive` key, and `@objectstack/plugin-audit` gains exports. Both packages take a `minor` changeset. ## Measured Every reading below is from `origin/main` at `81e6edd46` and is a count, a status or a boolean. No body value was printed. 1. **Which deployments can have an `archive` datasource, and who reads it.** - There is no special configuration. The Archiver resolves `archive.to` by driver name (`packages/objectql/src/lifecycle/lifecycle-service.ts:1344`, `engine.datasource(name)` → `packages/objectql/src/engine.ts:21671`, the `drivers` map). A driver is registered under the name `archive` in one of three ways: - (a) A stack-config `datasources` entry. Nothing routes objects to an archive, so the ADR-0062 D2 gate connects it only with `autoConnect: true` (`packages/services/service-datasource/src/datasource-connection-service.ts:301-305`, `packages/spec/src/data/datasource.zod.ts:665`). - (b) A runtime datasource row: a `sys_metadata` row of type `datasource`, created in Setup → Datasources and rehydrated by `os serve` at boot (`packages/services/service-datasource/src/datasource-admin-plugin.ts:125-139`, `:734-760`). - (c) A host plugin that publishes a `driver.archive` service (`packages/objectql/src/plugin.ts:1060-1076`). - No shipped template or example declares one. There are zero `name: 'archive'` datasources under `examples/` and `packages/create-objectstack`. Every deployment that declares none keeps every row hot: `archive-pending` (`lifecycle-service.ts:1349-1355`). - Readers: only the Archiver reads the archive through the platform. `engine.datasource(` has one caller, `lifecycle-service.ts:1344`, and the engine never routes `sys_audit_log` there. Otherwise there is direct database access, and the datasource admin door: `GET /datasources/:name/remote-tables` plus the object-draft flow, gated by `requireDatasourceAdmin` (`packages/services/service-datasource/src/admin-routes.ts:706-707`). A federated object an admin binds over the archived table would serve it over REST. 2. **Does the archive copy pass through the mask?** No. It is a verbatim driver-to-driver copy: `hot.find` → `cold.upsert(object, row, ['id'])` → `hot.bulkDelete` (`lifecycle-service.ts:1471-1520`), with no hook, no writer and no mask. So a hot copy written before #24688 and archived before the operator runs the mask lands in the archive in clear. Pinned below in case 4: a copy masked hot is archived masked. 3. **A supported way to run the mask over the archive?** None before this PR: the runner took the engine only (`packages/plugins/plugin-audit/src/mail-log-body-migration.ts`, `migrateMailLogBodyCopies`). Two facts made one sound: - The runner's reads and writes fit the `IDataDriver` contract (`find` with `where` / `fields` / `orderBy` / `limit`, and `update(object, id, data, { preserveAudit })`). - `bootSchemaStack` already connects declared datasources when asked to (`composeDeclaredDatasources`, `packages/cli/src/utils/schema-migrate.ts:1345-1350`). 4. **`sys_activity`**: no archive. Its lifecycle is `telemetry`, with 14-day retention and rotation (`packages/plugins/plugin-audit/src/objects/sys-activity.object.ts:29-34`), so its copies are only ever hot. ## What changed - **`@objectstack/plugin-audit`.** - `migrateArchivedMailLogBodyCopies(driver, logger, options)` runs the same pages and planners through a datasource's driver, as the Archiver itself does. `preserveAudit` keeps each row's `updated_at`. - An archive table nothing was archived into yet reads as no rows, by `isMissingTableError`. Any other refused read is a failure. - New `objects` option on both runners. The engine runner's behaviour is unchanged. - **`os migrate audit-mail-bodies`.** - After the hot run, it reads each copy table's declared `lifecycle.archive.to` and runs the archive runner over `engine.getDriverByName(to)`. - It boots with `composeDeclaredDatasources: true`, so a declared archive is connected as `os serve` connects it. - When the target is not connected but an active runtime datasource row of that name exists, the archive reads `not-connected` and the command exits `1`. It does not report a clean archive. - The success path ends with `exitOneShotCommand`, as `plan` / `apply` / `unmapped-columns` do. Without it, the pool the declared connection opened kept the process alive after "Graceful shutdown complete". Measured here: it hung, and `TaskStop` ended it. - **Docs:** the command's section of `content/docs/deployment/cli.mdx`, plus its row in the command table. - **Changeset:** `.changeset/24899-archived-mail-bodies.md` (`minor` for both packages). ## Proof - **End to end.** `packages/cli/src/commands/migrate/audit-mail-bodies.archive.integration.test.ts`, 7/7. - The fixture is a project that declares an `archive` SQLite datasource (`autoConnect: true`). A served-shape boot seeds 18.0-writer copies, and the real Archiver (`lifecycle.sweep()`) moves 3 rows. 2 of them hold a clear body. - Dry run: hot to-mask 1; archive `examined`, scanned 2, to-mask 2; nothing written. - `--apply`: archive rewritten 2; clear copies 0 in either store; every other archived column equal, `updated_at` included. - Re-run: rewritten 0, archived rows unchanged. - The copy that was hot during the run, aged and archived by a second sweep: archived masked (archive rows 4, clear 0). - No run printed the sentinel. - Runtime-row archive: `not-connected`, exit 1. No archive at all: `none`, exit 0. - **Ablation.** Removing the archive lookup (`const driver = undefined`) turns cases 1–4 red (4 failed, 3 passed). Restored to the HEAD blob `b6e1355a`. - **Runner unit tests** over a real `SqliteWasmDriver` store provisioned as the Archiver does (`syncSchema` + `upsert`): dry run, apply, idempotent re-run, an archive with no table yet, a refused read. ## Local verification (head `39fc914f`; the diff since `8a87750c` is one test-file header comment) - `@objectstack/plugin-audit`: `pnpm test` 983/983 (60 files); `typecheck` green. - `@objectstack/cli`: build + `typecheck` green. The `unit` project: 4634/4635. The one failure was `test/hook-timeout-override-refusal.test.ts` CONTROL, a 5000 ms timeout while the gate sweep ran beside it. Re-run alone: 4/4. It spawns a process this diff does not touch. - `@objectstack/cli` `integration`, run locally because this diff touches that layer: the new archive pin 7/7; `data-commands.absent-database` + `schema-migrate.one-shot-family` 151/151. `test/json-stdout-purity.e2e.test.ts` is left to CI. - Changed files: `eslint --no-inline-config` clean. - `node scripts/pm/dispatch-gates.mjs --commands`: 101 derived, every one run, exit codes captured before any pipe; `--ran` reconciles 101 / 97 run / 4 NOT MEASURED / 0 unrun. - **The census was exit 1 (2) on `39fc914f`. It is exit 0 on `4fb9afa1`.** - This diff adds one write call site (the archive runner's `driver.update`), so the count goes 232 → 233. - The seat widened the surface in amendment 6295968920 and again in 6296052814. - `3340a4ad` ran `node scripts/tenant-audit-census.mjs --write`. - `105a8110` and `4fb9afa1` made the 11 hand-written prose figures +1, numbers only. - On `4fb9afa1`, `node scripts/check-tenant-audit-census.mjs --self-test` passes 27 cases, and the plain run reports 233 write call sites certified and 23 prose figures held. Both exit 0. - The new census row: `packages/plugins/plugin-audit/src/mail-log-body-migration.ts` · `update` · object chosen at run time (the CLI limits the runner to `sys_audit_log`) · tenancy `undecidable` · context `options unreadable` · 1. The write goes to the archive datasource through its own driver, which no tenant scope covers. It masks every organization's archived copies by design, and only the operator command reaches it. - **exit 3, prerequisite not met (4), NOT MEASURED, left to CI:** `check-plugin-teardown-shape --self-test` (shallow clone), `spec check:skill-examples`, `check:dual-build-cjs-loads`, `check:i18n-coverage` (each needs the full monorepo build). - every other family: exit 0. - `node scripts/pm/check-governed-merges.mjs --branch origin/claude/issue-24899-archived-mail-bodies`: NOT governed; 791 changed lines. ## Surface question (answered) The seat widened the card's surface to the census regeneration and its 11 prose figures (6295968920, 6296052814 on #24899, plus the line-210 figure by message). The census is regenerated by the generator only, and `Lint & Repo Gates`' census step is green on `4fb9afa1`. ## Acceptance notes - **Behaviour change of the boot:** the command now connects the datasources the config declares, as `os serve` does. A declared datasource that fails to connect therefore fails the command's boot, as it fails `os serve`'s. Before, the command never tried to connect it. - **Stale comment, fixed here** (`f72911d8`, amendment 6296052814): `packages/cli/src/utils/schema-migrate.ts:1176-1179` now names `os migrate audit-mail-bodies` among the callers of `composeDeclaredDatasources`. This diff had made the old text false. - **Not covered by this command:** an archive registered only by a host plugin (`driver.archive`) that the CLI boot does not compose. It reads `none` unless a runtime datasource row of that name exists. A code-declared archive *without* `autoConnect: true` is never connected by `os serve` either, so the Archiver never archived to it. - **17.x:** the writer mask (#24688) and this command shipped in 18.0.0. A 17.x line, if cut, has neither, so it would owe the whole mail-body note (hot and archive), not only this half. --- _Generated by [Claude Code](https://claude.ai/code/session_01WQWCrorWxW5zDh4n6jCEF5)_ <sub>Census, surface and stale-comment lines updated by the epic seat on 2026-10-20 (about 16:50Z) to match head `4fb9afa1`.</sub> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 865d2c8 commit 3d8960c

10 files changed

Lines changed: 896 additions & 69 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/cli': minor
3+
'@objectstack/plugin-audit': minor
4+
---
5+
6+
feat(cli,plugin-audit): `os migrate audit-mail-bodies` also masks the mail bodies in the audit copies already moved to the `archive` datasource
7+
8+
Clause-②: yes (widening)
9+
10+
**Re-run `os migrate audit-mail-bodies --apply` on a deployment with an archive datasource.** The 18.0.0 command masked the hot audit tables only.
11+
12+
- **What was left in clear.** `sys_audit_log` declares `lifecycle.archive: { after: '90d', to: 'archive', keep: '7y' }`. Past the hot window, the lifecycle Archiver copies its rows to the datasource named `archive` as they are, then deletes them hot. A `sys_email` / `sys_identity_email` copy archived before the audit writer masked the mail body still holds `body_text` / `body_html` in clear there, password-reset, verification, magic-link and invitation links included, for as long as the archive keeps it. The engine never routes `sys_audit_log` to the archive, so the hot run could not reach those rows.
13+
- **What changed.** After the hot run, the command reads the archive target each copy table declares (`sys_audit_log` only; `sys_activity` declares none) and runs the same mask, in the same dry-run or `--apply` mode, through that datasource's driver. Each archived row keeps every other column, `updated_at` included. The run is idempotent and its report is counts only. A hot copy masked first is archived masked, so one run covers both the copies already archived and the ones still to be.
14+
- **Which archives it reaches.** The command now connects the managed datasources the stack config declares, through the same connect service `os serve` uses. An archive datasource is declared with `autoConnect: true`, since nothing routes objects to it. It is NOT examined, and the run reports `not-connected` and exits `1` rather than reporting a clean archive, in two cases. In the first, the archive is declared but this boot did not connect it; the entry carries the connect status and reason. In the second, the archive is a runtime datasource created in Setup → Datasources, which `os serve` connects and this command does not; declare it in the config's `datasources` and re-run.
15+
- **What `none` means.** No datasource of that name is declared and connected on this boot, none was attempted, and no active runtime datasource row names one. That is all it checks. An archive a host registers in code outside the stack config is not reached and reads `none`. To mask it, declare it in the stack config's `datasources` with `autoConnect: true`.
16+
- **The command's boot can now fail on a declared datasource.** A bound, mapped or boot-critical declared datasource that cannot connect now fails the command's boot with exit `1`, as it fails `os serve`'s. `OS_ALLOW_DRIVER_CONNECT_FAILURE` is the same escape.
17+
- **Run it with the server stopped**, or re-run it once and read 0 to mask in both halves. A live server's Archiver can move a row between the hot run and the archive run.
18+
- **New in the `--json` document:** an `archive` array, one entry per declared target: `{ datasource, objects, status, report?, connection? }`, with `status` one of `examined`, `none`, `not-connected` or `unknown`. The `report` key and its shape are unchanged.
19+
- **New exports from `@objectstack/plugin-audit`:** `migrateArchivedMailLogBodyCopies(driver, logger, options)`, the runner over a datasource's driver, and an `objects` option on both runners, which limits a run to the named copy tables. Types `MailLogBodyArchiveDriver`, `MailLogBodyCopyObject` and `MailLogBodyMigrationOptions`.

‎content/docs/deployment/cli.mdx‎

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,7 +1204,7 @@ where the data lives.
12041204
| `os migrate comment-reactions` | Carry the emoji reactions a 17.x deployment stored in the retired `sys_comment.reactions` column into `sys_comment_reaction` records, list every stored value that does not read, and record the verified run that lets `os migrate apply --allow-destructive` drop the column |
12051205
| `os migrate organization-ownership` | The v18 ownership ceremony (ADR-0131 D10): write a plan of what each table's rows will become, then `--apply` that reviewed plan. It upgrades a database 17.x or an 18.0 pre-release (`18.0.0-next.*`) created; a v18 server refuses to start on one until it has run. See [below](#os-migrate-organization-ownership) |
12061206
| `os migrate activity-changed-fields` | Stamp the two read-rule columns (`changed_fields`, `milestone_key`) on the `sys_activity` rows written before they existed. Until it runs, those rows are withheld from every reader but the system, so run it at once after upgrading. No flag; idempotent |
1207-
| `os migrate audit-mail-bodies` | Mask the mail bodies (`body_text`, `body_html`) that `sys_audit_log` and `sys_activity` copies of `sys_email` / `sys_identity_email` rows still hold in clear, written before the audit writer masked them. Reports counts only. No flag; idempotent. See [below](#os-migrate-audit-mail-bodies) |
1207+
| `os migrate audit-mail-bodies` | Mask the mail bodies (`body_text`, `body_html`) that `sys_audit_log` and `sys_activity` copies of `sys_email` / `sys_identity_email` rows still hold in clear, written before the audit writer masked them. Covers the `archive` datasource `sys_audit_log` archives to as well. Reports counts only. No flag; idempotent. See [below](#os-migrate-audit-mail-bodies) |
12081208
| `os migrate comment-parent-pair` | Stamp the parent pair (`object_name`, `record_id`) on the `sys_comment` rows written before it existed. Until it runs, a broad comment read past 2,000 candidate rows omits comments the reader may see, and its total agrees with the short list, so run it at once after upgrading. No flag; idempotent |
12091209
| `os migrate meta --stored` | Replay the metadata conversion chain over this deployment's `sys_metadata` rows and rewrite the ones still carrying a pre-protocol shape. Hygiene, not a gate — nothing depends on it having run |
12101210
| `os migrate security-catalog-overlays` | List the environment-wide permission-set and position rows (the legacy plurals `permissions` / `positions` included) stored over a name a configured package holds — the rows a v18 cold boot refuses — each with the package that holds its name; `--apply` deletes them, one history tombstone per row. Run it before the first v18 boot, with the flags and environment the deployment boots with: the platform security plugin's sets are held only where `os serve` composes it (an auth secret set, and the `auth` tier on), and the step takes `serve`'s `--preset` and `--dev` with `serve`'s meaning. Adopts nothing; exits 1 while it lists a row |
@@ -1501,9 +1501,55 @@ value.
15011501
15021502
**Idempotent**: a masked body is the mask, so a second run masks nothing, and
15031503
re-running is how an interrupted run is finished. A failed read or write exits
1504-
`1`. The copies age out with each table's retention (14 days for activity, 90
1505-
for the audit log, unless the deployment lengthens it); run it once after
1506-
upgrading rather than wait for that.
1504+
`1`. Activity copies age out with its 14-day retention. Audit-log copies do not
1505+
age out: with no archive datasource they are retained hot, and with one they
1506+
are moved there after 90 days and kept for seven years. Run it once after
1507+
upgrading.
1508+
1509+
**Run it with the server stopped**, or re-run it once and read 0 to mask in
1510+
both halves. A live server's Archiver sweeps on its own schedule, and a row it
1511+
moves between the hot run and the archive run can land in the archive
1512+
unmasked.
1513+
1514+
**The archive datasource too.** `sys_audit_log` declares
1515+
`lifecycle.archive: { after: '90d', to: 'archive', keep: '7y' }`: past the hot
1516+
window, the lifecycle Archiver copies its rows to the datasource named
1517+
`archive` as they are, and deletes them hot. A copy archived before the writer
1518+
masked still holds the body there, for up to seven years. After the hot run,
1519+
the command reads the archive target `sys_audit_log` declares and runs the same
1520+
mask, in the same dry-run or `--apply` mode, over that datasource. Its rows keep
1521+
every other column, `updated_at` included. A hot copy masked first is archived
1522+
masked, so one run covers both the copies already archived and the ones still
1523+
to be. `sys_activity` declares no archive: its copies are only ever hot.
1524+
1525+
The command connects the managed datasources the stack config declares through
1526+
the same connect service `os serve` uses. A managed datasource nothing routes
1527+
to is connected only with `autoConnect: true`, which is how an archive
1528+
datasource is declared:
1529+
1530+
```ts
1531+
datasources: [
1532+
{ name: 'archive', driver: 'sqlite', config: { filename: './data/archive.db' }, autoConnect: true },
1533+
],
1534+
```
1535+
1536+
The `--json` document carries one `archive` entry per declared target, with a
1537+
`status`:
1538+
1539+
| `status` | Meaning | Exit |
1540+
| :--- | :--- | :--- |
1541+
| `examined` | Connected and masked (or counted); `report` holds the counts | `0`, or `1` on a failed read or write |
1542+
| `none` | No datasource of that name is declared and connected on this boot, none was attempted, and no active runtime datasource row names one. That is all it checked | `0` |
1543+
| `not-connected` | The archive was **not** examined. Either the stack config declares it and this boot did not connect it (`connection` carries the connect status and reason), or the database holds an active runtime datasource of that name (one created in Setup → Datasources), which `os serve` connects and this command does not | `1` |
1544+
| `unknown` | Not connected, and the datasource rows could not be read | `1` |
1545+
1546+
On `not-connected` for a runtime datasource, declare the archive datasource in
1547+
the stack config's `datasources` (with `autoConnect: true`), then re-run. For a
1548+
declared one, make it reachable from where the command runs, then re-run.
1549+
1550+
An archive that a host registers in code outside the stack config (a plugin
1551+
that publishes the driver itself) is not reached and reads `none`. To mask it,
1552+
declare it in the stack config's `datasources` with `autoConnect: true`.
15071553
15081554
#### `os migrate organization-ownership`
15091555

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

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

123123
The same holds twice over for the context. An options argument spelled as a
124124
literal can be read; one spelled `options`, `{ ...opts }`, or handed through a
125-
forwarding shim cannot, and **50 of the 232 sites are spelled that way**. A
125+
forwarding shim cannot, and **51 of the 233 sites are spelled that way**. A
126126
context resolved from an inline literal or a local `const` can be tested for
127127
`isSystem`; one arriving from a helper call cannot.
128128

@@ -150,8 +150,8 @@ now **0**: nothing on this surface threads a context that provably lacks the fla
150150

151151
**"No tenant context" counted sites it had not read.** An options argument the
152152
walker could not parse was folded into the same bucket as one it had read and
153-
found empty. That published **58 sites "carrying no tenant context at all"**
154-
when 8 said so and 50 were simply unread — an over-claim in the *alarming*
153+
found empty. That published **59 sites "carrying no tenant context at all"**
154+
when 8 said so and 51 were simply unread — an over-claim in the *alarming*
155155
direction, on the very figure this page tells other cards to cite. `carries` is
156156
now three-valued, and an unreadable argument can never contribute to the
157157
provable count.
@@ -187,10 +187,10 @@ reproduce them. Where it disagrees, it disagrees on the page:
187187

188188
| carried figure | where it survives | this census |
189189
| :--- | :--- | ---: |
190-
| 175 write call sites | quoted in the merged changeset | **232** |
190+
| 175 write call sites | quoted in the merged changeset | **233** |
191191
| 24 carrying no tenant context | quoted in the merged changeset | **2** provable and tenancy-enabled; **29** more whose options argument is unreadable |
192-
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card | **156 of 232** decidable, **76** undecidable |
193-
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration** | **not reproduced**: 131 decidably elevated, 0 decidably not, 93 undecidable |
192+
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card | **156 of 233** decidable, **77** undecidable |
193+
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration** | **not reproduced**: 131 decidably elevated, 0 decidably not, 94 undecidable |
194194
| 141 and 132, two independent re-derivations | the card that filed this work | — |
195195

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

208208
The fourth row is the one worth flagging to anyone citing it. **The 135 / 77%
209209
figure has no surviving corroboration anywhere in the tree.** This census reads
210-
131 of 232 (56%) as decidably elevated, with 93 more whose elevation is a
210+
131 of 233 (56%) as decidably elevated, with 94 more whose elevation is a
211211
run-time fact — so the claim is neither confirmed nor refuted, and the honest
212212
answer is that a static reading cannot settle it.
213213

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

224224
| what | count |
225225
| :--- | ---: |
226-
| write call sites on the application surface | **232** |
226+
| write call sites on the application surface | **233** |
227227
| …whose object name is statically decidable | 156 |
228-
| …whose object name is chosen at run time | 76 |
228+
| …whose object name is chosen at run time | 77 |
229229
| …against an object with tenancy ENABLED | 155 |
230230
| …against an object that declares tenancy off | 1 |
231231
| threading a tenant context | 174 |
232232
| PROVABLY carrying none (options read, no context key) | **8** |
233233
| …of those, against a decidably tenancy-enabled object | **2** |
234-
| options argument UNREADABLE — may or may not carry one | 50 |
234+
| options argument UNREADABLE — may or may not carry one | 51 |
235235
| …of those, against a decidably tenancy-enabled object | 29 |
236236
| threading a decidably ELEVATED (`isSystem`) context | 131 |
237237
| threading a context that is decidably NOT elevated | 0 |
238-
| threading a context whose elevation is a run-time fact | 93 |
238+
| threading a context whose elevation is a run-time fact | 94 |
239239

240240
| how the instrument reached the site | count |
241241
| :--- | ---: |
242-
| receiver carried a readable engine type | 191 |
242+
| receiver carried a readable engine type | 192 |
243243
| receiver erased, placed by the object NAME | 31 |
244244
| receiver erased, placed by an `object: string` PARAMETER | 5 |
245245
| receiver erased, placed by an `UNTYPED_RECEIVERS` row | 5 |
246246

247247
| object name spelled inline | 107 |
248248
| object name spelled through a `const` | 49 |
249249
| object name is an `object: string` parameter | 7 |
250-
| object name is some other run-time expression | 69 |
250+
| object name is some other run-time expression | 70 |
251251

252252
### Subtractions the census could NOT defend — enforced
253253

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

300-
Measured on 2026-10-20 at `300e9d436`.
300+
Measured on 2026-10-20 at `39fc914f7`.
301301

302302
| corpus scale (not enforced) | count |
303303
| :--- | ---: |
304-
| tracked non-test sources scanned | 675 |
305-
| engine-shaped types recognised | 80 |
304+
| tracked non-test sources scanned | 681 |
305+
| engine-shaped types recognised | 82 |
306306
| declared objects in the registry | 118 |
307-
| same-named calls subtracted as non-engine | 166 |
307+
| same-named calls subtracted as non-engine | 169 |
308308

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

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

Lines changed: 9 additions & 8 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 | 232 |
36+
| Write call sites | 233 |
3737
| Object name statically decidable | 156 |
38-
| Object name chosen at run time | 76 |
38+
| Object name chosen at run time | 77 |
3939
| Against a tenancy-enabled object | 155 |
4040
| Against an object declaring tenancy off | 1 |
4141
| Threading a tenant context | 174 |
4242
| Provably carrying none | 8 |
4343
| …and decidably tenancy-enabled | 2 |
44-
| Options argument unreadable | 50 |
44+
| Options argument unreadable | 51 |
4545
| …and decidably tenancy-enabled | 29 |
4646
| Threading a decidably elevated context | 131 |
4747
| Threading a decidably non-elevated context | 0 |
48-
| Threading a context of undecidable elevation | 93 |
48+
| Threading a context of undecidable elevation | 94 |
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-10-20 at `300e9d436`.
93+
Measured on 2026-10-20 at `39fc914f7`.
9494

9595
| corpus scale (not enforced) | count |
9696
| :--- | ---: |
97-
| tracked non-test sources scanned | 675 |
98-
| engine-shaped types recognised | 80 |
97+
| tracked non-test sources scanned | 681 |
98+
| engine-shaped types recognised | 82 |
9999
| declared objects in the registry | 118 |
100-
| same-named calls subtracted as non-engine | 166 |
100+
| same-named calls subtracted as non-engine | 169 |
101101

102102
## Every site
103103

@@ -121,6 +121,7 @@ Measured on 2026-10-20 at `300e9d436`.
121121
| `packages/plugins/plugin-audit/src/auth-event-audit.ts` | `insert` | `sys_audit_log` | enabled | options unreadable | 1 |
122122
| `packages/plugins/plugin-audit/src/comment-parent-pair-backfill.ts` | `update` | `sys_comment` | enabled | elevated | 1 |
123123
| `packages/plugins/plugin-audit/src/mail-log-body-migration.ts` | `update` | `object` | undecidable | elevated | 1 |
124+
| `packages/plugins/plugin-audit/src/mail-log-body-migration.ts` | `update` | `object` | undecidable | options unreadable | 1 |
124125
| `packages/plugins/plugin-audit/src/read-audit.ts` | `insert` | `sys_audit_log` | enabled | options unreadable | 1 |
125126
| `packages/plugins/plugin-audit/src/stored-metadata-body-migration.ts` | `update` | `object` | undecidable | elevated | 1 |
126127
| `packages/plugins/plugin-auth/src/admin-import-users.ts` | `insert` | `sys_audit_log` | enabled | options unreadable | 1 |

0 commit comments

Comments
 (0)