Administration — User Journeys
Administration — User Journeys
Section titled “Administration — User Journeys”Last verified: S210 A5-administration (29 April 2026) against S195-S209
- kh-prod-readiness-S10/S11/S12/S13.
Overview
Section titled “Overview”Administration covers the day-to-day jobs the admin (and, for a subset, the editor) performs to keep Canonical healthy: team management, taxonomy + tag-morphology curation, content ownership, governance configuration, layer vocabulary, notification preferences, pipeline health monitoring, and the operator-side jobs that run alongside the product (taxonomy sync, GDPR export, advisor review).
Most journeys live inside /settings. A few — Provenance pipeline health,
Quality Review, MCP-driven governance review — live on dedicated routes.
Operator jobs (GDPR export, advisor lint, row-count diff, staging refresh)
are CLI-driven and ride on the runbooks in docs/runbooks/ and
docs/handover/.
Entry Points
Section titled “Entry Points”| Entry Point | Route | Accessible By |
|---|---|---|
| Settings | /settings | All roles (sections vary by role) |
| Provenance | /provenance | Admin only |
| Quality Review queue | /review | Admin (Editor for content review) |
| MCP governance tool | governance.review MCP tool | Admin/Editor authenticated MCP client |
| GDPR export CLI | bun run scripts/export-user-data.ts | Operator (Liam) with prod service-role key |
| Staging refresh / DB rebuild | supabase db push --linked + seed-e2e-users.ts | Operator |
| Supabase advisor lint | bun run scripts/run-supabase-advisors.ts | Operator + CI nightly |
/settings shows three groups: Personal (Profile, Organisation,
Connections), Content Management (Content Organisation, Content Owners,
Organisations & People, Guides, Tag Morphology), and System (Team,
Quality Review, Reviewer Assignments, Provenance). Editors see Personal
plus admin/editor-flagged sections (Organisation, Tag Morphology); viewers
see Personal only.
User Journeys
Section titled “User Journeys”Journey 1: Modifying System Taxonomy
Section titled “Journey 1: Modifying System Taxonomy”Actor: Admin Goal: Keep the core taxonomy in sync with domain language shifts and land the change end-to-end (DB → classifier prompt → snapshot → plugin). Preconditions: Authenticated Admin.
- Access Categories tab
- Route:
/settings?section=content-organisation&tab=categories - Component:
ContentOrganisationSection→TaxonomySection - User sees: Tree view of
taxonomy_domainsandtaxonomy_subtopicswith create/edit/delete controls. ATaxonomyDriftBannersurfaces whentaxonomy_sync_state.last_sync_hashdoes not match the DB- computed hash.
- Route:
- Edit a domain or subtopic
- Action: Add/rename/disable a domain or subtopic.
- Result:
PATCH /api/taxonomy/domains/[id](or subtopic equivalent) persists the change. The drift banner appears (or stays) until the downstream sync lands.
- Run taxonomy sync
- Action: Press the “Sync taxonomy” action exposed by the drift banner.
- Result:
POST /api/admin/taxonomy-synccompares hashes; on mismatch it inserts arunningrow inpipeline_runsand dispatches the GitHub Actionstaxonomy-sync.ymlworkflow with the pipeline run ID.
- Workflow regenerates artefacts
- The workflow regenerates
lib/ai/skills/classification.md,scripts/tests/fixtures/taxonomy_snapshot.json, and the plugin bundle, commits them, and callsPOST /api/admin/taxonomy-sync/callbackwith the run outcome — flipping thepipeline_runsrow tocompleted/failedand updatingtaxonomy_sync_state.last_sync_hash.
- The workflow regenerates
- Verify sync state
- Route:
/settings?section=content-organisation&tab=categories - User sees: Drift banner cleared. Optional confirmation via
GET /api/admin/taxonomy-sync/status.
- Route:
Edge cases
Section titled “Edge cases”- Hash already in sync: The dispatch endpoint returns
{ dispatched: false, reason: 'in_sync' }and records a no-oppipeline_runsrow. - GitHub dispatch fails:
pipeline_runsflips tofailed, Sentry fires, and the route returns 502 with the actionable error. - Two admins editing concurrently: Last write wins; the drift banner remains until a sync run completes.
Journey 2: Team Management — Invite + Role Change
Section titled “Journey 2: Team Management — Invite + Role Change”Actor: Admin
Goal: Invite a new colleague and promote them from viewer to editor.
Preconditions: Authenticated Admin. The inviting domain must satisfy
the hook_restrict_signup_to_phew_domain allowlist (currently
@phew.org.uk only — see docs/reference/auth-hooks.md).
- Invite by email
- Route:
/settings?section=team - Component:
TeamSection - Action: Fill the invite form (email, optional display name, role).
- Result:
POST /api/admin/users/invitecreates the auth user viaauth.admin.inviteUserByEmail(). Theon_auth_user_createdtrigger seeds a viewer-defaultuser_rolesrow AND inserts theuser_profilesmirror row in one body.
- Route:
- Wait for first sign-in
- The invitee follows the magic-link email. The auth-hook gate enforces the domain allowlist; non-Phew emails reject with HTTP 403.
- Promote to editor
- Route:
/settings?section=team - Action: On the user row, select the role dropdown and switch
viewer→editor. - Result:
PATCH /api/admin/users/[userId]updatesuser_roles.role. The promoted user can now edit content, manage review queues, and seeEditorViewlayouts.
- Route:
Variations
Section titled “Variations”- Already-active user, role change only: Skip step 1.
- Display name update: Same
PATCHendpoint acceptsdisplay_name. The TeamSection renders display names viaget_user_display_names()(mirrorsauth.usersviauser_profiles— no GoTrue dependency).
Edge cases
Section titled “Edge cases”- Pipeline service account row (
PIPELINE_SYSTEM_USER_ID) is filtered at the DB layer inGET /api/admin/usersso the Team list shows humans only. auth.admin.listUsers()500s (S156-class GoTrue regression): the bulk read ofuser_profiles + user_rolesstill resolves;last_sign_in_atdegrades to NULL. The UI displays “Never” rather than surfacing an error.- Role lookup DB failure:
getAuthorisedClientreturnsrole_lookup_failed→ HTTP 500 (not silent downgrade to viewer).
Journey 3: User Deactivation
Section titled “Journey 3: User Deactivation”Actor: Admin Goal: Deactivate a colleague who has left the organisation. Preconditions: Authenticated Admin.
- Open Team section
- Route:
/settings?section=team - Component:
TeamSection - User sees: Single responsive flex-row list (
TeamMemberRow); each row carries display name, email, role, last sign-in, and an inline ghostDeactivatebutton.
- Route:
- Confirm deactivation
- Action: Click
Deactivate→ confirmationAlertDialogfires. - Result:
DELETE /api/admin/users/[userId]deactivates the user.
- Action: Click
- Reassign content ownership (manual follow-up — not automated)
- Route:
/settings?section=content-owners - Component:
ContentOwnerManagement - Reassign any items previously owned by the deactivated user.
- Route:
Edge cases
Section titled “Edge cases”- Self-deactivation: Currently not gated client-side. The API route must enforce — see Current Limitations.
- Notification sweep: Outstanding
notificationsrows for the deactivated user remain until the 24-hourexpires_atdefault lapses.
Journey 4: Content Organisation — Tags + Layers
Section titled “Journey 4: Content Organisation — Tags + Layers”Actor: Admin (Tag Morphology also Editor) Goal: Curate tags and depth-level layer vocabulary. Preconditions: Authenticated Admin (or Editor for Tag Morphology).
- Manage tags (clean-up)
- Route:
/settings?section=content-organisation&tab=tags - Component:
TagsSection→TagsCleanup(auto-selected when duplicates exist) orTagsBrowse. - User sees: Duplicate clusters, domain-grouped views, bulk-action toolbar; or virtual-scrolled per-tag CRUD with merge/rename/delete.
- Result:
PATCH /api/tags/[id]orDELETE /api/tags/[id].
- Route:
- Curate tag morphology drift queue (S195)
- Route:
/settings?section=tag-morphology - Component:
TagMorphologySection - User sees: Pending drift flags from the corpus regression eval
(
stored_tagvsproposed_canonical, withusage_countandaffected_content_ids). - Action: Triage each flag —
accept(apply canonical),add_override(record domain-specific exception), ordismiss. - Result:
PATCH /api/admin/tag-morphology/flags/[id]records the decision plusdecided_by+decided_at+decision_rationale.
- Route:
- Manage layer vocabulary
- Route:
/settings?section=content-organisation&tab=depth-levels - Component:
LayersSection - Action: Add/rename/reorder/disable layer keys.
- Result:
POST/PATCH /api/layers[/id]updateslayer_vocabulary. Affects content depth classification and theLayerSwitcherNav.
- Route:
Journey 5: Governance Configuration
Section titled “Journey 5: Governance Configuration”Actor: Admin Goal: Tune freshness thresholds, review cadences, and per-domain auto-flag policies. Preconditions: Authenticated Admin.
- Open Governance settings
- Route:
/settings?section=governance(sidebar label “Quality Review”) - Component:
GovernanceSection - User sees: Per-domain configuration form for review cadence, freshness thresholds, auto-flag toggle, reviewer ID, timeout.
- Route:
- Update rules
- Action: Edit thresholds or toggle
auto_flag_on_freshness_transition. - Result:
POST /api/governanceupdatesgovernance_config. Cron jobs (freshness-transitions,review-cadence,quality-score) read these values on next run.
- Action: Edit thresholds or toggle
Journey 6: Content Owner Management
Section titled “Journey 6: Content Owner Management”Actor: Admin Goal: Assign or change content ownership for governance accountability. Preconditions: Authenticated Admin.
- Open Content Owners
- Route:
/settings?section=content-owners - Component:
ContentOwnerManagement - User sees: List of content items with assigned owners.
- Route:
- Assign or reassign owner
- Action: Select a user from the dropdown.
- Result: Ownership recorded on
content_items.content_owner_id. The assigned user receivesreview_overduenotifications when thereview-cadencecron fires for items pastnext_review_date.
Journey 7: Pipeline Health Monitoring
Section titled “Journey 7: Pipeline Health Monitoring”Actor: Admin Goal: Check pipeline health — most recent runs, durations, failures. Preconditions: Authenticated Admin.
- Open Provenance
- Route:
/provenance(linked from the Settings sidebar Provenance entry) - Component:
PipelineHealthTabincomponents/provenance/ - User sees: Rollup summary cards, recent
pipeline_runswith status, duration, and any error message; time-range and pipeline-kind filters.
- Route:
- Drill into a failure
- Component:
PipelineFailureDrawer - User sees: Error detail, originating cron schedule (e.g.
freshness-transitions,review-cadence,intelligence-poll),resultJSON.
- Component:
- Cross-reference per-item provenance
- Tab:
per-item - User pastes a content item UUID and sees AI cost figures (embedding, classification, summarisation), models used, and processing history.
- Per AI-visibility policy,
/provenanceis the only surface that shows model names + token counts + cost.
- Tab:
Variations
Section titled “Variations”- Audit feed: Tab
auditshows the activity feed lifted from the old Settings Activity section./activityand/settings?section=activityredirect to/provenance?tab=audit. - Verification PDF export:
ExportAuditPdfButtontriggersGET /api/admin/provenance/export/verification-history— A4 monthly PDF rendered via React-PDF; download logged viarecordPipelineRun().
Journey 8: Notification Preferences
Section titled “Journey 8: Notification Preferences”Actor: All roles (Profile section is the entry) Goal: Toggle email notification categories. Preconditions: Authenticated user.
- Open Profile
- Route:
/settings?section=profile - Component:
ProfileSection→NotificationPreferences
- Route:
- Toggle preferences
- Action: Flip Weekly Change Report, Review assignments, or Owned content flags.
- Result:
PUT /api/notifications/preferencesupserts viasb(). All three default ON (silence-first with release valve).
The NotificationPreferences schema is strict — empty body / unknown
fields / all-undefined patches reject. Backend cron and digest jobs
query user_notification_prefs directly to decide whether to dispatch.
Journey 9: Quality Review Queue
Section titled “Journey 9: Quality Review Queue”Actor: Admin / Editor (Editor for content review only) Goal: Triage pending governance review items. Preconditions: Authenticated Admin or Editor.
- Open Quality Review
- Route:
/review - Surfaces items with
governance_review_status = 'pending'plus items flaggedreview_overdueby thereview-cadencecron.
- Route:
- Approve / request changes / revert
- Action: Pick one of three actions per item.
- Result:
POST /api/governance/review(or the symmetricgovernance.reviewMCP tool) updatescontent_items.governance_review_status. Onapprove, cadence auto-renewal advancesnext_review_datetoGREATEST(current, today) + review_cadence_daysand stampsverified_at.
The MCP route uses the same helper so renewal is consistent across web + LLM-driven review surfaces.
Journey 10: GDPR Data Subject Export (Operator-Side)
Section titled “Journey 10: GDPR Data Subject Export (Operator-Side)”Actor: Operator (Liam) — admin role on Vercel deploy + holder of the
prod service-role key.
Goal: Fulfil a verified UK GDPR Article 15 (right of access) or
Article 20 (right to data portability) request within the statutory
one-calendar-month deadline.
Preconditions: Verified subject identity per docs/handover/gdpr-data-export.md §2.
- Verify identity (manual)
- Confirm the requester demonstrates ≥2 of: email control, account knowledge, photo ID. Photo ID deleted immediately after verification.
- Run export script
- Command:
bun run scripts/export-user-data.ts --env=prod --user-id <uuid> --output ./exports/ - Or
--email <addr>for email-keyed lookup. - Article scope flag:
--article=15(default, full PII inventory) or--article=20(portability subset).
- Command:
- Inspect output bundle
- Output dir:
<output>/<uuid>-<timestamp>/ - Contains: per-table JSON files (~20 PII tables), CSV summaries (UTF-8 BOM), Markdown index, SHA-256 manifest.
- Output dir:
- Send bundle
- Deliver to subject via secure channel (encrypted email, signed download).
Edge cases
Section titled “Edge cases”- Subject not found: Exit code 1 (no
auth.usersrow). - Export error: Exit code 2; investigate per runbook §4.
- Statutory deadline at risk: Notify subject of Article 12 §3 two- month extension before deadline expires.
The script is read-only against prod. Sandbox-stall fallback: re-invoke
with dangerouslyDisableSandbox: true per the Bun-fetch HTTP 204 gotcha.
Journey 11: Supabase Advisor Review (Operator-Side)
Section titled “Journey 11: Supabase Advisor Review (Operator-Side)”Actor: Operator
Goal: Triage a new finding surfaced by the nightly advisor lint or
PR-blocking lint workflow.
Preconditions: Operator with SUPABASE_ACCESS_TOKEN PAT.
- Receive failure signal
- CI:
Supabase advisorsworkflow fails on a PR or scheduled run. - Or: Operator runs
bun run scripts/run-supabase-advisors.ts --env=prodlocally.
- CI:
- Inspect the diff
- Script output lists new findings vs the committed baseline at
docs/audits/kh-production-readiness-phase-1/supabase-advisor-baseline.json.
- Script output lists new findings vs the committed baseline at
- Decide: fix or rebaseline
- Fix: Add the missing
REVOKE EXECUTE, RLS policy, FK index, etc. The next migration replay + advisor lint should clear the finding. - Rebaseline: Run the script with
--capture-baselineto overwrite the baseline JSON. Only do this when the finding is intentional and reviewed.
- Fix: Add the missing
Journey 12: Staging Refresh / Database Rebuild (Operator-Side)
Section titled “Journey 12: Staging Refresh / Database Rebuild (Operator-Side)”Actor: Operator
Goal: Reset the persistent staging Supabase branch
(turayklvaunphgbgscat) to known-good state after schema drift, monthly
cadence, or post-incident.
Preconditions: production-readiness branch HEAD ≥ main; all
migration files present locally.
- Pre-flight
git statusclean on the prod-readiness worktree.- Capture pre-reset migration counts via
mcp__supabase__list_migrationsfor both prod and staging refs.
- Reset
- Command:
/opt/homebrew/bin/supabase link --project-ref turayklvaunphgbgscat && /opt/homebrew/bin/supabase db push --linked - On protected branches (staging is protected),
mcp__supabase__reset_branchis a no-op —db push --linkedis the canonical mechanism (per S4 empirical correction in the runbook).
- Command:
- Re-seed test users
- Command:
bun run scripts/seed-e2e-users.ts - Idempotent provisioning via
auth.admin.createUser(). verifyPipelineUserShape()returns exit code 2 if the S156 corrective migration is needed.
- Command:
- Verify parity
- Command:
bun run scripts/verify-user-profiles-parity.ts --env=staging - Compares
user_profilescount tocount_auth_users()RPC. - Optional:
bun run scripts/db-row-count-diff.ts --source=prod --target=stagingfor per-table row-count diff with allowlist.
- Command:
Full procedure: docs/runbooks/staging-refresh.md.
Role Permissions Summary
Section titled “Role Permissions Summary”| Settings Section | Viewer | Editor | Admin |
|---|---|---|---|
| Profile | Yes | Yes | Yes |
| Organisation | No | Yes | Yes |
| Connections | Yes | Yes | Yes |
| Content Organisation | No | No | Yes |
| Content Owners | No | No | Yes |
| Organisations & People | No | No | Yes |
| Guides | No | No | Yes |
| Tag Morphology | No | Yes | Yes |
| Team | No | No | Yes |
| Quality Review | No | No | Yes |
| Reviewer Assignments | No | No | Yes |
| Provenance (out-link) | No | No | Yes |
| Operator-side capability | Viewer | Editor | Admin | Notes |
|---|---|---|---|---|
| GDPR data export script | No | No | Yes (operator) | Requires prod service-role key; run on a trusted host |
| Supabase advisor lint | No | No | Yes (operator) | Requires SUPABASE_ACCESS_TOKEN PAT |
Staging refresh / db push --linked | No | No | Yes (operator) | Requires POSTGRES_PASSWORD + project-link |
| Cron route invocation (Bearer secret) | No | No | Yes (cron) | Authorization: Bearer <CRON_SECRET> header from Vercel Cron |
Editors gain access to Organisation and Tag Morphology sections; viewers still see only the Personal group (Profile + Connections). The “For developers” accordion inside
ConnectionsSectionis gated byuseUserRole().canAdmin. Notification preferences (NotificationPreferences) live inside the Profile section and are available to all roles (S189 P1-18).
Current Limitations
Section titled “Current Limitations”- Settings forms (taxonomy, tags, layers, governance) perform direct updates without versioning/history tracking on admin metadata schemas.
- Content owner reassignment after deactivation is manual — no automated redistribution.
- Self-deactivation is not gated client-side; the API route should reject self-DELETE.
- The Provenance
costanddisputestabs are placeholder stubs. - Auth-hook dashboard wiring (
hook_restrict_signup_to_phew_domain→before-user-createdevent) is NOT captured in SQL — must be re-configured after any project reset. - Multi-client domain allowlist is hardcoded; table-driven allowlist is tracked as backlog OPS-29.
- E2E smoke / MCP eval / migration replay CI gates currently sit with
continue-on-error: trueuntil the staging eval-fixture seed lands (roadmap §9.16.10). - Branch protection enforcement gates on the GitHub Pro upgrade — checks
run and report regardless (
docs/runbooks/ci.md§3).
Related Documentation
Section titled “Related Documentation”- Technical reference:
docs/product-functionality/administration/technical.md - Workflows:
docs/product-functionality/administration/workflows.md - Auth hooks:
docs/reference/auth-hooks.md - Local development:
docs/runbooks/local-development.md - Staging refresh:
docs/runbooks/staging-refresh.md - GitHub Environments:
docs/runbooks/github-environments.md - CI runbook:
docs/runbooks/ci.md - GDPR export:
docs/handover/gdpr-data-export.md - Schema reference:
docs/reference/SCHEMA-QUICK-REFERENCE.md - AI-visibility policy:
docs/reference/ai-visibility-policy.md