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

12 KiB
Raw Blame History

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:

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 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:

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:

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.
  • 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.

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:

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:

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.
  • 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.