TenantAtlas/specs/249-customer-review-workspace/research.md
ahmido aacd82849a
Some checks failed
Main Confidence / confidence (push) Failing after 54s
feat(reviews): add CustomerReviewWorkspace with audit logging and RBAC enforcement (#289)
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
2026-04-28 07:15:41 +00:00

166 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 repos 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 repos 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.