# Research — Customer Review Workspace v1 **Date**: 2026-04-27 **Spec**: [spec.md](spec.md) This document resolves the planning decisions that shape the smallest safe implementation slice for Spec 249. ## Decision 1 — Place the new surface as a native admin reviews page **Decision**: Implement the customer-safe workspace as a new native Filament page under the existing admin reviews family, with the planned route shape `/admin/reviews/workspace`. Do not create a new panel, a public/customer portal shell, or a new Resource just to host the view. **Rationale**: - The repo already has native workspace-level read-only pages for reporting and monitoring. - The existing review, review-pack, and evidence Resources already own tenant-scoped detail and proof routes. - A dedicated page keeps the first slice calm and customer-safe without overloading an operator-oriented registry. **Evidence**: - [../../apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php](../../apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php) already provides the workspace review register pattern. - [../../apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php](../../apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php) already provides a workspace-scoped read-only page pattern with tenant prefilters. - [../../apps/platform/bootstrap/providers.php](../../apps/platform/bootstrap/providers.php) already registers the existing panel providers. No new provider registration is needed. **Alternatives considered**: - Extend `ReviewRegister` into a dual-persona page. - Rejected: it already carries operator-oriented filters and export semantics, which would blur the customer-safe default path. - Create a new customer portal or new identity plane. - Rejected: outside the bounded v1 scope and unnecessary because the current admin plane plus readonly-capable roles already exists. ## Decision 2 — Reuse `TenantReviewRegisterService` as the entitlement and query seam **Decision**: Prefer extending or reusing [../../apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php](../../apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php) for workspace access checks, entitled-tenant discovery, and the base review query before adding any new helper. **Rationale**: - The service already centralizes workspace membership and entitled tenant selection for the current review register. - Reusing it keeps entitlement logic in one place and avoids new raw tenant-role queries inside the page. - It is the narrowest existing seam that can be extended toward latest-published-per-tenant behavior. **Evidence**: - `authorizedTenants(...)` already derives tenant scope from the canonical role/capability map. - `query(...)` already scopes tenant reviews to the current workspace and eager-loads `tenant`, `evidenceSnapshot`, and `currentExportReviewPack`. - `canAccessWorkspace(...)` already exposes the workspace-membership check needed for deny-as-not-found page gating. **Alternatives considered**: - Build a new persisted workspace summary table. - Rejected: violates the no-new-persistence rule for a derived read surface. - Recreate entitlement logic directly inside the page class. - Rejected: duplicates existing membership and capability behavior. ## Decision 3 — Derive the default path from the latest published tenant review per entitled tenant **Decision**: The default workspace page should derive each tenant summary from the latest published `TenantReview` for that entitled tenant, with eager-loaded `currentExportReviewPack` and `evidenceSnapshot` relationships. **Rationale**: - The spec requires the default path to stay customer-safe and exclude draft, failed, and other internal-only states. - The current `TenantReview` model already distinguishes published reviews and holds the summary relationships the new page needs. - This keeps the page read-oriented and avoids a separate customer-review lifecycle. **Evidence**: - [../../apps/platform/app/Models/TenantReview.php](../../apps/platform/app/Models/TenantReview.php) already exposes `published()` and `currentExportReviewPack()`. - [../../apps/platform/app/Services/TenantReviews/TenantReviewService.php](../../apps/platform/app/Services/TenantReviews/TenantReviewService.php) already stores review summary payloads, evidence basis, and export readiness. - Existing review composition already emits `finding_outcomes` and accepted-risk related payloads through the review artifact family. **Alternatives considered**: - Surface draft or ready reviews when no published review exists. - Rejected: leaks internal lifecycle meaning into the customer-safe path. - Create a second customer-review publication model. - Rejected: duplicates review truth and imports unnecessary workflow complexity. ## Decision 4 — Keep page state Livewire-safe and tenant-prefilter aware **Decision**: Tenant launch context, requested tenant filters, and any remembered page state must live in public, query-backed, or session-backed state, following the existing workspace-page patterns. Do not keep required filter state in private properties. **Rationale**: - Existing workspace pages already show how canonical admin pages preserve tenant prefilters and survive Livewire follow-up requests. - This repo has already hit Livewire state-reset issues when tenant context lived in non-hydrated private properties. - The workspace page needs tenant prefiltering from review, evidence, and related entry points. **Evidence**: - [../../apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php](../../apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php) uses canonical admin filter-state sync on mount. - [../../apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php](../../apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php) documents its page-state contract for tenant prefilters and remembered search/filter state. - Repo memory already records that private page state can reset during Livewire actions on admin canonical pages. **Alternatives considered**: - Keep launch context in a private property only. - Rejected: too brittle across Livewire requests. - Use only client-side state. - Rejected: breaks server-side truth and shareable canonical page behavior. ## Decision 5 — Reuse artifact-truth and redaction seams for customer-safe disclosure **Decision**: Reuse [../../apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php](../../apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php), `SurfaceCompressionContext`, and [../../apps/platform/app/Support/RedactionIntegrity.php](../../apps/platform/app/Support/RedactionIntegrity.php) for outcome, freshness, and redaction-safe wording. **Rationale**: - These seams already normalize review, review-pack, and evidence truth into operator-safe summaries. - Reusing them preserves vocabulary and prevents a second customer-review explanation system. - `RedactionIntegrity` already owns the repo’s protected-value and support-diagnostics notes. **Evidence**: - `ArtifactTruthPresenter::for(...)` already supports `TenantReview`, `ReviewPack`, and `EvidenceSnapshot`. - `ReviewRegister`, `EvidenceOverview`, `TenantReviewResource`, and `ReviewPackResource` already depend on these truth envelopes. - `RedactionIntegrity` already defines reusable disclosure notes for protected values and support diagnostics. **Alternatives considered**: - Introduce a customer-only presenter or status taxonomy. - Rejected: duplicates shared artifact truth and increases review drift risk. - Inline page-local disclosure strings only. - Rejected: likely to diverge from existing review and pack semantics. ## Decision 6 — Keep review-pack consumption on the existing signed download path **Decision**: Pack consumption should stay on the existing signed route and download controller, with the workspace page only generating or surfacing the already-authorized download path through [../../apps/platform/app/Services/ReviewPackService.php](../../apps/platform/app/Services/ReviewPackService.php). **Rationale**: - The repo already has a real signed download route and a dedicated download controller. - Reusing that path keeps the new page consumption-only and avoids inventing a customer-specific download endpoint. - The page must not trigger pack generation or regeneration. **Evidence**: - [../../apps/platform/app/Services/ReviewPackService.php](../../apps/platform/app/Services/ReviewPackService.php) already generates signed download URLs. - [../../apps/platform/routes/web.php](../../apps/platform/routes/web.php) already exposes `/admin/review-packs/{reviewPack}/download` as the signed download route. - [../../apps/platform/app/Http/Controllers/ReviewPackDownloadController.php](../../apps/platform/app/Http/Controllers/ReviewPackDownloadController.php) already enforces pack readiness and expiry constraints. **Alternatives considered**: - Add a new workspace-page-specific download endpoint. - Rejected: duplicates current signed download behavior. - Offer generate/regenerate from the workspace page. - Rejected: out of scope and not customer-safe for v1. ## Decision 7 — Reuse the current audit pipeline and add new action IDs only if needed **Decision**: Reuse `WorkspaceAuditLogger` and `AuditActionId` for any explicit artifact access or download events surfaced by the new page, and only add new stable action IDs if the existing review/export path does not already provide a truthful event. **Rationale**: - The repo already has a canonical workspace-scoped audit path. - This slice needs auditability for explicit artifact consumption, not a new access-analytics subsystem. - Stable action IDs are preferable to page-local logging if an additional event is truly required. **Evidence**: - [../../apps/platform/app/Services/TenantReviews/TenantReviewService.php](../../apps/platform/app/Services/TenantReviews/TenantReviewService.php) already logs review creation/refresh through `WorkspaceAuditLogger`. - [../../apps/platform/app/Services/ReviewPackService.php](../../apps/platform/app/Services/ReviewPackService.php) already logs review-pack export activity. - [../../apps/platform/app/Support/Audit/AuditActionId.php](../../apps/platform/app/Support/Audit/AuditActionId.php) is the stable audit action registry. **Alternatives considered**: - Add a new customer-review audit table. - Rejected: violates the no-new-persistence rule. - Emit page-render audits for every visit. - Rejected: too noisy and not aligned with the explicit-artifact-access requirement. ## Decision 8 — Keep the slice Filament-native, asset-light, and non-searchable **Decision**: Keep the slice on the existing Filament v5 / Livewire v4 stack, do not add a new Resource or global-search entry, and plan for no new asset bundle unless implementation proves otherwise. **Rationale**: - The feature is a new page over existing truth, not a new object family. - Existing review, pack, and evidence Resources already disable global search because they are tenant-scoped. - A native page avoids a second shell and keeps the deploy story unchanged. **Evidence**: - `TenantReviewResource`, `ReviewPackResource`, and `EvidenceSnapshotResource` already set global search off. - Panel providers are already registered in [../../apps/platform/bootstrap/providers.php](../../apps/platform/bootstrap/providers.php). - The repo’s Filament guidance already expects provider registration to remain in `bootstrap/providers.php` and assets to stay minimal unless explicitly registered. **Alternatives considered**: - Add a new searchable Resource just for the workspace page. - Rejected: the surface is a page-level dashboard, not a new record type. - Add a custom asset bundle or custom portal shell up front. - Rejected: unnecessary for the first read-only slice.