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(security): retire delegated_from from sys_user_permission_set (ADR-0049 enforce-or-remove) (#9998)
The runtime delegation gate is structurally scoped to sys_user_position, so
on the permission-set grant table the column was declared and data-door-
writable while no runtime consumer read it. Maintainer ruling 2026-08-18:
REMOVE. Lint D3 rule scoped to the position table; docs per-object grant-
column table updated (#9876); ADR-0087 semantic ledger entry
ups-delegated-from-column-retired registered.
Fixes#9730
Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx
Co-authored-by: Claude <noreply@anthropic.com>
Maintainer ruling 2026-08-18: **REMOVE**. The runtime delegation gate is
9
+
structurally scoped to `sys_user_position` — `isDelegationWrite` returns `false`
10
+
for every other object, so `assertSelfDelegation` was unreachable for
11
+
`sys_user_permission_set` — and the explain engine reads delegation provenance
12
+
from position rows only. On the permission-set grant table the column was
13
+
therefore declared and data-door-writable while **no runtime consumer read
14
+
it**: its only enforcement was the authoring-time lint rule requiring a
15
+
`reason` on delegation rows, which a row written through the generic data door
16
+
never meets. A declared-but-unenforced writable column on a security object is
17
+
the declare-not-enforce trap in its pure form — an author stamping
18
+
`delegated_from` on a permission-set grant believed they constrained
19
+
delegation, and nothing refused or honoured it. Producers measured at zero:
20
+
the only object literals naming both the table and the column were lint test
21
+
fixtures.
22
+
23
+
Migration (FROM → TO):
24
+
25
+
| Wrote | Write instead |
26
+
|---|---|
27
+
|`delegated_from` on a `sys_user_permission_set` seed row or data-door write | Delete the key. Provenance prose belongs in `reason` (still declared on both grant tables); actual delegation-of-duty belongs on `sys_user_position`, where `delegated_from` remains declared **and** runtime-enforced (ADR-0091 D3). |
28
+
29
+
One-line fix: delete `delegated_from` from any authored `sys_user_permission_set` row.
ADR-0091 D1 declares four further nullable columns on both grant tables:
301
-
`reason`, `delegated_from`, `last_certified_at`, `certified_by`. They are
302
-
declared together and **enforced separately**, so they are listed here one by
303
-
one rather than as one set of audit columns — what a value in any of them is
304
-
worth depends on the column *and* on which grant table it sits on. Access
300
+
ADR-0091 D1 declared four further nullable columns on both grant tables:
301
+
`reason`, `delegated_from`, `last_certified_at`, `certified_by`. Three of the
302
+
four are still declared on both; `delegated_from` now exists **only on
303
+
`sys_user_position`** — it was retired from `sys_user_permission_set` under
304
+
ADR-0049 enforce-or-remove (maintainer ruling 2026-08-18): the runtime
305
+
delegation gate is structurally scoped to the position table, so on the
306
+
permission-set table the column was writable provenance no runtime consumer
307
+
ever read. The columns are declared together but **enforced separately**, so
308
+
they are listed here one by one rather than as one set of audit columns —
309
+
what a value in any of them is worth depends on the column *and* on which
310
+
grant table it sits on. Access
305
311
recertification is a compliance surface (SOX / ISO 27001 access review), where
306
312
"the platform maintains this column" and "the platform stores what you write
307
313
here" are very different statements:
308
314
309
315
| Column | On `sys_user_position`| On `sys_user_permission_set`|
310
316
|---|---|---|
311
317
|`reason`|**Enforced at runtime.** The D3 delegation gate rejects a delegation insert whose row carries no non-empty `reason` — the dual-audit half described below. |**Written by the platform, read by nothing.** The org-admin grant auto-derived from a membership grade stamps its own provenance here; no gate, resolver or lint reads the value back. |
312
-
|`delegated_from`|**Enforced at runtime, and load-bearing.** Stamping it is what *makes* a write a delegation: the gate requires it to name the writer and refuses to re-delegate a row that itself arrived by delegation, and the explain engine attributes the position "via delegation from X, until Y". |**Authoring lint only — no runtime reader.**Both readers opposite are guarded on `sys_user_position` (the gate's delegation branch, and explain's delegation attribution alike). A seed row that sets it must still carry a `reason` (below), but at runtime the column is provenance you record, not provenance the platform checks or acts on. |
318
+
|`delegated_from`|**Enforced at runtime, and load-bearing.** Stamping it is what *makes* a write a delegation: the gate requires it to name the writer and refuses to re-delegate a row that itself arrived by delegation, and the explain engine attributes the position "via delegation from X, until Y". |**Not declared — retired.**Removed under ADR-0049 enforce-or-remove (maintainer ruling 2026-08-18): both runtime readers opposite are guarded on `sys_user_position`, so here the column was provenance an author could record and nothing checked or acted on. A row written with the key today is refused as an undeclared field (`400 INVALID_FIELD`). If permission-set-granularity delegation is ever wanted, the column returns together with a runtime reader in the same change. |
313
319
|`last_certified_at` / `certified_by`|**Inert** — the ADR-0091 D5 recertification *substrate*, storage and nothing more. |**Inert** — identically. |
314
320
315
321
The D5 pair is worth spelling out, because it is the pair a compliance reader
@@ -334,9 +340,11 @@ contributor state ("held until 2026-08-01 — expired"), so "why did access
334
340
disappear" is self-answering. Two authoring lint rules cover seed grants: one
335
341
mirrors D2 — a seed grant whose `valid_until` is already past (or unparseable)
336
342
is dead on arrival (error) — and one mirrors the D3 dual audit: a delegation
337
-
row (`delegated_from`) without `reason` is an error. The second runs on **both**
338
-
grant tables, so on `sys_user_permission_set`, where no runtime gate reads
339
-
`delegated_from`, it is the only enforcement that column has.
343
+
row (`delegated_from`) without `reason` is an error. The first runs on **both**
344
+
grant tables; the second runs on `sys_user_position` only, the one table that
345
+
declares `delegated_from` — on `sys_user_permission_set` this lint used to be
346
+
the retired column's only enforcement, which is precisely why the column is
347
+
gone rather than still linted.
340
348
341
349
**Delegation of duty (职务代理, ADR-0091 D3)** builds on this substrate and is
342
350
enforced today. A position opts in with `delegatable: true`; a holder may then
0 commit comments