Skip to content

Quality & Governance — User Journeys

Last verified: Session 210 (29 April 2026) Pending updates: None

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 → archived via 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 PointRouteAccessible By
Review Queue/reviewEditor, Admin
Governance Review (needs-attention)Banner / dashboard surfacesAll authenticated (count); Editor+ to act
Item detail (publication transitions, cadence editor)/item/[id]All to view; Editor+ to edit
Change Reports/change-reportsAll roles
Settings → Quality Review (preset config)/settingsAll to view; Admin only to edit
Settings → Profile → Notification Preferences (auto-gen + email opt-out)/settingsSelf

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).

  1. Open the queue

    • Route: /review
    • Component: ReviewQueuePanel + ReviewCadenceCard
    • The queue defaults to status='unverified' sorted created_at desc. The ReviewCadenceCard shows aggregate review-health stats above the queue including overdue counts, due-soon, and per-domain breakdowns.
  2. Apply filters

    • Component: ReviewFilters popover.
    • Available controls: Assigned to me, Overdue reviews (S205 WP-E T2, count pill from stats.overdue), Status (Unverified / Verified / Flagged / Drafts / All), Domain, Content type, Source file, Source document.
    • The Overdue toggle widens the unverified filter to OR-include items with governance_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 filter publication_status != 'draft'.
  3. 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.
  4. Take an action

    • Component: ReviewActionBar.
    • VerifyPOST /api/review/action { action: "verify" }. Sets verified_at + verified_by on the content item. Records a row in verification_history (action_type='verify'). Resolves any open review_needed flags in ingestion_quality_log.
    • FlagPOST /api/review/action { action: "flag" }. Inserts a review_needed flag (severity warning) in ingestion_quality_log. Clears verified_at/verified_by. Records a verification_history row (action_type='flag').
    • Skip → no DB write; advances to next item.
    • UnverifyPOST /api/review/action { action: "unverify" }. Clears verified_at/verified_by. Records action_type='unverify'.
    • UnflagPOST /api/review/action { action: "unflag" }. Resolves the most recent unresolved review_needed flag for the item.
  5. Track session progress

    • ReviewProgressBar shows session count and verified/flagged delta.
    • ReviewSessionSummary opens a recap pill of the current session.
  • 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 stale or expired (auto_flag_on_freshness_transition=true). Note: ageing does not trigger governance auto-flagging — only stale and expired do.
    • next_review_date < CURRENT_DATE and the item was NULL or 'approved' — daily 03:45 UTC review-cadence cron flips it to 'review_overdue'.
  • 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.

  • 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'}.

  1. Discover pending items

    • The needs-attention banner reads GET /api/governance/review?count_only=true.
    • GET /api/governance/review lists the items, sorted by governance_review_due ascending (nulls last).
  2. Choose an action

    • POST /api/governance/review with one of:
      • action: "approve" — sets governance_review_status='approved', governance_reviewer_id=user.id, verified_at=NOW(). Auto-renews next_review_date for 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" — sets governance_review_status='changes_requested', governance_reviewer_id=user.id. Notifies the content owner + last editor.
      • action: "revert" — sets governance_review_status='reverted', clears governance_review_due. (Revert reads the previous content_history version; the rollback itself is wired through the dedicated rollback path.)
  3. Notification dispatch (best-effort)

    • Notifications fire to content_owner_id and updated_by (excluding the acting reviewer). Notification dispatch failure must NOT roll back the governance update — it’s a try…catch swallowed warning.
  • 'review_overdue' items take the same path. S205 §5.5 added 'review_overdue' to ALLOWED_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).

  • Item not in pending or review_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.

  1. Open the item

    • Route: /item/[id]. The item header surfaces GovernanceBadge (if governance_review_status is set), VerificationBadge, QualityBadge, and ReviewCadenceBadge. The publication-status surface is rendered contextually (chip/badge — UI dedicated tab is §5.2 Phase 4 EP2 build).
  2. 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']).
  3. Side effects per transition

    • published → archived: stamps archived_at = NOW(), archived_by = user.id, and optionally archive_reason if provided.
    • archived → published / draft / in_review (un-archive): clears archived_at. Preserves archived_by and archive_reason for audit trail.
    • All other transitions: state-only change.
  4. History row written

    • content_history insert with change_type='publication_state' and change_reason='Transition from {from} to {to}' (+ (reason: {archive_reason}) suffix on archive). The auto_version_content_history trigger fills version.
Current →AdminEditorViewer
draftin_review, publishedin_review
in_reviewpublished, draftpublished, draft
publishedarchived, draft
archivedpublished, draft

Disallowed everywhere (always 409): draft→archived, in_review→archived, archived→in_review, published→in_review.

  • 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_status value → 400 (Zod superRefine + defensive re-check).

Actor: Editor or Admin Goal: Configure when an item should be re-reviewed (one-off or recurring). Preconditions: Editing a content item.

  1. Open ReviewCadenceEditor on the item (mounted only when the parent passes readOnly={false}, i.e. EditorView).
  2. 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.
  3. Set next_review_date with <input type="date">. Editor writes via PATCH /api/items/:id with { field: 'next_review_date', value: 'YYYY-MM-DD' }.
  4. Cadence value writes via PATCH /api/items/:id { field: 'review_cadence_days', value: '90' | null }.
  • Cadence visible to all viewers. The Provenance “Review Schedule” subsection on per-item-tab.tsx displays Next review date / Review cadence / Last reviewed via formatDateUK.
  • 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.
  • next_review_date < CURRENT_DATE → daily review-cadence cron flips governance_review_status to '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.

  1. Navigate to Settings → Quality Review (GovernanceSection).
  2. Pick a domain from the dropdown (excludes domains already configured).
  3. 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.”
  4. Save. POST /api/governance with { domain, preset }. The server maps preset → concrete column values via PRESET_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.

  • Recalculate quality scores action exists alongside the preset list to trigger a fresh score recompute outside the weekly cron.
  • Non-admin tries to POST → 403.
  • Domain already configured → upsert path updates the row.

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:

BadgeComponentReads
VerificationBadgeverification-badge.tsxverified_at (Unverified vs Verified)
QualityBadgequality-badge.tsxquality_score 0–100 + label
GovernanceBadgegovernance-badge.tsxgovernance_review_status
ReviewCadenceBadgereview-cadence-badge.tsxnext_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.

  1. 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.current ref) so it fires once per session.
  2. Choose a period (period dropdown): 1 day / 7 days (default) / 14 days / 30 days / Custom. Custom reveals an inline filter panel with dateFrom / dateTo / domain / keywords.

  3. Click Generate. A Cancel button (OPS-23, S191) replaces Generate while the request is in flight, backed by AbortController.

  4. Read the report. ChangeReportView renders:

    • 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.
  5. Cost guard. If the selected period contains ≥150 items (DIGEST_AUTO_GEN_MAX_ITEMS), the API responds 413 with code='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.

  6. Opt out of auto-gen at Settings → Profile → Notification Preferences → “Auto-generate weekly reports” toggle. Stored on user_notification_prefs.auto_generate_change_reports (default true).

  • 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_ITEMS rejection.
  • Rate limit 5/min/user (/api/change-reports/generate) → standard rate-limit 429.
  • Anthropic upstream failure → AIServiceError propagates 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:

  • ReviewHistorySection on the item detail page — verification history (verification_history table), 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_reason on 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.

ActionViewerEditorAdmin
View Review QueueNoYesYes
Verify / Flag / Skip / Unverify / Unflag contentNoYesYes
View Governance Review queue + countYes (read)YesYes
Approve / Request Changes / RevertNoYesYes
Configure Governance PresetsNoNoYes
Set / clear next_review_date, review_cadence_daysNoYesYes
Publication: draft → in_reviewNoYesYes
Publication: in_review → publishedNoYes (per §5.3 gate)Yes
Publication: draft → publishedNoNoYes
Publication: published → archivedNoNoYes
Publication: archived → publishedNoNoYes
View Change ReportsYesYesYes
Generate Change ReportsNoYesYes
Hard-delete a content itemNoNoYes
Soft-delete (archive) a content itemNoYesYes
  • No publication-review queue tab. UI surfaces for in_review items ship with §5.2 Phase 4 EP2 build (deferred).
  • request_changes doesn’t enforce a deadline. The reviewer marks the item changes_requested but 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_days is 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_prefs but no SMTP/SES integration — in-app only.
  • 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.md Appendix D — canonical change_reason vocabulary.