Context. surface tenant-admin · domain governance · route /audit · auth signed-in (tenant) + scope:tenant:admin:{tenantId} (or tenant:admin:*) · source apps/oshun/tenant-admin/src/app/audit/page.tsx
Last walked. —
Purpose#
A tenant-scoped, severity-ranked audit-log explorer. It renders the records that
pass applyAuditFilter for a tenant-admin viewer, with a per-severity summary
and a hash-chain digest line per record. It is the audit workspace
(group: governance) in @oshun/navigation, strictly isolated to the viewer's
tenant.
Entry points#
- Shell sidebar Governance › Audit item (
TenantSidebar,shortLabel: 'Audit', path/audit). - Direct URL
/audit(bookmarkable; scope-gated).
Layout regions#
Wrapped in <TenantShell session currentWorkspaceId="audit">:
- Sidebar / Header: standard chrome (header crumb "Audit log").
- Main (
<section data-testid="tenant-audit-panel" data-tenant-id={...}>):<h1>Tenant audit log</h1>- Subtitle: record count +
info N · warning N · critical Nsummary, noting strict scoping to<code>{tenantId}</code>and that cross-tenant access requires platform-operator scope. <ol data-testid="tenant-audit-records">(styles.recordList) — one<li>per record withdata-record-id,data-severity,data-action:- Record header:
<time>(localized), severity badge, action<code>, andactorId · actorRole. - Record body:
target: {class}(+ optional target id + correlation). - Record digest:
digest {contentDigest}(+ optionalprior {digest}).
- Record header:
States#
- Loading — N/A; synchronous server render from
seedAuditRecords()+applyAuditFilter()(no fetch). - Empty — reachable only if a filter removes all records; the current
AuditFilteris all-null(no filtering), so the seeded 4 records always show. Empty<ol>would render no<li>and the count reads "0 records". - Populated (short) — always 4 seeded records:
member.invited(info),role.assigned(info),policy.attestation_submitted(warning),member.suspended(critical). - Populated (long) — N/A; fixed 4-record seed; no pagination/virtualization.
- Error (recoverable) — N/A; no query.
- Error (unrecoverable) — no custom handler; render throw → Next default.
- Offline — N/A (fully server-rendered).
- Gated — no session →
redirect('/unauthorized?reason=missing-session&returnTo=/audit'); middleware blocks unauthenticated requests upstream. Cross-tenant records are filtered out byapplyAuditFilteragainst theExplorerViewer. - Standalone PWA — N/A; no manifest. (verify.)
Interactions#
- Audit record list items (
<li data-record-id>indata-testid="tenant-audit-records")- Function: render-only; severity colorizes via
severityClass()(severity_info/severity_warning/severity_critical). No expand, filter, sort, or export controls exist in code — read-only display. - Keyboard: not focusable.
- Screen reader: ordered-list semantics;
<time dateTime>carries ISO value; severity rendered as text inside the badge. - Touch target: N/A (non-interactive).
- Mobile (≤ 640 px):
audit.module.cssrecord layout. - Disabled when: N/A.
- Offline behavior: N/A.
- Telemetry: None.
- Function: render-only; severity colorizes via
- Note: the workspace
primaryJobdescribes "filters, saved investigations, and exportable bundles", but none of those controls are present in the current page (see Open questions).
Data & contracts#
- Reads:
getTenantServerSession();applyAuditFilter({ records, filter, viewer })from@oshun/tenant-console— inputAuditLogRecord[],AuditFilter,ExplorerViewer { role: 'tenant-admin', tenantId }; returns the filteredreadonly AuditLogRecord[]. Records come from the in-fileseedAuditRecords(tenantId). No BFF call. - Writes: None.
- Realtime: None.
- Caching: SSR (dynamic; cookie read). Deterministic per tenant id.
- Auth/role check:
middleware.ts+ pagegetTenantServerSession()→canEnterTenantShell. Tenant isolation additionally enforced byapplyAuditFilterviaExplorerViewer.tenantId.
Cross-references#
- Domain logic:
libs/oshun/tenant-console/src/audit-explorer/index.ts(applyAuditFilter,AuditFilter,AuditLogRecord,ExplorerViewer). - Workspace model:
libs/oshun/navigation/src/tenant-ia.ts(audit,group: governance). - Operator-admin audit explorer (cross-tenant):
../ADMIN_WALKTHROUGH/workspaces/operations/analytics.md(AdminAuditLogExplorerPanel). - Sibling routes: home.md, data.md.
/policy,/roles,/statuslack files yet.
Open questions / known gaps#
- Records are hard-coded seed data; no BFF read of the canonical tenant audit stream is wired. Document the eventual endpoint + hash-chain verification source.
- No filter UI, saved-investigation, or export controls despite the
primaryJobdescribing them — theAuditFilteris fixed all-null. Track these as unbuilt.