Skip to content

Commit 5b79a34

Browse files
authored
feat(spec): register the 13 orphan dogfood proofs — five advance the ADR-0054 ratchet, eight say why they can't (#3868)
The gate flagged 13 `@proof:` tags no class in proof-registry.mts claimed. Silencing that is trivial and worthless; the useful question is the one ADR-0054 §3 asks: is there an authorable property whose `live` status this proof gates? FIVE HAVE ONE — bound, taking the ratchet from 10 bound paths to 17: attachments-permission-matrix -> object.enable.files showcase-d3-d4-capabilities -> permission.rowLevelSecurity.check showcase-scope-depth -> permission.objects.readScope owner-anchor-and-bulk-writes -> permission.objects.modifyAllRecords semantic-roles-served -> object.highlightFields/.stageField/.fieldGroups Three of those gate access-widening or capability properties, where "declared but not applied" fails in the safe-LOOKING direction and so goes unnoticed longest. EIGHT DO NOT — registered with an honest blockedReason: two guard properties already bound to a sibling proof (one entry carries one `proof` ref); one needs the `app` type governed; two are runtime principal-resolution invariants; two act on stack-level roles/sharingRules; one is a breadth guard over all of ADR-0090. One deliberate non-binding worth naming: owner-anchor-and-bulk-writes binds modifyAllRecords, NOT the more obvious-by-name allowTransfer — the proof only mentions the latter in a comment and never authors it. Binding a property a proof does not exercise is the same false comfort as a preview renderer standing in for a runtime consumer, which is what #3686 spent its length unwinding. Verified the bound proofs run: the only skipIf among them covers the attachments enterprise cross-tenant block, not the FILES_DISABLED assertion. The gate now runs with ZERO warnings — the orphan list joins the stale-evidence list at empty, so both mean something again. The README ratchet table was itself stale (5 listed, 10 bound); now complete, with the unbound set and its reasons.
1 parent 1d4756e commit 5b79a34

6 files changed

Lines changed: 352 additions & 8 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
feat(spec): register the 13 orphan dogfood proofs — five advance the ADR-0054 ratchet, eight say why they can't
6+
7+
The gate flagged 13 `@proof:` tags under `packages/qa/dogfood/test/**` that no
8+
class in `proof-registry.mts` claimed. Silencing that warning is trivial and
9+
worthless; the useful question is the one ADR-0054 §3 actually asks: **is there
10+
an authorable property whose `live` status this proof gates?** Each of the 13 was
11+
re-read against that.
12+
13+
**Five had one, and are now BOUND** — a `live` classification on these entries
14+
requires its proof, so the ratchet advances from 10 bound paths to 17:
15+
16+
| Proof | Now gates | Why it qualifies |
17+
|---|---|---|
18+
| `attachments-permission-matrix` | `object.enable.files` | the #2727 opt-in gate proven in BOTH directions — the fixture carries a deliberate non-declaring object (`att_nofiles`) that must be refused 403 FILES_DISABLED |
19+
| `showcase-d3-d4-capabilities` | `permission.rowLevelSecurity.check` | authors `check: 'owner == current_user.email'` and proves the write POST-image is validated (distinct from `using`, which filters the pre-image) |
20+
| `showcase-scope-depth` | `permission.objects.readScope` | authors `unit` / `unit_and_below` profiles and proves the owner-match widens, with cross-BU still isolated |
21+
| `owner-anchor-and-bulk-writes` | `permission.objects.modifyAllRecords` | member denied the transfer, privileged caller allowed — both directions |
22+
| `semantic-roles-served` | `object.highlightFields`, `.stageField`, `.fieldGroups` | asserts all three survive defineStack → artifact → registry → REST verbatim (incl. `stageField: false` as a strict false) |
23+
24+
**Eight do not, and record why** rather than faking a binding — the shape the
25+
registry already used for `permission-set-projection`:
26+
27+
- `flow-runas-schedule` and `showcase-scope-depth-fallback` guard properties
28+
(`flow.runAs`, `permission.objects.readScope`) that are *already* bound to a
29+
sibling proof. A ledger entry carries one `proof` ref, so a second gate on the
30+
same property is not representable — they run unconditionally instead.
31+
- `me-apps-and-everyone-baseline` enforces `app.requiredPermissions` /
32+
`app.tabPermissions`; `app` is not a governed type yet. Bind when it lands.
33+
- `showcase-agent-intersection` / `showcase-agent-scope-ceiling` guard runtime
34+
principal-resolution invariants (`onBehalfOf`, OAuth scope → ceiling set), not
35+
authorable metadata.
36+
- `showcase-bu-hierarchy-sharing` / `showcase-declarative-rbac-seeding` act on
37+
stack-level `roles`/`sharingRules`, not a per-type property surface.
38+
- `showcase-permission-zoo` is a breadth guard over the whole ADR-0090 surface;
39+
binding it to any one entry would misrepresent both.
40+
41+
**One deliberate non-binding worth naming.** `owner-anchor-and-bulk-writes` binds
42+
`modifyAllRecords`, not the sibling `allowTransfer` — the proof only *mentions*
43+
`allowTransfer` in a comment and never authors it. Binding a property a proof
44+
does not exercise is the same false comfort as a preview renderer standing in for
45+
a runtime consumer, which is the error this ledger spent #3686 unwinding.
46+
47+
Also verified the bound proofs actually run: the only `skipIf` among them covers
48+
`attachments-permission-matrix`'s enterprise cross-tenant block, not the
49+
FILES_DISABLED assertion the binding rests on.
50+
51+
The gate now runs with **zero warnings** — the orphan list joins the
52+
stale-evidence list at empty, so both mean something again. The ledger README's
53+
ratchet table was itself stale (5 classes listed, 10 bound) and is now complete,
54+
with the unbound set and its reasons alongside.

packages/spec/liveness/README.md

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,52 @@ proof gets wired in.
214214
once it has *both* a runtime proof *and* a governed ledger entry to carry it — the
215215
binding lands one class at a time (ADR-0054 §3), never as a big-bang backfill.
216216

217-
| High-risk class | Bound? | Ledger binding | Proof |
218-
|---|---|---|---|
219-
| Field types | ✅ enforced | `field.type` | `field-zoo-roundtrip.dogfood.test.ts#field-type-roundtrip` |
220-
| RLS / sharing | ✅ enforced | `permission.rowLevelSecurity.using` | `rls-fixture.dogfood.test.ts#rls-by-id-write` |
221-
| Master-detail controlled-by-parent | ✅ enforced | `object.sharingModel` | `controlled-by-parent.dogfood.test.ts#cbp-controlled-by-parent` |
222-
| Flow nodes | ✅ enforced | `flow.nodes.type` | `flow-node.dogfood.test.ts#flow-node-execution` |
223-
| Analytics dims/measures | ✅ enforced | `dataset.dimensions.dateGranularity` | `analytics-timezone.dogfood.test.ts#analytics-tz-bucketing` |
224-
| Form layout/section/widget | ⛔ pending || none yet (`view` is governed as of #2998 Track B, so `view.form.*` can carry the binding — the dogfood proof is the missing half) |
217+
`proof-registry.mts` is the source of truth; the tables below mirror it.
218+
219+
**Bound — a `live` classification on these entries REQUIRES its proof:**
220+
221+
| High-risk class | Ledger binding | Proof (`packages/qa/dogfood/test/…`) |
222+
|---|---|---|
223+
| Field types | `field.type` | `field-zoo-roundtrip#field-type-roundtrip` |
224+
| RLS / sharing (pre-image) | `permission.rowLevelSecurity.using` | `rls-fixture#rls-by-id-write` |
225+
| RLS `check` (post-image) | `permission.rowLevelSecurity.check` | `showcase-d3-d4-capabilities` |
226+
| Master-detail controlled-by-parent | `object.sharingModel` | `controlled-by-parent#cbp-controlled-by-parent` |
227+
| Scope-depth read grants | `permission.objects.readScope` | `showcase-scope-depth` |
228+
| Ownership anchor + bulk writes | `permission.objects.modifyAllRecords` | `owner-anchor-and-bulk-writes` |
229+
| Delegation of duty | `position.delegatable` | `delegation-of-duty#delegation-of-duty` |
230+
| Static readonly write | `field.readonly` | `showcase-static-readonly#readonly-static-write` |
231+
| Attachments capability gate | `object.enable.files` | `attachments-permission-matrix` |
232+
| Analytics dims/measures | `dataset.dimensions.dateGranularity` | `analytics-timezone#analytics-tz-bucketing` |
233+
| Flow nodes | `flow.nodes.type` | `flow-node#flow-node-execution` |
234+
| Flow runAs identity | `flow.runAs` | `flow-runas#flow-runas-identity` |
235+
| Data lifecycle (ADR-0057) | `object.lifecycle` | `storage-growth#adr0057-lifecycle-bounded-growth` |
236+
| Webhook materialization | `webhook.object` | `webhook-materialization#webhook-materialization` |
237+
| Object semantic roles (ADR-0085) | `object.highlightFields`, `.stageField`, `.fieldGroups` | `semantic-roles#semantic-roles-served` |
238+
239+
**Registered but unbound.** A proof with no authorable property to ratchet is
240+
still registered — otherwise its `@proof:` tag reads as an orphan — and records
241+
*why* rather than faking a binding:
242+
243+
| Proof | Why unbound |
244+
|---|---|
245+
| `form-widget-resolution` | no proof written yet (ADR-0054 Phase 2); `view.form.*` is governed and can carry it |
246+
| `permission-set-projection` | a storage invariant (ADR-0094), not an authorable property |
247+
| `flow-runas-schedule` | guards `flow.runAs`, already bound to `flow-runas-identity` — one entry carries one `proof` |
248+
| `showcase-scope-depth-fallback` | guards `permission.objects.readScope`, already bound — this is the CLI-wiring sibling |
249+
| `me-apps-and-everyone-baseline` | enforces `app.requiredPermissions` / `app.tabPermissions`; `app` is not governed yet |
250+
| `showcase-agent-intersection` | a runtime principal-resolution invariant (`onBehalfOf`), not authorable |
251+
| `showcase-agent-scope-ceiling` | the OAuth-scope → ceiling-set mapping lives in the runtime resolver |
252+
| `showcase-bu-hierarchy-sharing` | stack-level `sharingRules`, not a governed per-type property |
253+
| `showcase-declarative-rbac-seeding` | stack-level `roles`/`sharingRules` seeding, same shape |
254+
| `showcase-permission-zoo` | a breadth guard over the whole ADR-0090 surface, not a single-property gate |
255+
256+
Two habits this table is meant to enforce. **Register every proof**, so the
257+
orphan warning stays empty and means something. **Bind only what the proof
258+
actually authors**`owner-anchor-and-bulk-writes` binds `modifyAllRecords`,
259+
which it exercises in both directions, and *not* the sibling `allowTransfer`,
260+
which it only mentions in a comment. Binding a property a proof does not
261+
exercise is the same false comfort as a preview renderer standing in for a
262+
runtime consumer.
225263

226264
To bind a pending class: add its dogfood proof + `@proof:` tag, set `bound: true` and
227265
its `ledgerBindings` in `proof-registry.mts`, add the `proof` to the ledger entry, and

packages/spec/liveness/object.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,17 @@
3737
},
3838
"highlightFields": {
3939
"status": "live",
40+
"proof": "packages/qa/dogfood/test/semantic-roles.dogfood.test.ts#semantic-roles-served",
4041
"note": "ADR-0085 semantic role (renamed from compactLayout): objectui default list/grid columns (ObjectGrid/ObjectView/InterfaceListPage), child-record previews (first entry), record:details auto layout, detail highlight strip (first 4)."
4142
},
4243
"stageField": {
4344
"status": "live",
45+
"proof": "packages/qa/dogfood/test/semantic-roles.dogfood.test.ts#semantic-roles-served",
4446
"note": "ADR-0085 semantic role: objectui plugin-detail detectStatusField drives the record:path stepper; string names the lifecycle field, false suppresses heuristic stage detection."
4547
},
4648
"fieldGroups": {
4749
"status": "live",
50+
"proof": "packages/qa/dogfood/test/semantic-roles.dogfood.test.ts#semantic-roles-served",
4851
"note": "objectui form layout order."
4952
},
5053
"listViews": {
@@ -167,6 +170,7 @@
167170
},
168171
"files": {
169172
"status": "live",
173+
"proof": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts#attachments-permission-matrix",
170174
"evidence": "packages/plugins/plugin-audit/src/audit-writers.ts (enforceFilesCapability beforeInsert hook: sys_attachment rows may only target objects declaring enable.files true — 403 FILES_DISABLED otherwise); objectui RecordAttachmentsPanel renders the record Attachments surface (upload/list/download/delete) when the flag is true. Opt-in — spec default false (#2727)."
171175
},
172176
"feeds": {

packages/spec/liveness/permission.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,12 @@
7575
},
7676
"modifyAllRecords": {
7777
"status": "live",
78+
"proof": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts#owner-anchor-and-bulk-writes",
7879
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:60"
7980
},
8081
"readScope": {
8182
"status": "live",
83+
"proof": "packages/qa/dogfood/test/showcase-scope-depth.dogfood.test.ts#showcase-scope-depth",
8284
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts (getEffectiveScope) + packages/plugins/plugin-sharing/src/sharing-service.ts (owner-match widened by readScope; hierarchy values delegated to IHierarchyScopeResolver)",
8385
"note": "ADR-0057 D1 — read access DEPTH (own/own_and_reports/unit/unit_and_below/org). own/org enforced in open edition; hierarchy values via the enterprise hierarchy-scope-resolver (fail-closed to owner-only when absent; defineStack requires 'hierarchy-security'). Proven: packages/qa/dogfood/test/showcase-scope-depth.dogfood.test.ts."
8486
},
@@ -142,6 +144,7 @@
142144
},
143145
"check": {
144146
"status": "live",
147+
"proof": "packages/qa/dogfood/test/showcase-d3-d4-capabilities.dogfood.test.ts#showcase-d3-d4-capabilities",
145148
"evidence": "packages/plugins/plugin-security/src/rls-compiler.ts"
146149
},
147150
"positions": {

0 commit comments

Comments
 (0)