feat(l1): L1 workspace Phase 1 — role, seat enforcement, adhoc walker, audit #189
Reference in New Issue
Block a user
Delete Branch "feat/l1-workspace"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Phase 1 of the L1 Workspace feature: a new
l1_techrole betweenengineerandviewerfor frontline workers handling first-call resolution. Engineers can optionally cover L1 work via acan_cover_l1flag (orthogonal pattern, likeis_team_admin). Tagged in audit logs asacting_as: l1_coverageso coverage activity is distinguishable from native L1 work.Phase 1 ships only the adhoc walker variant (single-pane note-taking with debounced autosave). Tree-walker variant code is in place but unreachable from intake until Phase 2 wires
match_or_build. AI-built trees, KB connectors (IT Glue / Hudu / Microsoft Graph), and the BuildAbortedNoKB UX are Phase 2/3 — see plan "Out of scope for Phase 1".Spec:
docs/superpowers/specs/2026-05-28-l1-workspace-design.mdPlan:
docs/superpowers/plans/2026-05-28-l1-workspace-phase-1.mdAcceptance:
docs/superpowers/specs/2026-05-28-l1-workspace-phase-1-acceptance.mdWhat changes (high level)
Schema + migrations
account_rolevaluel1_tech(CHECK constraint extended)users.can_cover_l1booleaninternal_ticketstable — no-PSA fallback ticket model, RLS-isolatedl1_walk_sessionstable — per-session walked path / notes / resolution / escalation, target-consistency CHECK constraint, RLSaudit_logs.acting_ascolumn — populated at session terminal eventsBackend
require_l1,require_l1_or_coverage,require_l1_or_aboveseat_enforcementservice — enforces engineer + L1 seat limits at invite, accept-invite, and role-changeinternal_ticket_service— CRUD + status transitions + promote-to-PSAl1_session_service—start_flow/start_proposal/start_adhoc,record_step,update_notes,resolve,escalate,escalate_without_walk+_resolve_acting_asreturning'l1_coverage'for engineers/l1/*endpoints (intake, queue, sessions/active, sessions/{id}, step, notes, resolve, escalate, escalate-without-walk)abandonedafter 24hFrontend
usePermissionsextensions:isL1Tech,canCoverL1,canUseL1Surface,canUseEngineerSurface/l1/*routes wrapped inL1RouteGuard+L1CoverageBannersession_kindto tree or adhoc variantl1@andengineer-coverage@usersRisk + rollback
New surface gated behind
require_l1_or_coverage. No existing surface modified except role-based nav + invite seat enforcement. Rollback path: revert this PR (no destructive migrations needed — the tables are additive). Migration downgrade scripts ship but assume an empty DB (matches our convention).Test plan
For engineer + l1_tech roles, check_seat_available is called at each mutation point. Returns 402 Payment Required with structured detail {code: 'seat_limit_exceeded', role, current, limit, upgrade_url} when seats are full. Grandfathering: existing over-seated accounts keep existing users; only new mutations are blocked. Also updates AccountInviteCreate and AccountRoleUpdate schemas to accept l1_tech as a valid role value. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Returns {engineer: SeatCheckResult, l1_tech: SeatCheckResult} for the authenticated engineer's account. Powers the SeatCounterWidget UI in the admin/users + account/users surfaces. Engineer+ access only. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Per spec §5.6.1, audit rows are written at session terminal events (resolve, escalate, escalate_without_walk). log_audit gains an optional acting_as parameter that propagates the session's acting_as tag ('l1_coverage' for engineer coverers, null for native L1 users). Final code review flagged this as Important — column existed but was never populated. Four new integration tests cover all three paths. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>anycasts with structural error types (eslint)Frontend CI failed on @typescript-eslint/no-explicit-any in three L1 post-review fix sites. Replace `(err as any).response...` with the codebase's established structural cast `(err as { response?: { data?: { detail?: string } } })`, matching TicketPickerModal / FolderEditModal / ProceduralEditorPage. The AccountSettingsPage 402 handler gets the fuller seat-limit detail shape. tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>