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
fix(objectql): insert answers a driver unique violation with the DUPLICATE_RECORD envelope, on every driver (#14405)
* wip(objectql): envelope driver unique violations at the insert door (#14095)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
* test(objectql): pin the DUPLICATE_RECORD insert contract and retriage the resync pins (#14095)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
* fix(objectql): keep the driver diagnosis on the operator log line (#14095)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
* chore(objectql): changeset + regenerated system-context census (#14095)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
* test(objectql): type the registry double so tsconfig.test.json compiles the new suite (#14095)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
* chore: regenerate the system-context census on the merged tree (#14095)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
* fix(metadata-protocol): seed-loader operator line reaches through cause; retriage the two runtime disclosure pins (#14095)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
* chore: re-anchor the system-context census after the seed-loader edit (#14095)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
* chore: ratchet the error-status unpinned baseline down for DUPLICATE_RECORD (#14095)
`DuplicateRecordError` is the first producer to declare this code's status, so
the code leaves the unpinned census. Baseline written by
`check-error-status-conformance.mjs --update`; shrink-only, one line.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(metadata-protocol): the seed loader's operator line reaches through `cause`, so an enveloped driver fault still names what the database said (#14095)
6
+
7
+
The seed channel has two halves by design: the payload quotes a caught sentence only when the producer DECLARES a client refusal, and the log carries the caught sentence ALWAYS — because withholding text that nothing else records is indistinguishable from deleting the diagnostic, which is what makes a disclosure fix a net loss for whoever has to fix the database.
8
+
9
+
`seedFailureCause` read `err.message` and nothing else. That was complete while every producer put its whole diagnosis there. It stopped being complete the moment one of them started ENVELOPING: `engine.insert` now answers a driver unique violation with `DUPLICATE_RECORD` / `status: 409` and keeps the driver's own error whole on `cause`, so the platform sentence sits on `message` and `UNIQUE constraint failed: dt_acct.email` sits one hop down. Read off `message` alone, the operator line printed the platform sentence and the driver's words reached **neither the response nor the log** — the exact loss the two-halves design exists to prevent, arriving through a producer doing the right thing.
10
+
11
+
So the log follows the hop: `seedFailureCause` now walks the `cause` chain (bounded at 4, the depth `@objectstack/types`' unique-violation predicate walks) and prints the DEEPEST non-empty sentence — the one no wrapper above it restates. The walk is structural, never a type check: this package must not import `@objectstack/objectql`, and an envelope from any producer earns the same treatment.
12
+
13
+
`seedCauseLabel` moves with it, because the marker would otherwise go false. It used to ask "was this ERROR's text withheld from the payload?", which was the same question while the printed sentence was always `err.message`. Now the two differ: an enveloped fault has its PLATFORM sentence quoted to the caller and its DRIVER sentence printed to the operator, and the old question answered `Cause` — telling an operator the reporter saw words the reporter never saw. It now compares the sentence about to be printed against the one the payload actually quoted, so `Cause` means "these are the same words". All three populations stay correct: withheld outright, enveloped, and plainly declared.
14
+
15
+
No behaviour changes for a producer that carries no `cause` — the walk finds nothing and returns `err.message`, byte for byte as before.
Copy file name to clipboardExpand all lines: content/docs/permissions/system-context.mdx
+13-13Lines changed: 13 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -109,18 +109,18 @@ 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:10986`|
113
-
| 19 |**`readonly` strip bypassed — UPDATE, bulk/predicate**| objectql | Same, on the multi-row path |`objectql/src/engine.ts:11154`|
| 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:11049`|
113
+
| 19 |**`readonly` strip bypassed — UPDATE, bulk/predicate**| objectql | Same, on the multi-row path |`objectql/src/engine.ts:11217`|
| 21 |**`readonly` strip bypassed — INSERT (protocol ingress)**| metadata-protocol |`isSystem` is the **only** exemption here. `preserveAudit` is deliberately not read on this path (#6640) — a non-system historical import is still stripped on create |`metadata-protocol/src/protocol.ts:1746`|
116
-
| 22 | 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:9819`, `readonly-strict-errors.ts:66`|
117
-
| 23 |**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:5740`|
118
-
| 24 | 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:3604`, `:3614`, `:3641`|
116
+
| 22 | 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:9864`, `readonly-strict-errors.ts:66`|
117
+
| 23 |**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:5761`|
118
+
| 24 | 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:3605`, `:3615`, `:3642`|
| 26 | Identity write guard bypassed (ADR-0092) | plugin-auth | Get: direct writes to identity tables through the generic data path |`identity-write-guard.ts:98`|
121
-
| 27 | 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:6438`|
122
-
| 28 | 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:11747`|
123
-
| 29 | 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:11676`|
121
+
| 27 | 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:6459`|
122
+
| 28 | 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:11810`|
123
+
| 29 | 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:11739`|
124
124
125
125
### 3. Sharing (`plugin-sharing`)
126
126
@@ -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:3411`| objectql | Propagates `isSystem` into the hook session so hooks can tell engine self-writes from user writes |
183
-
| 63 |`objectql/src/engine.ts:14096`| objectql |`ScopedContext.isSystem` getter — re-exposes the underlying execution context's flag |
182
+
| 62 |`objectql/src/engine.ts:3412`| objectql | Propagates `isSystem` into the hook session so hooks can tell engine self-writes from user writes |
183
+
| 63 |`objectql/src/engine.ts:14159`| 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
@@ -193,9 +193,9 @@ assuming `isSystem` covers it is a documented source of bugs.
193
193
194
194
| Assumption | Reality | Anchor |
195
195
|:---|:---|:---|
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:1909` (rationale at `:1819`–`1821`, #3760), `flow.zod.ts:685`|
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:1971` (rationale at `:1881`–`1883`, #3760), `flow.zod.ts:685`|
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:9765`–`9782`|
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:9810`–`9827`|
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:1516` (#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:286`|
0 commit comments