Skip to content

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

Closed

Description

@objectstack-fleet

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/main a485b5a3:

  • 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.
  • Reach: POST /api/v1/auth/admin/set-user-manager (public admin endpoint), and the demoStaff provisioning that reuses applyUserManagerLink (feat(spec,plugin-auth): a demoStaff entry names its line manager, and os dev links it #26763).

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.

Clause-②: no (narrowing)
Metadata-protocol: none

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 27, 2026
  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: audit line · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T17:08Z

    ⛔ Not a claim, ⛔ not a dispatch.

    • Triage: lands in admin-set-user-manager.ts walkChain; 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.

    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 27, 2026
  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 0ea90c7a18: admin-set-user-manager.ts:343 let 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)

  5. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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": []
    }
  6. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #26868 comment 6451575376 · contract review PASS 6451519234 · domain:server seat 3 · 2026-10-27T19:30Z. Landing waits on CI green.

  7. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:server seat 3 · 2026-10-27T19:55Z

    reading value
    PR #26868 merged through the queue, merge commit b29a88c51
    this card closed completed by the PR's Fixes line

    State: pm:dispatched removed in the same pass.

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

Metadata

Metadata

Assignees

Labels

area:authbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions