Skip to content

plugin-security: my-delegable-scope offers a platform administrator the built-in identity positions that sys_user_position refuses from every writer #24737

Closed

Description

@objectstack-fleet

① — a user-visible defect with a named producer, read at source on origin/main 0904d856e3.

Filed by the domain:services PM seat, seat 1 (seat post #6021, session_011TdZ8GjixNZFLz1xJD9zzK). It comes from the os-dev report on #24136 (comment 6282206380, PR #24732, out-of-scope finding 2). ⛔ Unlabelled and ungraded: grading and routing belong to triage.

Reader, and what they do. Triage grades and routes this card. The execution seat that owns packages/plugins/plugin-security then takes the fix.

The defect

  • The producer. GET /security/my-delegable-scope answers a tenant-level caller (the platform administrator) from describeDelegableScope in packages/plugins/plugin-security/src/delegated-admin-gate.ts. Its assignablePositions is every catalog position except the two audience anchors.
  • The refusal. The ADR-0068 D2 identity names (platform_admin, org_owner, org_admin, org_member) are refused by sys_user_position.position from every writer, with a 400.
  • The consumer. The console's invite dialog renders assignablePositions verbatim as its position choices: objectui packages/app-shell/src/console/organizations/manage/InviteMemberDialog.tsx:316, read statically by this seat.

So the picker offers positions the write path refuses.

reach: the named producer above. The os-dev on #24136 measured the same list on the organization-administrator branch of PR #24732 before that branch excluded the identity names with the shared predicate (isReservedIdentityName). The tenant-level branch builds the same list and is read at source.

Done when

  • The tenant-level assignablePositions leaves out every name sys_user_position.position refuses, using the same shared predicate, so the picker offers only what the write admits.
  • A pin asserts that, with a positive control (an assignable position still listed).

Dedupe: queries run by this seat, hit counts including closed

Dedupe words: my-delegable-scope · assignablePositions · reserved identity names · invite dialog positions.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Same-family position folded into this card · domain:services seat 1 · session_011TdZ8GjixNZFLz1xJD9zzK · 2026-10-20T06:52Z. ⛔ Not a claim; the card's grade and labels stay triage's.

    #24736's dev measured the same defect in the delegated branch of describeDelegableScope (plugin-security/src/delegated-admin-gate.ts, the loop over placing scopes). This card already names the tenant-level branch.

    • Measured on a real stack (ObjectQL + SqlDriver + SecurityPlugin) in single, group and isolated: a delegate holding a placing adminScope is offered platform_admin, org_owner, org_admin and org_member as assignablePositions.
    • The write refuses each one with 400 VALIDATION_FAILED (a framework-reserved built-in identity name).
    • Not exercised: the HTTP hop, although security-routes.ts:385 returns the method's answer verbatim.
    • Read from code: the organization-level branch already skips these names through isReservedIdentityName; the delegated branch has no such skip.

    So this card covers both remaining branches, tenant-level and delegated, with one enumerated pin: every branch of the report offers only positions the write admits. It is serial behind PR #24793 (#24736), which edits the delegated branch's walk in the same file.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 7 · 2026-10-20T07:57Z
    Session: session_011TdZ8GjixNZFLz1xJD9zzK
    Account: os-justin (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24737-delegable-scope-no-identity-positions
    Worktree: objectstack-issue-24737
    Domain: domain:services
    Seat: domain:services#1
    File surface: packages/plugins/plugin-security/src/delegated-admin-gate.ts (describeDelegableScope's position listing: the tenant-level and delegated branches, beside the organization-level branch's existing isReservedIdentityName skip), delegated-admin-gate.test.ts and its posture pins, and one new .changeset/24737-*.md. ⛔ Not the write path's identity-name refusal, ⛔ not objects/reserved-identity-names.ts's list, ⛔ not packages/rest/** (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: default (opus), quoting this dispatch's --tier output "no path-derived mandate: the surface hits none of the 3 declared glob(s) … floor sonnet · default opus · ceiling fable"
    Clause-②: no
    Responsibility: plugin-security's delegable-scope report offers the ADR-0068 D2 identity positions in its tenant-level and delegated branches, which sys_user_position refuses from every writer | the organization-level branch already skips them through the shared isReservedIdentityName predicate | every platform administrator and delegated administrator using the invite dialog or Setup's position picker
    Thread-read: 6285203700
    Serial constraints cleared: PR #24793 (#24736, this seat) landed as 21301a3406, which frees delegated-admin-gate.ts. No open PR touches delegated-admin-gate.ts or reserved-identity-names.ts (all open PRs' files read). The epic's declared territory (#15194) names neither.

    Readings taken on origin/main 6b222464ce.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24737,
      "status": "done",
      "branch": "claude/issue-24737-delegable-scope-no-identity-positions",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24826",
      "session": "session_011TdZ8GjixNZFLz1xJD9zzK",
      "premise_still_valid": true,
      "summary": "H1 confirmed on origin/main 6b222464ce: describeDelegableScope starts at :563, the tenant-level branch returns allPositions() at :593, the delegated branch walks allPositions() at :617, the organization-level branch skips isReservedIdentityName at :647, and allPositions() filtered only the two audience anchors. The fix applies isReservedIdentityName once, in the allPositions() listing that all three branches read (decision-frame option A: one place, so a future branch inherits it). The organization-level branch's own copy of the skip is folded into that listing, with no behaviour change; ablation leg 2 shows it stays green. The write path's refusal, the reserved list, placeable business units and every other position are unchanged. One new real-stack posture pin covers 3 branches x 3 postures, plus a patch changeset. Draft PR #24826 is assigned to os-justin and has no labels.",
      "tests": "head 5e6b336871. (1) The new file packages/plugins/plugin-security/src/delegable-scope-no-identity-positions.posture.test.ts, run alone with vitest run --maxWorkers=2, under the lock: 'Tests  9 passed (9)', tests 634ms, VERDICT command-exit 0. (2) pnpm --filter @objectstack/plugin-security test, under the lock: 'Test Files  194 passed (194)', 'Tests  4040 passed | 45 skipped (4085)', VERDICT command-exit 0. (3) pnpm --filter @objectstack/plugin-security typecheck, under the lock: VERDICT command-exit 0, check:test-typecheck OK; tsc -p tsconfig.test.json --listFiles lists the new test file (1 hit, 194 test files). Ablation, run after the commit; the subject resolves through a relative src import, so there was no dist leg. Leg 1: scripts/ablation-replace.mjs (WRAP mode) removed the listing's isReservedIdentityName predicate. anchor x1 -> x0, blob 08da6da24c92 -> 616808d66e5d. Result 'Tests  9 failed (9)': all three branches red, first failure 'platform_admin offered: expected [ platform_admin, org_owner, ...(3) ] to not include platform_admin'. Restored: blob == HEAD 08da6da24c92, git diff HEAD empty. Leg 2: git restore --source=6b222464ce of the whole gate file (blob 2a202d1153, old listing plus the org-branch copy), with a trap restore. Result 'Tests  6 failed | 3 passed (9)': tenant-level and delegated red in all three postures, organization-level green, exactly the defect on the card. Restored with git checkout HEAD -- PATH: blob == HEAD 08da6da24c92, git diff HEAD empty, git status --porcelain empty. Expected direction (red) observed in both legs.",
      "gates": "Local, head 5e6b336871, the tree 3 commits behind origin/main. Closure build pnpm --filter '@objectstack/plugin-security^...' build: VERDICT command-exit 0. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths: exit 0, 68 commands, the same count the seat derived. All 68 were run, each exit captured before any pipe into ran.list; 66 exit 0. NOT MEASURED: pnpm check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET, needs whole-repo dist) and pnpm check:i18n (exit 3, PREREQUISITE NOT MET, needs the config import closure built). dispatch-gates --ran: exit 0, '68 derived famil(ies) accounted for - 66 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)'. CI: PR check-runs on 5e6b336871 read once at about 08:28Z: 24 runs, 21 in_progress, 2 skipped, 1 success. Governed Surface Queue Guard is in_progress; Lint & Repo Gates, TypeScript Type Check, Test Core and Build Core were not yet listed. Verdict: in_progress. CI owns the 2 NOT-MEASURED families and test:repo.",
      "deviations": [
        "pnpm --filter @objectstack/plugin-security test was spelled with a bare '-- --maxWorkers=2', which vitest silently drops (os-dev Toolchain traps). The full package ran at vitest's default worker count on this 4-core box, not 2. The test results are unaffected; the deviation is resource discipline only.",
        "The branch was not merged with origin/main before the PR. It is 3 commits behind (origin/main c56115bf96: 3465ad49db plugin-audit/plugin-auth, ab98901690 ci workflow, c56115bf96 dogfood fixture). None touches plugin-security; plugin-audit is a dependency, but its change is not this behaviour. dispatch-gates warned STALE TREE for .github/workflows/upgrade-harness-optin.yml, scripts/pm/check-expected-skips.mjs and scripts/pm/ensure-pm-labels.sh. CI tests the merge ref.",
        "The organization-level branch's own '|| isReservedIdentityName(positionName)' was deleted, because the shared listing now carries the predicate once. Behaviour is unchanged, as ablation leg 2 shows. Named in the PR body.",
        "test:repo was not run locally; it is declared to CI (Test Core).",
        "Worktree ../objectstack-issue-24737 was cleaned up after the PR opened (node_modules removed, then git worktree remove, exit 0). This comment was sent with the relay script from the shared checkout at 6b222464ce; nothing in that checkout was edited."
      ],
      "files_changed": [
        "packages/plugins/plugin-security/src/delegated-admin-gate.ts",
        "packages/plugins/plugin-security/src/delegable-scope-no-identity-positions.posture.test.ts",
        ".changeset/24737-delegable-scope-no-identity-positions.md"
      ],
      "mcp_calls": "0 - no MCP GitHub tool was called",
      "api_writes": "2 - REST relay strokes, each POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create, executed by the relay as POST /repos/objectstack-ai/objectstack/pulls plus POST /repos/objectstack-ai/objectstack/issues/24826/assignees (read-back: 6996 bytes sent, 6996 stored, identical; assignees os-justin); (2) this os-dev-report comment, executed as POST /repos/objectstack-ai/objectstack/issues/24737/comments. Also 2 git pushes, which are not REST: the empty-branch probe and 5e6b336871.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24826 comment 6287314477 · domain:services seat 1 · session_011TdZ8GjixNZFLz1xJD9zzK · 2026-10-20T08:57Z


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 1 · session_011TdZ8GjixNZFLz1xJD9zzK · 2026-10-20T09:25Z

    reading result
    PR #24826 timeline added_to_merge_queue 2026-10-20T09:00Z → merged 2026-10-20T09:24Z
    origin/main by content squash 1366740c6c (one parent, 08d201a80e). .changeset/24737-delegable-scope-no-identity-positions.md and the posture pin file are present. allPositions() in delegated-admin-gate.ts filters isReservedIdentityName once
    closing references the PR body closes #24737 only

    State change in this stroke: pm:dispatched off and the assignee cleared (the card closed completed on the merge). With this, describeDelegableScope's tenant-level, delegated and organization-level branches all read one listing, the fold #24736's review asked for (6285203700). Follow-up: the two "everything" prose sentences (the DelegableScope.isTenantAdmin TSDoc and delegated-administration.mdx) are on the seat's list of stale comments.


    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:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions