Skip to content

Role permission templates: 4 roles + capability toggles; remove guest & apprentice subsystems - #144

Merged
important-new merged 21 commits into
InspectorHub:mainfrom
important-new:feat/role-permission-templates
Jun 14, 2026
Merged

Role permission templates: 4 roles + capability toggles; remove guest & apprentice subsystems#144
important-new merged 21 commits into
InspectorHub:mainfrom
important-new:feat/role-permission-templates

Conversation

@important-new

Copy link
Copy Markdown
Contributor

Summary

Collapses the role taxonomy to 4 canonical rolesowner / manager / inspector / agent — each a fixed permission template, and adds a small capability layer so the long tail is handled by a few per-user toggles instead of a sprawling role set.

  • Single source of truth (server/lib/auth/roles.ts): ROLES + Role type; requireRole(...roles: Role[]) is typed, so role changes are compiler-guided. Legacy inspector→lead alias removed.
  • 4 roles, DB value == UI label: admin renamed to manager; the dead lead/specialist/apprentice/office values are gone from the type, the invite enum, and the drizzle column enums.
  • Capability layer: getCapabilities(role, overrides) + requireCapability(...) on 4 gates — publish reports, schedule-for-others, financial data, manage contacts — backed by a nullable users.permission_overrides JSON column. Owner is pinned all-on, agent all-off; inspector defaults are overridable per user.
  • Guest subsystem removed end-to-end (no billing advantage, full-seat cost). expires_at/assigned_section_ids/mentor_id are DEAD-marked, not dropped (D1 cannot drop FK-referenced columns).
  • Apprentice review-queue subsystem removed: apprentices become plain inspectors; "requires review" is re-homed onto the publish capability (former apprentices get publish=false via the data migration).
  • Team UI: 4-role dropdown + an "Advanced permissions" disclosure whose toggles carry through invite → accept onto the new member.
  • Migration guide for inspectors coming from Spectora / ISN (docs/migrating-roles.md).
  • Guardrails: ESLint rule banning bare role string literals outside the source-of-truth + a drift test asserting the drizzle role enums equal ROLES.

Large/commercial jobs continue to use the existing multi-inspector assignment axis (the per-inspection ['lead','helper'] enum) — untouched by this change, as is the commercial office property subtype.

Migrations

  • 0001 — add users.permission_overrides
  • 0002 — data remap of legacy role values (admin→manager, office→manager, lead/specialist/apprentice→inspector; former apprentices keep review-on-publish). Idempotent/defensive (no-op when absent).
  • 0003 — add tenant_invites.permission_overrides

Apply to remote D1 at deploy time per the migration SOP (export backup + time-travel bookmark first).

Test plan

  • type-check 0 · test:unit 1651 pass · test:web 370 pass · test:workers 41 pass · lint 0
  • Capability gates verified: owner pinned-on, agent denied at role layer, inspector default-denied on financial/contacts but grantable via override (the override path is effective because inspector is in the role list and the capability is the real gate).
  • Guest/apprentice removal: the normal member-invite, publish, and edit paths remain intact; orphaned guest_invites/apprentice_reviews tables are noted (D1 cannot drop them).

🤖 Generated with Claude Code

important-new and others added 21 commits June 13, 2026 19:03
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… become inspectors)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…roles.ts

Add no-restricted-syntax lint rule flagging bare RBAC role string literals
(owner/admin/manager/inspector/agent) outside the source-of-truth. The
:not() selector excludes requireRole() args (already typed as Role[]).
Comprehensive exemption block covers existing code where matches are either
type-safe (Drizzle column enum, TypeScript Role type) or non-RBAC (OpenAPI
tags/scopes, contact types, signer roles, presence roles).

Fix 4 files with genuinely unguarded RBAC comparisons to use new ROLE.*
constants (ROLE.OWNER, ROLE.ADMIN, ROLE.INSPECTOR, ROLE.AGENT) exported
from roles.ts alongside ROLES + Role:
  - server/lib/rbac/can-edit.ts — role === 'owner'|'admin'|'agent'|'inspector'
  - server/lib/report-section-numbering.ts — new Set(['owner','admin','inspector'])
  - server/api/availability.ts — ['admin','owner'].includes(userRole) x4
  - server/api/agent.ts — userRole === 'admin'

Add tests/unit/role-enum-drift.spec.ts: verifies that users.role and
tenant_invites.role drizzle column enums match ROLES exactly (both pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e/contacts gates

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o member

Add an "Advanced permissions" disclosure to the invite modal with the four
toggleable capabilities (Publish reports, Schedule for others, Financial data,
Manage contacts), defaulting to the selected role's template. The chosen diffs
flow invite -> accept -> users.permission_overrides:

- tenant_invites gains a nullable permission_overrides JSON column (mirrors
  users); migration 0003_invite_permission_overrides (single ADD COLUMN).
- InviteMemberSchema accepts an optional sparse capability map.
- TeamService.createInvite stores only toggles that differ from the role
  template (null when nothing differs); AuthService.joinTeam replays the stored
  overrides onto the new users row.
- Modal exports computeOverrideDiff/CAP_LABELS for direct unit testing.
- Remove the dead `role === "lead"` account-role check in TeamBanner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…renumber migrations 0002/0003 after living_the_fury

- Integrate InspectorHub#143 (DB-16 report cover photo + concierge retirement, deployed to prod)
- Renumber permission_overrides (0002) + role-value remap (0003) after main's 0001_living_the_fury
- Resolve inspection-patch-settings test: DB-16 POOL_KEY behavior + admin->manager rename
- 0002 hand-written as additive ADD COLUMN only (no D1-unsafe users table-recreate)
@important-new
important-new merged commit c7284ae into InspectorHub:main Jun 14, 2026
4 checks passed
@important-new
important-new deleted the feat/role-permission-templates branch June 14, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant