## Summary - add a read-first governance inbox page at `/admin/governance/inbox` - aggregate assigned findings, intake, stale operations, alert-delivery failures, and review follow-up into one canonical routing surface - add focused coverage for inbox authorization, navigation context, page behavior, and section builder logic - include the Spec Kit artifacts for spec 250 ## Notes - branch is synced with `dev` - this PR supersedes #290 for the governance inbox work Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #291
27 KiB
Implementation Plan: Decision-Based Governance Inbox v1
Branch: 250-decision-governance-inbox | Date: 2026-04-28 | Spec: spec.md
Input: Feature specification from 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.phpfor assigned-findings truth, urgency ordering, and workspace-shell tenant-prefilter behavior.apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.phpfor intake truth,Needs triagesemantics, and read-first queue behavior.apps/platform/app/Filament/Pages/Monitoring/Operations.phpplusapps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.phpfor 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, andapps/platform/app/Services/PortfolioTriage/TenantTriageReviewService.phpfor 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, andapps/platform/app/Models/TenantTriageReview.phpfor the source data contracts.apps/platform/app/Support/Navigation/CanonicalNavigationContext.php,apps/platform/app/Support/Navigation/RelatedNavigationResolver.php, andapps/platform/app/Support/OperationRunLinks.phpfor source-page routing and return-link continuity.apps/platform/app/Support/OperateHub/OperateHubShell.php,apps/platform/app/Support/Filament/CanonicalAdminTenantFilterState.php, andapps/platform/app/Support/Filament/TablePaginationProfiles.phpfor 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, andapps/platform/app/Support/Rbac/UiTooltips.phpfor 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 underapps/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
Pagein 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 searchableResource. 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 enforceCapabilities::TENANT_FINDINGS_ASSIGNorCapabilities::TENANT_FINDINGS_TRIAGEon 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 requireCapabilities::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
MyFindingsInboxandFindingsIntakeQueuerather 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
TenantTriageReviewstate 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:
OperationRunLinksand 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
404boundary; explicit out-of-scope tenant filters remain404; broad listings omit hidden rows. - RBAC-UX plane separation: PASS. Everything stays inside the admin
/adminplane. - 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
UnitandFeaturelanes 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:assetsstep.
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, data-model.md, quickstart.md, 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.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Governance/GovernanceInboxPageTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Governance/GovernanceInboxAuthorizationTest.phpexport 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-shellcoverage 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-featurefor contained assembly-seam notes;reject-or-splitif 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)
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)
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
MyFindingsInboxandFindingsIntakeQueue - 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
TenantTriageReviewfollow-up truth for review-family attention - rely on
CanonicalNavigationContextandOperationRunLinksfor drill-through continuity
Output: 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:
Phase 2 — Planning (for tasks.md)
Dependency-ordered implementation outline for the later tasks.md step:
- Add the native governance inbox page shell and read-only view in the admin plane.
- Resolve the bounded section assembly seam, preferring reuse of source-page query rules over a new workflow subsystem.
- Add family sections for assigned findings, intake, stale operations, alert-delivery failures, and triage follow-up.
- Reuse
CanonicalNavigationContext,RelatedNavigationResolver, andOperationRunLinksfor every drill-through path. - Add tenant and family filter state with honest empty-state behavior and
404handling for explicit out-of-scope tenant targeting. - 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:assetsonly matters if future registered assets are introduced. - Shared seam outcome:
document-in-feature. A bounded derived helper was required asapps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.phpbecause 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
MyFindingsInboxand tenant finding detail, intake routes toFindingsIntakeQueueand tenant finding detail, operations route throughOperationRunLinksinto the canonical tenantless monitoring detail, alerts route toAlertDeliveryResourceindex 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 receive403, 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.phppassed with10 passed (53 assertions). - Smoke evidence: integrated-browser smoke on
http://localhost/admin/governance/inboxpassed in an authenticated workspace session. The inbox loaded successfully, the operations-family CTA opened the canonical/admin/operationsroute withproblemClass=terminal_follow_upplus the sharednavpayload, the monitoring page rendered a visibleBack to governance inboxcontrol, 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 agentpassed. - Review outcome class:
acceptable-special-case. - Workflow outcome:
keep. - Exception note: none beyond the bounded section-builder seam already recorded above.