You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-security: my-delegable-scope offers a platform administrator the built-in identity positions that sys_user_position refuses from every writer #24737
① — a user-visible defect with a named producer, read at source on origin/main0904d856e3.
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
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.
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.
{
"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": []
}
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
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.
Activity
objectstack-fleet commentedon Oct 20, 2026
Same-family position folded into this card ·
domain:servicesseat 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.single,groupandisolated: a delegate holding a placingadminScopeis offeredplatform_admin,org_owner,org_adminandorg_memberasassignablePositions.VALIDATION_FAILED(a framework-reserved built-in identity name).security-routes.ts:385returns the method's answer verbatim.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
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 7 · 2026-10-20T07:57Z
Session:
session_011TdZ8GjixNZFLz1xJD9zzKAccount:
os-justin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24737-delegable-scope-no-identity-positionsWorktree:
objectstack-issue-24737Domain:
domain:servicesSeat:
domain:services#1File 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 existingisReservedIdentityNameskip),delegated-admin-gate.test.tsand its posture pins, and one new.changeset/24737-*.md. ⛔ Not the write path's identity-name refusal, ⛔ notobjects/reserved-identity-names.ts's list, ⛔ notpackages/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 pickerThread-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/main6b222464ce.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
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
objectstack-fleet commentedon Oct 20, 2026
ACCEPT → PR #24826 comment
6287314477·domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-20T08:57ZGenerated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Landed ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-20T09:25Zadded_to_merge_queue2026-10-20T09:00Z →merged2026-10-20T09:24Zorigin/mainby content1366740c6c(one parent,08d201a80e)..changeset/24737-delegable-scope-no-identity-positions.mdand the posture pin file are present.allPositions()indelegated-admin-gate.tsfiltersisReservedIdentityNameonceState change in this stroke:
pm:dispatchedoff and the assignee cleared (the card closedcompletedon 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 (theDelegableScope.isTenantAdminTSDoc anddelegated-administration.mdx) are on the seat's list of stale comments.Generated by Claude Code