You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(permissions): regenerate the system-context census from the merged tree
The merge brought in #16087, which inserted its own lines into `engine.ts` and
re-anchored this page for them. Both sides had edited this `merge=os-regen`
artifact, so the driver merged it with exit 0 while silently keeping one side;
`scripts/pm/os-regen-merge.sh` took main's bytes and this commit re-derives the
page from the merged tree with `pnpm gen:system-context-census`.
Blast radius measured, not assumed: 105 rows before and 105 after, row SET
identical once integers are normalised, 12 changed lines and all 12 identical
apart from line numbers — no row dropped, none added, no prose moved. The
deltas (+9/+12/+13/+14) are this branch's own cumulative insertion offsets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
Copy file name to clipboardExpand all lines: content/docs/permissions/system-context.mdx
+12-12Lines changed: 12 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -109,17 +109,17 @@ that silently does not happen.
109
109
110
110
| # | Behaviour when `isSystem`| Package | What you get / what you lose | Anchor |
111
111
|:--|:---|:---|:---|:---|
112
-
| 18 |**`readonly` strip bypassed — UPDATE, single row**| objectql | Get: a `readonly` field CAN be written. Lose: the protection that stops a caller seeding e.g. `approval_status`|`objectql/src/engine.ts:11688`|
113
-
| 19 |**`readonly` strip bypassed — UPDATE, bulk/predicate**| objectql | Same, on the multi-row path |`objectql/src/engine.ts:11871`|
114
-
| 20 |**`readonly` strip bypassed — INSERT**| objectql | Same, on create — one gate over BOTH create-side passes since the 2026-09-03 ruling moved the static-`readonly` strip in beside the runtime-owned one and deleted the DataProtocol ingress copy. `isSystem` is the **only** exemption on this path: `preserveAudit` is deliberately not read on create, so a non-system historical import is still stripped |`objectql/src/engine.ts:10336`|
115
-
| 21 | Strict-drop refusal never fires | objectql | Lose: a caller that opted into loud refusal gets **silence** — strict refuses exactly what the strip would have taken, and the strip took nothing |`objectql/src/engine.ts:10469`, `readonly-strict-errors.ts:66`|
116
-
| 22 |**Referential-integrity check skipped**| objectql | Get: writes proceed against unreachable/unresolvable targets. Lose: an `isSystem` caller can write a **dangling reference**|`objectql/src/engine.ts:6194`|
117
-
| 23 | Tenant-audit warning silenced; `bypassTenantAudit` threaded to the driver | objectql | Get: unscoped system writes stop warning. Lose: the signal that would flag a genuine user-path scoping bug |`objectql/src/engine.ts:3922`, `:3932`, `:3959`|
112
+
| 18 |**`readonly` strip bypassed — UPDATE, single row**| objectql | Get: a `readonly` field CAN be written. Lose: the protection that stops a caller seeding e.g. `approval_status`|`objectql/src/engine.ts:11746`|
113
+
| 19 |**`readonly` strip bypassed — UPDATE, bulk/predicate**| objectql | Same, on the multi-row path |`objectql/src/engine.ts:11929`|
114
+
| 20 |**`readonly` strip bypassed — INSERT**| objectql | Same, on create — one gate over BOTH create-side passes since the 2026-09-03 ruling moved the static-`readonly` strip in beside the runtime-owned one and deleted the DataProtocol ingress copy. `isSystem` is the **only** exemption on this path: `preserveAudit` is deliberately not read on create, so a non-system historical import is still stripped |`objectql/src/engine.ts:10394`|
115
+
| 21 | Strict-drop refusal never fires | objectql | Lose: a caller that opted into loud refusal gets **silence** — strict refuses exactly what the strip would have taken, and the strip took nothing |`objectql/src/engine.ts:10527`, `readonly-strict-errors.ts:66`|
116
+
| 22 |**Referential-integrity check skipped**| objectql | Get: writes proceed against unreachable/unresolvable targets. Lose: an `isSystem` caller can write a **dangling reference**|`objectql/src/engine.ts:6252`|
117
+
| 23 | Tenant-audit warning silenced; `bypassTenantAudit` threaded to the driver | objectql | Get: unscoped system writes stop warning. Lose: the signal that would flag a genuine user-path scoping bug |`objectql/src/engine.ts:3929`, `:3939`, `:3966`|
| 25 | Identity write guard bypassed (ADR-0092) | plugin-auth | Get: direct writes to identity tables through the generic data path |`identity-write-guard.ts:99`|
120
-
| 26 | Search-companion column **kept** in a read's rows when it was explicitly requested | objectql | Get: the internal companion column is readable. Lose: nothing for app code — this is the engine reading its own index |`objectql/src/engine.ts:6893`|
121
-
| 27 | Dependent-count disclosure on a blocked delete | objectql | Get: the count of blocking children. Nothing was elevated past the caller, so nothing is withheld |`objectql/src/engine.ts:12490`|
122
-
| 28 | Reference-cleanup log attributes the write to `'system'`| objectql | Get: an honest actor label instead of `anonymous` when the context carries neither `userId` nor `actor`|`objectql/src/engine.ts:12419`|
120
+
| 26 | Search-companion column **kept** in a read's rows when it was explicitly requested | objectql | Get: the internal companion column is readable. Lose: nothing for app code — this is the engine reading its own index |`objectql/src/engine.ts:6951`|
121
+
| 27 | Dependent-count disclosure on a blocked delete | objectql | Get: the count of blocking children. Nothing was elevated past the caller, so nothing is withheld |`objectql/src/engine.ts:12548`|
122
+
| 28 | Reference-cleanup log attributes the write to `'system'`| objectql | Get: an honest actor label instead of `anonymous` when the context carries neither `userId` nor `actor`|`objectql/src/engine.ts:12477`|
123
123
| 29 |**Bulk data event `organizationId` OMITTED** — the batch is published "not asserted" | plugin-security | Get: nothing — the `data.records.*` event still publishes. Lose: the per-organization attribution: this exit is taken before the security middleware composes any tenant wall, so it records no Layer 0 verdict on the operation (`OperationContext.tenantLayer0Verdict`, #15813), and the engine's bulk producer — which reads that recorded verdict and nothing else — omits the key rather than filling it from the caller's `tenantId`; a tenant-scoped consumer then does not deliver the event inside an organization wall (#15225) |`security-plugin.ts:1686`|
124
124
125
125
### 3. Sharing (`plugin-sharing`)
@@ -179,8 +179,8 @@ a reader tracing where elevation travels needs them.
179
179
180
180
| # | Site | Package | What it does |
181
181
|:--|:---|:---|:---|
182
-
| 62 |`objectql/src/engine.ts:3729`| objectql | Propagates `isSystem` into the hook session so hooks can tell engine self-writes from user writes |
183
-
| 63 |`objectql/src/engine.ts:14937`| objectql |`ScopedContext.isSystem` getter — re-exposes the underlying execution context's flag |
182
+
| 62 |`objectql/src/engine.ts:3736`| objectql | Propagates `isSystem` into the hook session so hooks can tell engine self-writes from user writes |
183
+
| 63 |`objectql/src/engine.ts:14995`| objectql |`ScopedContext.isSystem` getter — re-exposes the underlying execution context's flag |
184
184
| 64 |`plugin-reports/src/report-service.ts:556`| plugin-reports | Threads the flag into the engine call that runs a report |
185
185
| 65 |`body-runner.ts:279`| runtime | Rebuilds an `ExecutionContext` from a hook session, carrying the flag across |
186
186
@@ -195,7 +195,7 @@ assuming `isSystem` covers it is a documented source of bugs.
195
195
|:---|:---|:---|
196
196
| "It suppresses triggers / record-change automation" |**No.** Only `skipTriggers` does. A bare `{ isSystem: true }` on a seed write re-fired automation on freshly seeded rows and wedged first boot |`metadata-protocol/src/seed-loader.ts:2032` (rationale at `:1942`–`1944`, #3760), `flow.zod.ts:743`|
197
197
| "It skips the state machine" |**No.** That is `skipStateMachine`, carried by seed replay and by `treatAsHistorical` imports |`objectql/src/engine.ts` FSM gate; see [State Machine](/docs/protocol/objectql/state-machine)|
198
-
| "It skips validation rules" |**No.** Field shape, `format`, `script` and the rest still run. The `readonly` strip runs *before* validation precisely so a discarded value is not judged |`objectql/src/engine.ts:10319`–`10336`|
198
+
| "It skips validation rules" |**No.** Field shape, `format`, `script` and the rest still run. The `readonly` strip runs *before* validation precisely so a discarded value is not judged |`objectql/src/engine.ts:10377`–`10394`|
199
199
| "It preserves a supplied `updated_at` / `updated_by`" |**No.** That is `preserveAudit`, a separate opt-in — and an UPDATE-path exemption only |`field.zod.ts:1590` (#3493 / #6640) |
200
200
| "It stamps `created_by`" |**No.** Audit stamping reads `userId` from the context. A user-less system write stamps nothing — that is today's behaviour, not an error |`runtime-identity.ts:280`–`281`|
201
201
| "It bypasses every guard" |**No.** The last-admin guard applies to **every** context, `isSystem` included — the deprovision path that actually locks an org out is the system one |`last-admin-guard.ts:299`|
0 commit comments