# Implementation Plan: Decision-Based Governance Inbox v1 **Branch**: `250-decision-governance-inbox` | **Date**: 2026-04-28 | **Spec**: [spec.md](spec.md) **Input**: Feature specification from [spec.md](spec.md) **Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts. ## Summary Introduce one canonical workspace governance inbox inside the existing `/admin` plane by adding a native Filament v5 read-only page that composes existing findings, alerts, stale-operations, and portfolio-triage signals into one decision-first work surface. The page should answer the first operator question quickly, then route into the existing source pages for execution and proof instead of creating a new cross-domain task engine. This slice is explicitly composition-only. It does not replace `My Findings`, `Findings intake`, `Operations`, `Alerts`, or review-triage detail surfaces; it does not add acknowledge, snooze, claim, or assignment mutations; and it does not create persistence. Livewire remains v4 under Filament v5, panel-provider registration stays in `apps/platform/bootstrap/providers.php`, no new globally searchable resource is introduced, and no new asset bundle is expected for v1. ## Technical Context **Language/Version**: PHP 8.4, Laravel 12 **Primary Dependencies**: Filament v5, Livewire v4, Pest v4, existing findings, alerts, operations, and review-triage services **Storage**: PostgreSQL via existing `findings`, `operation_runs`, `alert_deliveries`, `tenant_reviews`, and `tenant_triage_reviews`; no new persistence planned **Testing**: Pest v4 unit plus feature coverage **Validation Lanes**: fast-feedback, confidence **Target Platform**: Laravel monolith in `apps/platform` running via Sail, with existing `/admin` and tenant-scoped `/admin/t/{tenant}` surfaces **Project Type**: Web application (Laravel monolith with Filament panels) **Performance Goals**: page render remains DB-only and workspace-scoped; no Graph calls, no queue starts, and no remote work on render; family previews should be fetched through bounded derived queries rather than one polymorphic persistence layer **Constraints**: preserve deny-as-not-found workspace and tenant isolation; keep the first slice in the existing admin plane; avoid new persistence, new workflow states, new task engines, and page-local mutation semantics; reuse source-page routing and action hierarchies **Scale/Scope**: 1 new admin page, 5 derived source families, 0 new runtime entities, and 1 bounded derived section assembly seam ## Likely Affected Repo Surfaces - `apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php` for assigned-findings truth, urgency ordering, and workspace-shell tenant-prefilter behavior. - `apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php` for intake truth, `Needs triage` semantics, and read-first queue behavior. - `apps/platform/app/Filament/Pages/Monitoring/Operations.php` plus `apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php` for stale or terminal-follow-up operation attention and canonical run drill-through. - `apps/platform/app/Filament/Pages/Monitoring/Alerts.php`, `apps/platform/app/Filament/Resources/AlertDeliveryResource.php`, and the existing alerts cluster for alert-family entry points and delivery-failure truth. - `apps/platform/app/Filament/Resources/TenantReviewResource.php`, `apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php`, and `apps/platform/app/Services/PortfolioTriage/TenantTriageReviewService.php` for review follow-up and triage-state truth. - `apps/platform/app/Models/Finding.php`, `apps/platform/app/Models/OperationRun.php`, `apps/platform/app/Models/AlertDelivery.php`, `apps/platform/app/Models/TenantReview.php`, and `apps/platform/app/Models/TenantTriageReview.php` for the source data contracts. - `apps/platform/app/Support/Navigation/CanonicalNavigationContext.php`, `apps/platform/app/Support/Navigation/RelatedNavigationResolver.php`, and `apps/platform/app/Support/OperationRunLinks.php` for source-page routing and return-link continuity. - `apps/platform/app/Support/OperateHub/OperateHubShell.php`, `apps/platform/app/Support/Filament/CanonicalAdminTenantFilterState.php`, and `apps/platform/app/Support/Filament/TablePaginationProfiles.php` for workspace scope and durable filter state. - `apps/platform/app/Support/Badges/BadgeRenderer.php`, `apps/platform/app/Support/Ui/ActionSurface/ActionSurfaceDeclaration.php`, `apps/platform/app/Support/Rbac/UiEnforcement.php`, and `apps/platform/app/Support/Rbac/UiTooltips.php` for existing status, action, and capability affordance patterns. - Likely new implementation files if code work later proceeds: `apps/platform/app/Filament/Pages/Governance/GovernanceInbox.php`, `apps/platform/resources/views/filament/pages/governance/governance-inbox.blade.php`, and a bounded support namespace under `apps/platform/app/Support/GovernanceInbox/` only if the page cannot stay readable with page-local composition. ## UI / Filament & Livewire Fit - Implement as a native Filament v5 `Page` in the existing admin plane, not as a new Resource, custom SPA shell, or second monitoring console. - Keep the inbox read-first and section-based. Each visible family should render one calm summary block plus bounded preview entries and one dominant CTA into the existing source surface. - Do not model the inbox as a polymorphic table over mixed Eloquent records if that forces a new persisted or generic task abstraction. Section composition over existing family queries is the preferred v1 shape. - Livewire v4 hydration must preserve tenant and family filter state through public, query-backed, or session-backed state. Do not rely on private properties for any state that must survive a Livewire interaction. - The new surface is a `Page`, not a globally searchable `Resource`. Existing source resources retain their current search posture. ## RBAC / Policy Fit - Workspace membership remains the first gate. The inbox should not render at all for non-members, and explicit out-of-scope tenant targeting must stay `404`. - Page access stays capability-derived: the actor must be a workspace member and have visibility to at least one family through the same capability contract the source page already uses. In-scope workspace members who lack every qualifying family capability should receive `403`, not a silent empty shell. - Findings families reuse tenant capability checks such as `Capabilities::TENANT_FINDINGS_VIEW`, while source mutations like claim or triage continue to enforce `Capabilities::TENANT_FINDINGS_ASSIGN` or `Capabilities::TENANT_FINDINGS_TRIAGE` on their existing surfaces. - Review follow-up entries reuse `Capabilities::TENANT_REVIEW_VIEW`; any manual follow-up mutation remains on the existing review/triage seam and continues to require `Capabilities::TENANT_TRIAGE_REVIEW_MANAGE`. - Alert-family visibility remains workspace-scoped through `Capabilities::ALERTS_VIEW`. - Operations entries must only appear when the underlying run destination would already be visible through the existing operation-viewer and tenant-entitlement rules. The inbox must not invent a weaker path. ## Audit / Logging Fit - The inbox is read-only and should not create a new page-view audit stream. - Existing mutation or download actions continue to log on their existing source surfaces. - The only acceptable additional audit work in v1 would be reuse of existing action IDs on underlying source pages if implementation discovers a missing drill-through event, but the inbox itself should not become a new audit-heavy surface. ## Data & Query Fit - Prefer derived section queries over a generic inbox-item projector or persisted cache. - The findings sections should reuse the same inclusion and urgency rules already owned by `MyFindingsInbox` and `FindingsIntakeQueue` rather than duplicating lifecycle logic with new constants. - The operations section should reuse the same stale or terminal-follow-up classification that already drives the canonical Operations page. Section-level operations CTAs may land on `/admin/operations`, but entry-level operation drill-through should land on the canonical run detail route `/admin/operations/{run}`. - The alert section should derive from alert-delivery failure truth and the alerts overview, not from alert-rule configuration state. - The review-follow-up section should derive from `TenantTriageReview` state and existing review register truth, not from a new parallel follow-up model. - If implementation needs one bounded derived assembly seam, it should remain a page-scoped support helper that normalizes sections and preview entries only. ## UI / Surface Guardrail Plan - **Guardrail scope**: changed surfaces - **Native vs custom classification summary**: native Filament - **Shared-family relevance**: governance queues, monitoring drill-through, navigation continuity, badge/status reuse - **State layers in scope**: page, URL-query - **Audience modes in scope**: operator-MSP - **Decision/diagnostic/raw hierarchy plan**: decision-first, diagnostics-second, support-raw-third on source pages only - **Raw/support gating plan**: hidden by default on the inbox page; source pages keep their existing capability-gated disclosure - **One-primary-action / duplicate-truth control**: each section gets one dominant CTA into an existing source surface; later detail stays off the inbox page - **Handling modes by drift class or surface**: review-mandatory - **Repository-signal treatment**: review-mandatory now; future hard-stop candidate if implementation introduces a generic task model or local mutations - **Special surface test profiles**: global-context-shell - **Required tests or manual smoke**: functional-core, state-contract - **Exception path and spread control**: none planned; any new cross-domain workflow state or local mutation must be treated as exception-required drift - **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes - **Systems touched**: `MyFindingsInbox`, `FindingsIntakeQueue`, `Operations`, `Alerts`, `AlertDeliveryResource`, `TenantReviewResource`, `TenantTriageReviewService`, `CanonicalNavigationContext`, `RelatedNavigationResolver`, `OperateHubShell`, `OperationRunLinks`, `BadgeRenderer`, `UiEnforcement`, and existing source-page action-surface declarations - **Shared abstractions reused**: `CanonicalNavigationContext`, `RelatedNavigationResolver`, `OperateHubShell`, `OperationRunLinks`, `BadgeRenderer`, `UiEnforcement`, `ActionSurfaceDeclaration`, and current source-page query rules - **New abstraction introduced? why?**: one bounded section or entry assembler may be needed to keep the page readable and deterministic across families, but it must remain derived and page-scoped - **Why the existing abstraction was sufficient or insufficient**: existing source pages are sufficient for truth and mutation, but insufficient as the first workspace attention surface because they only answer one family each - **Bounded deviation / spread control**: none planned. If a support namespace is added, it must stay under `Support/GovernanceInbox/`, remain read-only, and not become a cross-product task engine ## OperationRun UX Impact - **Touches OperationRun start/completion/link UX?**: yes, deep-link only - **Central contract reused**: `OperationRunLinks` and the existing tenantless operation viewer - **Delegated UX behaviors**: existing canonical run URL resolution and navigation context only - **Surface-owned behavior kept local**: deciding whether an operation attention entry appears and which existing run destination is primary - **Queued DB-notification policy**: `N/A` - **Terminal notification path**: `N/A` - **Exception path**: none ## Provider Boundary & Portability Fit - **Shared provider/platform boundary touched?**: no - **Provider-owned seams**: `N/A` - **Platform-core seams**: existing governance, alerts, operations, and review vocabulary only - **Neutral platform terms / contracts preserved**: `governance inbox`, `attention`, `operation`, `review follow-up`, `alert delivery failure`, and existing source nouns - **Retained provider-specific semantics and why**: none - **Bounded extraction or follow-up path**: `N/A` ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - Inventory-first / snapshot truth: PASS. The inbox consumes existing findings, operations, alerts, and review state only. - Read/write separation: PASS. The page stays read-only and pushes execution back to source surfaces. - Graph contract path: PASS. No new Graph calls or provider contract work is part of this slice. - Deterministic capabilities: PASS. The plan reuses existing capability registries and source-page rules. - Workspace isolation + tenant isolation: PASS. Workspace membership remains a `404` boundary; explicit out-of-scope tenant filters remain `404`; broad listings omit hidden rows. - RBAC-UX plane separation: PASS. Everything stays inside the admin `/admin` plane. - Destructive confirmation standard: PASS by non-use. The inbox introduces no destructive or risky action. - Global search safety: PASS. The new slice is a Page, not a searchable Resource. - OperationRun and Ops-UX: PASS by deep-link-only reuse. The page starts no run and adds no new run UX state. - Data minimization: PASS. Default-visible content stays limited to family, urgency, scope, and next action. - Test governance (TEST-GOV-001): PASS. Planned proof stays in focused `Unit` and `Feature` lanes only. - Proportionality / no premature abstraction: PASS with one bounded exception. If a section assembler is needed, it remains page-scoped and derived. - Persisted truth (PERSIST-001): PASS. No new table, cache, or stored attention entity is planned. - Behavioral state (STATE-001): PASS. The inbox reuses existing source states and does not add a second workflow state family. - Shared pattern first / UI semantics / Filament native UI: PASS. Existing navigation, badge, and queue semantics are reused. - Provider boundary (PROV-001): PASS. The slice stays on already-normalized platform seams. - Filament / Laravel planning contract: PASS. Filament v5 remains on Livewire v4, provider registration remains in `apps/platform/bootstrap/providers.php`, and no new panel is required. - Asset strategy: PASS. No new asset registration is planned; if implementation later registers an asset anyway, deployment keeps the normal `cd apps/platform && php artisan filament:assets` step. **Gate evaluation**: PASS. - The inbox stays inside the existing admin plane and current workspace or tenant membership model. - The page remains a read-only decision hub, not a new execution workflow. - Existing source pages and services are sufficient for v1 if implementation resists introducing a generic inbox state model. **Post-design re-check**: PASS (design artifacts: [research.md](research.md), [data-model.md](data-model.md), [quickstart.md](quickstart.md), [contracts/governance-inbox.openapi.yaml](contracts/governance-inbox.openapi.yaml)). ## Test Governance Check - **Test purpose / classification by changed surface**: Unit for section and preview assembly plus source-link decisions; Feature for page rendering, authorization, filter behavior, and navigation continuity - **Affected validation lanes**: fast-feedback, confidence - **Why this lane mix is the narrowest sufficient proof**: unit coverage proves family assembly without Filament boot cost; feature coverage proves page access, family visibility, tenant-prefilter behavior, and source-page routing on a native page - **Narrowest proving command(s)**: - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/GovernanceInbox/GovernanceInboxSectionBuilderTest.php` - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Governance/GovernanceInboxPageTest.php` - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Governance/GovernanceInboxAuthorizationTest.php` - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Governance/GovernanceInboxNavigationContextTest.php` - **Fixture / helper / factory / seed / context cost risks**: moderate; reuse findings, operation runs, alert deliveries, reviews, and triage-review fixtures rather than adding browser setup or generic workflow helpers - **Expensive defaults or shared helper growth introduced?**: no; any section assembler must stay cheap by default and avoid eager-loading broad unrelated state - **Heavy-family additions, promotions, or visibility changes**: none - **Surface-class relief / special coverage rule**: `global-context-shell` coverage is required because tenant-prefilter and navigation continuity are part of the page contract - **Closing validation and reviewer handoff**: rerun the four focused commands above, verify the page stays read-only, and verify every CTA lands on an existing source surface with hidden tenants omitted from counts and labels - **Budget / baseline / trend follow-up**: none expected beyond a small feature-local unit plus feature increase - **Review-stop questions**: lane fit, hidden fixture cost, accidental generic workflow helpers, source-page duplication risk - **Escalation path**: `document-in-feature` for contained assembly-seam notes; `reject-or-split` if implementation introduces a generic task model or local mutation lane - **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage - **Why no dedicated follow-up spec is needed**: routine read-surface and navigation upkeep stays inside this feature unless implementation proves a structural need for a broader workflow engine ## Rollout & Risk Controls - Keep the v1 audience anchored to existing workspace operators and tenant-entitled actors only. - Treat the page as a routing surface. Do not add local claim, acknowledge, snooze, or follow-up mutation actions during implementation. - Prefer extending existing source query seams over introducing new persisted or cross-domain workflow state. - Keep navigation labels aligned with the source pages so the inbox reads as an entry surface, not a replacement shell. - Validate the page with focused unit and feature coverage before considering any broader dashboard-entry or widget work. ## Project Structure ### Documentation (this feature) ```text specs/250-decision-governance-inbox/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── governance-inbox.openapi.yaml └── tasks.md # Created later by /speckit.tasks, not by this plan step ``` ### Source Code (repository root) ```text apps/platform/ ├── app/ │ ├── Filament/ │ │ ├── Pages/ │ │ │ ├── Findings/ │ │ │ │ ├── MyFindingsInbox.php │ │ │ │ └── FindingsIntakeQueue.php │ │ │ ├── Governance/ │ │ │ │ └── GovernanceInbox.php # likely new page if implementation proceeds │ │ │ ├── Monitoring/ │ │ │ │ ├── Operations.php │ │ │ │ └── Alerts.php │ │ │ └── Operations/ │ │ │ └── TenantlessOperationRunViewer.php │ │ └── Resources/ │ │ ├── AlertDeliveryResource.php │ │ └── TenantReviewResource.php │ ├── Models/ │ │ ├── Finding.php │ │ ├── OperationRun.php │ │ ├── AlertDelivery.php │ │ ├── TenantReview.php │ │ └── TenantTriageReview.php │ ├── Services/ │ │ ├── PortfolioTriage/TenantTriageReviewService.php │ │ └── TenantReviews/TenantReviewRegisterService.php │ ├── Support/ │ │ ├── Badges/ │ │ ├── Filament/ │ │ ├── GovernanceInbox/ # only if a bounded support seam is required │ │ ├── Navigation/ │ │ ├── OperateHub/ │ │ ├── OperationRunLinks.php │ │ ├── Rbac/ │ │ └── Ui/ActionSurface/ │ └── Policies/ ├── bootstrap/providers.php ├── resources/views/filament/pages/governance/ # likely new page view if implementation proceeds └── tests/ ├── Feature/Governance/ └── Unit/Support/GovernanceInbox/ ``` **Structure Decision**: Laravel monolith. Implementation should stay entirely inside `apps/platform`, add at most one new read-only page and matching view, and reuse existing source-page routing, RBAC, and status semantics rather than creating a separate workflow subsystem. ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | BLOAT-001 - bounded section or entry assembler | one page still needs deterministic cross-family section ordering and source-surface links | inline page composition alone risks duplicated ordering rules and unreadable page code once five families are involved | ## Proportionality Review - **Current operator problem**: operators cannot decide what needs attention first from one workspace surface despite the repo already having real findings, alerts, operations, and review-follow-up truth. - **Existing structure is insufficient because**: current pages answer only one family each and force entity-first reconstruction before the operator can act. - **Narrowest correct implementation**: add one read-only workspace inbox page over existing source-page queries and routing seams, with at most one bounded derived section or entry assembly helper. - **Ownership cost created**: one page, one view, one bounded derived assembly seam, and focused unit plus feature coverage. - **Alternative intentionally rejected**: a persisted inbox-item table or generic task engine was rejected because it adds durable workflow truth before the read-only decision surface is proven. - **Release truth**: current-release workflow compression, not future workboard preparation. ## Phase 0 — Research (output: research.md) Research resolved the remaining implementation-shaping decisions: - choose a section-based composition page over a polymorphic task table or persisted queue - reuse findings queue semantics from `MyFindingsInbox` and `FindingsIntakeQueue` - reuse stale or terminal-follow-up operation semantics from `Operations` - treat alert-delivery failures as the narrow alert-family slice for v1 instead of alert-rule configuration - reuse `TenantTriageReview` follow-up truth for review-family attention - rely on `CanonicalNavigationContext` and `OperationRunLinks` for drill-through continuity **Output**: [research.md](research.md) ## Phase 1 — Design (outputs: data-model.md, contracts/, quickstart.md) Design artifacts capture the narrow implementation shape: - existing persisted truth reused: findings, operation runs, alert deliveries, tenant reviews, and triage reviews - new code-owned truth limited to derived inbox sections and preview entries only - conceptual contract covers one workspace page with optional tenant and family filters plus source-surface links - quickstart documents the intended slice order, validation commands, and read-only posture **Artifacts**: - [data-model.md](data-model.md) - [contracts/governance-inbox.openapi.yaml](contracts/governance-inbox.openapi.yaml) - [quickstart.md](quickstart.md) ## Phase 2 — Planning (for tasks.md) Dependency-ordered implementation outline for the later `tasks.md` step: 1. Add the native governance inbox page shell and read-only view in the admin plane. 2. Resolve the bounded section assembly seam, preferring reuse of source-page query rules over a new workflow subsystem. 3. Add family sections for assigned findings, intake, stale operations, alert-delivery failures, and triage follow-up. 4. Reuse `CanonicalNavigationContext`, `RelatedNavigationResolver`, and `OperationRunLinks` for every drill-through path. 5. Add tenant and family filter state with honest empty-state behavior and `404` handling for explicit out-of-scope tenant targeting. 6. Add focused unit and feature tests only; no browser, queue, or heavy-governance family is expected. ## Guardrail / Exception / Smoke Coverage - Guardrail result: PASS. Filament remains v5 on Livewire v4, panel provider registration stays unchanged in `apps/platform/bootstrap/providers.php`, the slice adds no new globally searchable Resource, no destructive inbox action, and no new registered asset bundle. Deployment asset handling stays unchanged: `cd apps/platform && php artisan filament:assets` only matters if future registered assets are introduced. - Shared seam outcome: `document-in-feature`. A bounded derived helper was required as `apps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.php` because the existing source pages did not expose a reusable cross-family inbox API. The seam stayed page-scoped and read-only; no persisted inbox state or generic workflow engine was introduced. - Source CTA outcome: PASS. Assigned findings route to `MyFindingsInbox` and tenant finding detail, intake routes to `FindingsIntakeQueue` and tenant finding detail, operations route through `OperationRunLinks` into the canonical tenantless monitoring detail, alerts route to `AlertDeliveryResource` index or view, and review follow-up routes into the existing tenant review or customer review surfaces. The inbox page itself remains mutation-free. - Filter and authorization outcome: PASS. Workspace membership remains the first gate, explicit out-of-scope tenant filters still resolve as `404`, in-scope members with no visible families still receive `403`, and tenant or family filters stay query-only and capability-safe. - Validation lane result: `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/GovernanceInbox/GovernanceInboxSectionBuilderTest.php tests/Feature/Governance/GovernanceInboxAuthorizationTest.php tests/Feature/Governance/GovernanceInboxPageTest.php tests/Feature/Governance/GovernanceInboxNavigationContextTest.php` passed with `10 passed (53 assertions)`. - Smoke evidence: integrated-browser smoke on `http://localhost/admin/governance/inbox` passed in an authenticated workspace session. The inbox loaded successfully, the operations-family CTA opened the canonical `/admin/operations` route with `problemClass=terminal_follow_up` plus the shared `nav` payload, the monitoring page rendered a visible `Back to governance inbox` control, and that return link brought the session back to `/admin/governance/inbox`. - Formatting result: `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` passed. - Review outcome class: `acceptable-special-case`. - Workflow outcome: `keep`. - Exception note: none beyond the bounded section-builder seam already recorded above.