V1 Web PWA · Journey

Journey: Tenant audit-log investigation

A source-reconciled journey record for V1 Web PWA — shipped behavior, state boundaries, failure modes, and the automation evidence available today.

automated · 1 spec verdict: partial· 2026-05-29
9sections6 minread

On this page
Journey at a glance
ActorsPlatform operatorIncident or tenant investigatorCompliance reviewerSecond operator
Automation1 E2E spec(s) · verdict: partial· 2026-05-29

Journey flow#

Generated from the authored steps below — click a node to jump to that section.

flowchart TD s1["1. Open the two-layer audit page"] s2["2. Search the live admin event store"] s3["3. Save and reload a query"] s4["4. Export JSON or Markdown"] s5["5. Exercise the API-only patch and token seams"] s6["6. Delete and inspect the meta-audit trail"] s1 --> s2 --> s3 --> s4 --> s5 --> s6 click s1 href "#1-open-the-two-layer-audit-page" click s2 href "#2-search-the-live-admin-event-store" click s3 href "#3-save-and-reload-a-query" click s4 href "#4-export-json-or-markdown" click s5 href "#5-exercise-the-api-only-patch-and-token-seams" click s6 href "#6-delete-and-inspect-the-meta-audit-trail" s6 --> v(["verdict: partial"]) click v href "../results/tenant-audit-log-investigation.html" style v stroke:#d6a531,color:#d6a531

/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 boundaryworkspaceIdIn 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 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#

Cross-references#

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?