# Feature Specification: Stored Reports Surface v1

**Feature Branch**: `277-stored-reports-surface`  
**Created**: 2026-05-06  
**Status**: Ready for implementation  
**Input**: User description: "Promote the remaining manual candidate `Stored Reports Surface v1` as one bounded product surface over existing stored-report truth, so operators can browse and consume retained reports without introducing a new reporting engine, analytics console, customer portal, generic artifact framework, or lifecycle rewrite."

## Spec Candidate Check *(mandatory - SPEC-GATE-001)*

- **Problem**: TenantPilot already persists stored reports that feed evidence snapshots, tenant reviews, governance-package delivery, support context, and tenant-level diagnostics, but those artifacts still lack one first-class browse and detail surface.
- **Today's failure**: Operators can infer stored-report truth only indirectly through a tenant widget, evidence and review summaries, support-context identity references, or direct database-style tests. The current repo even shows `AdminRolesSummaryWidget` with `viewReportUrl` unresolved, so retained report truth exists without a calm product entry point.
- **User-visible improvement**: An entitled operator can open one tenant-scoped stored-reports surface, see which retained reports are current or historical, and inspect the report summary safely without dropping into database-style lookup or widget-only dead ends.
- **Smallest enterprise-capable version**: Add one tenant-scoped stored-reports register plus one read-only detail surface over the existing `StoredReport` rows, support the current two report families (`permission_posture` and `entra.admin_roles`) with bounded summary rendering, reuse existing lifecycle and retention truth, and adopt the new detail route as the drilldown target from the current tenant admin-roles widget.
- **Explicit non-goals**: No new report-generation engine, no analytics console, no customer portal, no report authoring flow, no report-edit or delete workflow, no raw JSON download surface, no generic report-schema registry, no artifact-lifecycle rewrite, no cross-tenant portfolio report hub, and no reopening of evidence, review, governance-packaging, or retention specs.
- **Permanent complexity imported**: One new tenant-scoped operator surface family, one bounded per-report-family presentation contract for two existing report types, one new read capability for permission-posture report browsing, and focused feature coverage for browse, detail, deep-link, and authorization behavior.
- **Why now**: The product owner explicitly promoted this remaining manual candidate, the backlog ranks it as priority 6 after higher-ranked manual items that are already specced or closed, and Spec 267 explicitly deferred stored-report browsing as a dedicated follow-up instead of hiding it inside lifecycle work.
- **Why not local**: A widget-only link with no first-class browse surface would repeat the current problem. Stored reports are already shared truth across tenant diagnostics, evidence, and review packaging, so they need one consistent browse and detail contract rather than more point-specific affordances.
- **Approval class**: Workflow Compression
- **Red flags triggered**: New operator surface, new read capability, and report-family-specific presentation logic. Defense: the slice introduces no new persistence, no generic framework, no workflow engine, and no customer-facing expansion; it productizes two repo-real report families only.
- **Score**: Nutzen: 2 | Dringlichkeit: 1 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 10/12**
- **Decision**: approve

## Spec Scope Fields *(mandatory)*

- **Scope**: tenant
- **Primary Routes**:
  - new tenant-scoped stored-reports collection at `/admin/t/{tenant}/stored-reports`
  - new tenant-scoped stored-report detail at `/admin/t/{tenant}/stored-reports/{report}`
  - existing tenant overview at `/admin/t/{tenant}` as a secondary launch point where the admin-roles widget gains a first-class report link
- **Data Ownership**:
  - `StoredReport` remains the tenant-owned persisted source of truth and continues to require both `workspace_id` and `tenant_id`
  - evidence snapshots, tenant reviews, review packs, and support bundles remain separate tenant-owned consumers of stored-report truth and do not become re-owned by the new surface
  - no new workspace-owned mirror, aggregate store, or generic artifact table is introduced
- **RBAC**:
  - workspace membership and tenant entitlement remain the first isolation boundaries
  - non-members or actors outside the entitled workspace or tenant scope remain deny-as-not-found (`404`)
  - report-family browsing is capability-first and type-aware: `entra.admin_roles` uses existing `Capabilities::ENTRA_ROLES_VIEW`, and `permission_posture` gains a bounded `permission_posture.view` read capability so the new surface does not piggyback on unrelated review or provider gates
  - collection visibility requires at least one in-scope stored-report read capability for the current tenant; row visibility and direct detail access remain filtered by report family
  - existing manage or rerun actions stay on their origin surfaces and keep their current manage gates; the stored-reports surface itself is read-only in v1

## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)*

- **Cross-cutting feature?**: yes
- **Interaction class(es)**: evidence/report viewers, navigation entry points, artifact status messaging, and retained-artifact detail disclosure
- **Systems touched**: `StoredReport`, `ArtifactTruthPresenter`, badge rendering, the tenant admin-roles widget, and existing artifact-reference vocabulary used in support and review contexts
- **Existing pattern(s) to extend**: shared governance-artifact truth, existing tenant-scoped read-only detail patterns, and the existing artifact-reference vocabulary already used in support and review contexts
- **Shared contract / presenter / builder / renderer to reuse**: `ArtifactTruthPresenter`, `ArtifactTruthEnvelope`, centralized badge semantics, and existing report-family summary truth already embedded in `AdminRolesSummaryWidget`, evidence-source providers, and tenant-review composition
- **Why the existing shared path is sufficient or insufficient**: existing shared paths already know how to classify stored reports as current vs historical retained artifacts and how to summarize both report families. They are insufficient only because there is no first-class browse and detail destination that every report-backed surface can reuse.
- **Allowed deviation and why**: none. V1 may use one bounded per-family presentation branch for the two repo-real report types, but it must not create a registry, plugin system, or second lifecycle language.
- **Consistency impact**: `Stored report`, `current`, `historical`, `retained`, `measured at`, `permission posture`, `Entra admin roles`, and report-family summary labels must mean the same thing across the new list, detail, and widget drilldown.
- **Review focus**: reviewers must confirm that the new surface reuses shared artifact truth and existing report-family summary semantics, keeps raw payloads secondary, and does not introduce a parallel local status or wording system.

## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)*

N/A - no `OperationRun` start, dedupe, terminal feedback, or new run link semantics are introduced in this slice. Existing scan, verification, or report-generation actions remain owned by their current surfaces and reuse their existing run UX unchanged.

## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)*

- **Shared provider/platform boundary touched?**: yes
- **Boundary classification**: mixed
- **Seams affected**: report-type labels, report-family summaries, artifact-reference wording, and widget drilldown language
- **Neutral platform terms preserved or introduced**: `stored report`, `current record`, `historical record`, `retained history`, `measured at`, `report family`, and `used by`
- **Provider-specific semantics retained and why**: `permission_posture` and `entra.admin_roles` remain the two repo-real report families, and their type-specific summary content stays visible because those records are already provider-shaped truth captured by the current product.
- **Why this does not deepen provider coupling accidentally**: v1 productizes the existing retained artifacts only. It does not introduce a new platform-wide report taxonomy, analytics layer, or provider-derived global navigation model. Provider-specific detail stays inside the report-family summary, while list and lifecycle semantics remain platform-owned.
- **Follow-up path**: future report-family expansion can stay local to stored-report presentation until the repo has multiple additional concrete report families that justify broader normalization

## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*

| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|---|---|---|---|---|---|---|
| Tenant stored-reports register | yes | Native Filament resource/page with shared badge and artifact-truth primitives | navigation, report viewers, status messaging | page, filters, list state | no | New primary browse surface for stored-report truth; no local design system or custom dashboard card |
| Stored-report detail | yes | Native Filament view/detail surface with shared artifact-truth primitives | report viewers, proof drilldowns, audience-aware disclosure | detail sections, progressive disclosure | no | New read-only inspection surface; no edit form, no report authoring, no destructive actions |

## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)*

| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|---|---|---|---|---|---|---|---|
| Tenant stored-reports register | Primary Decision Surface | Operator decides which retained report is current enough to inspect or which historical record matters for a current tenant conversation | report family, current vs historical state, measured time, and concise report-family summary | full report detail and fingerprint lineage | Primary because this is the first calm place to answer whether a report exists and whether the latest retained record is usable | Follows tenant-scoped diagnostics and widget launch workflows instead of forcing database-style lookup or widget-hunting | Removes the need to infer report truth from separate widgets, review summaries, or support bundles |
| Stored-report detail | Secondary Context Surface | Operator confirms what the retained report actually says and where it already matters downstream | artifact reference, current or historical truth, measured time, bounded family summary, and one related next step | raw payload, fingerprint lineage, and lower-level diagnostics only when explicitly revealed | Secondary because the operator should choose the report first, then read it in one focused detail surface | Keeps report consumption inside tenant scope and existing downstream proof workflows | Removes page-to-page reconstruction and keeps report truth from looking calmer than it is |

## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)*

| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention |
|---|---|---|---|---|---|---|---|
| Tenant stored-reports register | operator-MSP, support-platform | report family, lifecycle state, measured time, and concise summary values for the current tenant | fingerprint presence and current vs historical relationship | raw payloads and provider-shaped low-level detail stay off the list | `Open report` | raw payload and deep diagnostics stay secondary | The list states one calm summary per row; the detail page owns the full explanation |
| Stored-report detail | operator-MSP, support-platform | artifact reference, lifecycle and retention truth, measured time, and bounded report-family summary | fingerprint anchor and previous-fingerprint lineage | raw payload JSON, provider identifiers, and redacted support detail remain explicitly secondary or gated | `Open current report` on historical records only | raw payload and support-only context never replace the default summary | The detail header states current vs historical truth once; lower sections add supporting context without re-labelling the same state |

## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)*

| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Tenant stored-reports register | List / Table / Read-only | Read-only registry report | Open the current or relevant historical report | full-row click to detail | required | filter controls and history visibility stay secondary | none | `/admin/t/{tenant}/stored-reports` | `/admin/t/{tenant}/stored-reports/{report}` | active tenant, report family, lifecycle state | Stored reports / Stored report | whether a report exists, whether it is current, and when it was measured | none |
| Stored-report detail | Detail / Report viewer | Read-only detail report | Review report truth and, for historical rows, open the current retained report | sectioned detail page | forbidden | history navigation stays secondary | none | `/admin/t/{tenant}/stored-reports` | `/admin/t/{tenant}/stored-reports/{report}` | active tenant, report family, lifecycle state, measured time | Stored report | what this report says and whether it is current or historical | none |

## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)*

| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|---|---|---|---|---|---|---|---|---|---|---|
| Tenant stored-reports register | Tenant operator or support operator | Decide which retained report should be inspected now | Read-only registry | What stored reports exist for this tenant, and which one is current? | report family, current vs historical state, measured time, and concise summary values | fingerprint lineage and deeper diagnostic context | lifecycle, retention, measured-time freshness signal | none | Open report | none |
| Stored-report detail | Tenant operator or support operator | Decide whether the retained report is sufficient for the current tenant conversation and whether the latest row should be opened instead | Read-only detail report | What does this report say, and how current is it? | artifact reference, lifecycle truth, measured time, bounded type-specific summary, and fingerprint lineage | raw payload, fingerprint lineage, redaction notes, and support detail | lifecycle, retention, measured-time freshness signal | none | Open current report | none |

## Proportionality Review *(mandatory when structural complexity is introduced)*

- **New source of truth?**: no
- **New persisted entity/table/artifact?**: no
- **New abstraction?**: no new generic framework; one bounded per-family presentation branch only
- **New enum/state/reason family?**: no
- **New cross-domain UI framework/taxonomy?**: no
- **Current operator problem**: retained stored-report truth exists in the database and in downstream consumers, but operators still lack one first-class place to browse and inspect it safely.
- **Existing structure is insufficient because**: widgets, evidence summaries, review packaging, and support identity references all expose only fragments of stored-report truth, and none of them is the canonical browse or detail destination.
- **Narrowest correct implementation**: add one tenant-scoped register and one read-only detail surface over the existing `StoredReport` rows, reuse existing artifact-truth and summary semantics, and keep report generation and lifecycle mutation out of scope.
- **Ownership cost**: one new read-only surface family, one additional permission-posture read capability, focused tests, and review discipline around default-visible vs diagnostic detail.
- **Alternative intentionally rejected**: a generic report engine, analytics console, or artifact-framework expansion was rejected as too broad; local widget-only links were rejected because they would keep browse and detail truth fragmented.
- **Release truth**: current-release productization over already persisted report artifacts and already-real downstream consumers

### Compatibility posture

This feature assumes a pre-production environment.

Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-only tests are out of scope unless a later implementation explicitly proves them necessary.

Canonical productization of the current stored-report truth is preferred over building compatibility layers for future report families.

## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*

- **Test purpose / classification**: Feature
- **Validation lane(s)**: fast-feedback, confidence
- **Why this classification and these lanes are sufficient**: the slice is a tenant-scoped browse and detail surface over existing persisted truth. Focused feature coverage is the narrowest sufficient proof for list filtering, detail rendering, deep-link continuity, and authorization behavior. No new queue, browser-only interaction model, or heavy-governance harness is required by default.
- **New or expanded test families**: one new stored-reports surface family for register and detail behavior, plus focused updates to current widget or proof-link families that now launch into report detail
- **Fixture / helper cost impact**: low to moderate; reuse existing workspace, tenant, membership, `StoredReport`, evidence, and tenant-review fixtures without adding provider sync or queue defaults
- **Heavy-family visibility / justification**: none
- **Special surface test profile**: standard-native-filament, shared-detail-family
- **Standard-native relief or required special coverage**: standard Filament feature coverage is sufficient for list, detail, and link behavior; no browser smoke is required unless later implementation proves a rendered widget interaction cannot be verified reliably in feature tests
- **Reviewer handoff**: reviewers must confirm that report rows are type-filtered by capability, history remains readable without implying freshness, raw payload stays secondary, the admin-roles widget adopts the canonical detail route, and no new report-generation or analytics behavior appears
- **Budget / baseline / trend impact**: low feature-local increase only
- **Escalation needed**: none
- **Active feature PR close-out entry**: Guardrail
- **Planned validation commands**:
  - `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`

## Scope Boundaries *(required for this slice)*

### In Scope

- one tenant-scoped stored-reports register with search, report-family filters, and current vs historical visibility
- one read-only stored-report detail surface for the current two repo-real report families
- shared lifecycle and retention truth for stored reports reused from `ArtifactTruthPresenter`
- deep links into the new detail route from the current tenant admin-roles widget
- default-visible operator summary plus progressive disclosure for raw payload and lower-level diagnostics
- type-aware, capability-first row filtering and direct-route authorization
- explicit family-boundary behavior if an unexpected stored-report type appears in storage

### Non-Goals

- report generation, rescanning, scheduling, or analytics workflows
- raw JSON download, bulk export, or standalone support-bundle delivery from the stored-reports surface
- a customer-facing stored-report portal or customer-safe default-visible raw-report drilldown
- cross-tenant or workspace-wide stored-report registry in v1
- review, evidence, governance-package, or artifact-lifecycle contract rewrites
- generic report-schema registry, renderer framework, or artifact meta-framework

## Dependencies

- `docs/product/spec-candidates.md` manual-promotion backlog entry for `Stored Reports Surface v1`
- `docs/product/roadmap.md` manual-promotion order entry naming stored reports as priority 6
- `specs/153-evidence-domain-foundation/spec.md` as context only for evidence-source reuse
- `specs/155-tenant-review-layer/spec.md` as context only for report consumption inside tenant reviews
- `specs/260-governance-service-packaging/spec.md` as context only for report-backed proof and package consumption
- `specs/267-artifact-lifecycle-retention/spec.md` as context only for stored-report lifecycle and retention truth already prepared elsewhere
- current `StoredReport` model, evidence-source providers, tenant-review composition, and `ArtifactTruthPresenter`

## Assumptions

- v1 supports exactly the two repo-real stored-report families that already exist in code: `permission_posture` and `entra.admin_roles`
- stored reports remain immutable retained artifacts; current vs historical is derived from the latest retained record per tenant and report family rather than from a second state store
- the new surface is read-only and does not become the place where operators rerun scans, fix tenant configuration, or manage retention
- existing evidence and review surfaces continue to own their own business truth; the stored-reports surface only exposes the report artifact more directly
- customer-safe review flows may keep stored-report drilldowns secondary or hidden; customer-facing report consumption is not part of this slice

## Risks

- scope could drift into a generic reporting or analytics console if the surface starts to aggregate cross-tenant or multi-family reporting behavior
- raw payload disclosure could overexpose provider-shaped detail if progressive disclosure is not enforced consistently
- current vs historical state could look calmer than it is if measured time is not shown alongside lifecycle truth
- introducing a new permission-posture read capability could create RBAC drift if role mapping is widened carelessly instead of staying symmetrical and bounded
- unexpected future report families would require a follow-up spec before they become browseable on this surface

## Candidate Selection Gate Summary

- **Selected candidate**: Stored Reports Surface v1
- **Source locations**:
  - `docs/product/spec-candidates.md` manual-promotion backlog priority 6
  - `docs/product/roadmap.md` current manual-promotion ranking item 6
  - `specs/153-evidence-domain-foundation/spec.md`, `specs/155-tenant-review-layer/spec.md`, and `specs/260-governance-service-packaging/spec.md` as context-only anchors
- **Why selected now**: this was an explicit product decision, the higher-ranked remaining manual-promotion items are already specced or closed in current repo truth, and this is the next valid bounded product-surface follow-up without reopening already prepared packages.
- **Higher-ranked candidate guardrail result**:
  - `specs/265-decision-register-approval/spec.md` already exists and is ready for implementation
  - `specs/267-artifact-lifecycle-retention/spec.md` already exists and has implementation close-out context
  - `specs/274-billing-subscription-truth/spec.md` already exists
  - `specs/275-customer-facing-localization-adoption/spec.md` already exists
  - `specs/276-support-access-governance/spec.md` already exists
  - none of the above are reopened by this spec
- **Related-anchor guardrail result**:
  - `specs/153-evidence-domain-foundation/spec.md` remains context only and is not rewritten
  - `specs/155-tenant-review-layer/spec.md` remains context only and is not rewritten
  - `specs/260-governance-service-packaging/spec.md` remains context only and is not rewritten
  - `specs/267-artifact-lifecycle-retention/spec.md` remains context only and is not rewritten
- **Why this is the smallest viable implementation slice**: it adds one tenant-scoped browse and detail surface over existing stored-report rows, adopts that detail surface as the canonical drilldown target from the current admin-roles widget, and keeps generation, export, analytics, and lifecycle-mutation work out of scope.
- **Why close alternatives are deferred**:
  - a workspace-wide or cross-tenant stored-report hub is broader than the current tenant-scoped productization gap
  - a customer-facing report portal belongs to a separate customer-consumption decision, not this operator-first browse slice
  - report-generation, raw export, and analytics-console work are distinct workflow or platform layers and are not prerequisites for safe browsing
  - lifecycle-runtime work was already intentionally separated into Spec 267 and must stay separate from this browse/detail package

## Completed-Spec Guardrail Result

- **Spec 153 - Evidence Domain Foundation**: context only. It already established stored reports as evidence inputs and is not reopened by this surface slice.
- **Spec 155 - Tenant Review Layer**: context only. It already established review consumption of stored-report-backed evidence and is not reopened.
- **Spec 260 - Governance-as-a-Service Packaging v1**: context only. It remains the package and proof-consumption anchor and is not refreshed here.
- **Spec 267 - Governance Artifact Lifecycle & Retention v1**: context only. It already prepared stored-report lifecycle and retention semantics; this slice reuses them instead of recreating them.

## Follow-Up Candidates Explicitly Kept Out of Scope

- Workspace & Tenant Closure Lifecycle v1
- First Governed AI Runtime Consumer v1
- any future customer-facing stored-report consumption candidate beyond current operator-first browse/detail
- any future report-family expansion candidate beyond the current two repo-real stored-report types
- any future raw export or support-bundle delivery candidate for stored reports

## User Scenarios & Testing *(mandatory)*

### User Story 1 - Browse current stored reports for one tenant (Priority: P1)

As a tenant operator, I want one first-class stored-reports register for the active tenant so I can see which retained reports exist and which one is current without reconstructing that truth from widgets or downstream consumers.

**Why this priority**: This is the core productization gap. Without the register, stored reports remain substrate only.

**Independent Test**: Can be fully tested by seeding both report families for one tenant, opening the new tenant-scoped register, and verifying that current and historical rows, filters, and type-aware summaries render correctly for an entitled actor.

**Acceptance Scenarios**:

1. **Given** a tenant has current stored reports for both in-scope report families, **When** an entitled operator opens the stored-reports register, **Then** the list shows the current retained records with report-family summaries and measured time.
2. **Given** one report family also has older retained rows, **When** the operator enables history visibility, **Then** historical rows remain readable and clearly distinguished from the current record.
3. **Given** the same tenant has a report family the actor is not entitled to view, **When** the register loads, **Then** the hidden family does not appear in rows or filter options.

---

### User Story 2 - Inspect a retained report without false calmness (Priority: P1)

As an operator or support user, I want the stored-report detail to show what the report says, whether it is current or historical, and when it was measured so I can use it safely in evidence or review conversations.

**Why this priority**: A browse surface is not useful if the detail view still forces operators back into raw payloads or inferred freshness.

**Independent Test**: Can be fully tested by opening both a current and a historical stored-report detail page and verifying that lifecycle truth, measured time, and the type-specific summary are visible before any raw diagnostics.

**Acceptance Scenarios**:

1. **Given** an operator opens a current permission-posture stored report, **When** the detail page loads, **Then** it shows the posture score, required vs granted counts, measured time, and that the report is the current retained record.
2. **Given** an operator opens a historical Entra admin-roles report, **When** the detail page loads, **Then** it shows that the record remains readable history and offers a path to the current retained record without implying the historical report is still current.
3. **Given** raw payload detail exists, **When** the operator stays in the default detail view, **Then** the page keeps raw provider-shaped payloads secondary and does not replace the calm summary with low-level JSON.

---

### User Story 3 - Follow stored-report truth from the tenant admin-roles widget (Priority: P2)

As an operator using the tenant overview, I want the admin-roles widget to open one canonical stored-report detail so I can inspect the retained artifact without losing tenant context.

**Why this priority**: Product value comes from converging the one confirmed repo-real launch seam on one canonical drilldown target, not from adding one more isolated page.

**Independent Test**: Can be fully tested by opening the tenant admin-roles widget, following its report link into the new stored-report detail page, and confirming that tenant context and authorization remain intact.

**Acceptance Scenarios**:

1. **Given** the tenant admin-roles widget shows a stored report, **When** the actor opens the report from that widget, **Then** the app launches the canonical stored-report detail page for the active tenant.
2. **Given** the actor can open the tenant overview but cannot view Entra admin-roles reports, **When** the overview renders, **Then** the widget does not expose a stored-report launch affordance.
3. **Given** the actor copies a stored-report detail route from an entitled session into a session without the report-family capability, **When** the route is opened directly, **Then** the app preserves `404` or `403` behavior according to the established entitlement and capability rules without leaking hidden report identity.

### Edge Cases

- A tenant has only one retained row for a report family, but it is old; the surface must still show it as the current retained record while making the measured time obvious.
- An operator can view Entra admin-roles reports but not permission-posture reports; the list must not hint that hidden report families exist.
- A historical report is opened after a newer retained row is created; the detail page must keep the old row readable and clearly identify the newer current report.
- An unexpected future `report_type` appears in storage; the surface must fail safely without exposing new rows, filter options, or direct detail access until a follow-up spec adds that family.
- A stored-report detail route is opened after the row was pruned or otherwise removed; the app must fail safely without implying the artifact is still inspectable.

## Requirements *(mandatory)*

**Constitution alignment (required):** This feature adds no new Microsoft Graph calls, no write or mutate workflow, and no new queued background execution. It productizes read-only browsing and consumption of existing tenant-owned stored-report artifacts only.

**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** The slice introduces no new persistence, no new status family, and no generic framework. One bounded read capability plus one bounded two-family presentation surface is justified because two concrete report families already exist and are already consumed by evidence and review features.

**Constitution alignment (XCUT-001):** The feature extends existing artifact-truth, badge, and widget-drilldown paths. It must not introduce page-local lifecycle labels, custom badge maps, or a second report-viewer vocabulary.

**Constitution alignment (DECIDE-AUD-001 / OPSURF-001):** Default-visible content stays operator-first and calm: report family, current vs historical truth, measured time, and concise summary first; raw payload and provider-shaped detail remain explicitly secondary.

**Constitution alignment (PROV-001):** The browse surface stays platform-owned while acknowledging provider-shaped report families. It must not turn provider-specific payload fields into platform-core navigation or taxonomy truth.

**Constitution alignment (TEST-GOV-001):** Proof stays in focused feature coverage. No hidden browser or heavy-governance family may appear by default.

**Constitution alignment (OPS-UX / OPS-UX-START-001):** No new `OperationRun` lifecycle, start path, or notification model is introduced. Existing scan or verification actions remain where they already live.

**Constitution alignment (RBAC-UX):** The feature stays in the tenant/admin plane. Non-members or actors outside workspace or tenant scope remain `404`. Members lacking the required report-family capability remain `403`. Server-side authorization remains authoritative for list filtering, detail access, and deep-link routes.

**Constitution alignment (BADGE-001):** Current vs historical and retained-artifact status must reuse centralized badge semantics from artifact truth rather than page-local color mapping.

**Constitution alignment (UI-FIL-001):** The surface must be built with native Filament list and detail primitives, existing shared badge or artifact-truth helpers, and no ad-hoc card, button, or row styling.

**Constitution alignment (UI-NAMING-001):** Primary operator labels stay specific and stable: `Stored reports`, `Stored report`, `Open report`, `Current`, `Historical`, `Measured at`, `Permission posture`, and `Entra admin roles`. Implementation terms such as `payload`, `projection`, or `report schema` do not become primary surface labels.

**Constitution alignment (DECIDE-001):** The new tenant register is the primary decision surface, while detail is the secondary context surface. `AdminRolesSummaryWidget` becomes a launch point, not a competing primary surface.

**Constitution alignment (UI-CONST-001 / UI-SURF-001 / ACTSURF-001 / UI-HARD-001 / UI-EX-001 / UI-REVIEW-001 / HDR-001):** The list uses one primary open model, no redundant `View` action, no mixed catch-all action groups, and no destructive controls. The detail surface remains read-only and keeps raw diagnostics clearly secondary.

**Constitution alignment (UI-SEM-001 / LAYER-001 / TEST-TRUTH-001):** The feature must map directly from existing stored-report truth and existing report-family summaries to the surface. It must not add a new semantic envelope or report-interpretation layer beyond the already-existing artifact-truth path.

**Constitution alignment (Filament Action Surfaces):** The Action Surface Contract is satisfied if the collection uses one canonical row-open affordance, the detail is read-only, redundant `View` actions are absent, empty action groups are absent, and the surface adds no destructive actions.

**Constitution alignment (UX-001 - Layout & Information Architecture):** The list must provide search, report-family filters, and history visibility controls for the core browsing dimensions. The detail surface must use an Infolist-style inspection layout rather than a disabled edit form. Empty states must be specific and honest about where report generation actually happens.

### Functional Requirements

- **FR-277-001**: The system MUST provide one tenant-scoped stored-reports register for the active tenant.
- **FR-277-002**: The register MUST list only stored-report rows that belong to the active tenant and that the current actor is entitled to inspect.
- **FR-277-003**: The register MUST use type-aware capability filtering so report-family rows and filter options never leak hidden report families.
- **FR-277-004**: V1 MUST support full browse and detail presentation for exactly these repo-real stored-report families: `permission_posture` and `entra.admin_roles`.
- **FR-277-005**: The register MUST show report family, current vs historical state, measured time, and a bounded report-family summary for each visible row.
- **FR-277-006**: The register MUST let the actor search by report-family label or report reference and explicitly reveal retained history without breaking direct detail access to historical rows.
- **FR-277-007**: The detail surface MUST show the stored-report reference, report-family label, measured time, lifecycle truth, retention truth, and the integrity anchor when present.
- **FR-277-008**: The detail surface MUST reuse shared artifact-truth semantics so stored reports appear as `current` or `historical` retained artifacts using the existing lifecycle and retention contract.
- **FR-277-009**: Permission-posture detail MUST show a bounded summary of posture score, required vs granted counts, and missing or at-risk permission context from the stored payload.
- **FR-277-010**: Entra admin-roles detail MUST show a bounded summary of role totals, assignment totals, high-privilege assignment count, and the highest-risk assignment context from the stored payload.
- **FR-277-011**: Raw payloads, provider identifiers, and lower-level diagnostics MUST remain hidden or collapsed by default and MUST NOT replace the default-visible operator summary.
- **FR-277-012**: Historical report detail MUST remain readable and MUST provide a path to the current retained record when a newer report of the same family exists.
- **FR-277-013**: `AdminRolesSummaryWidget` MUST adopt the new stored-report detail route as its canonical drilldown target when the actor is entitled.
- **FR-277-014**: The stored-reports surface MUST remain read-only in v1 and MUST NOT introduce report generation, rerun, download, edit, delete, or retention-mutation actions.
- **FR-277-015**: The feature MUST NOT introduce global-search exposure or cross-tenant discovery for stored-report records in v1.
- **FR-277-016**: If an unexpected stored-report family appears in storage, v1 MUST keep it out of collection rows, filter options, and direct detail routes until a separate follow-up spec expands the supported family set.
- **FR-277-017**: The feature MUST add one positive and one negative authorization proof for both collection access and direct detail access.
- **FR-277-018**: The feature MUST preserve source-of-truth clarity by keeping evidence snapshots, tenant reviews, review packs, and stored reports as separate artifacts rather than flattening them into one synthetic report domain.

## UI Action Matrix *(mandatory when Filament is changed)*

| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|---|---|---|---|---|---|---|---|---|---|---|
| Stored-reports register | `/admin/t/{tenant}/stored-reports` | none | clickable row to stored-report detail | none | none | `Open tenant overview` | N/A | N/A | no new audit event; read-only browse only | Action-surface contract stays satisfied through one row-open model and no competing row actions |
| Stored-report detail | `/admin/t/{tenant}/stored-reports/{report}` | none | N/A | none | none | N/A | conditional `Open current report` on historical rows only | N/A | no new audit event; read-only inspect only | Detail is an inspection surface and must not become a disabled edit form or a mutation hub |

### Key Entities *(include if feature involves data)*

- **Stored Report**: A tenant-owned retained report artifact with `workspace_id`, `tenant_id`, `report_type`, payload, fingerprint, and optional previous-fingerprint lineage.
- **Stored Report Family Summary**: A bounded read-only presentation of the key operator-relevant values for one supported report family.
- **Stored Report Launch Seam**: The confirmed operator-facing path that opens the canonical stored-report detail route in v1, currently limited to `AdminRolesSummaryWidget`.

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: An entitled operator can reach a current stored report for a tenant in three interactions or fewer from the tenant overview via `AdminRolesSummaryWidget`.
- **SC-002**: In 100% of validated current vs historical scenarios, operators can distinguish the current retained report from historical retained reports in one inspection step.
- **SC-003**: In 100% of validated unauthorized scenarios, the surface leaks no hidden report-family presence across workspace, tenant, or capability boundaries.
- **SC-004**: In 100% of validated default-detail scenarios, the page shows operator-relevant summary first and keeps raw payload or provider-shaped diagnostics secondary.
- **SC-005**: Both repo-real report families become available through one first-class browse and detail surface without introducing a new report-generation or analytics workflow.

## Open Questions

- none

## Final Direction

This spec productizes stored reports as first-class retained artifacts without changing what they are. The surface stays deliberately narrow: one tenant-scoped register, one read-only detail page, two supported report families, shared lifecycle truth, and one confirmed widget drilldown seam. It closes a real operator gap while keeping report generation, customer-facing consumption, cross-tenant analytics, broader proof-link convergence, and lifecycle mutation as separate decisions.
