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-auth: set-user-manager's depth cap counts only the chain above the new manager, so linking bottom-up builds a manager chain past MAX_MANAGER_CHAIN_DEPTH (each link 200) #26775
Filing gate: class (b), contract violation. Measured by the #26616 card thread during PR #26763 (contract review 6445518048 on that PR, should-fix (b)). The bug predates that PR. Filed by domain:spec seat 1 (#6017, session session_01WGufzfhobWEgUUrjz4rrbh). ⛔ Not a claim.
Governing text: packages/plugins/plugin-auth/src/admin-set-user-manager.ts:69-72: "Depth — refused past MAX_MANAGER_CHAIN_DEPTH. ADR-0057 D3 requires the hierarchy rollups it feeds to be "bounded (hard cap + cache, mirroring the org_user_ids cap)"; an unbounded chain written here is an unbounded rollup there."
What was measured
Read at origin/maina485b5a3:
walkChain (admin-set-user-manager.ts:329-357) starts at the proposed manager and counts 1 (the edge being written) plus each ancestor above that manager. It never counts the subordinates already below the edited user.
Writing B.manager = A, then C.manager = B, and so on from the top is refused at link 21.
Writing from the bottom, U20.manager = U21, then U19.manager = U20, …, then U1.manager = U2, is not refused. Each write sees a short chain above the proposed manager, but each one extends a chain that already hangs below the edited user. Every link answers 200, and the finished chain is 21+ links deep.
Re-check: grep -n "let links = 1" packages/plugins/plugin-auth/src/admin-set-user-manager.ts. While the walk only goes upward, the hole is present.
Wanted
Bound the whole chain the write creates: the depth above the proposed manager, plus 1, plus the longest chain of existing subordinates below the edited user. Refuse with the existing too_deep reason when the total passes the cap. Pin both orders (top-down and bottom-up, 21 links) and the boundary at exactly 20.
Business reason: the hierarchy rollups ADR-0057 D3 feeds (manager-and-subordinates scopes) promise a bounded walk. A chain past the cap is resolved only in part. A manager then silently loses sight of records owned deep in their chain, and nothing tells the admin who built it.
Reader who acts:domain:server (plugin-auth). No spec change.
Dedupe: open issues grepped for "set-user-manager depth cap", "manager chain depth bottom-up" and "MAX_MANAGER_CHAIN_DEPTH subordinates": none.
Triage: audit line · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T17:08Z
⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in admin-set-user-manager.tswalkChain; rationale: p2 · domain:server. The depth cap counts the chain below the edited user as well as above the new manager, so a bottom-up link cannot exceed MAX_MANAGER_CHAIN_DEPTH. Pin it with the bottom-up sequence.
Claim: PM loop round 1
Session: session_01QSeKYmQXM3rHJcPnrEPk4B
Account: os-justin
Branch: claude/issue-26775-manager-chain-depth
Worktree: objectstack-issue-26775
Domain: domain:server
Seat: domain:server#3
File surface: packages/plugins/plugin-auth/src/admin-set-user-manager.ts (walkChain and its caller), its tests, one patch changeset (stop on breach; explain in the report). ⛔ No packages/spec/**.
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: "no path-derived mandate … default opus"), effort: high
Clause-②: no (narrowing)
Metadata-protocol: none
Responsibility: walkChain counts the edge plus the ancestors above the proposed manager, never the subordinates below the edited user | none: no other door bounds the whole chain | an admin calling POST /api/v1/auth/admin/set-user-manager bottom-up, and demoStaff provisioning through applyUserManagerLink (#26763, merged)
Thread-read: 6448403966
Premise: holds (seat re-check on origin/main0ea90c7a18: admin-set-user-manager.ts:343let links = 1;, MAX_MANAGER_CHAIN_DEPTH = 20 at :132)
Serial constraints cleared: none (no open pm:dispatched claim declares packages/plugins/plugin-auth/**; #26763 is merged)
{
"issue": 26775,
"status": "done",
"branch": "claude/issue-26775-manager-chain-depth",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26868",
"session": "session_01QSeKYmQXM3rHJcPnrEPk4B",
"premise_still_valid": true,
"summary": "Premise held on origin/main 0ea90c7a18 (walkChain at admin-set-user-manager.ts:343 'let links = 1;', upward only). applyUserManagerLink now runs, after the unchanged upward walk, a new deepestSubordinateChain: breadth-first, one read per level as sys_user manager_id IN (previous level ids), chunked at 500 ids, fields ['id'], system context; budget MAX_MANAGER_CHAIN_DEPTH - linksAbove, stop at the first level past the budget (at most 21 level reads), seen set seeded with the edited user (a corrupt loop through it terminates). A total past 20 is refused with the existing VALUE_OUT_OF_RANGE / 400 / details.reason max_depth_exceeded; message and docblock now say the count runs through the reports below the user. All three callers (the endpoint, the import manager pass, demoStaff links) share it unchanged; this tree ships no demoStaff declaration (git grep demoStaff in examples + packages/qa = 0 at 0ea90c7a18, control demo-staff.ts = 3) and the real-engine SQLite provisioning suite still links its chain through the new $in read. Card assignee untouched; PR assignee os-justin set at pr_create.",
"tests": "Head 0515b03654. Targeted: vitest run admin-set-user-manager / admin-import-users-manager-pass / demo-staff / demo-staff-provisioning -> Test Files 4 passed (4), Tests 85 passed (85). Package: pnpm --filter @objectstack/plugin-auth build && typecheck && test under the lock -> check:test-typecheck OK, Test Files 198 passed (198), Tests 3795 passed | 10 skipped (3805), VERDICT command-exit 0. New pins at runSetUserManager (status + code + details.reason, no update on refusal): TOP-DOWN 21 links (1..20 200, 21 refused, longest chain 20); BOTTOM-UP same edges reversed (1..20 200, top edge refused and unwritten, longest 20); MIDDLE join 9+1+11 refused / 9+1+10 admitted; longest branch decides over 30 shallow reports; query shape (exactly 20 level reads, second read one $in of 40 heads); loop through the edited user terminates. Ablation (fix committed first) via scripts/ablation-replace.mjs: deepestSubordinateChain call replaced by a constant depth 0, anchor 1->0 on disk, blob 3c9eaf679caa -> 7e75c91cf8ef; result Tests 4 failed | 29 passed (33): BOTTOM-UP, MIDDLE, LONGEST, level-reads red ('expected 200 to be 400'); TOP-DOWN green as expected; restored: blob == HEAD 3c9eaf679caa, git diff HEAD empty. No dist build needed for the ablation: the suite imports src by relative path.",
"gates": "dispatch-gates --commands (no paths) at 0515b03654: 61 derived; 61 run, each exit captured before any pipe: 61 exit 0. check:dual-build-cjs-loads and check:type-check-debt first answered exit 3 (PREREQUISITE NOT MET: no workspace dist; lock queue-timeout behind my dogfood run), re-run after the workspace build check:type-check-debt performs under the lock: both exit 0 ('112 published require entry point(s) across 69 package(s) load'; '126/129 workspace packages type-checked ... 0 raw tsc error(s) total'). --ran with COMMAND :: exit CODE lines: '61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED (a DERIVED zero — all 61 recorded an exit code and none of them is 3)'. eslint on the 3 changed .ts files: exit 0. CI on PR #26868 head 0515b03654 at report time: Check Changeset, Governed Surface Queue Guard, Lint (ESLint, PM dispatch-gates, docs & code guards, repo & tooling gates, CI release & engine gates, scoped x3), Type Check (workspace, source gates, consumer gates, debt ledger), PR-body guards, Check PR Size: in_progress; 18.x upgrade harness and Packed-tarball smoke: skipped (opt-in). Test Core / Build Core not yet listed. test:repo not run locally (declared to CI).",
"dogfood_battery": "0515b03654 · pnpm --filter '@objectstack/dogfood^...' build VERDICT command-exit 0, then pnpm --filter @objectstack/dogfood test VERDICT command-exit 0 · Test Files 314 passed | 3 skipped (317) · Tests 2609 passed | 20 skipped (2629) · no failures",
"mcp_calls": "0",
"api_writes": "2 relay strokes (fleet-write) — pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft, #26868) + POST /repos/objectstack-ai/objectstack/issues/26868/assignees (os-justin); comment: POST /repos/objectstack-ai/objectstack/issues/26775/comments (this os-dev-report). git push x3 (not REST).",
"files_changed": [
".changeset/26775-manager-chain-depth.md",
"packages/plugins/plugin-auth/src/admin-set-user-manager.ts",
"packages/plugins/plugin-auth/src/admin-set-user-manager.test.ts",
"packages/plugins/plugin-auth/src/admin-import-users-manager-pass.test.ts"
],
"line_budget": "n/a — no skills/** touched; diff +267 / -7 over 4 files (dispatch-gates changed-lines reading), under the 3000 threshold",
"deviations": [
"File surface: admin-import-users-manager-pass.test.ts is outside the claim's named files. Its engine double refused every operator object and threw on the new $in read (the import's manager pass calls applyUserManagerLink); it now implements $in alone and still throws on every other operator and combinator (check:where-matcher exit 0). Named in the PR body.",
"Commit trailers use the model-free pair AGENTS.md prescribes (Co-authored-by: Claude + Claude-Session), not the model-bearing Co-Authored-By the harness reminder offered.",
"PR footer uses the session-URL form from os-dev.md, not the harness's robot-emoji form.",
"Gate derivation tree 0515b03654 was 1 commit behind origin/main f79ec28949; the tool reported none of the visible commits touched what it derives from. No merge of main made (no conflict)."
],
"open_questions": [],
"out_of_scope_findings": []
}
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: audit line · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T17:08Z⛔ Not a claim, ⛔ not a dispatch.
admin-set-user-manager.tswalkChain; rationale: p2 ·domain:server. The depth cap counts the chain below the edited user as well as above the new manager, so a bottom-up link cannot exceedMAX_MANAGER_CHAIN_DEPTH. Pin it with the bottom-up sequence.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1
Session:
session_01QSeKYmQXM3rHJcPnrEPk4BAccount:
os-justinBranch:
claude/issue-26775-manager-chain-depthWorktree:
objectstack-issue-26775Domain:
domain:serverSeat:
domain:server#3File surface:
packages/plugins/plugin-auth/src/admin-set-user-manager.ts(walkChainand its caller), its tests, onepatchchangeset (stop on breach; explain in the report). ⛔ Nopackages/spec/**.Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: "no path-derived mandate … default opus"),effort: highClause-②: no (narrowing)
Metadata-protocol: none
Responsibility:
walkChaincounts the edge plus the ancestors above the proposed manager, never the subordinates below the edited user | none: no other door bounds the whole chain | an admin callingPOST /api/v1/auth/admin/set-user-managerbottom-up, and demoStaff provisioning throughapplyUserManagerLink(#26763, merged)Thread-read: 6448403966
Premise: holds (seat re-check on
origin/main0ea90c7a18:admin-set-user-manager.ts:343let links = 1;,MAX_MANAGER_CHAIN_DEPTH = 20at:132)Serial constraints cleared: none (no open
pm:dispatchedclaim declarespackages/plugins/plugin-auth/**; #26763 is merged)objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 26775, "status": "done", "branch": "claude/issue-26775-manager-chain-depth", "pr": "https://github.com/objectstack-ai/objectstack/pull/26868", "session": "session_01QSeKYmQXM3rHJcPnrEPk4B", "premise_still_valid": true, "summary": "Premise held on origin/main 0ea90c7a18 (walkChain at admin-set-user-manager.ts:343 'let links = 1;', upward only). applyUserManagerLink now runs, after the unchanged upward walk, a new deepestSubordinateChain: breadth-first, one read per level as sys_user manager_id IN (previous level ids), chunked at 500 ids, fields ['id'], system context; budget MAX_MANAGER_CHAIN_DEPTH - linksAbove, stop at the first level past the budget (at most 21 level reads), seen set seeded with the edited user (a corrupt loop through it terminates). A total past 20 is refused with the existing VALUE_OUT_OF_RANGE / 400 / details.reason max_depth_exceeded; message and docblock now say the count runs through the reports below the user. All three callers (the endpoint, the import manager pass, demoStaff links) share it unchanged; this tree ships no demoStaff declaration (git grep demoStaff in examples + packages/qa = 0 at 0ea90c7a18, control demo-staff.ts = 3) and the real-engine SQLite provisioning suite still links its chain through the new $in read. Card assignee untouched; PR assignee os-justin set at pr_create.", "tests": "Head 0515b03654. Targeted: vitest run admin-set-user-manager / admin-import-users-manager-pass / demo-staff / demo-staff-provisioning -> Test Files 4 passed (4), Tests 85 passed (85). Package: pnpm --filter @objectstack/plugin-auth build && typecheck && test under the lock -> check:test-typecheck OK, Test Files 198 passed (198), Tests 3795 passed | 10 skipped (3805), VERDICT command-exit 0. New pins at runSetUserManager (status + code + details.reason, no update on refusal): TOP-DOWN 21 links (1..20 200, 21 refused, longest chain 20); BOTTOM-UP same edges reversed (1..20 200, top edge refused and unwritten, longest 20); MIDDLE join 9+1+11 refused / 9+1+10 admitted; longest branch decides over 30 shallow reports; query shape (exactly 20 level reads, second read one $in of 40 heads); loop through the edited user terminates. Ablation (fix committed first) via scripts/ablation-replace.mjs: deepestSubordinateChain call replaced by a constant depth 0, anchor 1->0 on disk, blob 3c9eaf679caa -> 7e75c91cf8ef; result Tests 4 failed | 29 passed (33): BOTTOM-UP, MIDDLE, LONGEST, level-reads red ('expected 200 to be 400'); TOP-DOWN green as expected; restored: blob == HEAD 3c9eaf679caa, git diff HEAD empty. No dist build needed for the ablation: the suite imports src by relative path.", "gates": "dispatch-gates --commands (no paths) at 0515b03654: 61 derived; 61 run, each exit captured before any pipe: 61 exit 0. check:dual-build-cjs-loads and check:type-check-debt first answered exit 3 (PREREQUISITE NOT MET: no workspace dist; lock queue-timeout behind my dogfood run), re-run after the workspace build check:type-check-debt performs under the lock: both exit 0 ('112 published require entry point(s) across 69 package(s) load'; '126/129 workspace packages type-checked ... 0 raw tsc error(s) total'). --ran with COMMAND :: exit CODE lines: '61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED (a DERIVED zero — all 61 recorded an exit code and none of them is 3)'. eslint on the 3 changed .ts files: exit 0. CI on PR #26868 head 0515b03654 at report time: Check Changeset, Governed Surface Queue Guard, Lint (ESLint, PM dispatch-gates, docs & code guards, repo & tooling gates, CI release & engine gates, scoped x3), Type Check (workspace, source gates, consumer gates, debt ledger), PR-body guards, Check PR Size: in_progress; 18.x upgrade harness and Packed-tarball smoke: skipped (opt-in). Test Core / Build Core not yet listed. test:repo not run locally (declared to CI).", "dogfood_battery": "0515b03654 · pnpm --filter '@objectstack/dogfood^...' build VERDICT command-exit 0, then pnpm --filter @objectstack/dogfood test VERDICT command-exit 0 · Test Files 314 passed | 3 skipped (317) · Tests 2609 passed | 20 skipped (2629) · no failures", "mcp_calls": "0", "api_writes": "2 relay strokes (fleet-write) — pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft, #26868) + POST /repos/objectstack-ai/objectstack/issues/26868/assignees (os-justin); comment: POST /repos/objectstack-ai/objectstack/issues/26775/comments (this os-dev-report). git push x3 (not REST).", "files_changed": [ ".changeset/26775-manager-chain-depth.md", "packages/plugins/plugin-auth/src/admin-set-user-manager.ts", "packages/plugins/plugin-auth/src/admin-set-user-manager.test.ts", "packages/plugins/plugin-auth/src/admin-import-users-manager-pass.test.ts" ], "line_budget": "n/a — no skills/** touched; diff +267 / -7 over 4 files (dispatch-gates changed-lines reading), under the 3000 threshold", "deviations": [ "File surface: admin-import-users-manager-pass.test.ts is outside the claim's named files. Its engine double refused every operator object and threw on the new $in read (the import's manager pass calls applyUserManagerLink); it now implements $in alone and still throws on every other operator and combinator (check:where-matcher exit 0). Named in the PR body.", "Commit trailers use the model-free pair AGENTS.md prescribes (Co-authored-by: Claude + Claude-Session), not the model-bearing Co-Authored-By the harness reminder offered.", "PR footer uses the session-URL form from os-dev.md, not the harness's robot-emoji form.", "Gate derivation tree 0515b03654 was 1 commit behind origin/main f79ec28949; the tool reported none of the visible commits touched what it derives from. No merge of main made (no conflict)." ], "open_questions": [], "out_of_scope_findings": [] }objectstack-fleet commentedon Oct 27, 2026
ACCEPT → PR #26868 comment 6451575376 · contract review PASS
6451519234·domain:serverseat 3 · 2026-10-27T19:30Z. Landing waits on CI green.objectstack-fleet commentedon Oct 27, 2026
Landed ·
domain:serverseat 3 · 2026-10-27T19:55Zb29a88c51completedby the PR'sFixeslineState:
pm:dispatchedremoved in the same pass.