Skip to content

feat(security): master-detail controlled-by-parent permissions + verify topological synthesis (ADR-0055)#2049

Merged
os-zhuang merged 6 commits into
mainfrom
claude/adr-0055-permission-model
Jun 19, 2026
Merged

feat(security): master-detail controlled-by-parent permissions + verify topological synthesis (ADR-0055)#2049
os-zhuang merged 6 commits into
mainfrom
claude/adr-0055-permission-model

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What

Implements ADR-0055 end-to-end (the ADR is included, now Accepted): master-detail "controlled by parent" permissions — a detail object's read/write access derived from its master record — plus the verify harness capability that makes it provable.

Previously OWDModel.controlled_by_parent was declared but unenforced (no consumers; not authorable via sharingModel; the RLS compiler is relationship-blind). This closes that gap on the existing engine (no rewrite), guarded by a runtime proof.

Phases (all in this PR)

P0 — @objectstack/verify related-record topological synthesis. deriveCrudCases no longer skips objects with required relations: it builds the object dependency graph, orders it topologically (targets before dependents), and threads real ids (fillRelationalRefs) through both runners. Honest blocked verdicts remain for required-reference cycles and external/missing targets. Independently widens objectstack verify coverage. (10 unit tests.)

P1 — spec + read derivation. controlled_by_parent added to the authorable object.sharingModel enum. For such an object, the security middleware resolves the master records the user can read (reusing the master's own RLS via computeRlsFilter, run under a system context — no re-entry/recursion) and AND-injects masterFK IN (accessible master ids) onto reads. Empty set ⇒ fail closed. Zero RLS-compiler changes.

P2 — write enforcement + proof. The by-id write path is extended: a detail insert/update/delete requires CRUD edit on the master AND the master row visible under the master's write RLS (closes the #1994-class by-id hole for derived access). A dogfood RLS proof asserts both directions and is bound in the liveness ledger on object.sharingModel.

Verification (all local, green)

  • Full dogfood suite: 8 files, 73 tests — incl. the new controlled-by-parent proof (4/4: derived read denied, derived by-id write denied/row-unchanged, not over-blocked on own master) and the topology unit tests.
  • plugin-security unit tests: 102/102 (cbp path is gated to controlled_by_parent, so non-cbp objects are unaffected).
  • Liveness gate green — five bound high-risk classes now: field types, RLS, controlled-by-parent, analytics, flow nodes. 18 registry tests.
  • examples typecheck green (also fixes a pre-existing main breakage from feat(app-showcase): task default list switches across every record visualization #2048 in task-triage.page.ts that reddened TypeScript Type Check on every PR).

Honest v1 limits (in the ADR)

Accessible-master id-set is unbounded (large-tenant scale = documented future limit); master-detail chains are single-level (not transitively traversed).

Builds on ADR-0049 and ADR-0054.

🤖 Generated with Claude Code

…write (Proposed)

Records the architecture decision that the authorization model (already
Salesforce-shaped: permission sets + FLS + predicate RLS + ownership + sharing
rules + hierarchy) should close enumerable gaps via ADR-0049 enforce-or-remove
and ADR-0054 prove-it-runs, against a mainstream conformance checklist — NOT a
rewrite. Headline gap: master-detail `controlled_by_parent` is declared
(OWDModel) yet has zero runtime consumers, isn't reachable through the object's
`sharingModel` enum, and the RLS compiler is relationship-blind. Includes a
conformance matrix and per-gap implement/remove/not-do verdicts. Status: Proposed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
@vercel

vercel Bot commented Jun 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 19, 2026 3:51pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation size/s labels Jun 19, 2026
…by-parent design

Refocus from a high-level "gap-closure not rewrite" principle to the concrete
landing plan for master-detail permission inheritance — the substance an ADR
should carry. Grounded in an engine investigation:

- Read: auto-derive the user's accessible master-id set and constrain the detail's
  master FK via the EXISTING pre-resolved-membership mechanism
  (ExecutionContext.rlsMembership + the compiler's `field IN (current_user.<key>)`
  form) — zero RLS-compiler changes. Trade-off table vs subquery-join (compiler has
  no subquery support) and materialized column (dual-write/stale).
- Write: extend the #1994 by-id-write pre-image check to require master EDIT access.
- Spec: add controlled_by_parent to the authorable sharingModel enum (fixes the
  OWDModel/sharingModel inconsistency); detail must declare one required
  master_detail field.
- Proof (ADR-0054): a dogfood RLS proof; prerequisite is related-record topological
  synthesis in @objectstack/verify (P0). Honest limits: id-set size ceiling,
  single-level chains in v1. Status: Proposed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
@os-zhuang os-zhuang changed the title docs(adr): ADR-0055 — permission model evolves by gap-closure, not rewrite (Proposed) docs(adr): ADR-0055 — master-detail "controlled by parent" permissions, concrete design (Proposed) Jun 19, 2026
claude added 3 commits June 19, 2026 15:02
deriveCrudCases no longer skips objects with required relations. It builds the
object dependency graph from required relational fields, topologically orders it
(targets before dependents), and emits `relationalRefs` the runner fills with
real ids — so relationship-dense objects (the core of real apps) are verified,
not skipped. Both runners (runCrudVerification, runRlsProofs) thread a created-id
registry via fillRelationalRefs.

Honest `blocked` verdicts remain for the genuinely unsatisfiable: required
relation to an external/missing target, required-reference cycle (incl. required
self-reference), and cascade (target itself blocked). Optional relations never
block — filled best-effort, else left null.

This is the prerequisite harness for the master-detail controlled-by-parent RLS
proof (ADR-0055 P2), and independently widens `objectstack verify` coverage.

Tests: 10 unit cases (ordering, optional/required, external/missing, cascade,
cycle, self-ref, id threading). Full dogfood suite green (69 tests); rls
cross-owner counts unchanged on example-crm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
…ADR-0055 P1+P2)

Implements derived master-detail access — a detail object's read/write scope is
derived from its master record.

- spec: `controlled_by_parent` added to the authorable `object.sharingModel` enum
  (converges with OWDModel).
- security (read): for a controlled_by_parent object, resolve the master records
  the user can read (reusing the master's own RLS via computeRlsFilter, run under
  a system context — no middleware re-entry) and AND-inject
  `masterFK IN (accessible master ids)` onto the read AST. Empty set ⇒ fail closed.
  Zero RLS-compiler changes (builds the $in filter directly).
- security (write): extend the by-id-write path — a detail insert/update/delete
  requires CRUD edit on the master AND the master row visible under the master's
  write RLS. Closes the #1994-class by-id hole for derived access (the detail
  carries no authored RLS of its own).
- proof (ADR-0054): dogfood RLS proof — a member who can't read the master can
  neither read nor by-id-write the detail, and isn't over-blocked on a master they
  own. Bound in the liveness ledger on object.sharingModel.

Five high-risk classes now CI-enforced: field types, RLS, controlled-by-parent,
analytics, flow nodes. Honest v1 limits documented in ADR-0055 (id-set ceiling,
single-level chains). Full dogfood suite green (73 tests); liveness gate green;
18 registry tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
…ypecheck

- ADR-0055 status → Accepted (implemented in this PR, P0–P2).
- changeset for the controlled-by-parent feature (spec/plugin-security/verify minor).
- fix(app-showcase): task-triage userFilters tabs were missing the now-required
  pinned/isDefault/visible fields (a pre-existing main breakage from #2048 that
  reddened TypeScript Type Check on every PR) — examples typecheck now green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/dogfood, @objectstack/plugin-security, @objectstack/spec, @objectstack/verify.

89 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/cloud-artifact-api.mdx (via packages/spec)
  • content/docs/concepts/cluster-semantics.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/concepts/packages.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/concepts/setup-app.mdx (via @objectstack/spec)
  • content/docs/concepts/skills.mdx (via @objectstack/spec)
  • content/docs/concepts/webhook-delivery.mdx (via @objectstack/spec)
  • content/docs/getting-started/architecture.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/getting-started/core-concepts.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/guides/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/guides/ai-capabilities.mdx (via @objectstack/spec)
  • content/docs/guides/airtable-dashboard-analysis.mdx (via @objectstack/spec)
  • content/docs/guides/analytics-datasets.mdx (via @objectstack/spec)
  • content/docs/guides/api-reference.mdx (via @objectstack/spec)
  • content/docs/guides/business-logic.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/error-catalog.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-type-gallery.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-validation-rules.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/permissions-matrix.mdx (via packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/guides/cheatsheets/protocol-diagram.mdx (via packages/spec)
  • content/docs/guides/cheatsheets/query-cheat-sheet.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/quick-reference.mdx (via @objectstack/spec)
  • content/docs/guides/client-sdk.mdx (via @objectstack/spec)
  • content/docs/guides/common-patterns.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/auth-service.mdx (via packages/spec)
  • content/docs/guides/contracts/cache-service.mdx (via packages/spec)
  • content/docs/guides/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/index.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/guides/contracts/storage-service.mdx (via packages/spec)
  • content/docs/guides/data-modeling.mdx (via @objectstack/spec)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/spec)
  • content/docs/guides/driver-configuration.mdx (via @objectstack/spec)
  • content/docs/guides/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/guides/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/guides/formula.mdx (via @objectstack/spec)
  • content/docs/guides/hook-bodies.mdx (via packages/spec)
  • content/docs/guides/kernel-services.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/dashboard.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/guides/metadata/field.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/flow.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/index.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/object.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/validation.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/workflow.mdx (via @objectstack/spec)
  • content/docs/guides/packages.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/guides/plugin-development.mdx (via @objectstack/spec)
  • content/docs/guides/plugins.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/guides/project-scoping.mdx (via @objectstack/spec)
  • content/docs/guides/public-forms.mdx (via @objectstack/spec)
  • content/docs/guides/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/index.mdx (via packages/spec)
  • content/docs/guides/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/guides/security.mdx (via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/guides/seed-data.mdx (via @objectstack/spec)
  • content/docs/guides/skills.mdx (via @objectstack/spec)
  • content/docs/guides/standards.mdx (via @objectstack/spec)
  • content/docs/guides/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang os-zhuang changed the title docs(adr): ADR-0055 — master-detail "controlled by parent" permissions, concrete design (Proposed) feat(security): master-detail controlled-by-parent permissions + verify topological synthesis (ADR-0055) Jun 19, 2026
…n (ADR-0055)

Adds the canonical master-detail scenario to the showcase: `showcase_invoice_line`
now declares `sharingModel: 'controlled_by_parent'`, so a line's access is derived
from its parent `showcase_invoice` (a line is meaningless apart from its invoice).
Admin/default single-tenant behavior is unchanged (admin sees all invoices → all
lines); the showcase's own 20 tests still pass.

Verification (real HTTP stack — the boundary is server-side; no browser needed):
a dogfood proof boots the REAL showcase app with a member permission set carrying
an owner policy on the invoice, then asserts a member who cannot read an
admin-owned invoice can neither read nor by-id-write its lines, but can read/edit
lines under an invoice they own. 4/4 green; full dogfood suite 77 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
@github-actions github-actions Bot added size/xl and removed size/l labels Jun 19, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review June 19, 2026 16:02
@os-zhuang
os-zhuang merged commit 4c213c2 into main Jun 19, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/adr-0055-permission-model branch June 19, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants