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
54 changes: 54 additions & 0 deletions .changeset/liveness-register-orphan-proofs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
"@objectstack/spec": patch
---

feat(spec): register the 13 orphan dogfood proofs — five advance the ADR-0054 ratchet, eight say why they can't

The gate flagged 13 `@proof:` tags under `packages/qa/dogfood/test/**` that no
class in `proof-registry.mts` claimed. Silencing that warning is trivial and
worthless; the useful question is the one ADR-0054 §3 actually asks: **is there
an authorable property whose `live` status this proof gates?** Each of the 13 was
re-read against that.

**Five had one, and are now BOUND** — a `live` classification on these entries
requires its proof, so the ratchet advances from 10 bound paths to 17:

| Proof | Now gates | Why it qualifies |
|---|---|---|
| `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 |
| `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) |
| `showcase-scope-depth` | `permission.objects.readScope` | authors `unit` / `unit_and_below` profiles and proves the owner-match widens, with cross-BU still isolated |
| `owner-anchor-and-bulk-writes` | `permission.objects.modifyAllRecords` | member denied the transfer, privileged caller allowed — both directions |
| `semantic-roles-served` | `object.highlightFields`, `.stageField`, `.fieldGroups` | asserts all three survive defineStack → artifact → registry → REST verbatim (incl. `stageField: false` as a strict false) |

**Eight do not, and record why** rather than faking a binding — the shape the
registry already used for `permission-set-projection`:

- `flow-runas-schedule` and `showcase-scope-depth-fallback` guard properties
(`flow.runAs`, `permission.objects.readScope`) that are *already* bound to a
sibling proof. A ledger entry carries one `proof` ref, so a second gate on the
same property is not representable — they run unconditionally instead.
- `me-apps-and-everyone-baseline` enforces `app.requiredPermissions` /
`app.tabPermissions`; `app` is not a governed type yet. Bind when it lands.
- `showcase-agent-intersection` / `showcase-agent-scope-ceiling` guard runtime
principal-resolution invariants (`onBehalfOf`, OAuth scope → ceiling set), not
authorable metadata.
- `showcase-bu-hierarchy-sharing` / `showcase-declarative-rbac-seeding` act on
stack-level `roles`/`sharingRules`, not a per-type property surface.
- `showcase-permission-zoo` is a breadth guard over the whole ADR-0090 surface;
binding it to any one entry would misrepresent both.

**One deliberate non-binding worth naming.** `owner-anchor-and-bulk-writes` binds
`modifyAllRecords`, not the sibling `allowTransfer` — the proof only *mentions*
`allowTransfer` 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 the error this ledger spent #3686 unwinding.

Also verified the bound proofs actually run: the only `skipIf` among them covers
`attachments-permission-matrix`'s enterprise cross-tenant block, not the
FILES_DISABLED assertion the binding rests on.

The gate now runs with **zero warnings** — the orphan list joins the
stale-evidence list at empty, so both mean something again. The ledger README's
ratchet table was itself stale (5 classes listed, 10 bound) and is now complete,
with the unbound set and its reasons alongside.
54 changes: 46 additions & 8 deletions packages/spec/liveness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,52 @@ proof gets wired in.
once it has *both* a runtime proof *and* a governed ledger entry to carry it — the
binding lands one class at a time (ADR-0054 §3), never as a big-bang backfill.

| High-risk class | Bound? | Ledger binding | Proof |
|---|---|---|---|
| Field types | ✅ enforced | `field.type` | `field-zoo-roundtrip.dogfood.test.ts#field-type-roundtrip` |
| RLS / sharing | ✅ enforced | `permission.rowLevelSecurity.using` | `rls-fixture.dogfood.test.ts#rls-by-id-write` |
| Master-detail controlled-by-parent | ✅ enforced | `object.sharingModel` | `controlled-by-parent.dogfood.test.ts#cbp-controlled-by-parent` |
| Flow nodes | ✅ enforced | `flow.nodes.type` | `flow-node.dogfood.test.ts#flow-node-execution` |
| Analytics dims/measures | ✅ enforced | `dataset.dimensions.dateGranularity` | `analytics-timezone.dogfood.test.ts#analytics-tz-bucketing` |
| 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) |
`proof-registry.mts` is the source of truth; the tables below mirror it.

**Bound — a `live` classification on these entries REQUIRES its proof:**

| High-risk class | Ledger binding | Proof (`packages/qa/dogfood/test/…`) |
|---|---|---|
| Field types | `field.type` | `field-zoo-roundtrip#field-type-roundtrip` |
| RLS / sharing (pre-image) | `permission.rowLevelSecurity.using` | `rls-fixture#rls-by-id-write` |
| RLS `check` (post-image) | `permission.rowLevelSecurity.check` | `showcase-d3-d4-capabilities` |
| Master-detail controlled-by-parent | `object.sharingModel` | `controlled-by-parent#cbp-controlled-by-parent` |
| Scope-depth read grants | `permission.objects.readScope` | `showcase-scope-depth` |
| Ownership anchor + bulk writes | `permission.objects.modifyAllRecords` | `owner-anchor-and-bulk-writes` |
| Delegation of duty | `position.delegatable` | `delegation-of-duty#delegation-of-duty` |
| Static readonly write | `field.readonly` | `showcase-static-readonly#readonly-static-write` |
| Attachments capability gate | `object.enable.files` | `attachments-permission-matrix` |
| Analytics dims/measures | `dataset.dimensions.dateGranularity` | `analytics-timezone#analytics-tz-bucketing` |
| Flow nodes | `flow.nodes.type` | `flow-node#flow-node-execution` |
| Flow runAs identity | `flow.runAs` | `flow-runas#flow-runas-identity` |
| Data lifecycle (ADR-0057) | `object.lifecycle` | `storage-growth#adr0057-lifecycle-bounded-growth` |
| Webhook materialization | `webhook.object` | `webhook-materialization#webhook-materialization` |
| Object semantic roles (ADR-0085) | `object.highlightFields`, `.stageField`, `.fieldGroups` | `semantic-roles#semantic-roles-served` |

**Registered but unbound.** A proof with no authorable property to ratchet is
still registered — otherwise its `@proof:` tag reads as an orphan — and records
*why* rather than faking a binding:

| Proof | Why unbound |
|---|---|
| `form-widget-resolution` | no proof written yet (ADR-0054 Phase 2); `view.form.*` is governed and can carry it |
| `permission-set-projection` | a storage invariant (ADR-0094), not an authorable property |
| `flow-runas-schedule` | guards `flow.runAs`, already bound to `flow-runas-identity` — one entry carries one `proof` |
| `showcase-scope-depth-fallback` | guards `permission.objects.readScope`, already bound — this is the CLI-wiring sibling |
| `me-apps-and-everyone-baseline` | enforces `app.requiredPermissions` / `app.tabPermissions`; `app` is not governed yet |
| `showcase-agent-intersection` | a runtime principal-resolution invariant (`onBehalfOf`), not authorable |
| `showcase-agent-scope-ceiling` | the OAuth-scope → ceiling-set mapping lives in the runtime resolver |
| `showcase-bu-hierarchy-sharing` | stack-level `sharingRules`, not a governed per-type property |
| `showcase-declarative-rbac-seeding` | stack-level `roles`/`sharingRules` seeding, same shape |
| `showcase-permission-zoo` | a breadth guard over the whole ADR-0090 surface, not a single-property gate |

Two habits this table is meant to enforce. **Register every proof**, so the
orphan warning stays empty and means something. **Bind only what the proof
actually authors** — `owner-anchor-and-bulk-writes` binds `modifyAllRecords`,
which it exercises in both directions, and *not* the sibling `allowTransfer`,
which it only mentions in a comment. Binding a property a proof does not
exercise is the same false comfort as a preview renderer standing in for a
runtime consumer.

To bind a pending class: add its dogfood proof + `@proof:` tag, set `bound: true` and
its `ledgerBindings` in `proof-registry.mts`, add the `proof` to the ledger entry, and
Expand Down
4 changes: 4 additions & 0 deletions packages/spec/liveness/object.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,17 @@
},
"highlightFields": {
"status": "live",
"proof": "packages/qa/dogfood/test/semantic-roles.dogfood.test.ts#semantic-roles-served",
"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)."
},
"stageField": {
"status": "live",
"proof": "packages/qa/dogfood/test/semantic-roles.dogfood.test.ts#semantic-roles-served",
"note": "ADR-0085 semantic role: objectui plugin-detail detectStatusField drives the record:path stepper; string names the lifecycle field, false suppresses heuristic stage detection."
},
"fieldGroups": {
"status": "live",
"proof": "packages/qa/dogfood/test/semantic-roles.dogfood.test.ts#semantic-roles-served",
"note": "objectui form layout order."
},
"listViews": {
Expand Down Expand Up @@ -167,6 +170,7 @@
},
"files": {
"status": "live",
"proof": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts#attachments-permission-matrix",
"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)."
},
"feeds": {
Expand Down
3 changes: 3 additions & 0 deletions packages/spec/liveness/permission.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,12 @@
},
"modifyAllRecords": {
"status": "live",
"proof": "packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts#owner-anchor-and-bulk-writes",
"evidence": "packages/plugins/plugin-security/src/permission-evaluator.ts:60"
},
"readScope": {
"status": "live",
"proof": "packages/qa/dogfood/test/showcase-scope-depth.dogfood.test.ts#showcase-scope-depth",
"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)",
"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."
},
Expand Down Expand Up @@ -142,6 +144,7 @@
},
"check": {
"status": "live",
"proof": "packages/qa/dogfood/test/showcase-d3-d4-capabilities.dogfood.test.ts#showcase-d3-d4-capabilities",
"evidence": "packages/plugins/plugin-security/src/rls-compiler.ts"
},
"positions": {
Expand Down
Loading
Loading