TenantAtlas/specs/250-decision-governance-inbox/plan.md
ahmido 72bfb37ba7
Some checks failed
Main Confidence / confidence (push) Failing after 57s
feat: add decision-based governance inbox (#291)
## 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
2026-04-28 10:13:09 +00:00

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.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, 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.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)

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 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

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:

  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.