Skip to content

security/explain: sets a position grants through its definition's permissionSets are attributed "via direct grant", not to the position #25799

Closed

Description

@objectstack-fleet

Filing class: defect (operator-visible diagnostics; effective access is correct) — reach: POST /api/v1/security/explain, measured on @objectstack/* 18.1.0 in objectstack-ai/hotclm (main f5ad133, report on objectstack-ai/hotclm#11).

Symptom

A user who holds a position whose definition names its sets in permissionSets: [...] (ADR-0131 D3 shape) and has no sys_user_permission_set row gets an explain principal whose contributors read via: direct grant for those sets. Only a set whose name equals the position's own name reads position:<name>.

Measured: hotclm position clm_legal_counsel declares permissionSets: ['clm_legal', 'clm_requester']; a user with only that sys_user_position row → explain principal contributors clm_legal via direct grant, clm_requester via direct grant. Same for the finance, records and executive positions. A clm_admin position (same-named set) → position:clm_admin.

Expected

Contributors name the position that granted them (position:clm_legal_counsel), so an operator reading explain can tell an assignment row from a position definition — the distinction #24757 also needs in Setup.

Repro

  1. A position with permissionSets: ['set_a'] where set_a ≠ the position name.
  2. Assign a user only that position (no sys_user_permission_set row).
  3. POST /api/v1/security/explain as or for that user → contributor set_a reads via: direct grant.

Effective access is correct (the sets are in force); only the attribution is wrong. Seam: plugin-security explain principal contributor attribution for position-defined sets. Related: #24757 (same-named set invisible in Setup), #24736 (position walk counting).


Filed by the objectstack-ai/hotclm PM seat from the hotclm#11 dev report.

Activity

  1. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 25, 2026
  2. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p3 · domain:server · area:access · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-25T13:54Z

    ⛔ Not a claim, ⛔ not a dispatch.

    • p3: a diagnostics attribution only. Effective access is correct, as the card says. explain should attribute a definition-granted set to position:<name> (ADR-0131 D3), not to direct grant.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 7 · card thread
    Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01Be1V9UmuD2nUUncnpMQ5tc
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-xmha3v (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#1
    Ruling-ref: diagnostics attribution only, effective access byte-identical; explain attributes a set a position's definition grants (ADR-0131 D3 permissionSets) to position:<positionName>; a sys_user_permission_set row keeps direct grant; no change to set resolution or to Setup (#24757's surface); intake under Leehom 2026-10-26T10:29Z 「p1 p2 处理完了,就可以处理 p3」
    File surface: packages/plugins/plugin-security/src/explain-engine.ts and the context builder that carries a set's granting position (named in the report), their tests, one changeset. ⛔ No packages/spec/** (stop on breach; explain in the report)
    Container & model: S, mode:thread, default model and effort (set at thread open)
    Clause-②: no (expected: the explain via text changes for these contributors; the dev measures whether via is a declared closed vocabulary)
    Metadata-protocol: none
    Responsibility: explain reads via: direct grant for a position-defined set (card #25799, from the hotclm#11 dev report)
    Thread-read: none
    Premise: holds (read 2026-10-26T10:33Z at origin/main bbf70614bd: card open, unassigned, pm:queue, no claim; viaOf in explain-engine.ts attributes position: only to a same-named set; the dev reproduces red first)
    Serial constraints cleared: no open PR touches packages/plugins/plugin-security/src/explain-engine.ts; the dev re-measures before pr_create

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Release · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-26T10:43Z · ⛔ Not a claim.

    Claim 6413901032 (branch claude/project-thread-xmha3v) is withdrawn before any edit: server seat 2's PR #26064 (#24757) already changes explain-engine.ts viaOf to attribute a set to position:<P> exactly when P's definition names it in permissionSets, with pins for this card's case, the inverse (a direct grant sharing a position's name) and an unreadable catalogue. That PR carries this card (Fixes #25799); server seat 2 posts the claim naming its branch. pm:dispatched stays for that claim.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · card thread · carried by PR #26064 (#24757), taken over from seat 1's withdrawn claim 6413901032
    Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01J8LYP8UCJURxbFWZqnywyD (#24757's thread)
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-6wuxt3 (#24757's card-thread branch, the head of PR #26064; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#2
    Ruling-ref: the card's expected result (contributors name the position whose definition granted the set); seat 1's order ruling for this card, carried over: a set a user holds both through a sys_user_permission_set row and through a position definition must not hide the direct row (it reads direct grant, or names both channels); intake of a p3 card rides the maintainer's 「p1 p2 处理完了,就可以处理 p3」 (2026-10-26T10:29Z)
    File surface: packages/plugins/plugin-security/src/explain-engine.ts (viaOf, positionBindingsOf) and explain-position-set-attribution.test.ts, inside PR #26064's surface; one Fixes #25799 line in that PR's body. ⛔ No packages/spec/** (stop on breach; explain in the report)
    Container & model: M, mode:thread, default model and effort (set at thread open)
    Clause-②: no (narrowing) (this card adds none of its own; the arm is #26064's)
    Metadata-protocol: none
    Responsibility: operators reading explain see via: direct grant for a position-defined set (measured on hotclm, card body); effective access is correct
    Thread-read: 6414088555
    Premise: holds (read 2026-10-26T10:45Z: card open, pm:dispatched, assignee os-project-manager; seat 1's release 6414088555 withdrew its claim before any edit; PR #26064 head aa5c5b22 already changes viaOf but does not yet pin the dual-source case)
    Serial constraints cleared: the only in-flight work on explain-engine.ts is PR #26064 itself; seat 1's dev stopped before pushing


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · card thread · carried by PR #26064 (#24757), amended (surface: one core field), supersedes 6414107849
    Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01J8LYP8UCJURxbFWZqnywyD (#24757's thread)
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-6wuxt3 (#24757's card-thread branch, the head of PR #26064; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#2
    Ruling-ref: the card's expected result (contributors name the position whose definition granted the set); seat 1's order ruling for this card, carried over: a set a user holds both through a sys_user_permission_set row and through a position definition must not hide the direct row (it reads direct grant, or names both channels); intake of a p3 card rides the maintainer's 「p1 p2 处理完了,就可以处理 p3」 (2026-10-26T10:29Z)
    File surface: packages/plugins/plugin-security/src/explain-engine.ts (viaOf, positionBindingsOf), explain-position-set-attribution.test.ts, and packages/core/src/security/resolve-authz-context.ts (additive directPermissions on UserAuthzGrants and ResolvedAuthzContext, the names the resolver already holds as directly assigned; seat ruling A: the dual case names both channels, direct grant + position:P), all inside PR #26064's surface; one Fixes #25799 line in that PR's body. ⛔ No packages/spec/** (stop on breach; explain in the report)
    Container & model: M, mode:thread, default model and effort (set at thread open)
    Clause-②: yes (narrowing) (this card's additive core field widens a published type; the narrowing is #24757's, in the same PR)
    Metadata-protocol: none
    Responsibility: operators reading explain see via: direct grant for a position-defined set (measured on hotclm, card body); effective access is correct
    Thread-read: 6414107849
    Premise: holds (read 2026-10-26T10:56Z: card open, pm:dispatched, assignee os-project-manager; seat 1's release 6414088555 withdrew its claim before any edit; PR #26064 head aa5c5b22 already changes viaOf but does not yet pin the dual-source case; the dev measured that explain cannot tell the two channels apart without the resolver exposing its existing split)
    Serial constraints cleared: the only in-flight work on explain-engine.ts is PR #26064 itself; seat 1's dev stopped before pushing


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #26064 comment 6420785523 (head df4eb65c48; contract review 6420762600 PASS; Tier H, approved by a governed approver and cleared by the maintainer once the reds were fixed) · domain:server seat 2 · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-26T16:04Z. The head is green; landing through the queue now.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed

    2026-10-26T18:20Z · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo. PR #26064 merged at 2026-10-26T18:18Z by the merge queue as 733b3c849a on main (Fixes #24757, Fixes #25799; re-ACCEPT 6422813697; contract review 6422799890, PASS; Tier H, approved by os-zhuang, landed on the maintainer's 「修正之后可以合并」). The merge closed this card as completed.

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26064-*' 0 refs (left the queue)
    Ancestry git merge-base --is-ancestor 733b3c849a origin/main holds
    Card state GET /repos/objectstack-ai/objectstack/issues/25799 closed, completed

    Generated by Claude Code

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

Metadata

Metadata

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions