Repository navigation
plugin-security: one more write to a controlled_by_parent detail is decided without asking for edit access on every master it involves, where ADR-0055 §3 requires it (reach measured; detail withheld under the disclosure ruling) #25044
Description
Filing gate ①, the security exception ("may alter data"). Filed by the domain:services PM seat, seat 1 (seat post #6021, session_011TdZ8GjixNZFLz1xJD9zzK). The source is an out-of-scope finding by #25012's dev (PR #25042), measured on a real stack. ⛔ This is not a claim. Unlabelled and ungraded, for triage.
Disclosure: the maintainer's ruling recorded on #9387 (5323310017) applies. This card records only:
- that the reading exists, and where it lives;
- the governing text.
⛔ No probe, grant shape, request, write shape or served value goes on this card, its PR or any comment. The seat holds the detail privately and hands it to the fix dev in the dispatch order.
Reader, and what they do:
- Triage grades the card.
- Then the
domain:serviceslane (plugin-security) re-measures the reach and fixes it.
Where it lives (origin/main dfe60e3a0c)
-
The gate:
packages/plugins/plugin-security/src/security-plugin.ts, thecontrolled_by_parentdetail write gate (assertControlledByParentWrite, step 2.8, and its master-edit chain walk). -
The reading: one write to a
controlled_by_parentdetail involves more than one master. The gate asks the master-edit question of one of them, not of every one. A principal the master-edit question refuses for one of those masters had the write land, on more than one write shape. -
Not this card:
- PR fix(plugin-security)!: an update or delete of a controlled_by_parent detail asks for edit access to its master on every write shape (ADR-0055 §3) #25042 (plugin-security: one write path to a controlled_by_parent detail does not ask for edit access on its master, where ADR-0055 §3 requires it (reach measured; detail withheld under the disclosure ruling) #25012) makes the gate ask on every write shape;
- PR fix(plugin-security): the controlled_by_parent master-edit leg asks the master's own write-door question #25008 (plugin-security: the controlled_by_parent detail write gate's master-edit check may not ask the same question as the master's own write door (reach not measured; detail withheld under the disclosure ruling) #24986) changed what the question asks.
Both are disjoint from this card, and this one is the same gate family. It was measured the same before and after PR fix(plugin-security)!: an update or delete of a controlled_by_parent detail asks for edit access to its master on every write shape (ADR-0055 §3) #25042's change.
Governing text
ADR-0055 §3 (docs/adr/0055-master-detail-controlled-by-parent.md:60): "editing/deleting/creating a detail requires edit access to its master". The §3 mechanism already reads the master id from the incoming body for a create.
Status of the reading
- The reading was measured on a real
ObjectQL+SqlDriver+SecurityPlugin, for a member and an organization admin. The readings are private. - Serial: it lands after PR fix(plugin-security)!: an update or delete of a controlled_by_parent detail asks for edit access to its master on every write shape (ADR-0055 §3) #25042 in the same gate region.
Dedupe
-
Two MCP
search_issuesqueries over this gate family (21 and 8 hits). Their exact terms name the shape, so the seat holds them privately under the disclosure ruling. The nearest:- plugin-security: one write path to a controlled_by_parent detail does not ask for edit access on its master, where ADR-0055 §3 requires it (reach measured; detail withheld under the disclosure ruling) #25012 (open, in landing; a different write shape);
- plugin-security: the controlled_by_parent detail write gate's master-edit check may not ask the same question as the master's own write door (reach not measured; detail withheld under the disclosure ruling) #24986 and plugin-security: a controlled_by_parent detail is readable by a caller with no read on its master object, where ADR-0055 refuses it (detail withheld under the disclosure ruling) #24962 (closed);
- finding(security): every write to a controlled_by_parent detail TWO levels below its root master answers 403 "master … not editable" for a principal who edits that master — a regression against 17.7.0, measured on hotcrm's crm_quote_line_item #23093 (closed, chain depth);
None covers this reading. New.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 21, 2026
Triage (R553): p1 ·
domain:services·area:access·bug+security·pm:queue. ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-21T03:55Zcontrolled_by_parentdetail is decided without the master-edit question on every master it involves (ADR-0055 §3). Reach is measured.security-plugin.ts.5323310017) applies to this card, its PR and every comment. Detail is withheld pending the maintainer.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 25 · 2026-10-21T04:15Z
Session:
session_011TdZ8GjixNZFLz1xJD9zzKAccount:
os-justin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-25044-cbp-every-master-editWorktree:
objectstack-issue-25044Domain:
domain:servicesSeat:
domain:services#1File surface:
packages/plugins/plugin-security/src/security-plugin.ts: thecontrolled_by_parentdetail write gate (assertControlledByParentWriteand its master-edit chain walk) only;.changeset/25044-*.md.⛔
packages/objectql/**, ⛔packages/spec/**, ⛔computeControlledByParentFilter(#24932's region). ⛔ No master scope composed into a write (#24932's ruling B6303721609). (Stop on breach; explain in the report.)Container & model:
M,mode:subagent,model: default (opus), quoting the --tier output "no path-derived mandate … floor sonnet · default opus · ceiling fable"Clause-②: no
Responsibility:
one write to a controlled_by_parent detail is decided without asking for edit access on every master it involves (reach measured, detail private) | ADR-0055 §3 requires edit access to the master for every detail write | every deployment with controlled_by_parent objectsThread-read: 6307643601
Serial constraints cleared:
Serial after PR #25042 (#25012) in the same gate region, as triage R553 orders. The dev measures on main first and writes the fix only after PR #25042 lands and main is merged. No other open PR touches security-plugin.ts (every open PR's files were read at this claim).Order, per triage R553: measure first, then fix within the write gate on top of PR #25042. ⛔ The #9387 disclosure ruling (
5323310017) applies to this card, its PR and every comment.Readings taken on
origin/maineddffc3390.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{ "issue": 25044, "status": "done", "branch": "claude/issue-25044-cbp-every-master-edit", "pr": "https://github.com/objectstack-ai/objectstack/pull/25066", "session": "session_011TdZ8GjixNZFLz1xJD9zzK", "premise_still_valid": true, "summary": "Re-measured first, with throwaway probes that were never committed, on a real ObjectQL + SqlDriver + SecurityPlugin, for a member and an org admin. The reading holds on origin/main eddffc3390 (before PR #25042) and on PR #25042's head 033fc75a09 (after it). Then PR #25042 merged (b2890909aa, 05:23Z), and the empty branch was fast-forwarded onto it. Inside assertControlledByParentWrite only, after the gate has asked about the master(s) it already asks about, it now runs the same walk (assertControlledByParentChainEditable, no second copy) on the master id the write's body names, unless that master was already asked. Refusals use the by-id leg's envelope, and nothing is written. An insert's master is asked once (proven by a count pin). A write that selects no row involves no master. Draft PR #25066 is open and assigned os-justin, with a BREAKING minor changeset (Clause-②: no (narrowing), adr-0087 not-required (no-migration-prescription)). One dispatch mechanism assumption was measured false; it is reported as out-of-scope finding 1 (detail held privately by the seat) and kept out of this PR, because the in-place exemption's condition ② does not hold.", "tests": "On head a447bfdb8f, after the final commit, every run under os-verify-lock. New file src/controlled-by-parent-every-master-edit.test.ts: 17/17. Together with PR #25042's sibling pin file: 'Test Files 2 passed (2) · Tests 32 passed (32)'. Package run (pnpm --filter @objectstack/plugin-security exec vitest run): 'Test Files 202 passed (202) · Tests 4156 passed | 45 skipped (4201)', VERDICT command-exit 0. Typecheck: pnpm --filter @objectstack/plugin-security typecheck exit 0 (tsc on both programs, plus check:test-typecheck OK), and the new file is in the tsconfig.test.json program (--listFiles count 1). The new file red before the fix: on PR #25042's head without the fix (throwaway), 9 of 17 failed, every refusal pin among them. Ablation, with the fix committed first: 6 legs through scripts/ablation-replace.mjs (anchor must hit, blob must change; restore is git checkout HEAD --, proven by blob 22a893279f07 equal to HEAD and an empty git diff HEAD). The subject is imported relatively from src, so neither dist nor a build is on the path. Results: L1 (drop the by-id-site question) red 8; L2 (drop the predicate-site question) red 8; L3 (body's master instead of the stored one) red 2; L4 (blanket refusal instead of the walk) red 16; L5 (drop the no-row guard) red 1; L6 (drop the already-asked skip) red 1, an insert's master asked twice. The unmutated control passed 17/17. The first L4 attempt was a refused no-op (its replacement contained its anchor; the tool exited on the anchor count) and was rerun with a non-overlapping anchor; it is declared, not counted. test:repo was not run locally (declared to CI).", "gates": "Local derived set at a447bfdb8f: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 69 commands, each run with its exit code captured before any pipe. --ran verdict: '69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED (a DERIVED zero …)', exit 0. An earlier pass at faaf270aef, before the main merge, had 2 PREREQUISITE NOT MET (check:dual-build-cjs-loads and check:i18n, both needing a full workspace build). The rerun at the final head, after the dogfood closure build, measured both at exit 0. Closure build: pnpm --workspace-concurrency=2 --filter '@objectstack/dogfood^...' build, VERDICT command-exit 0. CI on PR #25066 head a447bfdb8f at report time: 'No other open PR may claim the same issue' success; '18.x upgrade harness (opt-in)' and 'Packed-tarball smoke (opt-in)' skipped; Lint · *, Type Check · *, Check Changeset, Governed Surface Queue Guard, 'The card this PR closes must claim this branch', 'Part-of PR must not also close its card', Check PR Size and the docs checks in_progress; Test Core and Build Core not yet listed. All of that is reported as in_progress, not waited on.", "dogfood_battery": "a447bfdb8f · os-verify-lock: VERDICT command-exit 0 (held 744s) · Test Files 285 passed | 3 skipped (288) · Tests 2375 passed | 18 skipped (2393) · no failures", "mcp_calls": "0 — no MCP GitHub calls of any kind", "api_writes": "2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204) by the session, executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls (draft, no labels) plus POST /repos/objectstack-ai/objectstack/issues/25066/assignees [os-justin], read back as 6153 bytes sent and 6153 stored, assignee os-justin; (2) this os-dev-report, POST /repos/objectstack-ai/objectstack/issues/25044/comments. Plus 3 git pushes (not REST): the empty branch, the fix, and the main merge.", "deviations": [ "BASE moved. The worktree was cut at eddffc3390 for the measurements, and its empty branch was pushed as the write probe. Once PR #25042 merged, the branch was fast-forwarded to b2890909aa, so BASE = b2890909aa. Then origin/main 72518d20f5 was merged (merge commit a447bfdb8f) before every final run.", "Before PR #25042 merged, the design was validated as a throwaway in this worktree: PR #25042's security-plugin.ts was restored to the tree only, the fix applied and the new test run, then everything was restored with git checkout HEAD -- and proven by blob equality and an empty git diff HEAD. Nothing was committed or pushed on that base, and the PR was re-read about every minute throughout.", "Commit trailers use AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude), and the PR footer uses AGENTS.md's session-URL form. The harness reminder's model-named trailer and its other footer were not used: AGENTS.md takes precedence and the pre-push hook refuses a model identifier.", "A dispatch mechanism assumption was measured false for one shape (finding 1). The route was changed: that shape is reported, not folded in (rule 3, the in-place exemption's condition ② fails)." ], "open_questions": [], "out_of_scope_findings": [ "class: a · reach: exception: security (may alter data). Measured on a real ObjectQL + SqlDriver + SecurityPlugin, for a member and an org admin, on origin/main eddffc3390, and unchanged by this PR. A second write shape to a controlled_by_parent detail is decided without the master-edit question for the masters it involves, in the same gate (assertControlledByParentWrite) on a different code path from this card's, and the write lands. Same family as #25044 and #25012: I propose a sibling card in this family, filed by the seat under the #9387 ruling. The evidence, the public door and the dedupe words are in private_evidence F1.", "class: a · reach: exception: security (may alter data). A further reading in the same gate family, measured on the same stack and unchanged by this PR; detail withheld under the #9387 ruling and handed to the seat privately (F2).", "carrier: PR #25066 Acceptance notes · noted, not filed. The new question is asked before the engine's own field strips run, so on a reference the engine would strip, a body naming a master the caller may not edit is now refused where it was dropped before (fail-closed). Carrier: none." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{ "issue": 25044, "status": "done", "round": "patch round 1 (seat REWORK: changeset headline only)", "branch": "claude/issue-25044-cbp-every-master-edit", "pr": "https://github.com/objectstack-ai/objectstack/pull/25066", "session": "session_011TdZ8GjixNZFLz1xJD9zzK", "head": "4b4cfed7af", "changeset_headline": { "before": "fix(plugin-security)!: a write to a `controlled_by_parent` detail asks for edit access on every master it involves (ADR-0055 §3)", "after": "fix(plugin-security)!: an update of a `controlled_by_parent` detail also asks for edit access on the master its body names (ADR-0055 §3)" }, "pr_title": "fix(plugin-security)!: an update of a controlled_by_parent detail also asks for edit access on the master its body names (ADR-0055 §3)", "pr_body_append": [ "Patch round 1 (4b4cfed7af): only the changeset headline changes, narrowed to the update shape this diff covers. Per contract review 6310100720 ③.1, no code or test line moved.", "`git diff --stat a447bfdb8f 4b4cfed7af`: `.changeset/25044-cbp-every-master-edit.md | 2 +-`, 1 file. Code is byte-identical, so the dogfood battery and the package and gate runs at a447bfdb8f stand." ], "proof": "`git diff --stat a447bfdb8f HEAD` shows '.changeset/25044-cbp-every-master-edit.md | 2 +-', '1 file changed, 1 insertion(+), 1 deletion(-)', and nothing else. No code, test or docblock was touched.", "gates": "node scripts/check-changeset-no-major.mjs --base origin/main: exit 0. node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0, with the notice 'ADR-0087 exemption (no-migration-prescription)'. Both exits were captured before any pipe.", "dogfood_battery": "Stands at a447bfdb8f: code is byte-identical at 4b4cfed7af, and the only diff is the changeset text. The a447bfdb8f reading was os-verify-lock VERDICT command-exit 0 · Test Files 285 passed | 3 skipped (288) · Tests 2375 passed | 18 skipped (2393).", "api_writes": "1 relay stroke: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204) by the session, executed as objectstack-fleet[bot] as POST /repos/objectstack-ai/objectstack/issues/25044/comments (this follow-up os-dev-report). Plus 1 git push (not REST): a447bfdb8f..4b4cfed7af.", "mcp_calls": "0. No MCP GitHub calls of any kind.", "deviations": [ "The worktree had been removed after the first report, so I re-created it on the existing branch at a447bfdb8f (git worktree add, then pnpm install --frozen-lockfile). It will be removed again after this report." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
ACCEPT → PR #25066 comment
6310590952·domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-21T07:31ZGenerated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Landed ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-21T08:02Z · ⛔ The #9387 disclosure rule applies.added_to_merge_queue2026-10-21T07:34Z →merged2026-10-21T08:02Zorigin/mainby contentf46ef4d70e(one parent,c67f244f5a), 3 files.security-plugin.tscarriesaskBodyNamedMaster, and the changeset headline is scoped to an update and the master its body namesState change in this stroke:
pm:dispatchedoff and the assignee cleared. The card closedcompletedon the merge.What changes: an update of a
controlled_by_parentdetail also asks for edit access to the master its body names (ADR-0055 §3). It is a verdict, in the by-id envelope, through the one chain walk; no master scope is composed (#24932's ruling B). This is a BREAKING minor (contract review6310100720, ACCEPT6310590952).Follow-ups, both abstract.
Generated by Claude Code