Status: v0.1 (drafted 2026-05-16) — pairs with Surface 7 MUX doc (CXO + Comms lane); CEO ratification of paired-deliverable approach received 2026-05-16 via MUX/UI Round 2 ratification (Architect decision walkthrough Item 2)
Date: 2026-05-16 (v0.1 — Phase 2 of MUX/UI Round 2 implementation; companion to #1095’s Pattern-071 first fix shipped this morning)
Supersedes: None (extends ADR-061 with the complementary READ-side architecture)
Issues: #1095 (transparency auth gates — Pattern-071 first fix; user-binding + admin gates on /api/v1/transparency/*), #1090 (MUX/UI gap — Round 2 synthesis), #1018 (audit envelope write-side; predecessor work)
Related: ADR-061 (LLM-touch boundary enforcement — WRITE-side four-element principle; this ADR is the READ-side companion), Pattern-071 (Audit Logs as Attack Surface, Emerging — #1095 first fix; this ADR codifies the architectural commitments), MUX/UI Round 2 synthesis (Surface 7 as paired ADR + MUX doc deliverables)
Deciders: Chief Architect (drafted); Lead Developer (#1095 implementation already shipped Pattern-071 first fix); CXO (Surface 7 MUX doc paired-lane drafting); CEO ratification of paired-approach via MUX/UI Round 2 (2026-05-16)
ADR-061 (LLM-Touch Boundary Enforcement, Ratified May 3, 2026; v1.1 amendment May 15) established the WRITE-side of the audit envelope architecture: every LLM-touch event records (which surface, raw output size, validation result, action taken) for operator legibility. The fourth element of the four-element principle — audit envelope — addresses that the system records each LLM-touch event with structured fields.
The READ-side was service-only until May 16. services/ethics/audit_transparency.get_user_audit_log (services/ethics/audit_transparency.py:343) provided programmatic access; the user-facing route surface was structurally absent. Per the MUX/UI Round 2 synthesis (May 15):
“This IS the load-bearing architectural piece — without it, ADR-061’s four-element principle is observably 3.5 elements in user-facing terms.”
The audit envelope was being written into a surface no user could reach. Without the read surface, operator legibility is structurally one-sided: the system records what it did, but the user has no path to inspect what was recorded about them.
Lead Developer shipped Pattern-071’s first fix this morning:
services/api/transparency.py: /api/v1/transparency/audit-log/{session_id}, /audit-summary/{session_id}, /stats, /health, /cleanupaudit-log, audit-summary) require the JWT user to own the session per ConversationDB. Admin override available via is_admin claim (SEC-RBAC pattern from files.py)stats, cleanup, health) require is_admin claim. Until SEC-RBAC global-admin lands, no production user has is_admin=True; these endpoints effectively return 403 for every caller — by designSecurityRedactor (services/ethics/audit_transparency.py:33) — email, SSN, phone variants, credit card, plus #1017 additions (API keys, URL-with-credentials)The implementation surfaces the read-side surface; this ADR codifies the architectural commitments that implementation represents and commits to the principle going forward.
Three reasons the implementation-only state is structurally insufficient:
ADR-061’s four-element WRITE-side principle (permissive input / schema validation / safe-fallback / audit envelope) has a structural complement at the READ side. At user-facing audit envelope read surfaces, four elements must be present at every endpoint:
BaseModel response shape with type guarantees. Consumers (UI, MCP client, programmatic) can rely on the contract; schema drift is caught at the route layer rather than at the consumer.ConversationDB; admin-scoped endpoints require explicit is_admin claim; uniform 403 messaging avoids existence-leak across non-owners. Authorization is route-layer architecture, not service-layer afterthought.The principle’s structural shape mirrors ADR-061’s WRITE-side four elements:
| WRITE-side (ADR-061) | READ-side (this ADR) |
|---|---|
| Permissive input shape | Explicit user-visible field set |
| Schema validation at consumption | Schema validation at request (response model) |
| Safe-fallback path | Safe-fallback for missing/redacted data |
| Audit envelope | Access control bound to JWT |
The audit envelope schema in services/database/models.py (EthicsAuditDB) captures many fields. Read-surface endpoints surface a deliberate subset:
User-visible (surfaced via audit-log endpoint):
session_id (user already knows their session)event_type (what kind of decision — ethics-decline, output-filter, etc.)boundary_category (which boundary triggered — PROFESSIONAL, PERSONAL, DATA_PRIVACY, HARASSMENT, INAPPROPRIATE_CONTENT)action_taken (the action class — DECLINE, REDACT, ALLOW)timestamp (when the decision was made)severity (CRITICAL / IMPORTANT / INFORMATIONAL)Internal-only (NOT surfaced at user-facing endpoints):
Admin-only (surfaced via audit-summary endpoint for admins, summarized form only):
The split is codified at the response model layer (AuditLogResponse, AuditSummaryResponse, TransparencyStatsResponse in services/api/transparency.py). Future fields added to the WRITE-side envelope require explicit decision on which bucket they belong to — defaulting to internal-only unless a deliberate decision surfaces them.
The user-binding rule shipped in #1095 represents three architectural commitments worth codifying:
Commitment 1 — Path-Parameter Authorization at the Route Boundary
When a route accepts session_id (or any resource ID) as a path parameter, the route MUST validate the JWT-authenticated user has authorization to access that resource BEFORE proceeding to service-layer logic. The validation lives at the route layer because:
_require_session_owner_or_admin) makes the rule auditableCommitment 2 — Admin Capability Is Explicit Claim, Not Heuristic
Admin-scoped endpoints check current_user.is_admin (a JWT claim) — not a role lookup, not a username check, not a path prefix convention. The claim is set by the auth layer; routes consume it as a boolean. Future admin endpoints follow the same pattern. Until SEC-RBAC global-admin lands, the claim is structurally always False in production — admin endpoints exist but return 403 for every caller. This is by design — the architecture is forward-compatible with SEC-RBAC; the production state during SEC-RBAC’s pendency is uniformly 403.
Commitment 3 — Uniform 403 Messaging (Pattern-071 Defensive Posture)
Unauthorized access returns the same 403 regardless of whether the resource doesn’t exist or the user lacks permission. This avoids the existence-leak attack surface Pattern-071 names — a non-owner cannot probe for valid session IDs by observing 404 vs. 403 responses.
Future audit-envelope read endpoints follow these conventions:
/api/v1/transparency/* (already established; /api/v1/ per project API conventions)/audit-{resource}/{session_id} — JWT user must own session per ConversationDB; admin override via is_admin claim/{resource} (no path-param resource ID; e.g., /stats, /cleanup, /health) — requires is_admin=True claimBaseModel response shapes with entries, total_entries, session_id, request_limit, timestamp (or analogous) — never Dict opaquelySecurityRedactor before return; redacted fields surface as <REDACTED-{type}> markers, not silently droppedAPIError per services/api/errors.py patterns; uniform 403 for unauthorizedThis ADR commits to architectural shape; it does NOT commit to:
BaseModel; some endpoints have very thin response shapes. Acceptable cost for schema-at-request clarity.The user-binding rule, admin-claim pattern, and uniform 403 messaging are already in production as of #1095 (Lead Dev commit 0161f089, May 16 AM). This ADR codifies what #1095 implemented as architectural commitment — implementation precedes ratification by hours, which is acceptable for codifying Pattern-071 first-fix shape (the rule shipped because it was urgent; the ADR captures it now while the shape is fresh).
| Element | Implementation surface | Verification |
|---|---|---|
| Explicit user-visible field set | AuditLogResponse, AuditSummaryResponse BaseModels in services/api/transparency.py:80-102 |
grep for response model definitions; each endpoint returns typed BaseModel |
| Schema validation at request | @transparency_router.get decorators with typed current_user: JWTClaims dependency + typed response return |
FastAPI route signature is the contract; mypy/pylint surface drift |
| Safe-fallback for missing/redacted | get_user_audit_log returns [] on error (services/ethics/audit_transparency.py:380); SecurityRedactor applies before return |
Test coverage in #1018 audit-write integration tests; redaction patterns at audit_transparency.py:33 |
| Access control JWT-bound | _require_session_owner_or_admin at services/api/transparency.py and current_user: JWTClaims = Depends(get_current_user) on all routes |
#1095 PR review + manual test of 403 cases |
Pattern-071 (Audit Logs as Attack Surface) was filed Emerging via #1017. #1095 was the first fix (Lead Dev’s transparency auth gates). This ADR’s commitments establish what future Pattern-071 fixes look like — second fix would extend the existing user-binding / admin-claim / uniform-403 shape to a new endpoint. When Pattern-071 reaches third fix consistent with this shape, the pattern reaches Proven status per the standard methodology-29 promotion trigger.
Per Round 2 synthesis, Surface 7 is “Full MUX doc + separate ADR-NN companion” with CXO + Comms drafting the MUX doc in parallel. This ADR is the architectural-commitment half; the MUX doc is the experience-design half. Phase 2 build implements both. Architectural commitments shape the user-visible surface; voice register, recovery affordances, and visual hierarchy shape the experience of consuming it.
docs/internal/architecture/current/adrs/adr-061-llm-touch-boundary-enforcement.mddocs/internal/architecture/current/patterns/pattern-071-audit-logs-as-attack-surface.mdmailboxes/{cohort}/inbox/mux-ui-gap-cxo-round-2-synthesis-2026-05-15.mdmailboxes/arch/sent/memo-arch-to-cxo-lead-comms-ppm-cc-ceo-pa-exec-mux-ui-round-2-ceo-ratification-2026-05-16.md0161f089 + close-out cc22560dservices/api/transparency.py (356 LOC; 5 endpoints)services/ethics/audit_transparency.py:343 (get_user_audit_log); :33 (SecurityRedactor); :411 (cleanup_old_entries)services/api/transparency.py:80-102 (AuditLogResponse, AuditSummaryResponse, TransparencyStatsResponse)/cleanup endpoint exists but policy is not codified— Chief Architect, 2026-05-16 v0.1 (READ-side companion to ADR-061; codifies #1095’s Pattern-071 first fix + future-direction commitments per MUX/UI Round 2 ratification)