Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions packages/spec/liveness/permission.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,22 +49,30 @@
"allowCreate": {
"status": "live",
"verifiedAt": "2026-07-30",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION insert→allowCreate)"
"proof": "packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts#showcase-crud-persona-matrix",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION insert→allowCreate)",
"note": "Proof-bound 2026-08-23 (#10959, adjudicated on PR #10934): the persona × CRUD matrix runs the create verb over real HTTP for every showcase permission set, in BOTH directions per cell — an allowed create returns an id and the row is really there, a denied one is 403 PERMISSION_DENIED and persisted nothing. Cells are judged as a UNION with the everyone-baseline set (ADR-0090 D5). One proof binds all four allow* verbs; multi-entry binding has precedent in `semantic-roles`."
},
"allowRead": {
"status": "live",
"verifiedAt": "2026-07-30",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION find/findOne/count/aggregate→allowRead)"
"proof": "packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts#showcase-crud-persona-matrix",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION find/findOne/count/aggregate→allowRead)",
"note": "Proof-bound 2026-08-23 (#10959) — the read verb of the same persona × CRUD matrix, both directions per cell (a readable list answers 200 with rows; a denied one is 403 PERMISSION_DENIED). See `allowCreate` for the binding rationale."
},
"allowEdit": {
"status": "live",
"verifiedAt": "2026-07-30",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION update→allowEdit)"
"proof": "packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts#showcase-crud-persona-matrix",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION update→allowEdit)",
"note": "Proof-bound 2026-08-23 (#10959) — the edit verb of the same persona × CRUD matrix, both directions per cell, asserted on POST-STATE (the allowed edit persisted; the denied edit changed nothing). See `allowCreate` for the binding rationale."
},
"allowDelete": {
"status": "live",
"verifiedAt": "2026-07-30",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION delete→allowDelete)"
"proof": "packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts#showcase-crud-persona-matrix",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:15 (OPERATION_TO_PERMISSION delete→allowDelete)",
"note": "Proof-bound 2026-08-23 (#10959) — the delete verb of the same persona × CRUD matrix, both directions per cell, asserted on POST-STATE (the deleted row is gone; the denied delete left the row standing). See `allowCreate` for the binding rationale."
},
"allowExport": {
"status": "live",
Expand DownExpand Up@@ -122,8 +130,9 @@
"readable": {
"status": "live",
"verifiedAt": "2026-07-30",
"proof": "packages/qa/dogfood/test/showcase-fls-read-mask-strip.dogfood.test.ts#showcase-fls-read-mask-strip",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:301",
"note": "FLS read-mask."
"note": "FLS read-mask. Proof-bound 2026-08-23 (#10959, adjudicated on PR #10934): the proof AUTHORS a scratch permission set carrying `readable: false` and asserts the runtime outcome both ways on the same field, row and request — the key is ABSENT from the wire (`'budget' in record` is false, which `toBeUndefined()` cannot distinguish from a mask), while the entitled caller gets the real value. The sibling `editable` is deliberately NOT bound to this proof: that file authors the key but asserts its refusal as a consequence of unreadability, not as the write-deny axis (`showcase-permission-zoo` pins that half)."
},
"editable": {
"status": "live",
Expand All@@ -135,9 +144,10 @@
},
"systemPermissions": {
"status": "live",
"verifiedAt": "2026-07-30",
"evidence": "packages/plugins/plugin-hono-server/src/hono-plugin.ts:1222",
"note": "PARTIAL — app-entry/nav visibility only, not a general capability gate."
"verifiedAt": "2026-08-23",
"proof": "packages/qa/dogfood/test/sharing-rule-org-less-caller.dogfood.test.ts#sharing-rule-org-less-caller",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:267 (getSystemPermissions — ADR-0066 D3 union of the capabilities a caller's permission sets grant, into ExecutionContext.systemPermissions) + packages/plugins/plugin-sharing/src/sharing-rule-service.ts:136 (assertCanManageRules — ADR-0111 D6: every sharing-rule verb, list and get included, refuses PERMISSION_DENIED unless the caller holds `manage_sharing` OR the legacy `manage_platform_settings` admin override, with system contexts bypassing; enforced in the SERVICE so non-REST callers are covered) + packages/plugins/plugin-sharing/src/sharing-plugin.ts:993 + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:897 (/auth/me/apps — `AppSchema.requiredPermissions ⊆ ctx.systemPermissions`, the app-entry/nav half)",
"note": "Re-verified 2026-08-23 (#10959) and the note CORRECTED — the previous text (\"PARTIAL — app-entry/nav visibility only, not a general capability gate\") was stale in both halves. (a) Its evidence pointer `plugin-hono-server/src/hono-plugin.ts:1222` no longer exists: that file is 717 lines and contains no `systemPermissions` reference at all — the app-entry consumer moved to `current-user-endpoints.ts`. (b) The scoping claim is falsified by ADR-0111 D6 (Accepted 2026-07-30, P0 implemented): `SharingRuleService.assertCanManageRules` reads `context.systemPermissions` and refuses the whole sharing-rule surface unless the caller holds `manage_sharing` or the legacy `manage_platform_settings` admin override (system contexts bypass), which is a DATA-LAYER authorization gate, not nav visibility — the admit set is wider than one capability, but every member of it is read from `systemPermissions`, which is what this entry classifies. The bound proof measures exactly that: it authors `system_permissions: ['manage_sharing']` on a permission set and asserts the refusal it gets back is the ORG-scope refusal and explicitly NOT `/requires the manage_sharing capability/` — i.e. the authored capability really did clear the service gate — with the org-bound holder of the same grant reading its own tenant (200) as the entitled contrast, over two organizations so a single-tenant fixture cannot pass on the broken build."
},
"tabPermissions": {
"status": "live",
Expand Down
77 changes: 51 additions & 26 deletions packages/spec/scripts/liveness/proof-registry.mts
Original file line numberDiff line numberDiff line change
Expand Up@@ -708,15 +708,29 @@ export const HIGH_RISK_CLASSES: HighRiskClass[] = [
proofId: 'sharing-rule-org-less-caller',
proofRef:
'packages/qa/dogfood/test/sharing-rule-org-less-caller.dogfood.test.ts#sharing-rule-org-less-caller',
bound: false,
ledgerBindings: [],
blockedReason:
'binding PROPOSED, not adopted: the file authors `system_permissions: [\'manage_sharing\']` on a '
+ 'permission set, so `permission.systemPermissions` (live, carrying no proof) is a candidate — '
+ "but that entry's note scopes it to \"app-entry/nav visibility only, not a general capability "
+ 'gate\", which this proof measures as narrower than reality. Re-verifying the note is part of '
+ 'the binding decision, and a ledger edit is the spec seat\'s review (#10773), not a '
+ 'registration side effect.',
bound: true,
// Bound 2026-08-23 (#10959) on the spec seat's adjudication of PR #10934,
// whose item ③ was conditional: bind only if the entry's note turned out
// stale. The re-verification says stale on BOTH halves, so the note was
// corrected in the same PR and the binding follows.
// • The old evidence pointer (`plugin-hono-server/src/hono-plugin.ts:1222`)
// does not exist — that file is 717 lines and never mentions
// `systemPermissions`; the app-entry consumer moved to
// `current-user-endpoints.ts` (`/auth/me/apps`).
// • The old note's scoping claim ("app-entry/nav visibility only, not a
// general capability gate") is falsified by ADR-0111 D6:
// `SharingRuleService.assertCanManageRules` reads
// `context.systemPermissions` and refuses every sharing-rule verb
// unless the caller holds `manage_sharing` or the legacy
// `manage_platform_settings` admin override (system contexts bypass)
// — a data-layer gate, not nav visibility. The admit set is wider than
// one capability, but every member of it is read from
// `systemPermissions`, which is what the bound entry classifies.
// The proof authors the key and proves the gate was CLEARED by it: the
// refusal it asserts is the org-scope one and explicitly NOT
// /requires the manage_sharing capability/, with the org-bound holder of
// the same grant reading its own tenant as the entitled contrast.
ledgerBindings: [{ type: 'permission', path: 'systemPermissions' }],
},
{
id: 'crud-persona-matrix',
Expand All@@ -733,14 +747,22 @@ export const HIGH_RISK_CLASSES: HighRiskClass[] = [
proofId: 'showcase-crud-persona-matrix',
proofRef:
'packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts#showcase-crud-persona-matrix',
bound: false,
ledgerBindings: [],
blockedReason:
'binding PROPOSED, not adopted: the four cells map exactly onto `permission.objects.allowCreate '
+ '/ allowRead / allowEdit / allowDelete` (all live, none carrying a proof), and a multi-entry '
+ 'binding has precedent in `semantic-roles`. Whether a persona-breadth matrix should anchor '
+ 'four entries or a chosen subset is a judgment call, and the ledger edit is the spec seat\'s '
+ 'review (#10773).',
bound: true,
// Bound 2026-08-23 (#10959) — ADOPT ALL FOUR, on the spec seat's
// adjudication of PR #10934. The scope worry the registration raised (one
// breadth proof anchoring four properties) is answered by the file's own
// shape: the EXACT allow/deny split is asserted (54 allow / 54 deny, one
// verdict per set × object × verb), so a narrowing sweep — or a persona
// silently failing to provision — breaks the build instead of quietly
// shrinking what these four entries cite. Each verb is exercised in both
// directions per cell and on post-state, not just on status codes.
// Multi-entry binding has precedent in `semantic-roles`, which binds three.
ledgerBindings: [
{ type: 'permission', path: 'objects.allowCreate' },
{ type: 'permission', path: 'objects.allowRead' },
{ type: 'permission', path: 'objects.allowEdit' },
{ type: 'permission', path: 'objects.allowDelete' },
],
},
{
id: 'fls-read-strip',
Expand All@@ -756,15 +778,18 @@ export const HIGH_RISK_CLASSES: HighRiskClass[] = [
proofId: 'showcase-fls-read-mask-strip',
proofRef:
'packages/qa/dogfood/test/showcase-fls-read-mask-strip.dogfood.test.ts#showcase-fls-read-mask-strip',
bound: false,
ledgerBindings: [],
blockedReason:
'binding PROPOSED, not adopted — the strongest of the three: the file AUTHORS a scratch '
+ 'permission set carrying `readable: false` and asserts the runtime outcome both ways, and '
+ '`permission.fields.readable` (live, note "FLS read-mask") carries no proof. Not '
+ '`fields.editable` alongside it: the file authors that key but asserts its refusal as a '
+ 'consequence of unreadability rather than as the write-deny axis, which '
+ '`showcase-permission-zoo` already pins. The ledger edit is the spec seat\'s review (#10773).',
bound: true,
// Bound 2026-08-23 (#10959) — the strongest of the three, on the spec
// seat's adjudication of PR #10934. The file AUTHORS a scratch permission
// set carrying `readable: false` and asserts the runtime outcome both ways
// on the SAME field, row and request, so `permission.fields.readable`'s
// `live` status is exactly what it gates.
// NOT `fields.editable` alongside it: the file authors that key but
// asserts its refusal as a CONSEQUENCE of unreadability rather than as the
// write-deny axis, which `showcase-permission-zoo` already pins. Binding it
// would repeat the owner-anchor / `allowTransfer` mistake — a proof cited
// for a property it does not exercise.
ledgerBindings: [{ type: 'permission', path: 'fields.readable' }],
},
];

Expand Down
23 changes: 23 additions & 0 deletions packages/spec/scripts/liveness/proof-registry.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -136,6 +136,29 @@ describe('registry invariants', () => {
// without a runtime proof; its new dogfood file asserts post-state in
// three postures (widen / independent-of-read / fail-closed seam).
'permission/objects.writeScope',
// Bound 2026-08-23 (#10959) — the three proofs PR #10934 registered
// with a PROPOSED binding, adopted after the spec seat adjudicated
// them. Six paths, because two of the three are multi-entry.
//
// `showcase-fls-read-mask-strip` → the READ axis only. Its sibling
// `permission/fields.editable` is deliberately absent: that proof
// authors the key but asserts its refusal as a consequence of
// unreadability, not as the write-deny axis.
'permission/fields.readable',
// `showcase-crud-persona-matrix` → all four verbs. The exact allow/deny
// split is asserted per cell, so a narrowing sweep breaks the build
// rather than quietly shrinking what these four cite (`semantic-roles`
// is the multi-entry precedent).
'permission/objects.allowCreate',
'permission/objects.allowRead',
'permission/objects.allowEdit',
'permission/objects.allowDelete',
// `sharing-rule-org-less-caller` → `systemPermissions`. The card held
// this one back pending a measurement: its ledger note claimed
// "app-entry/nav visibility only". The note was stale — ADR-0111 D6
// makes it a data-layer gate at `SharingRuleService` — so the note was
// corrected and the binding adopted in the same PR.
'permission/systemPermissions',
].sort(),
);
});
Expand Down
Loading