Skip to content

Decision needed: should the sys_comment parent gates run the parent's owner-match at the caller's real write DEPTH, or stay at own? #7144

Description

@os-zhuang

Split out of #7141 (PR #7143) rather than decided there, because it is a widening and the two candidate shapes that card named diverge here — on nothing else.

The fact

ISharingService.canEdit widens its owner-match by the access DEPTH the caller
holds on the probed object, read from the middleware-private __writeScope key
(plugin-sharing/src/sharing-service.ts — matchesOwnerScope, and
if (writeScope === 'org') return true). plugin-security supplies that key
explicitly for the object it is probing, via the private
resolveWriteScopeForSharing (security-plugin.ts:2516).

The sys_comment gates in plugin-audit supply no depth. The five-field
projection #7141 removed never carried one, and PR #7143 deliberately kept it
that way: an absent depth leaves the owner-match at its narrowest (own), which
is the safe direction and is byte-for-byte what the projection produced.

The consequence, stated as behaviour

A caller whose write depth on the PARENT object is unit / unit_and_below /
org can edit that parent record directly through the CRUD path (where the
middleware stamps the depth for that object) but is refused when they try to
edit or delete a comment on it, because this gate asks the same service the same
question with the depth omitted. It is a divergence in the restrictive
direction — no data leaks — between the comment gate and the parent's real edit
authority. Whether that is correct or is a bug depends on what the comment gate
is meant to mean, which is a product decision, not a code reading.

Why it was not simply fixed in #7143

The tool a package outside plugin-security has for this is the published
ISecurityService.resolveWriteScope. It fails open on one input:
getEffectiveScope returns 'org' when no permission set matches the object
(permission-evaluator.ts:255), and the service's own doc block flags that
'org' as "non-authoritative on its own". Passed to canEdit as __writeScope
it becomes fully authoritative, and matchesOwnerScope then returns true for
any owned row of an unmatched object — a real widening, not a theoretical
one. Taking that route needs either a narrower contract method (one that
distinguishes "org depth" from "nothing matched") or the gate resolving depth
some other way.

Note this is the same question for service-storage's attachment kit, which
gates on canEdit the same way.

What a decision would look like

  1. Keep own (today, and fix(plugin-audit): forward the caller's execution envelope to the sys_comment sharing gates (#7141) #7143's behaviour) — the comment gate is
    deliberately tighter than the parent's edit authority. Then say so in the
    contract's doc block, so the next reader does not "fix" it.
  2. Resolve the parent's depth — the comment gate inherits the parent's real
    edit authority. Needs a depth primitive that does not fail open on an
    unmatched object before it can be wired.

Related: #7141, PR #7143, #6523, #6206, ADR-0057 D1, ADR-0111 D1.

Activity

  1. claude commented on Aug 9, 2026

    @claude
    Contributor

    Triage: needs-user-decision + domain:identity.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    ContributorAuthor

    Maintainer ruling (2026-08-10, directed in session session_01BPWqbmEFU8gJepBJTHESXd): Option 1 — the comment gate stays at own.

    The sys_comment gates are deliberately tighter than the parent's edit authority. Record exactly that in the contract's doc block so the next reader does not "fix" it into a widening. Option 2 is off the table until a depth primitive exists that distinguishes "org depth" from "nothing matched" — the fail-open documented in this card (getEffectiveScope returning 'org' on no match) makes wiring resolveWriteScope into canEdit a real widening today. The same ruling covers service-storage's attachment kit, which gates on canEdit the same way.

    If product later wants comment authority to inherit the parent's depth, that starts with the safe primitive, not with this gate. needs-user-decision → pm:queue (doc-block card).


    Generated by Claude Code

  3. self-assigned this
    on Aug 10, 2026
  4. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    ContributorAuthor

    Claim — identity-lane PM seat (#6022), session session_01BM1tNf5U3nEbHKR4fo5qVQ. Dispatching to an os-dev subagent now, on the maintainer's Option 1 ruling of 2026-08-10 05:09:59Z.

    • Branch: claude/issue-7144-comment-gate-own-depth-contract
    • Worktree: ../objectstack-issue-7144 (dedicated per-task worktree)
    • Status: pm:queue → pm:dispatched. Thread re-read before claiming: two comments (triage grading + the ruling); no dev claim, no competing session ID.

    Serial constraints cleared: this lane's other in-flight card (#7230) is on plugin-audit/src/audit-writers.ts — a different file from the comment gate's call sites in comment-access-hooks.ts. #7141 / PR #7143 landed 22:10Z and is this card's baseline, not its target.

    Scope, as ruled — this is a doc-block card, not a behaviour change:

    • The sys_comment gates are deliberately tighter than the parent's edit authority. Record exactly that in the contract's doc block, so the next reader does not "fix" it into a widening. That sentence is the deliverable.
    • ⛔ Option 2 is off the table: wiring resolveWriteScope into canEdit is a real widening today, because getEffectiveScope returns 'org' on no match — the fail-open this card measured. Do not implement it, and do not "prepare" for it.
    • ⛔ No behaviour change at all. If your diff changes what any gate returns for any input, you have left the ruling.
    • The doc block must carry why, not just what — a bare "this is intentional" invites the same re-litigation the ruling is trying to end. Name the fail-open that blocks the alternative, so a future reader can tell "deliberately tighter" from "nobody got round to it".

    Boundary on the sibling: the ruling says it also covers service-storage's attachment kit, which gates on canEdit the same way — but that lands in #7145, domain:services, another seat's card. Do not edit service-storage. If the contract doc block is the shared authority both kits read, saying so once in that block is enough; leave the service-storage call site to its own seat.

    If a claim comment with a different session ID appears above this one, that claim wins by timestamp and this seat stands down.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions