user-record-and-reporting-linerun.md02_define/output/spec.mdA Sustentus organisation has no reporting structure. Nobody has a manager, there is no notion of a team, and there is nowhere to record who a person reports to. Every other part of the v1-user-hierarchy scope — inherited visibility, ownership handoff, regional roll-up — needs that single fact to exist first. This advances Q2-2026 Objective 1 (Establish Product-Market Fit with Vendor Partners) under the Refine-the-bridge initiative: vendor partners need one named owner and one reporting line per person before leaders can see their teams' work.
Every user record carries exactly seven facts — name, email, role, manager, region, country and status — and the manager pointer is the organisation structure. There is no separate chart.
Model (packages/services):
manager (ObjectId ref user, optional) joins the user schema. employment
(internal | external) joins it too — it is what distinguishes an unplaced internal person from
an external Expert reached by assignment (D-2, design notes), and it is not a second role.
isHierarchyTop (boolean) marks the organisation's single top-level user; a partial unique
index on {tenantId} where isHierarchyTop: true makes "exactly one top" (BR-2, D-3) a
database invariant. Indexes ship in a migration (autoIndex is off).internal for admin/csm/sdm and external for expert, vendor and
customer docs. Vendor-role and multi-role user documents are untouched and sit outside the
hierarchy in v1 — the hierarchy covers internal roles only.lead-scope.ts precedent: line-of, depth, cycle and validity checks. A
hierarchyService wraps it with tenant-scoped reads and the manager-change write.service-lead-ownership-and-handoff.User management (apps/web):
/users, /admin/users) show all seven facts at a glance: name, email, role,
manager (with Unplaced warning for internal users with no manager, Top for the top user,
By assignment for external Experts, Outside hierarchy for vendor-role/multi-role rows),
region, country and status. The grouped-by-person aggregation is extended to carry them.regions-and-countries, which replaces these inputs with pickers).inherited-visibility.regions-and-countries; this feature only stores them.service-lead-ownership-and-handoff; the hierarchy admits it when it
lands.03_build/output/notes.mdpackages/services/src/shared/hierarchy.ts (+ tests): the pure hierarchy
core — breadth-first, cycle-guarded, depth-capped traversal (lineOf,
chainAbove, subtreeDepth), the manager-change validator (BR-3/4/5), the
unplaced and effective-holder derivations (BR-6/8), employment defaults and
placement labels. Exported via @sustentus/services/shared.packages/services/src/db/models/user.ts: manager (ObjectId ref),
employment (internal/external), isHierarchyTop on the base schema;
tenantId_manager index and the tenantId_hierarchy_top_unique partial
unique index (one top per tenant, as a DB invariant).packages/services/src/db/migrations/1787529600000-user-reporting-line.ts:
builds both indexes; backfills employment (staff internal, expert/vendor/
customer external); per tenant appoints the oldest active single-role admin
as top and points every other active single-role internal doc at them
(Jamie's 2026-08-17 rulings). Vendor-role and multi-role docs untouched.packages/services/src/db/services/hierarchy/: hierarchyService — org
index, summary, manager candidates, setManager (validated write),
appointTop, effective holder.packages/services/src/db/services/users/index.ts: the grouped directory
rows now carry location, employment, manager (+ page-scoped $lookup for
the manager's name), isHierarchyTop and the per-person doc count.apps/web/lib/resolve-app-user-for-tenant.ts: BR-7 — a disabled
admin/csm/sdm resolves to a clean failure (DISABLED_ACCOUNT_ERROR), so
disabled staff cannot use the app. Expert docs keep isActive:false as
their onboarding gate; customers keep auto-reactivation.apps/web/components/users/users-table/users-table.tsx: Manager (placement
— manager name / Unplaced warning badge / Top of organisation / By
assignment / Outside hierarchy), Region and Country columns.apps/web/components/admin/users/hierarchy-summary.tsx (+ admin users
page): unplaced / disabled / deepest-line strip, top named.apps/web/components/users/user-profile/: reporting-line panel (manager
select over valid candidates, appoint-as-top, expert employment toggle),
region/country inputs on the edit form, header badges, and the
disabled-user "open items held by <manager>" note.apps/web/app/(app)/users/[id]/actions.ts: setUserManager,
appointHierarchyTop, setUserEmployment (all tenantConfig.manage);
updateTenantUser gains region/country and refuses disabling the top.appointTop clears the old marker and sets the new one in two writes (no
transaction); the partial unique index makes the failure mode "no top"
rather than "two tops".resolveAppUserForTenant); pages that render from the role claim alone
don't hit that path. Expert-role docs are deliberately exempt: their
isActive:false is the onboarding gate (pre-existing model debt — flagged
rather than changed here).tenantId_manager,
tenantId_hierarchy_top_unique).