Add `CustomerReviewWorkspace` page for tenant pre-filtered reviews Add customer workspace links to `EvidenceSnapshotResource`, `ReviewPackResource`, and `TenantReviewResource` Implement audit logging for `TenantReviewOpened` and `ReviewPackDownloaded` actions Update ReviewPack download controller to enforce tenant-scoped RBAC Add tests for ReviewPack download authorization and audit logging Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #289
12 KiB
Research — Customer Review Workspace v1
Date: 2026-04-27
Spec: 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 already provides the workspace review register pattern.
- ../../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 already registers the existing panel providers. No new provider registration is needed.
Alternatives considered:
- Extend
ReviewRegisterinto 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 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-loadstenant,evidenceSnapshot, andcurrentExportReviewPack.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
TenantReviewmodel 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 already exposes
published()andcurrentExportReviewPack(). - ../../apps/platform/app/Services/TenantReviews/TenantReviewService.php already stores review summary payloads, evidence basis, and export readiness.
- Existing review composition already emits
finding_outcomesand 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 uses canonical admin filter-state sync on mount.
- ../../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, SurfaceCompressionContext, and ../../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.
RedactionIntegrityalready owns the repo’s protected-value and support-diagnostics notes.
Evidence:
ArtifactTruthPresenter::for(...)already supportsTenantReview,ReviewPack, andEvidenceSnapshot.ReviewRegister,EvidenceOverview,TenantReviewResource, andReviewPackResourcealready depend on these truth envelopes.RedactionIntegrityalready 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.
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 already generates signed download URLs.
- ../../apps/platform/routes/web.php already exposes
/admin/review-packs/{reviewPack}/downloadas the signed download route. - ../../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 already logs review creation/refresh through
WorkspaceAuditLogger. - ../../apps/platform/app/Services/ReviewPackService.php already logs review-pack export activity.
- ../../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, andEvidenceSnapshotResourcealready set global search off.- Panel providers are already registered in ../../apps/platform/bootstrap/providers.php.
- The repo’s Filament guidance already expects provider registration to remain in
bootstrap/providers.phpand 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.