Skip to content

Commit 15058d6

Browse files
committed
docs(verify, plugin-security): the two-level controlled_by_parent write is described as ruled, and the coverage vouch lists the chain hop
Four texts this PR's change made false or incomplete, prose and comments only: the pending parent-write-reach release note, the verify README and the cli.mdx family description now state that a detail under a controlled_by_parent middle master is writable exactly when the caller may update that middle master directly (which needs edit access to the root), and that the family reports such a chain proven; the masterGateCoversThisWrite option docblock lists the chain hop among the call sites that vouch, pointing at that call site's own bullet. Claude-Session: https://claude.ai/code/session_01FzQy26KznSa5j2gnf3AzsE Co-authored-by: Claude <noreply@anthropic.com>
1 parent 343e21e commit 15058d6

4 files changed

Lines changed: 16 additions & 9 deletions

File tree

‎.changeset/15953-verify-parent-write-reach-family.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The promise is ADR-0055 §3 and its chain amendment: editing, deleting or creati
1616

1717
The writes are made by three verifier personas whose permission sets the family authors from the app's own objects — the root master by the all-records grant (`modifyAllRecords`) with every verb on the detail; the root master readable but not editable with every verb on the detail; the all-records grant with no delete on the detail — and by one persona per position the app declares, whose capability is what the app binds to the position. Each writes under a master chain another principal owns (so the all-records grant, not ownership, is exercised) and one it owns, on a detail another principal owns and one it owns. A refusal holds only as the platform's `403 PERMISSION_DENIED` naming the detail and the operation; any other refusal (a rule, a hook's own code, a refusal on another object) is `unattributed`. An object is `proven`, `contradicted` (a write met the other answer), `failed` (a door faulted), `inconclusive`, `excluded` (no one master: none, two, an undeclared one, a cycle, or a chain at the write gate's depth bound) or `not-derivable`. `contradicted + failed` objects, plus personas that could not be provisioned, are the report's `hardFailures`.
1818

19-
Today's engine refuses every write to a detail two levels below its root master, even where the caller edits the middle master, so on an app that declares such a chain the family reports a contradiction and the run fails.
19+
A detail two levels below its root master (a line under a quote under an account) is answered the same way: its write is admitted exactly when the caller holds the verb on the detail and may update the middle master directly, which also needs edit access to the root, and refused otherwise, so the family reports such a chain `proven`.
2020

2121
**`@objectstack/verify` — new exports.**
2222

‎content/docs/deployment/cli.mdx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,10 +2011,11 @@ os verify --parent-write-reach # Also prove each controlled_by_p
20112011
naming the detail and the operation (a rule, a hook's own code, a refusal
20122012
on another object) is reported inconclusive, never proven. An object
20132013
with two `master_detail` fields, none, or a chain at the write gate's depth
2014-
bound is reported `excluded`. Today's engine refuses every write to a detail
2015-
two levels below its root master (a line under a quote under an account),
2016-
even where the caller edits the middle master, so an app that declares such
2017-
a chain fails the run.
2014+
bound is reported `excluded`. A detail two levels below its root master (a
2015+
line under a quote under an account) is answered the same way: its write is
2016+
admitted exactly when the caller holds the verb on the detail and may update
2017+
the middle master directly, which also needs edit access to the root, and
2018+
refused otherwise, so such a chain is reported proven.
20182019
`--multi-tenant` boots org-scoped so tenant-isolation RLS policies
20192020
apply; a walled tenancy posture (`OS_TENANCY_POSTURE` set to `isolated` or
20202021
`group`) asks for the same boot.

‎packages/plugins/plugin-security/src/security-plugin.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,11 @@ interface RlsFilterOptions {
458458
* sits directly below it on the same middleware pass, guarded by a
459459
* SUPERSET of step 2.7's own condition (same `permissionSets`/`userId`/
460460
* `ql` triple, same single target id, one extra operation — `insert`);
461+
* • [#23093] the chain walk's master-edit leg
462+
* ({@link SecurityPlugin.assertMasterRowEditable}) sets it on a hop whose
463+
* master is itself `controlled_by_parent`, on a non-delegated write. Why
464+
* the walk covers that master's write is stated once, at that call site's
465+
* `masterGateCoversThisWrite` bullet — not restated here;
461466
* • the BULK write path does NOT, and must not: with no single id,
462467
* `assertControlledByParentWrite` returns early and nothing replaces the
463468
* floor. Reading the posture alone here would have widened bulk writes on

‎packages/verify/README.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,11 @@ names, while the REST envelope names the route's object whatever was refused.
133133
A refusal holds only as the platform's `403 PERMISSION_DENIED` naming the
134134
detail and the operation; any other refusal (a rule, a hook's own code, a
135135
refusal on another object) is `unattributed`. The system door bypasses
136-
authorization and is not asked. Today's engine refuses every write to a detail
137-
two levels below its root master, even where the caller edits the middle
138-
master, so on an app that declares such a chain the family reports a
139-
contradiction and fails the run.
136+
authorization and is not asked. A detail two levels below its root master is
137+
answered the same way: its write is admitted exactly when the caller holds the
138+
verb on the detail and may update the middle master directly, which also needs
139+
edit access to the root, and refused otherwise, so the family reports such a
140+
chain `proven`.
140141

141142
Exit code is **non-zero** on real failures (`create-failed`, `read-failed`,
142143
`fidelity-gaps`, `rls-hole`, with `--reference-shapes` a field with `holes` or

0 commit comments

Comments
 (0)