Skip to content

Commit 9afd22d

Browse files
committed
docs(adr): resolve 0094 addendum audit items; record sharing-rule seed-not-clobber tradeoff (#2909 T4)
The three 'Audit that…' per-type rows are now shipped decisions: sys_sharing_rule = record-authoritative with provenance + seed-not-clobber; sys_position = seed-only identity/display, locked by test; sys_capability = seed-not-clobber with scope seed-once. Record why sharing rules got no write gate (admin tuning surface), the customized-stamp boundaries, and the scope migration cost. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bJWtKmZ2mFpRFAmmmoeqe
1 parent 6a68da5 commit 9afd22d

1 file changed

Lines changed: 29 additions & 5 deletions

File tree

docs/adr/0094-sys-permission-set-pure-projection.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,14 +301,38 @@ only a seed-not-clobber discipline.
301301
| Type | Enforcement reads | Class | Decision |
302302
| :-- | :-- | :-- | :-- |
303303
| `sys_permission_set` | metadata (`PermissionEvaluator.resolvePermissionSets``metadata.list('permission')`, DB row only as fallback) | metadata-authoritative | **Record is a projection — done** (this ADR). |
304-
| `sys_sharing_rule` | the record, live (`sharing-plugin.ts` "rule evaluation reads `sys_sharing_rule` live"; `sharing-rule-service` `engine.find`) | **record-authoritative** | Declared rules are a **boot seed**; the record is the authority. Do **not** project. Audit that `bootstrapDeclaredSharingRules` preserves env-edited rows (seed-not-clobber) and that the metadata overlay is not read as a live override. |
305-
| `sys_position` | mixed — position→permission-set resolution is metadata-first for the *sets*, but the `sys_position` **record** (incl. its `permissions` field, bindings, `delegatable`, `admin` gating) is read live by the anchor gate and `DelegatedAdminGate` | **needs the seed-vs-authority audit** against the criterion above; likely record-authoritative for bindings with metadata seeding identity | Classify precisely, then either project (if the position *definition* is enforced from metadata) or make declared positions seed-only. |
306-
| `sys_capability` | the record (curated registry read for capability existence) | record-authoritative (registry) | Seed-only; low authoring surface. Audit seed-not-clobber. |
304+
| `sys_sharing_rule` | the record, live (`sharing-plugin.ts` "rule evaluation reads `sys_sharing_rule` live"; `sharing-rule-service` `engine.find`) | **record-authoritative** | **Resolved (#2909 P0/T1)**: declared rules are a **boot seed**; the record is the authority; not projected. Rows carry readonly `managed_by` (unified A4 tri-state) + `customized` provenance; the seeder (`defineRule` in seed mode, `managedBy:'package'`) adopts pristine/legacy rows and keeps updating them, but **never overwrites admin-authored or `customized` rows** — an admin's `active:false` on an over-sharing rule survives redeploys. A `beforeUpdate` hook stamps `customized` on any non-system edit of a seeded row. |
305+
| `sys_position` | mixed — position→permission-set resolution is metadata-first for the *sets*, but the `sys_position` **record** (incl. its `permissions` field, bindings, `delegatable`, `admin` gating) is read live by the anchor gate and `DelegatedAdminGate` | **record-authoritative** (bindings and lifecycle), with metadata seeding **identity + display only** | **Resolved (#2909 T2)**: declared positions are seed-only — `bootstrapDeclaredPositions` refreshes `label`/`description` and nothing else; bindings/`active`/`is_default`/`delegatable`/`managed_by` belong to the runtime/admin. Locked by `bootstrap-declared-positions.test.ts`. |
306+
| `sys_capability` | the record (curated registry read for capability existence) | record-authoritative (registry) | **Resolved (#2909 T3)**: seed-not-clobber holds — `label`/`description` are platform-owned and refresh each boot; `managed_by`/`active` were already preserved; `scope` is an admin-editable classification face and is now **seed-once** (insert only). Locked by `bootstrap-system-capabilities.test.ts`. |
307307

308308
Only `sys_permission_set` was both metadata-authoritative **and** carried a
309309
harmful, actively-drifting split-brain, which is why it was fixed first and in
310-
full. The others are recorded here with their class so the follow-up work is
311-
scoped, not rediscovered — tracked in framework#2909.
310+
full. The follow-up work above landed via framework#2909.
311+
312+
#### Recorded tradeoffs (#2909)
313+
314+
- **Why `sys_sharing_rule` chose seed-not-clobber over a write gate
315+
(projection-lite).** A self-consistent alternative existed: gate
316+
package-managed rows against admin writes (as #2918 does for
317+
`sys_position`/`sys_capability`), making the boot overwrite harmless. It was
318+
rejected because sharing rules are a first-class admin authoring *and
319+
tuning* surface in Setup — including narrowing or deactivating a seeded
320+
rule that turned out to over-share. Locking package rows would amputate
321+
that product capability, and the classification criterion (enforcement
322+
reads the record) already points at record authority. Admin edits are
323+
therefore *remembered* (`customized`), not blocked. Only the two provenance
324+
columns themselves are `readonly` (engine-stripped from non-system
325+
payloads), so provenance cannot be forged or cleared through the data door.
326+
- **Known boundaries of the `customized` stamp**: multi-row updates (no
327+
single `input.id`) are not stamped — every rule-editing UI path updates by
328+
id; an admin who *deletes* a package rule gets it back pristine on the next
329+
boot (delete ≠ customize; deactivation is the supported way to retire a
330+
seeded rule); a "reset to package default" affordance (system-context clear
331+
of `customized`) is future work.
332+
- **`sys_capability.scope` is seed-once**: a curated scope change shipped in
333+
a new platform version no longer propagates to existing rows and needs a
334+
data migration. Accepted — silently reverting an admin's reclassification
335+
every boot was the worse failure mode.
312336

313337
### Why an addendum, not a new ADR
314338

0 commit comments

Comments
 (0)