Skip to Content

← All archived runs

Run: licence-class-reporting

run.md

Run: licence-class-reporting

  • branch: claude/licence-class-reporting
  • pr: #850

02_define/output/spec.md

Spec: Licence classes — counted and reported, never enforced

  • slug: licence-class-reporting
  • personas: Admin
  • touches: packages/services/src/permissions/licence.ts, packages/services/src/permissions/licence.test.ts, apps/web/app/(app)/admin/settings/scope/page.tsx, apps/web/app/(app)/admin/settings/scope/_components/licence-band.tsx, apps/web/app/(app)/admin/settings/scope/_components/licence-report.tsx (new), apps/docs/app/technical/packages/services/page.mdx
  • complexity: trivial

Problem

Once every user holds exactly one role, the business needs to know what its user base costs to licence — but as a number it can look at, not as a gate that locks people out of a platform they are mid-project on (FR-12, BR-27; proves AC-8).

Proposed change

A licence report derived entirely from the role each user already holds — no new stored data.

  • The mapping (LICENCE_CLASS_BY_ROLE) is completed per Jamie's ruling (2026-08-17): Admin → Platform, Sales → Sales, CSM → Sales, SDM → Delivery, Expert → Delivery, Customer → Delivery; Vendor stays Sales. Every role now maps to exactly one of the three classes — the customer's former "no class" is retired, so the map carries no nulls and every user appears in exactly one class.
  • The classes stay the three Paul settled: Platform (which cannot exist without Admin, nor Admin without it), Sales, and Delivery. What a class costs or composes stays a commercial decision outside the product (D-11) — nothing here carries a price, a seat count, or an entitlement.
  • The report lives on the existing admin Scope & licences tab, which already renders the class band and states "reporting only":
    • each class card gains its seat count — how many of this organisation's users fall in the class;
    • a new licence report section lists every user — name, email, role, licence class — with the count per class derived from the same rows, so the totals always add up to the list.
  • Never a gate (BR-27): nothing reads a licence class to allow or deny anything — sign-in, routes, actions and templates are untouched. The class changes the moment the role does, because it is derived at render, never stored.

Acceptance criteria

  • Every user appears in the licence report with exactly one licence class.
  • The report shows the count per class.
  • Changing a user's role changes their licence class in the report.
  • No licence class ever prevents a user from signing in or working.

Out of scope

  • Billing, seat purchasing, licence enforcement and usage metering — explicitly outside the scope.
  • Any pricing attached to a class — a commercial decision, not a product one (D-11).
  • The demonstration app's mock licence page — frozen; it keeps its own local mapping.

Open questions

  • none

03_build/output/notes.md

Build notes: licence-class-reporting

  • commits: feat: licence-class-reporting — every user in exactly one class

What changed

  • packages/services/src/permissions/licence.ts: LICENCE_CLASS_BY_ROLE completed per Jamie's ruling (2026-08-17) — Admin → Platform, Sales → Sales, CSM → Sales (was Delivery), SDM → Delivery, Expert → Delivery, Customer → Delivery (was unclassed); Vendor stays Sales. The map's type tightens to Record<UserRole, LicenceClass> — no nulls, every user in exactly one class. licence.test.ts now asserts the full partition (every role counted, none twice, total = all roles).
  • apps/web/app/(app)/admin/settings/scope/page.tsx: the Scope & licences tab gains the per-user licence report — userService.listTenantDirectory mapped through LICENCE_CLASS_BY_ROLE at render, sorted by class then name; counts per class derive from the same rows, so the totals always add up to the list. The "no licence class" branches are gone with the nulls.
  • _components/licence-report.tsx (new): the report table — User, Email, Role, Licence class — with per-class counts and the total in the header.
  • _components/licence-band.tsx: each class card shows its seat count ("N users in this organisation"); the unclassed-roles footnote is retired.
  • apps/docs/app/technical/packages/services/page.mdx: the licence-class paragraph's customer parenthetical updated — this PR falsified it.

Acceptance criteria status

  • Every user appears in the licence report with exactly one licence class — the map has no nulls and the report lists the full tenant directory.
  • The report shows the count per class — in the report header and on each class card.
  • Changing a user's role changes their licence class in the report — the class is derived at render from the role, never stored.
  • No licence class ever prevents a user from signing in or working — nothing reads the mapping for access; sign-in, routes, actions and templates are untouched (BR-27).

Notes for Verify

  • No migration and no stored data — the class is pure derivation, so there is nothing to backfill and nothing that can drift.
  • A multi-role person (the v1 edge held outside the hierarchy) holds one user record per role and appears in the report once per record — each row still carries exactly one class. The premise of this batch is that these people are being phased to one role each.
  • The demonstration app keeps its own local mock mapping (frozen app) — its admin licences page is presentation only and was not touched.
  • Stacked on team-region-country-filters; last of the six-stub batch.