# Implementation Plan: Audit Log Foundation **Branch**: `134-audit-log-foundation` | **Date**: 2026-03-11 | **Spec**: [spec.md](./spec.md) **Input**: Feature specification from `/specs/134-audit-log-foundation/spec.md` **Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts. ## Summary Turn the existing placeholder Audit Log page into a real workspace-scoped Monitoring surface backed by a normalized, immutable audit event foundation. Extend the existing `audit_logs` persistence model rather than creating a second history table, consolidate the current tenant and workspace audit writers behind one reusable recorder with actor and target snapshot support, centralize event taxonomy and outcome semantics, instrument the highest-value governance and operational workflows at service or job boundaries, and expose a filterable audit list with detail inspection and permission-aware related links at `/admin/audit-log`. ## Technical Context **Language/Version**: PHP 8.4.15 / Laravel 12 **Primary Dependencies**: Filament v5, Livewire v4.0+, Tailwind CSS v4 **Storage**: PostgreSQL via Laravel Sail; existing `audit_logs` table expanded in place; JSON context payload remains application-shaped rather than raw archival payloads **Testing**: Pest v4 feature and unit tests on PHPUnit 12 **Target Platform**: Laravel Sail web application with canonical workspace Monitoring routes under `/admin` and tenant-context navigation under `/admin/t/{tenant}` **Project Type**: Laravel monolith / Filament web application **Performance Goals**: Audit page remains DB-only at render time, default result sets are indexed and reverse-chronological, filter option loading is bounded to current workspace scope, and entry inspection never requires remote calls **Constraints**: Preserve `/admin/audit-log` as the canonical route; keep workspace and tenant isolation semantics intact; no new Microsoft Graph calls; no user-facing edit or delete path for audit events; retain compatibility with existing `audit_logs` readers while migrating to richer actor, target, and outcome semantics **Scale/Scope**: One expanded audit event store, one reusable recorder foundation, one canonical Monitoring page, first-wave instrumentation across baselines, findings, backup/restore, operation outcomes, and selected workspace-admin changes, plus focused regression coverage ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - Inventory-first: PASS — no inventory-vs-snapshot semantics change; the feature introduces a cross-domain evidence layer over existing records. - Read/write separation: PASS — existing write workflows keep their current preview and confirmation behavior where applicable, and this feature adds stronger audit coverage rather than replacing those safeguards. - Graph contract path: PASS — no new Microsoft Graph calls or contracts are introduced. - Deterministic capabilities: PASS — the feature reuses the canonical capability registry, specifically `Capabilities::AUDIT_VIEW`, plus existing target-resource capabilities for related links. - RBAC-UX planes and isolation: PASS — the canonical audit surface remains in the `/admin` workspace plane; no `/system` route is introduced; non-members remain 404; workspace members lacking `audit.view` are 403; target drill-downs preserve their own 404/403 behavior. - Workspace isolation: PASS — audit queries are explicitly bounded by active workspace context before any filter or result shaping. - RBAC-UX destructive confirmation: PASS / N/A — the audit surface itself is read-only and introduces no destructive action. - RBAC-UX global search: PASS — no new globally searchable resource is introduced. - Tenant isolation: PASS — tenant-owned events appear only through workspace-bounded queries plus entitled tenant filtering; tenant context is additive filter state, not a second canonical audit plane. - Run observability: PASS — existing long-running workflows continue to use `OperationRun`; this plan adds high-value audit entries alongside those runs rather than bypassing them. - Ops-UX 3-surface feedback: PASS — operational audit coverage must not add new progress surfaces or custom completion notifications. - Ops-UX lifecycle: PASS — no direct `OperationRun` lifecycle updates are added; operation-related audit writes occur beside existing service-owned transitions. - Ops-UX summary counts: PASS — existing run producers remain the only source of `summary_counts`; audit rows summarize outcomes secondarily. - Ops-UX guards: PASS — existing regression guards remain intact; new tests focus on audit coverage and visibility. - Ops-UX system runs: PASS — initiator-null workflows remain auditable through the audit stream without introducing terminal DB notifications. - Automation: PASS — no new queued orchestration rules are added beyond emitting audit entries at existing service/job boundaries. - Data minimization: PASS — existing `AuditContextSanitizer` is retained and expanded as needed to protect secrets, tokens, and oversized payloads. - Badge semantics (BADGE-001): PASS — audit outcome and actor-kind badges must be added through `BadgeCatalog` / `BadgeRenderer`, not page-local mappings. - UI naming (UI-NAMING-001): PASS — event summaries, list labels, and detail copy use domain-first `Verb + Object` vocabulary and keep raw event keys secondary. - Filament UI Action Surface Contract: PASS — the custom Monitoring page gains list filtering and inspection only; no bulk or destructive mutations are introduced. - Filament UI UX-001: PASS — the audit page remains a structured Monitoring work surface with search, sort, filters, explicit empty state, and readable detail inspection rather than raw JSON-first presentation. - Filament v5 / Livewire v4 compliance: PASS — the design stays inside the existing Filament v5 / Livewire v4 application. - Provider registration (`bootstrap/providers.php`): PASS — no new panel provider is introduced; the existing admin panel provider remains registered in `bootstrap/providers.php`. - Global search resource rule: PASS — no new Resource is made globally searchable. - Asset strategy: PASS — no heavy new assets are required; existing deploy-time `php artisan filament:assets` behavior remains sufficient. ## Project Structure ### Documentation (this feature) ```text specs/134-audit-log-foundation/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── audit-log-review.openapi.yaml ├── checklists/ │ └── requirements.md └── tasks.md ``` ### Source Code (repository root) ```text app/ ├── Filament/ │ └── Pages/ │ └── Monitoring/ │ └── AuditLog.php # MODIFY — replace placeholder with real table/inspection page ├── Models/ │ ├── AuditLog.php # MODIFY — richer casts/relations/immutability semantics │ ├── BaselineProfile.php # reference — covered target domain │ ├── BackupSet.php # reference — covered target domain │ ├── Finding.php # reference — covered target domain │ ├── OperationRun.php # reference — related operational context │ └── RestoreRun.php # reference — covered target domain ├── Services/ │ ├── Audit/ │ │ ├── WorkspaceAuditLogger.php # MODIFY or adapt into shared recorder facade │ │ ├── ... # NEW shared actor/target/context recorder helpers │ ├── Intune/ │ │ └── AuditLogger.php # MODIFY or deprecate behind shared recorder │ ├── Findings/ │ │ └── FindingWorkflowService.php # MODIFY — normalize event writes │ ├── Settings/ │ │ └── SettingsWriter.php # MODIFY — adopt richer target/context semantics │ ├── Auth/ │ │ └── WorkspaceMembershipManager.php # MODIFY — preserve admin-change coverage in shared taxonomy │ └── SystemConsole/ │ └── SystemConsoleAuditLogger.php # MODIFY — compatibility with shared recorder and actor kinds ├── Support/ │ ├── Audit/ │ │ ├── AuditActionId.php # MODIFY — expand or supersede taxonomy registry │ │ ├── AuditContextSanitizer.php # MODIFY — retain safe redaction boundary │ │ └── ... # NEW audit actor/target/outcome enums or value objects │ ├── Badges/ │ │ ├── BadgeCatalog.php # MODIFY — add audit outcome/actor-kind badge domains if needed │ │ └── BadgeDomain.php # MODIFY — add audit badge domains if needed │ ├── Filament/ │ │ ├── FilterOptionCatalog.php # MODIFY — audit filter options if centralized here │ │ └── FilterPresets.php # reference — date-range filter pattern │ └── Navigation/ │ └── RelatedNavigationResolver.php # reference — permission-aware drill-down links database/ ├── migrations/ │ └── ... # NEW migration(s) evolving `audit_logs` shape and indexes resources/ └── views/ └── filament/ └── pages/ └── monitoring/ └── audit-log.blade.php # MODIFY if custom page shell remains tests/ ├── Feature/ │ ├── Filament/ │ │ └── AuditLog*Test.php # NEW canonical audit page access/filter/detail tests │ └── Monitoring/ │ └── AuditCoverage*Test.php # NEW workflow-to-audit coverage tests where feature-level fit is better └── Unit/ └── Audit/ └── *Test.php # NEW recorder, taxonomy, redaction, actor/target snapshot tests ``` **Structure Decision**: Keep the feature inside the existing Laravel/Filament monolith and evolve the existing `audit_logs` subsystem in place. The implementation centers on one shared audit recorder foundation, one canonical Monitoring page at `/admin/audit-log`, targeted migrations for richer event semantics and indexing, and focused Pest coverage rather than introducing a separate reporting service or external audit pipeline. ## Complexity Tracking > No Constitution Check violations. No justifications needed. | Violation | Why Needed | Simpler Alternative Rejected Because | | — | — | — | ## Phase 0 — Research (DONE) Output: - `specs/134-audit-log-foundation/research.md` Key findings captured: - The repo already has an `AuditLog` model, a legacy tenant-scoped `App\Services\Intune\AuditLogger`, and a newer `App\Services\Audit\WorkspaceAuditLogger`, so the feature should consolidate and expand an existing foundation rather than create a parallel one. - Current `audit_logs` persistence is too narrow for the new spec: it stores `action`, `status`, `resource_type`, `resource_id`, actor email or name, `metadata`, and `recorded_at`, but lacks first-class actor type, target label snapshots, normalized outcome semantics, and strong canonical indexing for the planned filters. - The codebase already has early taxonomy centralization in `App\Support\Audit\AuditActionId`, but many covered workflows still use free-form action strings such as `finding.triaged`, so event naming needs one authoritative registry. - The existing placeholder Monitoring page at `/admin/audit-log` can stay in place as the canonical route, while existing table patterns from `OperationRunResource` and `AlertDeliveryResource` provide the best reusable filter, date-range, and drill-down conventions. - `AuditContextSanitizer` already provides a redaction boundary via `SecretClassificationService`; the new design should reuse and tighten that boundary rather than invent a second redaction path. - Current commands such as `tenantpilot:purge-nonpersistent` still delete `audit_logs`, which conflicts with the new explicit retention posture and must be corrected in implementation. ## Phase 1 — Design & Contracts (DONE) Outputs: - `specs/134-audit-log-foundation/data-model.md` - `specs/134-audit-log-foundation/contracts/audit-log-review.openapi.yaml` - `specs/134-audit-log-foundation/quickstart.md` Design highlights: - Extend `audit_logs` in place with richer actor, target, summary, outcome, context, and relation semantics while preserving compatibility for existing readers during migration. - Consolidate `AuditLogger`, `WorkspaceAuditLogger`, and system-console wrappers behind one shared audit recorder foundation with actor and target snapshot helpers and a single event taxonomy registry. - Keep `/admin/audit-log` as the canonical workspace Monitoring surface, with tenant context expressed only as a default filter and never as a second route or second audit model. - Instrument high-value domain boundaries at service and job edges, especially findings workflow, baseline capture or compare, backup and restore flows, operation outcomes, and workspace-admin changes. - Make the audit UI summary-first with bounded filters, permission-aware related links, and an inspection surface that keeps raw structured payload secondary. ## Phase 1 — Agent Context Update (DONE) Run: - `.specify/scripts/bash/update-agent-context.sh copilot` ## Phase 2 — Implementation Outline (tasks created in `/speckit.tasks`) ### Step 1 — Normalize the audit event contract and retention posture Goal: implement FR-134-01 through FR-134-07, FR-134-19 through FR-134-22, and the initial rollout constraints. Changes: - Define the canonical audit taxonomy and outcome semantics in a single shared registry, expanding or superseding `AuditActionId` so covered workflows stop inventing ad hoc event names. - Define actor-kind and target-kind semantics, including human, system, scheduler, and optional integration actor classes. - Make the retention stance explicit in implementation by stopping short-lived purge flows from casually deleting audit history and documenting the v1 “keep until policy is defined” posture. Tests: - Add unit tests for event-type normalization, outcome mapping, and redaction boundaries. - Add regression coverage proving non-persistent purge flows no longer remove durable audit entries. ### Step 2 — Evolve `audit_logs` into the first-class event store Goal: implement FR-134-02 through FR-134-06, FR-134-17, and DR1 through DR6. Changes: - Add the missing audit semantics to `audit_logs` through additive migrations and compatibility-safe backfills: richer summary field, outcome, actor type, actor label snapshot, target label snapshot, optional operation or domain relation shortcuts, and stronger workspace or tenant or event indexes. - Update `App\Models\AuditLog` with explicit casts, relations, and helper accessors for the new semantics. - Preserve intelligibility for existing rows with fallback mapping or backfill rules rather than treating old rows as unreadable. - Enforce immutable-by-default application behavior by keeping the model append-oriented and by ensuring no user-facing edit or delete flows exist. Tests: - Add migration tests or schema assertions for new indexes and nullable-scope rules. - Add unit tests for label snapshot fallback and old-row compatibility. ### Step 3 — Consolidate audit writing into one reusable recorder foundation Goal: implement FR-134-18 and support all event-writing coverage tasks. Changes: - Introduce one shared recorder with actor, target, summary, and context composition helpers. - Adapt `Intune\AuditLogger`, `WorkspaceAuditLogger`, and `SystemConsoleAuditLogger` into wrappers or compatibility layers over the shared recorder so current call sites can migrate incrementally. - Centralize redaction and payload shaping through `AuditContextSanitizer` and explicit context schemas. Tests: - Add unit tests for actor resolution, target resolution, target label snapshots, context shaping, and immutable write behavior. - Add regression tests ensuring wrappers still produce valid first-class audit rows. ### Step 4 — Instrument first-wave governance and admin event sources Goal: implement FR-134-08 through FR-134-10 plus the governance and administration acceptance criteria. Changes: - Normalize findings workflow events in `FindingWorkflowService` so assignment, status changes, reopen, resolve, close, and risk-acceptance actions use the shared taxonomy and richer actor or target context. - Expand baseline-related writes so baseline profile creation, update, status change, archive, capture start or completion or failure, and compare start or completion or failure use the shared recorder and target snapshots. - Keep existing workspace-admin writes from `WorkspaceMembershipManager`, `SettingsWriter`, onboarding flows, verification acknowledgements, and alert configuration changes aligned to the same taxonomy and summary format. Tests: - Add focused feature and unit tests for findings and baseline audit coverage. - Add positive and negative authorization coverage for workspace-admin changes that surface in the audit log. ### Step 5 — Instrument backup, restore, and operations outcomes Goal: implement FR-134-11 and FR-134-12 while respecting existing Ops-UX rules. Changes: - Normalize backup set creation, update, archive, and retention-related audit coverage explicitly rather than treating them as generic backup workflow side effects. - Normalize restore initiation, completion, failure, and partial outcomes from existing restore services and jobs. - Add high-value operation completion, failure, and retry or rerun audit coverage without changing `OperationRun` ownership or feedback surfaces. - Preserve the existing Ops-UX contract explicitly: queued feedback stays presenter-owned via `OperationUxPresenter`, terminal notifications remain initiator-only `OperationRunCompleted`, and `OperationRun.status` / `OperationRun.outcome` transitions continue to flow only through `OperationRunService`. Tests: - Add focused workflow tests for backup and restore audit coverage, especially partial or failed outcomes. - Add regression tests proving operation-run notifications and lifecycle ownership remain unchanged while audit entries are added, including guards for queued-toast usage, terminal notification exactness, and no direct job-level DB notifications or status/outcome transitions outside `OperationRunService`. ### Step 6 — Replace the placeholder Monitoring page with a real audit work surface Goal: implement FR-134-13 through FR-134-16, FR-134-21, FR-134-24, UX1 through UX7, and IA1 through IA4. Changes: - Keep `App\Filament\Pages\Monitoring\AuditLog` as the canonical route owner and implement it as a workspace-scoped table surface with reverse-chronological sorting, search, core filters, and a clear empty state. - Reuse `FilterPresets`, `FilterOptionCatalog`, `OperateHubShell`, and related-navigation helpers for tenant default filtering, date-range filters, and permission-aware drill-down links. - Add a detail inspection surface that emphasizes summary, actor, target, outcome, context, and only then raw identifiers or raw context. - Add centralized badge semantics for audit outcomes and actor kinds. - Enforce the custom-page Action Surface Contract and UX-001 expectations explicitly, including a stable inspection affordance, no unsupported bulk actions, and an empty state with exactly one clear CTA. - Keep the audit page DB-only at render time by ensuring filter loading and detail inspection rely only on application data already stored in PostgreSQL and never trigger remote calls. Tests: - Add feature tests for authorized access, 404 non-member denial, 403 missing-capability denial, filter behavior, empty states, and permission-aware related links. - Add regression tests proving the old placeholder content is gone, raw JSON is not the primary UI, the Action Surface Contract stays satisfied for the custom Monitoring page, and render-time inspection remains DB-only with no external calls. ### Step 7 — Verify compatibility and complete rollout hardening Goal: finish AC1 through AC10 and protect future expansion. Changes: - Audit existing consumers such as system-console access logs and preserve compatibility or explicitly scope them out of the workspace audit page while still reading from the same event store. - Review all existing direct `AuditLog::query()` readers and adapt them to the richer schema where needed. - Confirm deployment assumptions: no new panel provider, no heavy assets, and no deviation from current `php artisan filament:assets` deployment step. Tests: - Run focused Pest suites for audit model, recorder, Filament page access and filters, and first-wave workflow instrumentation. - Run Pint on dirty files during implementation. ## Constitution Check (Post-Design) Re-check result: PASS. - Livewire v4.0+ compliance: preserved because the design remains inside the existing Filament v5 and Livewire v4 application. - Provider registration location: unchanged; no new panel provider is introduced, and the current panel remains registered in `bootstrap/providers.php`. - Globally searchable resources: unchanged; no new globally searchable Resource is added. - Destructive actions: unchanged on the audit page; it remains read-only with inspection and related-link affordances only. - Asset strategy: unchanged; no heavy or shared asset registration is required, and current deployment behavior including `php artisan filament:assets` remains sufficient. - Testing plan: add unit coverage for recorder, taxonomy, actor or target snapshots, and redaction; add feature coverage for audit page access, filters, details, empty states, and permission-aware links; add workflow coverage for findings, baselines, backup or restore, operation outcomes, and workspace-admin event emission.