Quality & Governance — User Journeys
Last verified: Session 210 (29 April 2026) Pending updates: None
Quality & Governance — User Journeys
Section titled “Quality & Governance — User Journeys”Overview
Section titled “Overview”The Quality & Governance area covers the workflows users follow to keep KB content accurate, fresh, and trustworthy. Four overlapping subsystems show up in the UI:
- Content review at
/review— verify/flag/skip individual items. - Governance review via
/api/governance/review— approve / request_changes / revert items flagged by quality or freshness bridges. - Publication lifecycle — admins/editors transition items between
draft → in_review → published → archivedvia the item detail page or MCP. - Change Reports at
/change-reports— summarise activity over a period.
Important distinction. The two review systems use different vocabularies and live behind different APIs:
- Content review (
/review,/api/review/action): verify / flag / skip / unverify / unflag. Asks “is this individual item trustworthy?”. - Governance review (
/api/governance/review): approve / request_changes / revert. Asks “has the recent edit (or freshness/quality drop) been triaged?”.
Items can be in both queues at once — they answer different questions.
Entry Points
Section titled “Entry Points”| Entry Point | Route | Accessible By |
|---|---|---|
| Review Queue | /review | Editor, Admin |
| Governance Review (needs-attention) | Banner / dashboard surfaces | All authenticated (count); Editor+ to act |
| Item detail (publication transitions, cadence editor) | /item/[id] | All to view; Editor+ to edit |
| Change Reports | /change-reports | All roles |
| Settings → Quality Review (preset config) | /settings | All to view; Admin only to edit |
| Settings → Profile → Notification Preferences (auto-gen + email opt-out) | /settings | Self |
User Journeys
Section titled “User Journeys”Journey 1: Processing the Review Queue
Section titled “Journey 1: Processing the Review Queue”Actor: Editor Goal: Triage the review queue — verify trustworthy items, flag those needing attention. Preconditions: Items exist in the queue (unverified, flagged, or auto-flagged by quality/freshness bridges or the review-cadence cron).
-
Open the queue
- Route:
/review - Component:
ReviewQueuePanel+ReviewCadenceCard - The queue defaults to
status='unverified'sortedcreated_at desc. TheReviewCadenceCardshows aggregate review-health stats above the queue including overdue counts, due-soon, and per-domain breakdowns.
- Route:
-
Apply filters
- Component:
ReviewFilterspopover. - Available controls:
Assigned to me,Overdue reviews(S205 WP-E T2, count pill fromstats.overdue), Status (Unverified / Verified / Flagged / Drafts / All), Domain, Content type, Source file, Source document. - The Overdue toggle widens the
unverifiedfilter to OR-include items withgovernance_review_status='review_overdue', so verified-but-overdue items surface alongside unverified ones. - The Drafts tab now reads
publication_status='draft'(post-§5.2 Phase 2.5). Other tabs filterpublication_status != 'draft'.
- Component:
-
Choose a sort
- Sort options:
created_at(default desc),confidence_asc(lowest classification confidence first),quality_score_asc(lowest quality score first). Sort is URL-bound for shareability.
- Sort options:
-
Take an action
- Component:
ReviewActionBar. - Verify →
POST /api/review/action{ action: "verify" }. Setsverified_at+verified_byon the content item. Records a row inverification_history(action_type='verify'). Resolves any openreview_neededflags iningestion_quality_log. - Flag →
POST /api/review/action{ action: "flag" }. Inserts areview_neededflag (severitywarning) iningestion_quality_log. Clearsverified_at/verified_by. Records averification_historyrow (action_type='flag'). - Skip → no DB write; advances to next item.
- Unverify →
POST /api/review/action{ action: "unverify" }. Clearsverified_at/verified_by. Recordsaction_type='unverify'. - Unflag →
POST /api/review/action{ action: "unflag" }. Resolves the most recent unresolvedreview_neededflag for the item.
- Component:
-
Track session progress
ReviewProgressBarshows session count and verified/flagged delta.ReviewSessionSummaryopens a recap pill of the current session.
Variations
Section titled “Variations”-
Auto-flagged items. Items can appear in the queue automatically when:
- Quality score drops below the per-domain
quality_score_threshold(auto_flag_on_quality_drop=true, see Light-touch vs Strict presets). - Freshness transitions to
staleorexpired(auto_flag_on_freshness_transition=true). Note:ageingdoes not trigger governance auto-flagging — onlystaleandexpireddo. next_review_date < CURRENT_DATEand the item wasNULLor'approved'— daily 03:45 UTCreview-cadencecron flips it to'review_overdue'.
- Quality score drops below the per-domain
-
Review assignment scoping. With “Assigned to me” enabled, the queue intersects against the user’s active
review_assignments(filter_domains, filter_content_types). Empty intersection returns an empty result.
Edge cases
Section titled “Edge cases”- No items match the filter → empty state with hint to broaden filters.
- User has no active assignments and toggles “Assigned to me” → empty list immediately (no API round-trip needed beyond the assignment lookup).
Journey 2: Governance Review (Approve / Request Changes / Revert)
Section titled “Journey 2: Governance Review (Approve / Request Changes / Revert)”Actor: Editor or Admin
Goal: Act on items flagged by governance bridges (quality drop, freshness
transition, or cadence overdue).
Preconditions: Items have
governance_review_status ∈ {'pending', 'review_overdue'}.
-
Discover pending items
- The needs-attention banner reads
GET /api/governance/review?count_only=true. GET /api/governance/reviewlists the items, sorted bygovernance_review_dueascending (nulls last).
- The needs-attention banner reads
-
Choose an action
POST /api/governance/reviewwith one of:action: "approve"— setsgovernance_review_status='approved',governance_reviewer_id=user.id,verified_at=NOW(). Auto-renewsnext_review_datefor items with a configured cadence (S206 WP-A Phase 2 / §5.5 Phase 2 T2):GREATEST(current_next_review_date, today) + cadenceDays.action: "request_changes"— setsgovernance_review_status='changes_requested',governance_reviewer_id=user.id. Notifies the content owner + last editor.action: "revert"— setsgovernance_review_status='reverted', clearsgovernance_review_due. (Revert reads the previous content_history version; the rollback itself is wired through the dedicated rollback path.)
-
Notification dispatch (best-effort)
- Notifications fire to
content_owner_idandupdated_by(excluding the acting reviewer). Notification dispatch failure must NOT roll back the governance update — it’s atry…catchswallowed warning.
- Notifications fire to
Variations
Section titled “Variations”-
'review_overdue'items take the same path. S205 §5.5 added'review_overdue'toALLOWED_REVIEW_INPUT_STATUSES, so the same handler approves and renews them — without that allow-list, the route would 4xx reject every overdue item. -
Editor vs admin. Both roles can approve / request_changes / revert. Admin-only actions live on a different surface (preset config, hard delete).
Edge cases
Section titled “Edge cases”- Item not in
pendingorreview_overdue→ 400 “Item is not pending governance review”. - Item not found → 404.
- Concurrent reviewer races → 500 wrapped in
safeErrorMessage.
Journey 3: Publication Lifecycle Transitions
Section titled “Journey 3: Publication Lifecycle Transitions”Actor: Editor (limited) or Admin (full)
Goal: Move an item between draft → in_review → published → archived.
Preconditions: User can read the item; transition matrix permits the
specific role × from-state × to-state combination.
-
Open the item
- Route:
/item/[id]. The item header surfacesGovernanceBadge(ifgovernance_review_statusis set),VerificationBadge,QualityBadge, andReviewCadenceBadge. The publication-status surface is rendered contextually (chip/badge — UI dedicated tab is §5.2 Phase 4 EP2 build).
- Route:
-
Submit a transition
- Component (admin/editor): publication-status control or MCP
update_publication_status. - Wire:
PATCH /api/items/[id]with{ field: 'publication_status', value: 'published', archive_reason?: '…' }. - The route validates the transition against
computeAllowedTransitions(fromStatus, role):- 403 if the role has zero transitions out of the current state (e.g.
editor on
'published'rows). - 409 if the role can transition out but not to the requested target
(e.g. editor
draft → archived— editor’s allowed targets are only['in_review']).
- 403 if the role has zero transitions out of the current state (e.g.
editor on
- Component (admin/editor): publication-status control or MCP
-
Side effects per transition
published → archived: stampsarchived_at = NOW(),archived_by = user.id, and optionallyarchive_reasonif provided.archived → published / draft / in_review(un-archive): clearsarchived_at. Preservesarchived_byandarchive_reasonfor audit trail.- All other transitions: state-only change.
-
History row written
content_historyinsert withchange_type='publication_state'andchange_reason='Transition from {from} to {to}'(+(reason: {archive_reason})suffix on archive). Theauto_version_content_historytrigger fillsversion.
Allowed transition matrix
Section titled “Allowed transition matrix”| Current → | Admin | Editor | Viewer |
|---|---|---|---|
draft | in_review, published | in_review | — |
in_review | published, draft | published, draft | — |
published | archived, draft | — | — |
archived | published, draft | — | — |
Disallowed everywhere (always 409): draft→archived, in_review→archived,
archived→in_review, published→in_review.
Edge cases
Section titled “Edge cases”- Concurrent state change between the read and the UPDATE → 409 “Concurrent state change detected; please retry.” (optimistic-concurrency guard).
- Item not found → 404.
- Invalid
publication_statusvalue → 400 (Zod superRefine + defensive re-check).
Journey 4: Setting a Review Cadence
Section titled “Journey 4: Setting a Review Cadence”Actor: Editor or Admin Goal: Configure when an item should be re-reviewed (one-off or recurring). Preconditions: Editing a content item.
- Open
ReviewCadenceEditoron the item (mounted only when the parent passesreadOnly={false}, i.e. EditorView). - Pick a preset from the Select:
No recurring review/Every 3 months(90d) /Every 6 months(182d) /Every 12 months(365d) /Custom(1..1095 days). Custom validates client-side against the DB CHECK range. - Set
next_review_datewith<input type="date">. Editor writes viaPATCH /api/items/:idwith{ field: 'next_review_date', value: 'YYYY-MM-DD' }. - Cadence value writes via
PATCH /api/items/:id{ field: 'review_cadence_days', value: '90' | null }.
Variations
Section titled “Variations”- Cadence visible to all viewers. The Provenance “Review Schedule”
subsection on
per-item-tab.tsxdisplays Next review date / Review cadence / Last reviewed viaformatDateUK. - Auto-renewal on approve. When an admin/editor approves a governance
review on an item with a cadence,
computeNextReviewDate(...)advances the date automatically — see Journey 2.
Edge cases
Section titled “Edge cases”next_review_date < CURRENT_DATE→ dailyreview-cadencecron flipsgovernance_review_statusto'review_overdue'and notifies the owner (or admins if unowned). Idempotent within-day (one notification per recipient per UTC day).
Journey 5: Configuring Per-Domain Governance (Admin)
Section titled “Journey 5: Configuring Per-Domain Governance (Admin)”Actor: Admin Goal: Choose how strictly each domain is governed. Preconditions: User has admin role.
- Navigate to Settings → Quality Review (
GovernanceSection). - Pick a domain from the dropdown (excludes domains already configured).
- Choose a preset:
- Light-touch:
posture='open',auto_flag_on_quality_drop=false,auto_flag_on_freshness_transition=false, threshold 40, no cooldown. “All edits land immediately. Low-scoring items surface to your attention, but nothing is blocked.” - Strict:
posture='review_on_change', both auto-flags on, threshold 60, 7-day timeout, 14-day cooldown. “Edits to this domain are held for review. Stale or low-quality items are automatically flagged.”
- Light-touch:
- Save.
POST /api/governancewith{ domain, preset }. The server maps preset → concrete column values viaPRESET_VALUES.
The 7-field per-domain dialog (P0-16, S177) is gone — admins now pick a
preset and the columns are populated server-side. Existing rows can be
inferred via inferPreset(posture) for legacy data.
Variations
Section titled “Variations”- Recalculate quality scores action exists alongside the preset list to trigger a fresh score recompute outside the weekly cron.
Edge cases
Section titled “Edge cases”- Non-admin tries to POST → 403.
- Domain already configured → upsert path updates the row.
Journey 6: Trust Signals at a Glance
Section titled “Journey 6: Trust Signals at a Glance”Actor: Any viewer/editor/admin Goal: Decide whether a content item is trustworthy before using it. Preconditions: Browsing a content card or item detail page.
The status row on browse cards and item details surfaces four orthogonal badges:
| Badge | Component | Reads |
|---|---|---|
VerificationBadge | verification-badge.tsx | verified_at (Unverified vs Verified) |
QualityBadge | quality-badge.tsx | quality_score 0–100 + label |
GovernanceBadge | governance-badge.tsx | governance_review_status |
ReviewCadenceBadge | review-cadence-badge.tsx | next_review_date + governance_review_status |
WCAG 2.1 AA: every badge pairs colour with text/icon — colour is never the
sole signal. governance_review_status='review_overdue' always wins
precedence over date-based bands in the cadence badge.
Editors hover QualityBadge to see the five-component breakdown
(Freshness/30, Confidence/20, Completeness/20, Summary/15,
Citations/15). Viewers see a simplified “Quality: Good” tooltip.
Journey 7: Generating and Reading Change Reports
Section titled “Journey 7: Generating and Reading Change Reports”Actor: Any role (read), Editor+ (generate) Goal: Understand what changed in the KB over a recent period. Preconditions: Account >24h old (auto-gen guard); content exists.
-
Navigate to
/change-reports.- If the account is >24h old, no cached report exists, and the user has
auto_generate_change_reports=true(default ON), a weekly report auto-generates on first visit. - Auto-gen is gated client-side (
autoGenTriggered.currentref) so it fires once per session.
- If the account is >24h old, no cached report exists, and the user has
-
Choose a period (period dropdown):
1 day/7 days(default) /14 days/30 days/Custom. Custom reveals an inline filter panel withdateFrom/dateTo/ domain / keywords. -
Click Generate. A Cancel button (OPS-23, S191) replaces Generate while the request is in flight, backed by
AbortController. -
Read the report.
ChangeReportViewrenders:- Narrative summary — AI-generated prose (“you captured…”, “your focus was on…”).
- By-domain sections — top items + key themes per domain.
- Cross-domain themes — 3–7 theme clusters spanning domains.
- Review activity this period — modified/verified/flagged delta counts.
- Current KB Health (OPS-19, S191) — freshness state-of-the-world snapshot (fresh/aging/stale/expired). Not a period delta — a current-state breakdown of the entire KB.
-
Cost guard. If the selected period contains ≥150 items (
DIGEST_AUTO_GEN_MAX_ITEMS), the API responds 413 withcode='DIGEST_TOO_MANY_ITEMS'+data: { item_count, max }. The page renders an actionable empty state with the count and limit, asking the user to narrow the period or add a domain filter. -
Opt out of auto-gen at Settings → Profile → Notification Preferences → “Auto-generate weekly reports” toggle. Stored on
user_notification_prefs.auto_generate_change_reports(defaulttrue).
Variations
Section titled “Variations”- Daily digests use a lighter prompt (less prose, focus on “what’s new today”).
- Filtered reports (domain/keywords) narrow the input set before the
cost guard fires — a domain filter is the recommended way to recover from
a
DIGEST_TOO_MANY_ITEMSrejection.
Edge cases
Section titled “Edge cases”- Rate limit 5/min/user (
/api/change-reports/generate) → standard rate-limit 429. - Anthropic upstream failure →
AIServiceErrorpropagates with original HTTP status.
Journey 8: Provenance — Why was this changed?
Section titled “Journey 8: Provenance — Why was this changed?”Actor: Any role (read) Goal: Understand who changed an item, when, and why. Preconditions: Item has version history.
Every content_history row carries a change_reason field (S153 mandatory
guard). Display surfaces:
ReviewHistorySectionon the item detail page — verification history (verification_historytable), separate audit trail of verify/flag/unverify actions.- Provenance Per-Item tab (
per-item-tab.tsx) — version timeline with change_reason, change_summary, and snapshot diffs. - Editor “Why change?” optional text input on content-tab editors —
captured into
content_history.change_reasonon PATCH. NULL when left blank.
Canonical change_reason values are documented in
docs/reference/data-entry-points.md Appendix D. The column is free-text;
new values only need updating the appendix, the guard test
(__tests__/validation/content-history-change-reason.test.ts), and the
call site.
Role Permissions Summary
Section titled “Role Permissions Summary”| Action | Viewer | Editor | Admin |
|---|---|---|---|
| View Review Queue | No | Yes | Yes |
| Verify / Flag / Skip / Unverify / Unflag content | No | Yes | Yes |
| View Governance Review queue + count | Yes (read) | Yes | Yes |
| Approve / Request Changes / Revert | No | Yes | Yes |
| Configure Governance Presets | No | No | Yes |
Set / clear next_review_date, review_cadence_days | No | Yes | Yes |
Publication: draft → in_review | No | Yes | Yes |
Publication: in_review → published | No | Yes (per §5.3 gate) | Yes |
Publication: draft → published | No | No | Yes |
Publication: published → archived | No | No | Yes |
Publication: archived → published | No | No | Yes |
| View Change Reports | Yes | Yes | Yes |
| Generate Change Reports | No | Yes | Yes |
| Hard-delete a content item | No | No | Yes |
| Soft-delete (archive) a content item | No | Yes | Yes |
Current Limitations
Section titled “Current Limitations”- No publication-review queue tab. UI surfaces for
in_reviewitems ship with §5.2 Phase 4 EP2 build (deferred). request_changesdoesn’t enforce a deadline. The reviewer marks the itemchanges_requestedbut the content owner has no SLA — a follow-up cron is roadmap.- Visibility is not gated by
publication_status. Any authenticated user can read draft/in_review items today; the column drives workflow, not access. Visibility flip is §5.2 Phase 3. - Content review and governance review are not unified. Two queues, two vocabularies — by design (different questions). UI surfaces them side-by-side rather than mashing into one timeline.
auto_flag_cooldown_daysis a one-shot. It suppresses the next-after-verify flag, but rapid quality flapping over multiple cycles isn’t fully validated.- Email notifications not yet wired. Toggles exist on
user_notification_prefsbut no SMTP/SES integration — in-app only.
Related Documentation
Section titled “Related Documentation”docs/product-functionality/quality-governance/technical.md— schema, API, components, configuration, tests.docs/product-functionality/quality-governance/workflows.md— automated flows, cron jobs, state machines.docs/reference/state-of-the-product.md§Governance & Review — capability summary.docs/specs/publication-lifecycle-state-machine-spec.md— §5.2 spec (transition matrix, role gate, side effects).docs/specs/p0-document-control-lifecycle-spec.md— §5.5 spec (review cadence, quality score Phase 5 modifier).docs/reference/data-entry-points.mdAppendix D — canonicalchange_reasonvocabulary.