TenantAtlas/specs/277-stored-reports-surface/plan.md
ahmido c44f683aa6 277-stored-reports-surface → platform-dev (#333)
Auto-created PR: committing all local changes and pushing branch `277-stored-reports-surface` to remote.

Please review and adjust the title/description as needed.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #333
2026-05-06 00:04:53 +00:00

23 KiB
Raw Blame History

Implementation Plan: Stored Reports Surface v1

Branch: 277-stored-reports-surface | Date: 2026-05-06 | Spec: spec.md Input: Feature specification from specs/277-stored-reports-surface/spec.md

Summary

Prepare one bounded tenant-scoped browse and inspect surface over the repos existing StoredReport truth. The narrow implementation path is to add one read-only Filament resource with a register and a view page under the tenant panel, reuse ArtifactTruthPresenter::forStoredReport() for current versus historical retained lifecycle truth, introduce one explicit permission_posture.view capability alongside the existing entra_roles.view gate, and point the current admin-roles widget at the canonical stored-report detail route.

This slice stays explicitly narrow. Filament remains v5 on Livewire v4, provider registration remains unchanged in apps/platform/bootstrap/providers.php, the stored-report resource stays out of global search in v1, no new asset registration is expected, and no new report engine, analytics console, cross-tenant hub, raw-download surface, or lifecycle-taxonomy rewrite is planned.

Inherited Baseline / Explicit Delta

Inherited baseline

  • StoredReport already exists as tenant-owned persisted truth with workspace_id, tenant_id, report_type, payload, fingerprint, and previous-fingerprint lineage.
  • ArtifactTruthPresenter::forStoredReport() already derives the retained lifecycle contract for stored reports and classifies each record as current or historical.
  • EntraAdminRolesReportService and PermissionPostureFindingGenerator already produce the only two repo-real stored-report families in scope for v1.
  • Evidence-source providers already anchor permission-posture and Entra-admin-roles evidence items to StoredReport identity via source_record_type and source_record_id.
  • AdminRolesSummaryWidget already resolves the latest Entra admin-roles report for the active tenant, but today it leaves viewReportUrl unset and therefore lacks a first-class drilldown.
  • There is no current first-class Filament stored-report register or detail surface in the tenant panel.

Explicit delta in this plan

  • Add one tenant-scoped, read-only stored-reports register at /admin/t/{tenant}/stored-reports.
  • Add one tenant-scoped, read-only stored-report detail page at /admin/t/{tenant}/stored-reports/{report}.
  • Add one explicit permission_posture.view capability and keep list and detail visibility family-aware.
  • Reuse stored-report lifecycle, retention, and badge semantics from existing artifact-truth helpers instead of creating a second report-status system.
  • Adopt the new detail route as the canonical drilldown target from AdminRolesSummaryWidget.
  • Keep any unexpected report family outside v1 browse and detail scope instead of adding a local fallback renderer.

Technical Context

Language/Version: PHP 8.4, Laravel 12
Primary Dependencies: Filament v5, Livewire v4, Pest v4, existing StoredReport, ArtifactTruthPresenter, BadgeCatalog/BadgeRenderer, existing tenant-panel resource patterns, current report-producing services for permission posture and Entra admin roles
Storage: PostgreSQL via existing stored_reports, evidence_snapshots, tenant_reviews, and review_packs; no new persistence planned
Testing: Pest v4 feature coverage plus focused updates to an existing widget test
Validation Lanes: fast-feedback, confidence Target Platform: Laravel monolith in apps/platform, tenant/admin plane under /admin/t/{tenant}/... Project Type: web application
Performance Goals: keep list and detail DB-only, tenant-scoped, and eager-load-light; avoid new queues, Graph calls, or report regeneration side effects
Constraints: no new report engine, no analytics console, no global-search exposure, no cross-tenant browse surface, no raw payload download, no new persisted truth, and no new generic report registry
Scale/Scope: 1 new read-only resource surface, 2 supported stored-report families, 1 bounded new capability, and convergence on the current admin-roles widget seam only

Likely Affected Repo Surfaces

  • apps/platform/app/Filament/Resources/StoredReportResource.php as the new tenant-scoped read-only resource.
  • apps/platform/app/Filament/Resources/StoredReportResource/Pages/ListStoredReports.php for the register.
  • apps/platform/app/Filament/Resources/StoredReportResource/Pages/ViewStoredReport.php for the detail surface.
  • apps/platform/app/Models/StoredReport.php and apps/platform/database/factories/StoredReportFactory.php for query shape and test setup reuse.
  • apps/platform/app/Support/Auth/Capabilities.php and apps/platform/app/Services/Auth/RoleCapabilityMap.php for the new permission_posture.view capability and bounded role mapping.
  • apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php as the existing lifecycle-truth dependency for stored reports, likely reused without broadening the artifact envelope.
  • apps/platform/app/Filament/Widgets/Tenant/AdminRolesSummaryWidget.php and apps/platform/resources/views/filament/widgets/tenant/admin-roles-summary.blade.php for the canonical detail launch target.
  • apps/platform/app/Services/PermissionPosture/PermissionPostureFindingGenerator.php and apps/platform/app/Services/EntraAdminRoles/EntraAdminRolesReportService.php as payload-shape anchors only, not as new workflow scope.
  • apps/platform/app/Services/Evidence/Sources/PermissionPostureSource.php and apps/platform/app/Services/Evidence/Sources/EntraAdminRolesSource.php as source-record anchors that explain downstream consumer truth.
  • apps/platform/tests/Feature/StoredReports/StoredReportResourceTest.php, apps/platform/tests/Feature/StoredReports/StoredReportEntitlementEnforcementTest.php, apps/platform/tests/Feature/StoredReports/StoredReportDetailPresentationTest.php, and apps/platform/tests/Feature/EntraAdminRoles/AdminRolesSummaryWidgetTest.php as the bounded proving path.

Filament v5 / Panel Notes

  • Livewire v4.0+ compliance: The new register and detail surfaces should be native Filament resource pages under Livewire v4. No Livewire v3 compatibility or custom page framework is planned.
  • Provider registration location: No new panel or provider is introduced. Existing provider registration remains in apps/platform/bootstrap/providers.php.
  • Global search: The stored-report surface stays out of global search in v1. If a resource is used, it should remain isGloballySearchable = false, so the Filament view-page rule is not relied on for search exposure.
  • Destructive actions: None are in scope. The surface remains read-only and must not acquire edit, delete, rerun, or retention-mutation actions.
  • Asset strategy: No new Filament assets are planned. If a later implementation unexpectedly registers shared assets, deployment remains the standard cd apps/platform && php artisan filament:assets path.

Stored-Report Surface Fit

  • Implement the stored-report surface as one native Filament resource family rather than a custom dashboard page or local widget-only view.
  • Use a tenant-scoped register as the primary decision surface with clickable rows and no competing inline View action.
  • Use a read-only view page with an Infolist-style inspection layout rather than a disabled edit form.
  • Reuse ArtifactTruthPresenter::forStoredReport() for lifecycle and retention truth and keep any extra “current report” lookup local to the detail surface.
  • Keep list state bounded to search, report-family filter, and history visibility. Avoid local JavaScript state machines or a second query-string vocabulary beyond what Filament table state needs.
  • Keep support limited to the two repo-real report families in v1. Any unexpected family remains outside browse and detail scope until a follow-up spec expands support.

RBAC / Data Ownership / Auditability Fit

  • StoredReport remains tenant-owned truth with both workspace_id and tenant_id required. No workspace-owned mirror, generic artifact table, or reporting aggregate is introduced.
  • Collection visibility should require at least one in-scope stored-report family capability for the current tenant.
  • Detail access should remain family-aware:
    • entra.admin_roles uses existing Capabilities::ENTRA_ROLES_VIEW
    • permission_posture adds explicit Capabilities::PERMISSION_POSTURE_VIEW
  • The new permission_posture.view capability should map to the same read-only tenant roles that already consume governance evidence (owner, manager, operator, readonly) unless implementation review proves a narrower current-release truth.
  • Non-members or actors outside workspace or tenant scope remain 404. In-scope actors missing the relevant report-family capability remain 403.
  • Read-only browsing does not justify a new audit action family. Auditability for this slice comes from immutable stored-report identity, measured time, fingerprint lineage, and existing lifecycle truth rather than new browse-access logging.

UI / Surface Guardrail Plan

  • Guardrail scope: changed surfaces
  • Native vs custom classification summary: native Filament
  • Shared-family relevance: evidence/report viewers, navigation entry points, artifact status messaging, and retained-artifact detail disclosure
  • State layers in scope: page, detail, URL-query
  • Audience modes in scope: operator-MSP, support-platform
  • Decision/diagnostic/raw hierarchy plan: decision-first, diagnostics-second, support-raw-third
  • Raw/support gating plan: raw payload and provider-shaped identifiers remain collapsed and lower-priority on detail; no raw payload appears on the register
  • One-primary-action / duplicate-truth control: the register keeps one primary open model through row click; the detail surface exposes Open current report only when viewing a historical row and avoids repeating lifecycle truth in multiple sections
  • Handling modes by drift class or surface: review-mandatory
  • Repository-signal treatment: review-mandatory now; future hard-stop candidate if the slice drifts into a report console, generic viewer framework, or cross-tenant hub
  • Special surface test profiles: standard-native-filament, shared-detail-family
  • Required tests or manual smoke: functional-core, state-contract
  • Exception path and spread control: none planned; any request to widen support beyond the two repo-real families requires a follow-up spec rather than a local fallback
  • Active feature PR close-out entry: Guardrail

Shared Pattern & System Fit

  • Cross-cutting feature marker: yes
  • Systems touched: StoredReport, ArtifactTruthPresenter, centralized badge semantics, AdminRolesSummaryWidget, and report-producing services as payload anchors
  • Shared abstractions reused: ArtifactTruthPresenter, ArtifactTruthEnvelope, BadgeCatalog, BadgeRenderer, current tenant-panel resource conventions, and the existing report payload shapes already produced by permission posture and Entra admin roles
  • New abstraction introduced? why?: none planned. If implementation needs a tiny private extraction helper for family summaries, keep it local to the stored-report surface and do not turn it into a registry or shared framework.
  • Why the existing abstraction was sufficient or insufficient: lifecycle truth, badge semantics, and stored-report identity already exist. What is insufficient today is a first-class tenant browse and inspect destination.
  • Bounded deviation / spread control: only the two repo-real report families get full summary rendering in v1; additional families require a follow-up spec instead of local fallback logic.

OperationRun UX Impact

  • Touches OperationRun start/completion/link UX?: no
  • Central contract reused: N/A
  • Delegated UX behaviors: N/A
  • Surface-owned behavior kept local: read-only routing and disclosure only
  • Queued DB-notification policy: N/A
  • Terminal notification path: N/A
  • Exception path: none

Provider Boundary & Portability Fit

  • Shared provider/platform boundary touched?: yes
  • Provider-owned seams: stored-report payload interpretation for permission_posture and entra.admin_roles
  • Platform-core seams: stored report, current, historical, retained, measured at, register/detail routing, and downstream proof-link wording
  • Neutral platform terms / contracts preserved: stored report, current record, historical record, retained history, measured at, open report
  • Retained provider-specific semantics and why: the two report-family labels and their bounded summary facts stay visible because they are already current product truth and are not being generalized into a broader platform taxonomy
  • Bounded extraction or follow-up path: future report-family expansion stays local to this surface until additional repo-real families justify broader normalization

Constitution Check

GATE: Must pass before implementation begins and again after the design artifacts are complete.

  • Inventory-first / snapshot truth: PASS. The slice surfaces already-retained report truth only and does not change inventory or snapshot semantics.
  • Read/write separation: PASS. The surface is read-only and introduces no mutation path.
  • Graph contract path: PASS. No new Graph call, provider client, or scan path is added.
  • Deterministic capabilities: PASS. Report-family visibility stays on explicit capability constants and role mappings.
  • Workspace and tenant isolation: PASS. StoredReport remains tenant-owned and all routes stay tenant-scoped.
  • RBAC-UX plane separation: PASS. Everything remains in the tenant/admin plane under /admin/t/{tenant}/...; no /system crossover is added.
  • Destructive action discipline: PASS by non-use. No destructive or mutating actions are introduced.
  • Global search safety: PASS. The new surface is not globally searchable in v1.
  • OperationRun / Ops-UX: PASS by non-use. Existing scan or generation actions remain on their current surfaces.
  • Data minimization: PASS. No new persisted truth or raw payload export is introduced.
  • Test governance (TEST-GOV-001): PASS. Proof stays in focused feature coverage and one updated widget test.
  • Proportionality / no premature abstraction: PASS. The slice adds one bounded capability and one read-only surface family without a registry, engine, or new persistence.
  • Persisted truth (PERSIST-001): PASS. No new table, entity, or stored projection is planned.
  • Behavioral state (STATE-001): PASS. Current versus historical remains derived from existing stored-report truth.
  • UI semantics / shared pattern first / Filament-native UI: PASS. Native Filament resource pages and existing artifact-truth helpers remain the first path.
  • Provider boundary (PROV-001): PASS. Provider-shaped detail stays in family summaries and does not become platform-core route or taxonomy truth.
  • Filament / Laravel planning contract: PASS. Filament stays v5 on Livewire v4, provider registration remains in apps/platform/bootstrap/providers.php, the stored-report resource stays out of global search, and no asset changes are planned.

Gate evaluation: PASS.

Post-design re-check: PASS once research.md, data-model.md, quickstart.md, contracts/tenant-stored-reports-surface.logical.openapi.yaml, checklists/requirements.md, and tasks.md are present and aligned with this package.

Test Governance Check

  • Test purpose / classification by changed surface: Feature for register access, list behavior, detail presentation, and widget drilldown; no default Browser family planned
  • Affected validation lanes: fast-feedback, confidence
  • Why this lane mix is the narrowest sufficient proof: the slice is a native Filament register plus view surface over existing persisted truth, so list/detail behavior and widget drilldown can be proven in feature tests without widening into browser-only interaction coverage by default
  • Narrowest proving command(s):
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/StoredReports/StoredReportResourceTest.php tests/Feature/StoredReports/StoredReportEntitlementEnforcementTest.php
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/StoredReports/StoredReportDetailPresentationTest.php tests/Feature/EntraAdminRoles/AdminRolesSummaryWidgetTest.php
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
  • Fixture / helper / factory / seed / context cost risks: low to moderate; reuse current workspace, tenant, membership, and stored-report factory setup instead of introducing full provider or queue fixtures
  • Expensive defaults or shared helper growth introduced?: no; any family-summary helper should stay local and explicit
  • Heavy-family additions, promotions, or visibility changes: none planned
  • Surface-class relief / special coverage rule: standard-native-filament relief for the register; shared-detail-family coverage for the detail page
  • Closing validation and reviewer handoff: rerun the exact commands above, verify family-aware filtering and 404 versus 403 semantics, verify current versus historical truth is visible before raw diagnostics, confirm AdminRolesSummaryWidget launches the canonical detail route, and confirm no global-search exposure or raw-download action appears
  • Budget / baseline / trend follow-up: none expected beyond a contained feature-local increase
  • Review-stop questions: did the slice add a generic report framework, did it widen support beyond the two named families, did it leak hidden report families in rows or filter options, and did it turn raw payload into default-visible content
  • Escalation path: reject-or-split if implementation widens into analytics, cross-tenant browse, a report engine, or support for additional report families
  • Active feature PR close-out entry: Guardrail
  • Why no dedicated follow-up spec is needed: this package already captures the bounded browse/detail productization of current stored-report truth; broader reporting or customer-facing consumption remains explicitly separate
  • Test-governance outcome: keep

Review Checklist Status

  • Review checklist artifact: checklists/requirements.md
  • Review outcome class: acceptable-special-case
  • Workflow outcome: keep
  • Test-governance outcome: keep
  • Escalation rule: if implementation adds report generation, global search, raw export, cross-tenant browse, or a generic report-schema framework, flip the workflow outcome to split or reject-or-split before continuing

Rollout Considerations

  • Land the new permission_posture.view capability and the stored-report resource shell first so route ownership and authorization are clear before UI polish.
  • Keep the canonical entry point on the new tenant stored-reports register and the canonical secondary surface on the stored-report detail page.
  • Adopt the widget drilldown next, because AdminRolesSummaryWidget is the clearest current repo-real launch seam.
  • Keep convergence bounded to AdminRolesSummaryWidget; do not add new stored-report links on evidence, review, or review-pack pages in v1.
  • Keep the resource out of global search and keep deployment unchanged because no new assets or providers are expected.

Risk Controls

  • Reject any implementation that adds report generation, rerun, schedule, analytics, export, or delete behavior to this surface.
  • Reject any implementation that introduces a generic report registry, renderer framework, or new persisted artifact family.
  • Reject any implementation that exposes hidden report families through rows, filter options, or error semantics.
  • Reject any implementation that shows raw payload or provider identifiers before the calm operator summary.
  • Reject any implementation that widens support beyond the two named families without a follow-up spec.

Research & Design Outputs

  • research.md records the resource-versus-custom-surface decision, capability choice, canonical drilldown scope, supported-family boundary, and test-lane choice.
  • data-model.md captures existing StoredReport truth plus the derived row, detail, summary, and launch-seam contracts.
  • quickstart.md provides the bounded reviewer flow and focused validation commands.
  • contracts/tenant-stored-reports-surface.logical.openapi.yaml captures the logical tenant register and detail routes plus family-aware visibility rules.
  • checklists/requirements.md records the prep review outcome, workflow outcome, and test-governance outcome.
  • tasks.md keeps implementation bounded to the read-only stored-report surface and the current admin-roles widget seam.

Project Structure

Documentation (this feature)

specs/277-stored-reports-surface/
├── checklists/
│   └── requirements.md
├── contracts/
│   └── tenant-stored-reports-surface.logical.openapi.yaml
├── data-model.md
├── plan.md
├── quickstart.md
├── research.md
├── spec.md
└── tasks.md

Source Code (expected implementation surfaces)

apps/platform/
├── app/
│   ├── Filament/
│   │   ├── Resources/
│   │   │   ├── StoredReportResource.php
│   │   │   └── StoredReportResource/
│   │   │       └── Pages/
│   │   │           ├── ListStoredReports.php
│   │   │           └── ViewStoredReport.php
│   │   └── Widgets/
│   │       └── Tenant/
│   │           └── AdminRolesSummaryWidget.php
│   ├── Models/
│   │   └── StoredReport.php
│   ├── Services/
│   │   ├── Auth/
│   │   │   └── RoleCapabilityMap.php
│   │   ├── EntraAdminRoles/
│   │   │   └── EntraAdminRolesReportService.php
│   │   ├── Evidence/Sources/
│   │   │   ├── EntraAdminRolesSource.php
│   │   │   └── PermissionPostureSource.php
│   │   └── PermissionPosture/
│   │       └── PermissionPostureFindingGenerator.php
│   └── Support/
│       ├── Auth/
│       │   └── Capabilities.php
│       └── Ui/GovernanceArtifactTruth/
│           └── ArtifactTruthPresenter.php
├── database/
│   └── factories/
│       └── StoredReportFactory.php
└── tests/
    └── Feature/
        ├── EntraAdminRoles/
        │   └── AdminRolesSummaryWidgetTest.php
        └── StoredReports/
            ├── StoredReportDetailPresentationTest.php
            ├── StoredReportEntitlementEnforcementTest.php
            └── StoredReportResourceTest.php

Structure Decision: Keep the work inside the existing Laravel monolith and tenant-panel Filament resource conventions. No new base folders, panels, or shared framework layers are justified for this slice.

Complexity Tracking

Violation Why Needed Simpler Alternative Rejected Because
none N/A The plan stays inside existing stored-report truth, Filament resource conventions, and existing artifact-truth semantics.