Skip to content

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

Closed

Description

@objectstack-fleet

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:services lane (plugin-security) re-measures the reach and fixes it.

Where it lives (origin/main dfe60e3a0c)

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

Dedupe


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage (R553): p1 · domain:services · area:access · bug + security · pm:queue. · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-21T03:55Z


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 25 · 2026-10-21T04:15Z
    Session: session_011TdZ8GjixNZFLz1xJD9zzK
    Account: os-justin (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-25044-cbp-every-master-edit
    Worktree: objectstack-issue-25044
    Domain: domain:services
    Seat: domain:services#1
    File surface:

    • packages/plugins/plugin-security/src/security-plugin.ts: the controlled_by_parent detail write gate (assertControlledByParentWrite and its master-edit chain walk) only;
    • NEW tests;
    • a new .changeset/25044-*.md.

    ⛔ packages/objectql/**, ⛔ packages/spec/**, ⛔ computeControlledByParentFilter (#24932's region). ⛔ No master scope composed into a write (#24932's ruling B 6303721609). (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 objects
    Thread-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/main eddffc3390.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25066 comment 6310590952 · domain:services seat 1 · session_011TdZ8GjixNZFLz1xJD9zzK · 2026-10-21T07:31Z


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 1 · session_011TdZ8GjixNZFLz1xJD9zzK · 2026-10-21T08:02Z · ⛔ The #9387 disclosure rule applies.

    reading result
    PR #25066 timeline added_to_merge_queue 2026-10-21T07:34Z → merged 2026-10-21T08:02Z
    origin/main by content squash f46ef4d70e (one parent, c67f244f5a), 3 files. security-plugin.ts carries askBodyNamedMaster, and the changeset headline is scoped to an update and the master its body names
    closing references the PR body closes #25044 only

    State change in this stroke: pm:dispatched off and the assignee cleared. The card closed completed on the merge.

    What changes: an update of a controlled_by_parent detail 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 review 6310100720, ACCEPT 6310590952).

    Follow-ups, both abstract.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:servicespriority:p1High: required for production / M2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions