Skip to content

feat(access): durable forensic/raw MCP-access audit sink + tier-aware "Access" dashboard tab (JEF-490)#264

Merged
thejefflarson merged 1 commit into
mainfrom
thejefflarson/jef-490-forensicraw-mcp-access-audit-sink-access-dashboard-tab-tier
Jul 23, 2026
Merged

feat(access): durable forensic/raw MCP-access audit sink + tier-aware "Access" dashboard tab (JEF-490)#264
thejefflarson merged 1 commit into
mainfrom
thejefflarson/jef-490-forensicraw-mcp-access-audit-sink-access-dashboard-tab-tier

Conversation

@thejefflarson

Copy link
Copy Markdown
Owner

Sprint finale for JEF-490 — consumes the JEF-488 AuditSink seam. Closes JEF-490.

HALF A — the durable audit sink (Rust)

  • New dedicated mcp::AccessAuditSink (NOT the DecisionJournal — a disclosure audit is a distinct concern): an append-only, size-rotated subject·entry·tool·tier·time record on its OWN file on the journal PVC (PROTECTOR_MCP_AUDIT_PATH), plus a bounded in-memory ring replayed on boot. Reuses the decision journal's append/rotate/graceful-degrade pattern; unset/unwritable volume ⇒ in-memory only (never crashes).
  • Wired in run_loop as the sink the MCP server uses (replacing the default TracingAuditSink), the SAME Arc shared with the dashboard. Only forensic/raw disclosures are recorded — the dispatch is_disclosure gate is unchanged.
  • New read-only GET /api/access.json: no-store, GET-only, inherits the router-wide OIDC enforce layer (401 unauthenticated — no second gate). It is tier-aware: the audit rows are redacted to the CALLER's own tier — a redacted-tier viewer sees raw pull of [redacted — workload identity; forensic tier required] by alice@…; a forensic+ viewer sees the target. Reuses the JEF-488 sentinel string verbatim (render::S_ENTRY) and the redact tier ladder — one vocabulary across tool + screen.

HALF B — the "Access" Preact tab

  • Sixth tab Access, engine/web/src/access/view.jsx.
  • Section 1 "your access": a tier chip (word always present + glyph aria-hidden + colour; raw = the loud posture colour) over a cov-rows list of what each tier reveals/withholds.
  • Section 2 "forensic & raw pulls": a real semantic <table> with <th scope> headers, newest-first, DOM order = visual order; raw-pull rows carry the loud keyline; all identity/target strings are auto-escaped JSX children.
  • Empty state distinguishes "no forensic/raw pulls recorded" (calm, least-privilege working) from the log-reset fact — the "this log lives in memory and resets on restart" sub-line shows only when the sink is in-memory.

Durability

The sink is durable-on-PVC by default: the chart wires PROTECTOR_MCP_AUDIT_PATH onto the existing journal PVC (a distinct file, never overloaded onto the decision journal). It degrades to in-memory when the volume is absent/unwritable, and the Access tab reports that honestly.

Tests

  • Rust: durable round-trip + in-memory + unwritable-degrade (sink); dispatch records exactly one line for a raw pull and none for redacted; view_model tier-aware redaction (redacted viewer → sentinel, forensic+ → target); HTTP 401/no-store + end-to-end tier redaction via signed tokens; serialize-contract wire tags.
  • Web: Preact tests for the chip (word+glyph+aria), the real table + headers, the raw keyline, XSS escaping, and the durable-vs-in-memory empty states.
  • Gates: cargo fmt; cargo clippy --all-targets -- -D warnings clean; full cargo test green (894 lib + integration, incl. the 1,000-line file-size guard — run_loop's test block was split into run_loop/tests.rs); npm run build && npm test green (99 tests); helm lint clean.

Scope notes / decisions

  • preactTabs is mis-scoped: JEF-398 removed the per-tab flag — every tab is client-rendered automatically, so there is no engine.dashboard.preactTabs key in this chart to update. The Access tab ships with the bundle unconditionally. The cluster-repo follow-up is moot for the same reason (and ../cluster was not touched).
  • a11y tooling is mis-scoped (JEF-489 confirmed): no eslint/jsx-a11y/axe in this tree. Did a manual a11y pass instead (role/labels, word+glyph on the chip with aria-hidden glyph, semantic table headers, escaping, global focus ring); did not stand up the tooling (out of scope; noted as a follow-up).

🤖 Generated with Claude Code

https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP

… "Access" dashboard tab (JEF-490)

Consumes the JEF-488 AuditSink seam. Two halves:

HALF A — a DURABLE `AccessAuditSink` (a NEW dedicated sink, distinct from the
DecisionJournal): an append-only, size-rotated subject·entry·tool·tier·time
record on its OWN file on the journal PVC (`PROTECTOR_MCP_AUDIT_PATH`) plus a
bounded in-memory ring replayed on boot. Reuses the decision journal's
append/rotate/graceful-degrade PATTERN; unset/unwritable ⇒ in-memory only (no
crash). Wired in run_loop as the sink the MCP server uses, shared with the
dashboard. Only forensic/raw disclosures are recorded (the dispatch
`is_disclosure` gate is unchanged). A new read-only GET `/api/access.json`
(no-store, inherits the router-wide OIDC gate — 401 unauthenticated) exposes
the caller's own tier + the audit rows REDACTED TO THE CALLER'S OWN TIER: the
target-class of a raw pull is shown only to a forensic+ viewer, else the
verbatim JEF-488 workload-identity sentinel. Reuses the render.rs sentinel and
the redact tier ladder — one vocabulary across tool + screen.

HALF B — a sixth "Access" Preact tab: Section 1 "your access" (a tier chip —
word+glyph+colour, glyph aria-hidden, raw = the loud posture colour — over a
cov-rows list of what each tier reveals/withholds); Section 2 "forensic & raw
pulls" (a real semantic <table>, newest-first, raw rows carry the loud keyline,
identity/target are auto-escaped JSX children). The empty state honestly
distinguishes least-privilege-working from the log-reset fact (the "resets on
restart" sub-line shows only when the sink is in-memory).

Tests: durable round-trip + in-memory + unwritable-degrade for the sink;
dispatch records exactly one line for a raw pull and none for redacted;
view_model tier-aware redaction (redacted viewer sees the sentinel, forensic+
sees the target); HTTP 401/no-store + end-to-end tier redaction via signed
tokens; serialize contract for the wire tags; Preact tests for the chip,
table, escaping, and empty states. Split run_loop's test block into
run_loop/tests.rs to stay under the 1,000-line cap.

Also wires `PROTECTOR_MCP_AUDIT_PATH` onto the existing journal PVC in the
protector chart (durable-on-PVC by default).

Closes JEF-490

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP
@thejefflarson
thejefflarson enabled auto-merge (squash) July 23, 2026 06:38
@thejefflarson
thejefflarson merged commit d318c94 into main Jul 23, 2026
6 checks passed
@thejefflarson
thejefflarson deleted the thejefflarson/jef-490-forensicraw-mcp-access-audit-sink-access-dashboard-tab-tier branch July 23, 2026 06:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant