Skip to Content

← All archived runs

Run: inherited-visibility

run.md

Run: inherited-visibility

  • branch: claude/inherited-visibility
  • pr: #845

02_define/output/spec.md

Spec: Inherited visibility — see your team, act on nothing

  • slug: inherited-visibility
  • personas: Admin, CSM, SDM, Expert
  • touches: packages/services/src/db/services/leads/{lead-scope,lead-party}.ts, packages/services/src/db/services/leads/index.ts, apps/web/lib/workspace/lead-guard.ts
  • complexity: standard

Problem

Today a person sees either only their own work or, as an administrator, everything. A leader cannot see their people's customers, service leads or delivery work, so they chase by email. The reporting line from user-record-and-reporting-line exists to answer exactly this — but nothing reads it yet (FR-4). This advances Q2-2026 Objective 1 (Establish Product-Market Fit with Vendor Partners): a vendor partner's leaders must see their whole team's pipeline without leaving the platform.

Proposed change

Visibility is inherited upward through the reporting hierarchy, and only visibility is.

  • One predicate, and everything else calls it (design notes): the lead read scope grows a line-aware layer. An own-scoped viewer's filter becomes their own work plus every lead held by anyone below them in their reporting line — the whole team, all levels down, across the csm, sdm and expert slots. The team is computed from the shared hierarchy core (user-record-and-reporting-line), whose traversal is cycle-guarded and depth-capped, so a broken or incomplete line yields a short team — visibility fails safe, narrowing and never widening (BR-12). A viewer outside the hierarchy (external expert, vendor-role or multi-role document) gets no team at all.
  • Visibility only, never ownership (BR-11, D-5): the party guards that authorise actions (isDirectLeadParty, the workspace lever guard, every action's own permission gate) are untouched. The workspace page becomes viewable for a lead in the viewer's line (isLeadViewerVisible = direct party or reached through the line); the workspace levers keep requiring the viewer to be a direct party — a manager can look, not act.
  • Administrators see everything in their organisation regardless of where they sit — the existing full-scope short-circuit stands.
  • Internal Experts belong to the reporting hierarchy (D-2): only internal, single-role experts sit in lines, so a manager's team filter reaches their assigned delivery work through the expert slot. External Experts are reached through the engagement — assignments, the bid pool and their own proposals — exactly as today, and never through a manager.
  • Owns / Can see / Can change stay separate (§7, Paul's ruling): Owns is the party slot on the record, Can see is this read predicate, Can change is the permission template — three mechanisms that never collapse into one another. The Sales role joins the same predicate when service-lead-ownership-and-handoff lands; the SDM's reach follows their hierarchy and, from team-region-country-filters, the Region/Country filters — no separate SDM regional assignment is added (D-7).

Acceptance criteria

  • A manager sees their own work plus everything belonging to their line, all levels down.
  • A manager sees nothing belonging to anyone outside their line.
  • A manager cannot act on a team member's records by virtue of the reporting line alone.
  • A broken or incomplete reporting line leaves the person seeing only their own work.
  • An internal Expert's work rolls up their reporting line.
  • An external Expert's work is reached through the engagement, not through a manager.

Out of scope

  • The My Team / Region / Country filters — that is team-region-country-filters.
  • Any manager power to act on a team member's records — deferred to a future release.
  • Ownership transfer rules — that is service-lead-ownership-and-handoff.
  • Retiring the legacy SDM area assignment structure — the dashboards' reach converges on the hierarchy plus filters in team-region-country-filters.
  • Non-lead surfaces (invoices, CSAT, portfolios) — they follow the same predicate as the filter work lands in stub 5; this run establishes the model on the platform's primary work object.

Open questions

  • none

03_build/output/notes.md

Build notes: inherited-visibility

  • commits: feat: inherited-visibility — see your team, act on nothing

What changed

  • leads/lead-scope.ts: withTeamVisibility(base, teamMemberIds) — the pure widening of an own-scope lead filter to the viewer's line across the csm, sdm and expert slots. Null base (admin full scope) and an empty team pass through untouched. Unit-tested, including the expert viewer's $or flattening and the fail-safe empty-team case.
  • leads/lead-party.ts: reachesLeadThroughLine(lead, teamIds) — the pure view-side predicate ("someone below me holds a slot on this lead"), plus refId exported. The customer slot is deliberately not reachable. Tested.
  • leads/index.ts: scopedLeadFilter now widens the own filter with viewerTeamIds — the viewer's line from the shared hierarchy core (teamOf, cycle-guarded and depth-capped), empty for anyone outside the hierarchy and on any hierarchy read failure, so a fault narrows and never widens (BR-12). New public isLeadViewerVisible = direct party OR reached through the line — used by the view guard only.
  • apps/web/lib/workspace/lead-guard.ts: canViewLead now calls isLeadViewerVisible; requireWorkspaceParty (every workspace lever) stays on isLeadViewerParty — a manager can look at a team member's lead workspace but every action still requires being a direct party (BR-11, D-5).

Acceptance criteria status

  • A manager sees their own work plus everything belonging to their line, all levels down — the board/list filter and the workspace view guard both read the full line from the shared core.
  • A manager sees nothing belonging to anyone outside their line — the widening adds only the line's ids; nothing else changes.
  • A manager cannot act on a team member's records by virtue of the reporting line alone — lever guard and per-action permission gates untouched; only the view path widened.
  • A broken or incomplete reporting line leaves the person seeing only their own work — cycle-guarded traversal returns a short line; hierarchy failures resolve to an empty team; both are unit-tested at the core.
  • An internal Expert's work rolls up their reporting line — the expert slot is part of the team filter, and only internal single-role experts appear in lines.
  • An external Expert's work is reached through the engagement, not through a manager — external experts are never hierarchy members, and their bid-pool/proposal access rules are untouched.

Notes for Verify

  • The board calls findByStatuses per kanban column, and each call now loads the tenant's org index once (a small indexed find). If that read shows up in traces, lift the team computation to the page and thread it through — the pure filter already takes the ids.
  • Non-lead surfaces (portfolios, invoices, CSAT) still show own work only; they adopt the same predicate when the My Team filter lands in team-region-country-filters (stated in the spec's out-of-scope).
  • Workspace UI may render lever buttons to a manager-viewer; the server-side lever guard rejects them ("Service lead not found."). Cosmetic gating can follow with the filters work.
  • Stacked on regions-and-countries.