operator-admin-isis-provenance.mdoperator-admin.mdoperator-overview.mdoperator-studio.mdoperator-tenant.md04-auth-session.md05-notifications.mdJourney flow#
Generated from the authored steps below — click a node to jump to that section.
An operator opens an incident, filters and selects it, records a review decision, applies shared lifecycle actions, follows any available evidence link, and resolves the record with an audited rationale. The current implementation has two related but different data planes:
/operator/adminrenders a six-item editorial/tenant/auto fixture unioned with live public-scene abuse reports. Its queue filters and selection are interactive./v1/admin/operator-incidentsowns the shared lifecycle state for the six seededINC-2036…INC-2041records. It supports acknowledge, assign, escalate, snooze, and resolve, with durable snapshot wiring when the admin DB is configured.
AdminInboxDecisionPanel is a third, per-operator verdict record. It is useful
for review accountability but does not replace the shared incident lifecycle.
Background incident creation, realtime/on-call delivery, a complete downstream
routing matrix, SLA timers, and automatic queue removal after resolve remain
outside the shipped browser path.
Personas#
- House operator — filters, selects, assigns, and resolves the queue.
- Crisis-trained reviewer — target owner for crisis-class S1 work; routing and paging are not yet wired here.
- Domain reviewer — receives editorial, evidence, tenant, or abuse context when an explicit deep link is available.
- On-call operator — target recipient for SLA escalation; the current store contains no paging adapter.
Pre-conditions#
- The operator is authenticated with an accepted admin/operator scope.
/operator/admincan load its fixture and any available public-report union.- The incident lifecycle store is registered. In a deploy with an admin DB,
wireDurableOperatorIncidentshydrates and write-through persists its full status, assignment, and history snapshot. - The audit-event store is reachable; every successful lifecycle verb records a
discrete
incident.*event. - Six seeded lifecycle incidents align by ID, severity, title, and category with the six base inbox rows. Live public reports do not automatically become rows in that lifecycle store.
Steps#
1. Incident enters the current catalog#
- The base inbox contains six seeded incidents: one S1, two S2, and three S3 rows across Auto, Editorial, and Tenant categories.
- Live public-scene abuse reports are transformed into additional Auto-lane rows; self-harm maps to S1 and each row carries its public scene short code.
- A background producer creates an arbitrary new shared lifecycle incident through a production ingestion route. The lifecycle store is seeded and exposes no create endpoint.
- An S1 creation pages on-call and emits a realtime operator notification.
2. Open and orient in /operator/admin#
- The page identifies Lilith Admin / House / Inbox and shows open/critical fixture counts.
- All, Editorial, Tenant, and Auto are real button filters.
- Each row exposes code, severity, time, actor, title, subtitle, and category through stable DOM anchors.
- The initially selected drift incident paints its detail card, source rows, per-operator decision panel, and shared lifecycle actions.
- Anonymous access redirects before the operator console renders.
The header count is derived from the loaded inbox catalog. It does not currently subscribe to lifecycle mutations, so resolving an incident in the action panel does not remove its fixture row or decrement the header in the same view.
3. Filter and select#
- All renders the complete union.
- Auto, Editorial, and Tenant filter by each row's real category field.
- Selecting a row updates the detail card, severity, evidence blocks, decision target, and lifecycle-action target.
- Filter selection is encoded in the URL for reload/share continuity. It is component-local today.
- Severity ordering is enforced as a queue sort invariant for live unioned rows. The base fixture order is authored, not recomputed by the client.
4. Record a per-operator review verdict#
- Choose a verdict in
AdminInboxDecisionPanel. - Enter a rationale of at least 20 trimmed characters; shorter input cannot submit.
- Record the decision through
POST /v1/admin/operator-inbox/:itemId/decision. - The response exposes a real audit ID; GET readback rehydrates the decision and reload preserves the visible state.
- Reopen is a real BFF operation for this decision record.
- Missing auth, insufficient scope, malformed verdict, and short rationale fail without a fabricated success.
This verdict is scoped to the operator and inbox item. Shared status changes are performed by the next step.
5. Apply shared lifecycle actions#
- Acknowledge records the acting operator once. A second acknowledge
receives 409
ALREADY_ACKNOWLEDGED. - Assign requires a non-empty assignee and appends an
assignedhistory row. - Escalate only raises severity (
S3→S2orS2→S1) and requires a rationale of at least 50 characters. S1 exposes no higher target. - Snooze offers 1h, 4h, and 24h in the UI. The server forbids S1, caps S2 at 24h, and caps S3 at 7d.
- Resolve requires one of
actioned,no-violation,duplicate,referred, orwont-fixplus a 50-character rationale. - Every successful verb appends shared history and records a corresponding
audit event (
incident.acknowledged,.assigned,.escalated,.snoozed, or.resolved).
6. Follow the evidence link that actually exists#
- A unioned public-report row links back to
/scene/<shortCode>. - A row with
provenanceHrefexposes its explicit provenance link. - Tenant, Studio, Veritas, Nyx, and appeal rows all deep-link into a context-preserving resolution surface. Those links are not present for every fixture class today.
- Every destination provides “Back to incident” with the incident ID and current filter preserved.
7. Resolve and verify readback#
- Select a non-terminal incident, choose a resolution class, supply at least 50 characters, and submit.
- The action panel shows
data-incident-status="resolved"only after the real BFF response succeeds. - The shared store rejects a second resolve with 409 and keeps the first resolution canonical.
- With durable snapshot binding, the shared status/history survives a store restart and rehydrate.
- The browser refetches the shared incident after reload and removes it from the default open queue. Current E2E verifies the write response; the fixture-backed queue is not driven by that store readback.
Post-conditions#
- The selected incident's shared lifecycle store contains every successful
mutation in order and, after resolve, a terminal
resolvedstate and resolution class. - Each successful lifecycle verb has an audit-event record; rejected requests produce no mutation event.
- Any per-operator verdict remains separately readable with its own audit ID.
- The visible action panel never invents a status after a 400, 403, 409, or service failure.
- Queue removal, SLA metrics, on-call escalation, and downstream-action foreign keys are not post-conditions of the current runtime.
Failure modes#
- Concurrent acknowledge — the second writer receives 409; the first actor remains canonical.
- Short escalation or resolution rationale — UI disables submit and the
server independently returns 400
RATIONALE_TOO_SHORTwith no mutation. - Non-monotonic/top-severity escalation — the store rejects the change.
- Invalid resolution class — the store rejects the change.
- S1 snooze / excessive duration — server policy rejects it.
- Missing or insufficient operator scope — the BFF returns 401/403 and the browser reports an honest error.
- Network split — no optimistic status is painted; the user can retry.
- Duplicate producer fires — no ingestion/debounce path currently increments an occurrence count.
- Realtime channel loss — there is no missed-window reconciliation contract for newly created incidents in this surface.
- SLA breach — no timer or paging adapter is connected to the lifecycle store.
- Broken audit/provenance chain — define the exact fail-closed boundary without blocking unrelated publishes globally.
E2E coverage#
apps/oshun/web/e2e/incident-triage.spec.tscovers the real-BFF/operator/incidentsfeed,/operator/adminanatomy, exact base rows, filters, selection reactivity, public-report union, decision-panel audit receipt, assignee persistence, acknowledge → escalate → resolve, anonymous redirect, and short-rationale server rejection.apps/oshun/web/e2e/operator-inbox-decision-bff.spec.tscovers decision create/read/reopen, payload/auth gates, and per-operator isolation.- Store and route tests cover assign/snooze policies, terminal transitions, audit emission, and durable snapshot hydrate/write-through.
- Coverage depth: deep for the existing queue/selection/decision/lifecycle core. Incident ingestion, paging/realtime delivery, full deep-link routing, live queue readback after lifecycle mutation, SLA automation, and producer dedupe remain uncovered product legs.
Per-view files touched by this journey#
operator/operator-admin.md— filterable inbox, selected detail, decisions, and lifecycle actions.operator/operator-overview.md— console entry.shell/04-auth-session.md— operator gate.shell/05-notifications.md— target realtime and paging behavior.operator/operator-studio.md,operator/operator-tenant.md, andoperator/operator-admin-isis-provenance.md— partial/future resolution destinations.
Cross-references#
citation-drift-resolution.md— the evidence-resolution branch.public-scene-abuse-report.md— a live producer unioned into the browser inbox.user-flag-to-review.md— customer report origin.V1/features.md— Trust and Safety, severity/SLAs, and audit explorer requirements.- Current UI:
apps/oshun/web/src/components/lilith/AdminInboxConsole.tsx,AdminInboxDecisionPanel.tsx, andAdminInboxIncidentActions.tsx. - Current routes/stores:
apps/oshun/bff/src/routes/admin-operator-incidents.ts,apps/oshun/bff/src/routes/admin-operator-inbox.ts, andapps/oshun/bff/src/admin/operator-incident-store.ts.
Open questions / known gaps#
- Add a production incident-ingestion contract and dedupe/recurrence model.
- Make the browser inbox list/counts read shared lifecycle status and refetch after a mutation.
- Complete and test the context-preserving deep-link matrix.
- Define the realtime snapshot/reconnect channel and on-call paging adapter.
- Implement SLA clocks, breach events, and post-incident-review obligation.
- Decide how live public-report union rows become shared lifecycle records before exposing lifecycle controls for those IDs.