# Journey: Tenant audit-log investigation

`/operator/audit` combines two distinct read surfaces: a compact server-rendered
ledger from `/v1/audit`, and an interactive explorer over the shared admin audit
event store. The explorer can filter, save/load an operator-owned query, export
JSON or Markdown, and delete it. The BFF additionally supports description,
bookmark, patch, and share-token operations, but those controls are not in the
UI—and the issued share token has no read/consume route.

## Personas

- **Platform operator** — holds `admin:*` and searches events across operator
  workspaces.
- **Incident or tenant investigator** — narrows a prefix, actor, workspace, text
  needle, and time interval.
- **Compliance reviewer** — downloads a reproducible evidence bundle and needs
  to know whether integrity and retention claims are actually enforced.
- **Second operator** — illustrates the boundary between a token being issued
  and a genuinely shareable investigation.

## Pre-conditions

- The operator is signed in and the page-level operator gate allows
  `/operator/audit`.
- Interactive `/v1/admin/audit-log/*` access requires exactly `admin:*` in
  `admin-audit-log.ts`. `admin:studio` and tenant-console workspace scopes do
  not pass this current gate, although the explorer's error copy still names
  `admin:studio`.
- The admin audit-events store may be durable on configured Postgres; without
  the admin database, events/investigations are process-local.
- Saved investigations are bucketed by authenticated operator user id, not by
  tenant. This is a platform-wide explorer, not a tenant-admin projection.

## Steps

### 1. Open the two-layer audit page

The upper ledger server-loads `/v1/audit` and renders When, Actor, Action, and
Target newest-first. That endpoint is a separate compact audit contract from the
explorer below; matching rows between the two stores is not guaranteed.

`/operator/admin` and `/operator/incidents` provide links to `/operator/audit`.
Anonymous navigation is redirected before either layer renders.

### 2. Search the live admin event store

`OperatorAuditExplorer` calls `GET /v1/admin/audit-log/events` with optional
`needle`, one event-type prefix, comma-separated actor/workspace ids, from/to
Unix seconds, and a UI-fixed limit of 25. The store performs case-insensitive
needle matching over title, subtitle, event type, and workspace and returns
newest-first events plus the normalized filter.

The prefix is free text rather than a typed taxonomy picker. Useful live
families include `audit.investigation.*`, `admin.integrations.*`,
`admin.bulk_*`, `incident.*`, `tenant.scim_provision.*`, and
`tenant.oneroster.*`.

### 3. Save and reload a query

The shipped UI accepts only an investigation title and the current filter.
`POST /v1/admin/audit-log/investigations` creates an operator-owned record; the
saved list can reload that filter and re-run the search. A maximum of 100
records per operator is enforced by the store.

The API also accepts an optional 2,000-character description and up to 200
bookmarked event ids, and `PATCH` can update title, description, filter, or
bookmarks. No description editor, event bookmark button, or PATCH action is
rendered in `OperatorAuditExplorer`.

### 4. Export JSON or Markdown

Export re-runs the saved filter with a 500-event limit and resolves bookmarked
ids against the current event store. Missing bookmarks are silently omitted.
JSON contains the investigation, matched events, surviving bookmarks, and
`generatedAt`; Markdown contains the title, description, created/updated times,
optional bookmarks, and matched rows.

The browser receives the payload and creates a local Blob download. No export
artifact is stored server-side. Neither format includes a manifest hash or
signature, and the route does not enforce a retention boundary.

### 5. Exercise the API-only patch and token seams

Real HTTP coverage patches notes/bookmarks and posts `/:investigationId/share`.
Share returns a raw token once and stores its SHA-256 hash with the
investigation. There is no endpoint that accepts the token to read an
investigation, no shared-link UI, and no cross-operator authorization flow. The
current feature is token issuance, not working collaboration.

### 6. Delete and inspect the meta-audit trail

Delete removes the operator-owned investigation and its stored token hash.
Successful create, update, share, export, and delete operations append
`audit.investigation.<action>` rows with filter/bookmark metadata; raw share
tokens are excluded. Invalid create and duplicate delete do not mint phantom
events.

## Post-conditions

- The operator can search the live admin event store and save/reload one named
  query from the browser.
- JSON and Markdown downloads reflect a fresh export-time query, not a frozen
  event snapshot.
- Investigation lifecycle actions are themselves auditable and the raw share
  token is not placed in audit payloads.
- Description, bookmark, patch, and share-token creation work at the API layer
  only.
- Cross-operator share consumption, tamper-evident export, tenant-admin
  isolation, redaction, concurrency control, and retention indication are not
  shipped.

## Failure modes

- **Scope-copy mismatch** — the server accepts only `admin:*`, while explorer
  denial copy says `admin:* or admin:studio`.
- **Two unrelated ledgers** — the upper `/v1/audit` table can be green while the
  lower admin event store is empty or unavailable.
- **No tenant boundary** — `workspaceIdIn` is an optional filter, not an
  authorization constraint; this route is platform-operator only.
- **Share token dead end** — a valid issued token cannot open anything because
  no consumer route exists.
- **Bookmark disappearance** — deleted/missing events vanish silently from an
  export; the UI reports no missing count.
- **No chain of custody** — downloads have no canonical digest, signature, or
  verification command.
- **Last-write-wins PATCH** — there is no ETag/version precondition for
  concurrent updates.
- **Retention ambiguity** — old rows can disappear from the event store without
  a query warning; downloaded Blobs are outside server retention control.
- **Durability configuration** — without the admin database, saved
  investigations and event history do not survive restart.

## E2E coverage

- [`apps/oshun/web/e2e/tenant-audit-log-investigation.spec.ts`](../../apps/oshun/web/e2e/tenant-audit-log-investigation.spec.ts)
  covers the upper ledger, anonymous gate and entry links; real-BFF 401/403,
  event filtering, create/list/PATCH/share/export/delete and meta-audit
  read-back; plus UI filtering, save/load, JSON/Markdown Blob export, and
  delete.
- **Coverage depth: deep for shipped/API seams, partial for the named
  collaboration outcome.** Bookmark/share are API-only, and no test can prove
  token consumption, cryptographic export verification, redaction, or
  tenant-scoped access because those contracts are absent.

## Per-view files touched

- [`operator/operator-audit.md`](../operator/operator-audit.md) — static ledger
  plus interactive explorer.
- [`operator/operator-admin.md`](../operator/operator-admin.md) — audit entry
  and downstream operator actions.
- [`operator/operator-tenant.md`](../operator/operator-tenant.md) — tenant
  context used as an optional workspace filter.
- [`operator/operator-admin-isis-provenance.md`](../operator/operator-admin-isis-provenance.md)
  — provenance/drift follow-up.
- [`shell/04-auth-session.md`](../shell/04-auth-session.md) — operator role and
  scope gate.

## Cross-references

- [`incident-triage.md`](./incident-triage.md) — incident events searched here.
- [`tenant-bulk-member-invite-scim.md`](./tenant-bulk-member-invite-scim.md) —
  tenant/SCIM audit producers.
- [`tenant-integrations-api-keys-webhooks.md`](./tenant-integrations-api-keys-webhooks.md)
  — integrations event producers.
- Sources: `apps/oshun/web/src/app/operator/audit/page.tsx`,
  `apps/oshun/web/src/components/lilith/OperatorAuditExplorer.tsx`,
  `apps/oshun/bff/src/routes/admin-audit-log.ts`, and the two
  `admin-audit-*-store.ts` files.

## Open questions

- Is this intentionally platform-only, or what tenant-scoped role and enforced
  workspace predicate should a tenant admin receive?
- Should the upper `/v1/audit` ledger be retired or reconciled with the shared
  admin audit-event store?
- What route verifies a share token, re-checks current scope, and renders a
  read-only investigation without leaking the token?
- Which canonical serialization, digest/signature, and verification utility make
  exported evidence tamper-evident?
- How should the UI expose descriptions, bookmarks, missing events, redaction,
  retention boundaries, and optimistic concurrency?
