Compare commits

...

28 Commits

Author SHA1 Message Date
Ahmed Darrazi
7940ea7755 feat: productize customer review workspace
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 58s
2026-04-30 20:14:02 +02:00
Ahmed Darrazi
1bf369b561 Merge remote-tracking branch 'origin/dev' into platform-dev
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 57s
2026-04-30 16:36:03 +02:00
Ahmed Darrazi
a2bb5b7729 chore: commit all changes (automated)
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 59s
2026-04-30 16:25:12 +02:00
Ahmed Darrazi
bb78049271 Merge remote-tracking branch 'origin/dev' into platform-dev
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m0s
# Conflicts:
#	apps/platform/app/Support/Navigation/CanonicalNavigationContext.php
2026-04-30 09:50:04 +02:00
7d17d39060 feat(specs/043): cross tenant compare and promotion (#307)
Implements platform feature branch `feat/043-cross-tenant-compare-and-promotion`.

Target branch: `platform-dev`.

Follow-up integration path after merge:

`platform-dev` → `dev`.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #307
2026-04-30 07:45:15 +00:00
Ahmed Darrazi
a35cd88bff Merge remote-tracking branch 'origin/dev' into platform-dev
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m3s
2026-04-30 00:43:39 +02:00
926b0fe4f3 feat(specs/257): governance decision convergence (#304)
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 56s
Automatisch erstellter PR: Implementiert Spec 257 — Governance decision convergence.

Branch: 257-governance-decision-convergence

Bitte Review und Merge gegen `platform-dev`.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #304
2026-04-29 22:36:05 +00:00
Ahmed Darrazi
a74a6791ad Merge remote-tracking branch 'origin/dev' into platform-dev
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m49s
2026-04-29 22:50:20 +02:00
52ebf63af1 feat(specs/256): external support desk handoff (#301)
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 2m6s
Implement external support desk handoff (spec 256). Created and pushed branch `256-external-support-desk-handoff`.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #301
2026-04-29 20:16:40 +00:00
Ahmed Darrazi
2e2b125107 Merge remote-tracking branch 'origin/dev' into platform-dev
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m15s
2026-04-29 14:58:56 +02:00
Ahmed Darrazi
4b0dc2a62e chore: commit workspace changes (automated)
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 53s
2026-04-29 14:56:17 +02:00
Ahmed Darrazi
34351a281d Merge remote-tracking branch 'origin/dev' into platform-dev
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 56s
2026-04-29 14:37:00 +02:00
51ea80ca05 Automatische PR: 255-enforce-finding-creation-invariants → platform-dev (#298)
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m5s
Automatisch erstellt: Commit & Push aus Workspace (WIP)

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #298
2026-04-29 12:26:21 +00:00
Ahmed Darrazi
e36bd3ca9c merge: sync dev into platform-dev
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 56s
2026-04-29 09:47:47 +02:00
b511b08371 feat: remove findings acknowledged compatibility and unify canonical operation types (#296)
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m0s
This PR removes the legacy "acknowledged" status compatibility for findings and unifies the canonical operation types (e.g., transitioning from baseline_capture to baseline.capture). It includes updated tests, models, and services to reflect these changes.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #296
2026-04-29 07:34:39 +00:00
Ahmed Darrazi
f53f149f99 Merge remote-tracking branch 'origin/platform-dev' into platform-dev
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m3s
# Conflicts:
#	.github/agents/copilot-instructions.md
2026-04-29 00:08:57 +02:00
2fa8fc0f87 refactor: remove findings lifecycle backfill runtime surfaces (#294)
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 51s
## Summary
- decommission the legacy findings lifecycle backfill substrate across command, job, service, and UI layers
- remove related platform capabilities, operation catalog entries, and action surface exemptions
- add regression and removal verification tests to ensure runtime integrity and surface absence
- include spec, plan, tasks, and data-model artifacts for the removal slice

## Scope
- active spec: specs/253-remove-findings-backfill-runtime-surfaces
- target branch: dev

## Validation
- integrated regression and removal verification tests for console, findings, and system ops surfaces
- audit log and capability trace verification for the removal path

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #294
2026-04-28 22:00:51 +00:00
Ahmed Darrazi
44e6a1eb05 Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-28 21:46:29 +02:00
Ahmed Darrazi
4f7c1a6c94 Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-28 15:41:58 +02:00
Ahmed Darrazi
4325e1ed8d Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-28 12:18:08 +02:00
Ahmed Darrazi
4ae4c2ee95 chore: add gitea MCP helper script
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 58s
2026-04-28 09:26:51 +02:00
Ahmed Darrazi
32b6dcb937 Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-28 09:22:09 +02:00
Ahmed Darrazi
f7bc4f2787 Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-27 23:22:08 +02:00
Ahmed Darrazi
0739018ee5 Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-27 19:36:43 +02:00
Ahmed Darrazi
9a02261f5c Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-27 15:03:58 +02:00
Ahmed Darrazi
65ec1d5904 Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-27 10:33:23 +02:00
Ahmed Darrazi
f05857c276 Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-27 02:13:30 +02:00
Ahmed Darrazi
9f5d3293c5 Merge remote-tracking branch 'origin/dev' into platform-dev 2026-04-26 22:53:42 +02:00
67 changed files with 3117 additions and 377 deletions

View File

@ -1,5 +1,10 @@
# Research T186 — settings_apply capability verification (LEGACY / DEPRECATED) # Research T186 — settings_apply capability verification (LEGACY / DEPRECATED)
> **Status:** Superseded
> **Last reviewed:** 2026-04-30
> **Use for:** Historical investigation context only if a later Settings Catalog write-path regression needs provenance
> **Do not use for:** Active feature research or current implementation truth
> DEPRECATED: Do not add new research notes under `.specify/`. > DEPRECATED: Do not add new research notes under `.specify/`.
> Active feature research should live under `specs/<NNN>-<slug>/`. > Active feature research should live under `specs/<NNN>-<slug>/`.
> Legacy history lives under `spechistory/`. > Legacy history lives under `spechistory/`.

View File

@ -5,13 +5,17 @@
namespace App\Filament\Pages\Reviews; namespace App\Filament\Pages\Reviews;
use App\Filament\Resources\TenantReviewResource; use App\Filament\Resources\TenantReviewResource;
use App\Models\EvidenceSnapshot;
use App\Models\FindingException;
use App\Models\Tenant; use App\Models\Tenant;
use App\Models\ReviewPack; use App\Models\ReviewPack;
use App\Models\TenantReview; use App\Models\TenantReview;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\ReviewPackService; use App\Services\ReviewPackService;
use App\Services\TenantReviews\TenantReviewRegisterService; use App\Services\TenantReviews\TenantReviewRegisterService;
use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\Findings\FindingOutcomeSemantics; use App\Support\Findings\FindingOutcomeSemantics;
use App\Support\Filament\TablePaginationProfiles; use App\Support\Filament\TablePaginationProfiles;
@ -36,6 +40,7 @@
use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use UnitEnum; use UnitEnum;
@ -45,7 +50,7 @@ class CustomerReviewWorkspace extends Page implements HasTable
public const string DETAIL_CONTEXT_QUERY_KEY = 'customer_workspace'; public const string DETAIL_CONTEXT_QUERY_KEY = 'customer_workspace';
private const string SOURCE_SURFACE = 'customer_review_workspace'; public const string SOURCE_SURFACE = 'customer_review_workspace';
protected static bool $isDiscovered = false; protected static bool $isDiscovered = false;
@ -109,6 +114,7 @@ public function mount(): void
$this->authorizePageAccess(); $this->authorizePageAccess();
$this->applyRequestedTenantPrefilter(); $this->applyRequestedTenantPrefilter();
$this->mountInteractsWithTable(); $this->mountInteractsWithTable();
$this->auditWorkspaceOpen();
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
@ -166,6 +172,10 @@ public function table(Table $table): Table
->label(__('localization.review.accepted_risks')) ->label(__('localization.review.accepted_risks'))
->getStateUsing(fn (Tenant $record): string => $this->acceptedRiskSummary($record)) ->getStateUsing(fn (Tenant $record): string => $this->acceptedRiskSummary($record))
->wrap(), ->wrap(),
TextColumn::make('evidence_proof_state')
->label(__('localization.review.evidence_proof'))
->getStateUsing(fn (Tenant $record): string => $this->evidenceProofAvailability($record))
->wrap(),
TextColumn::make('published_at') TextColumn::make('published_at')
->label(__('localization.review.published')) ->label(__('localization.review.published'))
->getStateUsing(fn (Tenant $record): ?string => $this->latestPublishedAt($record)?->toDateTimeString()) ->getStateUsing(fn (Tenant $record): ?string => $this->latestPublishedAt($record)?->toDateTimeString())
@ -173,7 +183,8 @@ public function table(Table $table): Table
->placeholder('—'), ->placeholder('—'),
TextColumn::make('review_pack_state') TextColumn::make('review_pack_state')
->label(__('localization.review.review_pack')) ->label(__('localization.review.review_pack'))
->getStateUsing(fn (Tenant $record): string => $this->reviewPackAvailability($record)), ->getStateUsing(fn (Tenant $record): string => $this->reviewPackAvailability($record))
->wrap(),
]) ])
->filters([ ->filters([
SelectFilter::make('tenant_id') SelectFilter::make('tenant_id')
@ -260,6 +271,32 @@ private function authorizePageAccess(): void
} }
} }
private function auditWorkspaceOpen(): void
{
$user = auth()->user();
$workspace = $this->workspace();
if (! $user instanceof User || ! $workspace instanceof Workspace) {
return;
}
app(WorkspaceAuditLogger::class)->log(
workspace: $workspace,
action: AuditActionId::CustomerReviewWorkspaceOpened,
context: [
'metadata' => [
'source_surface' => self::SOURCE_SURFACE,
'tenant_filter_id' => $this->currentTenantFilterId(),
'entitled_tenant_count' => count($this->authorizedTenants()),
],
],
actor: $user,
resourceType: 'customer_review_workspace',
resourceId: (string) $workspace->getKey(),
targetLabel: __('localization.review.customer_review_workspace'),
);
}
private function workspaceQuery(): Builder private function workspaceQuery(): Builder
{ {
$user = auth()->user(); $user = auth()->user();
@ -518,31 +555,116 @@ private function acceptedRiskSummary(Tenant $tenant): string
$validGovernedCount = (int) ($riskAcceptance['valid_governed_count'] ?? 0); $validGovernedCount = (int) ($riskAcceptance['valid_governed_count'] ?? 0);
$warningCount = (int) ($riskAcceptance['warning_count'] ?? 0); $warningCount = (int) ($riskAcceptance['warning_count'] ?? 0);
return match (true) { $countSummary = match (true) {
$statusMarkedCount === 0 => __('localization.review.no_accepted_risks_recorded'), $statusMarkedCount === 0 => __('localization.review.no_accepted_risks_recorded'),
$warningCount > 0 => __('localization.review.accepted_risks_need_follow_up', ['warnings' => $warningCount, 'total' => $statusMarkedCount]), $warningCount > 0 => __('localization.review.accepted_risks_need_follow_up', ['warnings' => $warningCount, 'total' => $statusMarkedCount]),
$validGovernedCount > 0 => __('localization.review.accepted_risks_governed', ['count' => $validGovernedCount]), $validGovernedCount > 0 => __('localization.review.accepted_risks_governed', ['count' => $validGovernedCount]),
default => __('localization.review.accepted_risks_on_record', ['count' => $statusMarkedCount]), default => __('localization.review.accepted_risks_on_record', ['count' => $statusMarkedCount]),
}; };
$accountability = $this->acceptedRiskAccountability($tenant);
return $accountability === null
? $countSummary
: $countSummary.' '.$accountability;
} }
private function reviewPackAvailability(Tenant $tenant): string private function reviewPackAvailability(Tenant $tenant): string
{ {
if (! $this->latestPublishedReview($tenant) instanceof TenantReview) {
return __('localization.review.no_published_review_available');
}
$pack = $this->latestReviewPack($tenant); $pack = $this->latestReviewPack($tenant);
$user = auth()->user();
if (! $pack instanceof ReviewPack) { if (! $pack instanceof ReviewPack) {
return __('localization.review.unavailable'); return __('localization.review.no_current_review_pack');
}
if (! $user instanceof User || ! $user->can(Capabilities::REVIEW_PACK_VIEW, $tenant)) {
return __('localization.review.review_pack_access_unavailable');
} }
if ($pack->status !== ReviewPackStatus::Ready->value) { if ($pack->status !== ReviewPackStatus::Ready->value) {
return __('localization.review.unavailable'); return __('localization.review.review_pack_unavailable');
} }
if ($pack->expires_at !== null && $pack->expires_at->isPast()) { if ($pack->expires_at !== null && $pack->expires_at->isPast()) {
return __('localization.review.unavailable'); return __('localization.review.review_pack_expired');
} }
return __('localization.review.available'); return __('localization.review.review_pack_available');
}
private function evidenceProofAvailability(Tenant $tenant): string
{
$review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) {
return __('localization.review.no_published_review_available');
}
$snapshot = $review->evidenceSnapshot;
$user = auth()->user();
if (! $snapshot instanceof EvidenceSnapshot) {
return __('localization.review.evidence_proof_absent');
}
if (! $user instanceof User || ! $user->can(Capabilities::EVIDENCE_VIEW, $tenant)) {
return __('localization.review.evidence_proof_access_unavailable');
}
if ((string) $snapshot->status === 'expired' || ($snapshot->expires_at !== null && $snapshot->expires_at->isPast())) {
return __('localization.review.evidence_proof_expired');
}
return __('localization.review.evidence_proof_available');
}
private function acceptedRiskAccountability(Tenant $tenant): ?string
{
$exception = FindingException::query()
->with(['owner', 'approver', 'currentDecision'])
->where('workspace_id', (int) $tenant->workspace_id)
->where('tenant_id', (int) $tenant->getKey())
->current()
->orderByRaw("case when current_validity_state in ('valid', 'expiring') then 0 else 1 end")
->latest('approved_at')
->latest('requested_at')
->latest('id')
->first();
if (! $exception instanceof FindingException) {
return null;
}
$accountable = $exception->owner?->name
?? $exception->approver?->name;
$decisionType = $exception->currentDecision?->decision_type;
$reviewDue = $exception->review_due_at ?? $exception->expires_at;
$reason = is_string($exception->request_reason) ? trim($exception->request_reason) : '';
$parts = [];
if (is_string($accountable) && trim($accountable) !== '') {
$parts[] = $reviewDue === null
? __('localization.review.accepted_risk_accountable', ['name' => $accountable])
: __('localization.review.accepted_risk_accountable_until', [
'name' => $accountable,
'date' => $reviewDue->toDateString(),
]);
} elseif (is_string($decisionType) && trim($decisionType) !== '') {
$parts[] = __('localization.review.accepted_risk_partial_accountability');
}
if ($reason !== '') {
$parts[] = __('localization.review.accepted_risk_reason', [
'reason' => Str::limit($reason, 160),
]);
}
return $parts === [] ? null : implode(' ', $parts);
} }
private function navigationContext(): ?CanonicalNavigationContext private function navigationContext(): ?CanonicalNavigationContext

View File

@ -174,9 +174,12 @@ public static function infolist(Schema $schema): Schema
->label('Operation') ->label('Operation')
->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—') ->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—')
->url(fn (EvidenceSnapshot $record): ?string => $record->operation_run_id ? OperationRunLinks::tenantlessView((int) $record->operation_run_id) : null) ->url(fn (EvidenceSnapshot $record): ?string => $record->operation_run_id ? OperationRunLinks::tenantlessView((int) $record->operation_run_id) : null)
->openUrlInNewTab(), ->openUrlInNewTab()
TextEntry::make('fingerprint')->copyable()->placeholder('—')->columnSpanFull()->fontFamily('mono'), ->hidden(fn (): bool => static::isCustomerWorkspaceFlow()),
TextEntry::make('previous_fingerprint')->copyable()->placeholder('—')->columnSpanFull()->fontFamily('mono'), TextEntry::make('fingerprint')->copyable()->placeholder('—')->columnSpanFull()->fontFamily('mono')
->hidden(fn (): bool => static::isCustomerWorkspaceFlow()),
TextEntry::make('previous_fingerprint')->copyable()->placeholder('—')->columnSpanFull()->fontFamily('mono')
->hidden(fn (): bool => static::isCustomerWorkspaceFlow()),
]) ])
->columns(2), ->columns(2),
Section::make('Summary') Section::make('Summary')
@ -222,6 +225,7 @@ public static function infolist(Schema $schema): Schema
->label('Raw summary JSON') ->label('Raw summary JSON')
->view('filament.infolists.entries.snapshot-json') ->view('filament.infolists.entries.snapshot-json')
->state(fn (EvidenceSnapshotItem $record): array => is_array($record->summary_payload) ? $record->summary_payload : []) ->state(fn (EvidenceSnapshotItem $record): array => is_array($record->summary_payload) ? $record->summary_payload : [])
->hidden(fn (): bool => static::isCustomerWorkspaceFlow())
->columnSpanFull(), ->columnSpanFull(),
]) ])
->columns(4), ->columns(4),
@ -236,7 +240,7 @@ public static function relatedContextEntries(EvidenceSnapshot $record): array
{ {
$entries = []; $entries = [];
if (is_numeric($record->operation_run_id)) { if (! static::isCustomerWorkspaceFlow() && is_numeric($record->operation_run_id)) {
$entries[] = RelatedContextEntry::available( $entries[] = RelatedContextEntry::available(
key: 'operation_run', key: 'operation_run',
label: 'Operation', label: 'Operation',
@ -255,12 +259,20 @@ public static function relatedContextEntries(EvidenceSnapshot $record): array
->first(); ->first();
if ($pack instanceof \App\Models\ReviewPack && $pack->tenant instanceof Tenant) { if ($pack instanceof \App\Models\ReviewPack && $pack->tenant instanceof Tenant) {
$packUrl = ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $pack->tenant);
if (static::isCustomerWorkspaceFlow()) {
$packUrl = static::appendQuery($packUrl, [
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
]);
}
$entries[] = RelatedContextEntry::available( $entries[] = RelatedContextEntry::available(
key: 'review_pack', key: 'review_pack',
label: 'Review pack', label: 'Review pack',
value: sprintf('#%d', (int) $pack->getKey()), value: sprintf('#%d', (int) $pack->getKey()),
secondaryValue: 'Inspect the latest executive-pack output for this evidence basis.', secondaryValue: 'Inspect the latest executive-pack output for this evidence basis.',
targetUrl: ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $pack->tenant), targetUrl: $packUrl,
targetKind: 'direct_record', targetKind: 'direct_record',
priority: 20, priority: 20,
actionLabel: 'View review pack', actionLabel: 'View review pack',
@ -285,6 +297,23 @@ public static function relatedContextEntries(EvidenceSnapshot $record): array
return $entries; return $entries;
} }
public static function isCustomerWorkspaceFlow(): bool
{
return request()->query('source_surface') === CustomerReviewWorkspace::SOURCE_SURFACE;
}
/**
* @param array<string, mixed> $query
*/
private static function appendQuery(string $url, array $query): string
{
if ($query === []) {
return $url;
}
return $url.(str_contains($url, '?') ? '&' : '?').http_build_query($query);
}
public static function table(Table $table): Table public static function table(Table $table): Table
{ {
return $table return $table

View File

@ -5,8 +5,13 @@
namespace App\Filament\Resources\EvidenceSnapshotResource\Pages; namespace App\Filament\Resources\EvidenceSnapshotResource\Pages;
use App\Filament\Resources\EvidenceSnapshotResource; use App\Filament\Resources\EvidenceSnapshotResource;
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Models\EvidenceSnapshot;
use App\Models\Tenant;
use App\Models\User; use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\Evidence\EvidenceSnapshotService; use App\Services\Evidence\EvidenceSnapshotService;
use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\Rbac\UiEnforcement; use App\Support\Rbac\UiEnforcement;
use App\Support\Ui\GovernanceActions\GovernanceActionCatalog; use App\Support\Ui\GovernanceActions\GovernanceActionCatalog;
@ -20,6 +25,13 @@ class ViewEvidenceSnapshot extends ViewRecord
{ {
protected static string $resource = EvidenceSnapshotResource::class; protected static string $resource = EvidenceSnapshotResource::class;
public function mount(int|string $record): void
{
parent::mount($record);
$this->auditCustomerWorkspaceProofOpen();
}
protected function resolveRecord(int|string $key): Model protected function resolveRecord(int|string $key): Model
{ {
return EvidenceSnapshotResource::resolveScopedRecordOrFail($key); return EvidenceSnapshotResource::resolveScopedRecordOrFail($key);
@ -27,6 +39,10 @@ protected function resolveRecord(int|string $key): Model
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
if (EvidenceSnapshotResource::isCustomerWorkspaceFlow()) {
return [];
}
$refreshRule = GovernanceActionCatalog::rule('refresh_evidence'); $refreshRule = GovernanceActionCatalog::rule('refresh_evidence');
$expireRule = GovernanceActionCatalog::rule('expire_snapshot'); $expireRule = GovernanceActionCatalog::rule('expire_snapshot');
@ -90,4 +106,41 @@ protected function getHeaderActions(): array
->apply(), ->apply(),
]; ];
} }
private function auditCustomerWorkspaceProofOpen(): void
{
if (! EvidenceSnapshotResource::isCustomerWorkspaceFlow()) {
return;
}
$record = $this->record;
$user = auth()->user();
if (! $record instanceof EvidenceSnapshot || ! $user instanceof User) {
return;
}
$tenant = $record->tenant;
if (! $tenant instanceof Tenant) {
return;
}
app(WorkspaceAuditLogger::class)->log(
workspace: $tenant->workspace,
action: AuditActionId::EvidenceSnapshotOpened,
context: [
'metadata' => [
'evidence_snapshot_id' => (int) $record->getKey(),
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
],
],
actor: $user,
resourceType: 'evidence_snapshot',
resourceId: (string) $record->getKey(),
targetLabel: sprintf('Evidence snapshot #%d', (int) $record->getKey()),
tenant: $tenant,
operationRunId: $record->operation_run_id !== null ? (int) $record->operation_run_id : null,
);
}
} }

View File

@ -148,7 +148,8 @@ public static function infolist(Schema $schema): Schema
TextEntry::make('file_size') TextEntry::make('file_size')
->label('File size') ->label('File size')
->formatStateUsing(fn ($state): string => $state ? Number::fileSize((int) $state) : '—'), ->formatStateUsing(fn ($state): string => $state ? Number::fileSize((int) $state) : '—'),
TextEntry::make('sha256')->label('SHA-256')->copyable()->placeholder('—'), TextEntry::make('sha256')->label('SHA-256')->copyable()->placeholder('—')
->hidden(fn (): bool => static::isCustomerWorkspaceFlow()),
]) ])
->columns(2) ->columns(2)
->columnSpanFull(), ->columnSpanFull(),
@ -184,6 +185,7 @@ public static function infolist(Schema $schema): Schema
->formatStateUsing(fn ($state): string => $state ? 'Yes' : 'No'), ->formatStateUsing(fn ($state): string => $state ? 'Yes' : 'No'),
]) ])
->columns(2) ->columns(2)
->hidden(fn (): bool => static::isCustomerWorkspaceFlow())
->columnSpanFull(), ->columnSpanFull(),
Section::make('Metadata') Section::make('Metadata')
@ -227,9 +229,12 @@ public static function infolist(Schema $schema): Schema
return OperationRunLinks::tenantlessView((int) $record->operation_run_id); return OperationRunLinks::tenantlessView((int) $record->operation_run_id);
}) })
->openUrlInNewTab() ->openUrlInNewTab()
->hidden(fn (): bool => static::isCustomerWorkspaceFlow())
->placeholder('—'), ->placeholder('—'),
TextEntry::make('fingerprint')->label('Fingerprint')->copyable()->placeholder('—'), TextEntry::make('fingerprint')->label('Fingerprint')->copyable()->placeholder('—')
TextEntry::make('previous_fingerprint')->label('Previous fingerprint')->copyable()->placeholder('—'), ->hidden(fn (): bool => static::isCustomerWorkspaceFlow()),
TextEntry::make('previous_fingerprint')->label('Previous fingerprint')->copyable()->placeholder('—')
->hidden(fn (): bool => static::isCustomerWorkspaceFlow()),
TextEntry::make('created_at')->label('Created')->dateTime(), TextEntry::make('created_at')->label('Created')->dateTime(),
]) ])
->columns(2) ->columns(2)
@ -243,9 +248,7 @@ public static function infolist(Schema $schema): Schema
TextEntry::make('evidenceSnapshot.id') TextEntry::make('evidenceSnapshot.id')
->label('Snapshot') ->label('Snapshot')
->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—') ->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—')
->url(fn (ReviewPack $record): ?string => $record->evidenceSnapshot ->url(fn (ReviewPack $record): ?string => static::evidenceSnapshotUrl($record)),
? TenantEvidenceSnapshotResource::getUrl('view', ['record' => $record->evidenceSnapshot], tenant: $record->tenant)
: null),
TextEntry::make('evidenceSnapshot.completeness_state') TextEntry::make('evidenceSnapshot.completeness_state')
->label('Snapshot completeness') ->label('Snapshot completeness')
->badge() ->badge()
@ -429,6 +432,36 @@ public static function getPages(): array
]; ];
} }
public static function isCustomerWorkspaceFlow(): bool
{
return request()->query('source_surface') === CustomerReviewWorkspace::SOURCE_SURFACE;
}
private static function evidenceSnapshotUrl(ReviewPack $record): ?string
{
if (! $record->evidenceSnapshot) {
return null;
}
$url = TenantEvidenceSnapshotResource::getUrl('view', ['record' => $record->evidenceSnapshot], tenant: $record->tenant);
return static::isCustomerWorkspaceFlow()
? static::appendQuery($url, ['source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE])
: $url;
}
/**
* @param array<string, mixed> $query
*/
private static function appendQuery(string $url, array $query): string
{
if ($query === []) {
return $url;
}
return $url.(str_contains($url, '?') ? '&' : '?').http_build_query($query);
}
private static function truthEnvelope(ReviewPack $record, bool $fresh = false): ArtifactTruthEnvelope private static function truthEnvelope(ReviewPack $record, bool $fresh = false): ArtifactTruthEnvelope
{ {
$presenter = app(ArtifactTruthPresenter::class); $presenter = app(ArtifactTruthPresenter::class);

View File

@ -19,6 +19,20 @@ class ViewReviewPack extends ViewRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
if (ReviewPackResource::isCustomerWorkspaceFlow()) {
return [
Actions\Action::make('download')
->label('Download')
->icon('heroicon-o-arrow-down-tray')
->color('primary')
->visible(fn (): bool => $this->record->status === ReviewPackStatus::Ready->value)
->url(fn (): string => app(ReviewPackService::class)->generateDownloadUrl($this->record, [
'source_surface' => \App\Filament\Pages\Reviews\CustomerReviewWorkspace::SOURCE_SURFACE,
]))
->openUrlInNewTab(),
];
}
$regenerateAction = UiEnforcement::forAction( $regenerateAction = UiEnforcement::forAction(
Actions\Action::make('regenerate') Actions\Action::make('regenerate')
->label('Regenerate') ->label('Regenerate')

View File

@ -215,6 +215,7 @@ public static function infolist(Schema $schema): Schema
TextEntry::make('fingerprint') TextEntry::make('fingerprint')
->copyable() ->copyable()
->placeholder('—') ->placeholder('—')
->hidden(fn (): bool => static::isCustomerWorkspaceMode())
->columnSpanFull() ->columnSpanFull()
->fontFamily('mono') ->fontFamily('mono')
->size(TextSize::ExtraSmall), ->size(TextSize::ExtraSmall),
@ -647,12 +648,19 @@ private static function summaryPresentation(TenantReview $record): array
return [ return [
'operator_explanation' => $truthEnvelope->operatorExplanation?->toArray(), 'operator_explanation' => $truthEnvelope->operatorExplanation?->toArray(),
'compressed_outcome' => static::compressedOutcome($record)->toArray(), 'compressed_outcome' => static::compressedOutcome($record)->toArray(),
'reason_semantics' => $reasonPresenter->semantics($truthEnvelope->reason?->toReasonResolutionEnvelope()), 'customer_workspace_mode' => static::isCustomerWorkspaceMode(),
'reason_semantics' => static::isCustomerWorkspaceMode()
? []
: $reasonPresenter->semantics($truthEnvelope->reason?->toReasonResolutionEnvelope()),
'highlights' => $highlights, 'highlights' => $highlights,
'next_actions' => is_array($summary['recommended_next_actions'] ?? null) ? $summary['recommended_next_actions'] : [], 'next_actions' => is_array($summary['recommended_next_actions'] ?? null) ? $summary['recommended_next_actions'] : [],
'publish_blockers' => is_array($summary['publish_blockers'] ?? null) ? $summary['publish_blockers'] : [], 'publish_blockers' => is_array($summary['publish_blockers'] ?? null) ? $summary['publish_blockers'] : [],
'context_links' => static::summaryContextLinks($record), 'context_links' => static::summaryContextLinks($record, static::isCustomerWorkspaceMode()),
'metrics' => [ 'metrics' => static::isCustomerWorkspaceMode() ? [
['label' => __('localization.review.findings'), 'value' => (string) ($summary['finding_count'] ?? 0)],
['label' => __('localization.review.pending_verification'), 'value' => (string) ($findingOutcomes[FindingOutcomeSemantics::OUTCOME_RESOLVED_PENDING_VERIFICATION] ?? 0)],
['label' => __('localization.review.verified_cleared'), 'value' => (string) ($findingOutcomes[FindingOutcomeSemantics::OUTCOME_VERIFIED_CLEARED] ?? 0)],
] : [
['label' => __('localization.review.findings'), 'value' => (string) ($summary['finding_count'] ?? 0)], ['label' => __('localization.review.findings'), 'value' => (string) ($summary['finding_count'] ?? 0)],
['label' => __('localization.review.reports'), 'value' => (string) ($summary['report_count'] ?? 0)], ['label' => __('localization.review.reports'), 'value' => (string) ($summary['report_count'] ?? 0)],
['label' => __('localization.review.operations'), 'value' => (string) ($summary['operation_count'] ?? 0)], ['label' => __('localization.review.operations'), 'value' => (string) ($summary['operation_count'] ?? 0)],
@ -664,13 +672,13 @@ private static function summaryPresentation(TenantReview $record): array
} }
/** /**
* @return array<int, array{title:string,label:string,url:string,description:string}> * @return array<int, array{title:string,label:string,url:?string,description:string}>
*/ */
private static function summaryContextLinks(TenantReview $record): array private static function summaryContextLinks(TenantReview $record, bool $customerWorkspaceMode = false): array
{ {
$links = []; $links = [];
if (is_numeric($record->operation_run_id)) { if (! $customerWorkspaceMode && is_numeric($record->operation_run_id)) {
$links[] = [ $links[] = [
'title' => __('localization.review.operation'), 'title' => __('localization.review.operation'),
'label' => __('localization.review.open_operation'), 'label' => __('localization.review.open_operation'),
@ -679,7 +687,7 @@ private static function summaryContextLinks(TenantReview $record): array
]; ];
} }
if ($record->currentExportReviewPack && $record->tenant) { if (! $customerWorkspaceMode && $record->currentExportReviewPack && $record->tenant) {
$links[] = [ $links[] = [
'title' => __('localization.review.executive_pack'), 'title' => __('localization.review.executive_pack'),
'label' => __('localization.review.view_executive_pack'), 'label' => __('localization.review.view_executive_pack'),
@ -698,11 +706,25 @@ private static function summaryContextLinks(TenantReview $record): array
} }
if ($record->evidenceSnapshot && $record->tenant) { if ($record->evidenceSnapshot && $record->tenant) {
$user = auth()->user();
$canViewEvidence = $user instanceof User && $user->can(Capabilities::EVIDENCE_VIEW, $record->tenant);
$evidenceUrl = $canViewEvidence
? EvidenceSnapshotResource::getUrl('view', ['record' => $record->evidenceSnapshot], tenant: $record->tenant)
: null;
if ($customerWorkspaceMode && $evidenceUrl !== null) {
$evidenceUrl = static::appendQuery($evidenceUrl, [
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
]);
}
$links[] = [ $links[] = [
'title' => __('localization.review.evidence_snapshot'), 'title' => __('localization.review.evidence_snapshot'),
'label' => __('localization.review.view_evidence_snapshot'), 'label' => __('localization.review.view_evidence_snapshot'),
'url' => EvidenceSnapshotResource::getUrl('view', ['record' => $record->evidenceSnapshot], tenant: $record->tenant), 'url' => $evidenceUrl,
'description' => __('localization.review.evidence_snapshot_description'), 'description' => $canViewEvidence
? __('localization.review.evidence_snapshot_description')
: __('localization.review.evidence_proof_access_unavailable'),
]; ];
} }
@ -783,4 +805,21 @@ private static function findingOutcomeSummary(array $summary): ?string
return app(FindingOutcomeSemantics::class)->compactOutcomeSummary($outcomeCounts); return app(FindingOutcomeSemantics::class)->compactOutcomeSummary($outcomeCounts);
} }
private static function isCustomerWorkspaceMode(): bool
{
return request()->boolean(CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY);
}
/**
* @param array<string, mixed> $query
*/
private static function appendQuery(string $url, array $query): string
{
if ($query === []) {
return $url;
}
return $url.(str_contains($url, '?') ? '&' : '?').http_build_query($query);
}
} }

View File

@ -6,15 +6,18 @@
use App\Filament\Pages\Reviews\CustomerReviewWorkspace; use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\TenantReviewResource; use App\Filament\Resources\TenantReviewResource;
use App\Models\ReviewPack;
use App\Models\Tenant; use App\Models\Tenant;
use App\Models\TenantReview; use App\Models\TenantReview;
use App\Models\User; use App\Models\User;
use App\Services\ReviewPackService;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\TenantReviews\TenantReviewLifecycleService; use App\Services\TenantReviews\TenantReviewLifecycleService;
use App\Services\TenantReviews\TenantReviewService; use App\Services\TenantReviews\TenantReviewService;
use App\Support\Audit\AuditActionId; use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\Rbac\UiEnforcement; use App\Support\Rbac\UiEnforcement;
use App\Support\ReviewPackStatus;
use App\Support\TenantReviewStatus; use App\Support\TenantReviewStatus;
use App\Support\Ui\GovernanceActions\GovernanceActionCatalog; use App\Support\Ui\GovernanceActions\GovernanceActionCatalog;
use Filament\Actions; use Filament\Actions;
@ -64,6 +67,12 @@ protected function authorizeAccess(): void
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
if ($this->isCustomerWorkspaceView()) {
return [
$this->downloadCurrentReviewPackAction(),
];
}
$secondaryActions = $this->secondaryLifecycleActions(); $secondaryActions = $this->secondaryLifecycleActions();
return array_values(array_filter([ return array_values(array_filter([
@ -343,6 +352,74 @@ private function archiveReviewAction(): Actions\Action
->apply(); ->apply();
} }
private function downloadCurrentReviewPackAction(): Actions\Action
{
return Actions\Action::make('download_current_review_pack')
->label(__('localization.review.download_current_review_pack'))
->icon('heroicon-o-arrow-down-tray')
->color('primary')
->disabled(fn (): bool => $this->currentReviewPackDownloadUrl() === null)
->tooltip(fn (): ?string => $this->currentReviewPackUnavailableReason())
->url(fn (): ?string => $this->currentReviewPackDownloadUrl())
->openUrlInNewTab();
}
private function currentReviewPackDownloadUrl(): ?string
{
$pack = $this->record->currentExportReviewPack;
$tenant = $this->record->tenant;
$user = auth()->user();
if (! $pack instanceof ReviewPack || ! $tenant instanceof Tenant || ! $user instanceof User) {
return null;
}
if (! $user->can(Capabilities::REVIEW_PACK_VIEW, $tenant)) {
return null;
}
if ($pack->status !== ReviewPackStatus::Ready->value) {
return null;
}
if ($pack->expires_at !== null && $pack->expires_at->isPast()) {
return null;
}
return app(ReviewPackService::class)->generateDownloadUrl($pack, [
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
]);
}
private function currentReviewPackUnavailableReason(): ?string
{
if ($this->currentReviewPackDownloadUrl() !== null) {
return null;
}
$pack = $this->record->currentExportReviewPack;
$tenant = $this->record->tenant;
$user = auth()->user();
if (! $pack instanceof ReviewPack) {
return __('localization.review.customer_review_pack_missing');
}
if (! $user instanceof User || ! $tenant instanceof Tenant || ! $user->can(Capabilities::REVIEW_PACK_VIEW, $tenant)) {
return __('localization.review.customer_review_pack_forbidden');
}
if ($pack->status !== ReviewPackStatus::Ready->value) {
return __('localization.review.customer_review_pack_not_ready');
}
if ($pack->expires_at !== null && $pack->expires_at->isPast()) {
return __('localization.review.customer_review_pack_expired');
}
return __('localization.review.customer_review_pack_unavailable');
}
private function isCustomerWorkspaceView(): bool private function isCustomerWorkspaceView(): bool
{ {
return request()->boolean(CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY); return request()->boolean(CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY);

View File

@ -91,6 +91,7 @@ enum AuditActionId: string
case EvidenceSnapshotCreated = 'evidence_snapshot.created'; case EvidenceSnapshotCreated = 'evidence_snapshot.created';
case EvidenceSnapshotRefreshed = 'evidence_snapshot.refreshed'; case EvidenceSnapshotRefreshed = 'evidence_snapshot.refreshed';
case EvidenceSnapshotExpired = 'evidence_snapshot.expired'; case EvidenceSnapshotExpired = 'evidence_snapshot.expired';
case EvidenceSnapshotOpened = 'evidence_snapshot.opened';
case TenantReviewCreated = 'tenant_review.created'; case TenantReviewCreated = 'tenant_review.created';
case TenantReviewRefreshed = 'tenant_review.refreshed'; case TenantReviewRefreshed = 'tenant_review.refreshed';
case TenantReviewPublished = 'tenant_review.published'; case TenantReviewPublished = 'tenant_review.published';
@ -98,6 +99,7 @@ enum AuditActionId: string
case TenantReviewOpened = 'tenant_review.opened'; case TenantReviewOpened = 'tenant_review.opened';
case TenantReviewExported = 'tenant_review.exported'; case TenantReviewExported = 'tenant_review.exported';
case TenantReviewSuccessorCreated = 'tenant_review.successor_created'; case TenantReviewSuccessorCreated = 'tenant_review.successor_created';
case CustomerReviewWorkspaceOpened = 'customer_review_workspace.opened';
case ReviewPackDownloaded = 'review_pack.downloaded'; case ReviewPackDownloaded = 'review_pack.downloaded';
case TenantTriageReviewMarkedReviewed = 'tenant_triage_review.marked_reviewed'; case TenantTriageReviewMarkedReviewed = 'tenant_triage_review.marked_reviewed';
case TenantTriageReviewMarkedFollowUpNeeded = 'tenant_triage_review.marked_follow_up_needed'; case TenantTriageReviewMarkedFollowUpNeeded = 'tenant_triage_review.marked_follow_up_needed';
@ -241,6 +243,7 @@ private static function labels(): array
self::EvidenceSnapshotCreated->value => 'Evidence snapshot created', self::EvidenceSnapshotCreated->value => 'Evidence snapshot created',
self::EvidenceSnapshotRefreshed->value => 'Evidence snapshot refreshed', self::EvidenceSnapshotRefreshed->value => 'Evidence snapshot refreshed',
self::EvidenceSnapshotExpired->value => 'Evidence snapshot expired', self::EvidenceSnapshotExpired->value => 'Evidence snapshot expired',
self::EvidenceSnapshotOpened->value => 'Evidence snapshot opened',
self::TenantReviewCreated->value => 'Tenant review created', self::TenantReviewCreated->value => 'Tenant review created',
self::TenantReviewRefreshed->value => 'Tenant review refreshed', self::TenantReviewRefreshed->value => 'Tenant review refreshed',
self::TenantReviewPublished->value => 'Tenant review published', self::TenantReviewPublished->value => 'Tenant review published',
@ -248,6 +251,7 @@ private static function labels(): array
self::TenantReviewOpened->value => 'Tenant review opened', self::TenantReviewOpened->value => 'Tenant review opened',
self::TenantReviewExported->value => 'Tenant review exported', self::TenantReviewExported->value => 'Tenant review exported',
self::TenantReviewSuccessorCreated->value => 'Tenant review next cycle created', self::TenantReviewSuccessorCreated->value => 'Tenant review next cycle created',
self::CustomerReviewWorkspaceOpened->value => 'Customer review workspace opened',
self::ReviewPackDownloaded->value => 'Review pack downloaded', self::ReviewPackDownloaded->value => 'Review pack downloaded',
self::TenantTriageReviewMarkedReviewed->value => 'Triage review marked reviewed', self::TenantTriageReviewMarkedReviewed->value => 'Triage review marked reviewed',
self::TenantTriageReviewMarkedFollowUpNeeded->value => 'Triage review marked follow-up needed', self::TenantTriageReviewMarkedFollowUpNeeded->value => 'Triage review marked follow-up needed',
@ -337,6 +341,7 @@ private static function summaries(): array
self::EvidenceSnapshotCreated->value => 'Evidence snapshot created', self::EvidenceSnapshotCreated->value => 'Evidence snapshot created',
self::EvidenceSnapshotRefreshed->value => 'Evidence snapshot refreshed', self::EvidenceSnapshotRefreshed->value => 'Evidence snapshot refreshed',
self::EvidenceSnapshotExpired->value => 'Evidence snapshot expired', self::EvidenceSnapshotExpired->value => 'Evidence snapshot expired',
self::EvidenceSnapshotOpened->value => 'Evidence snapshot opened',
self::TenantReviewCreated->value => 'Tenant review created', self::TenantReviewCreated->value => 'Tenant review created',
self::TenantReviewRefreshed->value => 'Tenant review refreshed', self::TenantReviewRefreshed->value => 'Tenant review refreshed',
self::TenantReviewPublished->value => 'Tenant review published', self::TenantReviewPublished->value => 'Tenant review published',
@ -344,6 +349,7 @@ private static function summaries(): array
self::TenantReviewOpened->value => 'Tenant review opened', self::TenantReviewOpened->value => 'Tenant review opened',
self::TenantReviewExported->value => 'Tenant review exported', self::TenantReviewExported->value => 'Tenant review exported',
self::TenantReviewSuccessorCreated->value => 'Tenant review next cycle created', self::TenantReviewSuccessorCreated->value => 'Tenant review next cycle created',
self::CustomerReviewWorkspaceOpened->value => 'Customer review workspace opened',
self::ReviewPackDownloaded->value => 'Review pack downloaded', self::ReviewPackDownloaded->value => 'Review pack downloaded',
self::SupportDiagnosticsOpened->value => 'Support diagnostics opened', self::SupportDiagnosticsOpened->value => 'Support diagnostics opened',
self::SupportRequestCreated->value => 'Support request created', self::SupportRequestCreated->value => 'Support request created',

View File

@ -138,10 +138,12 @@
'latest_review' => 'Letztes Review', 'latest_review' => 'Letztes Review',
'key_findings' => 'Wichtige Findings', 'key_findings' => 'Wichtige Findings',
'accepted_risks' => 'Akzeptierte Risiken', 'accepted_risks' => 'Akzeptierte Risiken',
'evidence_proof' => 'Evidence-Nachweis',
'published' => 'Veröffentlicht', 'published' => 'Veröffentlicht',
'review_pack' => 'Review-Pack', 'review_pack' => 'Review-Pack',
'open_latest_review' => 'Letztes Review öffnen', 'open_latest_review' => 'Letztes Review öffnen',
'download_review_pack' => 'Review-Pack herunterladen', 'download_review_pack' => 'Review-Pack herunterladen',
'download_current_review_pack' => 'Aktuelles Review-Pack herunterladen',
'no_entitled_tenants' => 'Keine berechtigten Tenants passen zu dieser Ansicht', 'no_entitled_tenants' => 'Keine berechtigten Tenants passen zu dieser Ansicht',
'clear_filters_description' => 'Löschen Sie die aktuellen Filter, um zum vollständigen Kundenreview-Workspace für Ihre berechtigten Tenants zurückzukehren.', 'clear_filters_description' => 'Löschen Sie die aktuellen Filter, um zum vollständigen Kundenreview-Workspace für Ihre berechtigten Tenants zurückzukehren.',
'adjust_filters_description' => 'Passen Sie die Filter an, um zum vollständigen Kundenreview-Workspace für Ihre berechtigten Tenants zurückzukehren.', 'adjust_filters_description' => 'Passen Sie die Filter an, um zum vollständigen Kundenreview-Workspace für Ihre berechtigten Tenants zurückzukehren.',
@ -154,8 +156,28 @@
'accepted_risks_need_follow_up' => ':warnings akzeptierte Risiken benötigen Governance-Nacharbeit (:total gesamt).', 'accepted_risks_need_follow_up' => ':warnings akzeptierte Risiken benötigen Governance-Nacharbeit (:total gesamt).',
'accepted_risks_governed' => ':count akzeptierte Risiken sind governed.', 'accepted_risks_governed' => ':count akzeptierte Risiken sind governed.',
'accepted_risks_on_record' => ':count akzeptierte Risiken sind erfasst.', 'accepted_risks_on_record' => ':count akzeptierte Risiken sind erfasst.',
'accepted_risk_accountable' => 'Verantwortlich: :name.',
'accepted_risk_accountable_until' => 'Verantwortlich: :name. Erneute Prüfung bis :date.',
'accepted_risk_reason' => 'Begründung: :reason.',
'accepted_risk_partial_accountability' => 'Die Verantwortlichkeit ist teilweise erfasst; Review-Owner-Details sind nicht vollständig verfügbar.',
'unavailable' => 'Nicht verfügbar', 'unavailable' => 'Nicht verfügbar',
'available' => 'Verfügbar', 'available' => 'Verfügbar',
'review_pack_available' => 'Aktuelles Review-Pack verfügbar',
'no_current_review_pack' => 'Noch kein aktuelles Review-Pack verfügbar',
'review_pack_access_unavailable' => 'Review-Pack-Zugriff ist für dieses Konto nicht verfügbar',
'review_pack_unavailable' => 'Review-Pack ist noch nicht bereit',
'review_pack_expired' => 'Review-Pack abgelaufen',
'evidence_proof_available' => 'Nachweiszusammenfassung verfügbar',
'evidence_proof_absent' => 'Noch keine Nachweiszusammenfassung verknüpft',
'evidence_proof_access_unavailable' => 'Nachweiszugriff ist für dieses Konto nicht verfügbar',
'evidence_proof_expired' => 'Nachweiszusammenfassung abgelaufen',
'customer_review_pack_unavailable' => 'Das aktuelle Review-Pack kann aus diesem kundensicheren Flow nicht heruntergeladen werden.',
'customer_review_pack_missing' => 'Diesem veröffentlichten Review ist noch kein aktuelles Review-Pack zugeordnet.',
'customer_review_pack_not_ready' => 'Das zugeordnete Review-Pack ist noch nicht für den Download bereit.',
'customer_review_pack_expired' => 'Das zugeordnete Review-Pack ist abgelaufen.',
'customer_review_pack_forbidden' => 'Dieses Konto kann das Review lesen, aber das aktuelle Review-Pack nicht herunterladen.',
'released_governance_record' => 'Veröffentlichter Governance-Nachweis',
'released_governance_record_available' => 'Dieses veröffentlichte Review ist für kundensichere Governance-Nutzung verfügbar.',
'outcome_summary' => 'Ergebniszusammenfassung', 'outcome_summary' => 'Ergebniszusammenfassung',
'review' => 'Review', 'review' => 'Review',
'review_date' => 'Review-Datum', 'review_date' => 'Review-Datum',

View File

@ -138,10 +138,12 @@
'latest_review' => 'Latest review', 'latest_review' => 'Latest review',
'key_findings' => 'Key findings', 'key_findings' => 'Key findings',
'accepted_risks' => 'Accepted risks', 'accepted_risks' => 'Accepted risks',
'evidence_proof' => 'Evidence proof',
'published' => 'Published', 'published' => 'Published',
'review_pack' => 'Review pack', 'review_pack' => 'Review pack',
'open_latest_review' => 'Open latest review', 'open_latest_review' => 'Open latest review',
'download_review_pack' => 'Download review pack', 'download_review_pack' => 'Download review pack',
'download_current_review_pack' => 'Download current review pack',
'no_entitled_tenants' => 'No entitled tenants match this view', 'no_entitled_tenants' => 'No entitled tenants match this view',
'clear_filters_description' => 'Clear the current filters to return to the full customer review workspace for your entitled tenants.', 'clear_filters_description' => 'Clear the current filters to return to the full customer review workspace for your entitled tenants.',
'adjust_filters_description' => 'Adjust filters to return to the full customer review workspace for your entitled tenants.', 'adjust_filters_description' => 'Adjust filters to return to the full customer review workspace for your entitled tenants.',
@ -154,8 +156,28 @@
'accepted_risks_need_follow_up' => ':warnings accepted risks need governance follow-up (:total total).', 'accepted_risks_need_follow_up' => ':warnings accepted risks need governance follow-up (:total total).',
'accepted_risks_governed' => ':count accepted risks are governed.', 'accepted_risks_governed' => ':count accepted risks are governed.',
'accepted_risks_on_record' => ':count accepted risks are on record.', 'accepted_risks_on_record' => ':count accepted risks are on record.',
'accepted_risk_accountable' => 'Accountable: :name.',
'accepted_risk_accountable_until' => 'Accountable: :name. Re-review by :date.',
'accepted_risk_reason' => 'Reason: :reason.',
'accepted_risk_partial_accountability' => 'Accountability is partially recorded; review owner details are not fully available.',
'unavailable' => 'Unavailable', 'unavailable' => 'Unavailable',
'available' => 'Available', 'available' => 'Available',
'review_pack_available' => 'Current review pack available',
'no_current_review_pack' => 'No current review pack available yet',
'review_pack_access_unavailable' => 'Review pack access is unavailable for this actor',
'review_pack_unavailable' => 'Review pack is not ready yet',
'review_pack_expired' => 'Review pack expired',
'evidence_proof_available' => 'Proof summary available',
'evidence_proof_absent' => 'No proof summary linked yet',
'evidence_proof_access_unavailable' => 'Proof access is unavailable for this actor',
'evidence_proof_expired' => 'Proof summary expired',
'customer_review_pack_unavailable' => 'The current review pack cannot be downloaded from this customer-safe flow.',
'customer_review_pack_missing' => 'No current review pack is attached to this released review yet.',
'customer_review_pack_not_ready' => 'The attached review pack is not ready for download yet.',
'customer_review_pack_expired' => 'The attached review pack has expired.',
'customer_review_pack_forbidden' => 'This account can read the review but cannot download the current review pack.',
'released_governance_record' => 'Released governance record',
'released_governance_record_available' => 'This released review is available for customer-safe governance consumption.',
'outcome_summary' => 'Outcome summary', 'outcome_summary' => 'Outcome summary',
'review' => 'Review', 'review' => 'Review',
'review_date' => 'Review date', 'review_date' => 'Review date',

View File

@ -10,6 +10,7 @@
$operatorExplanation = is_array($state['operator_explanation'] ?? null) ? $state['operator_explanation'] : []; $operatorExplanation = is_array($state['operator_explanation'] ?? null) ? $state['operator_explanation'] : [];
$compressedOutcome = is_array($state['compressed_outcome'] ?? null) ? $state['compressed_outcome'] : []; $compressedOutcome = is_array($state['compressed_outcome'] ?? null) ? $state['compressed_outcome'] : [];
$reasonSemantics = is_array($state['reason_semantics'] ?? null) ? $state['reason_semantics'] : []; $reasonSemantics = is_array($state['reason_semantics'] ?? null) ? $state['reason_semantics'] : [];
$customerWorkspaceMode = (bool) ($state['customer_workspace_mode'] ?? false);
$decisionDirection = is_string($compressedOutcome['decisionDirection'] ?? null) $decisionDirection = is_string($compressedOutcome['decisionDirection'] ?? null)
? trim((string) $compressedOutcome['decisionDirection']) ? trim((string) $compressedOutcome['decisionDirection'])
: null; : null;
@ -110,14 +111,20 @@
$description = is_string($link['description'] ?? null) ? $link['description'] : null; $description = is_string($link['description'] ?? null) ? $link['description'] : null;
@endphp @endphp
@continue($title === null || $label === null || $url === null) @continue($title === null || $label === null)
<div class="rounded-lg border border-gray-200 bg-white px-4 py-3 shadow-sm dark:border-gray-800 dark:bg-gray-900/70"> <div class="rounded-lg border border-gray-200 bg-white px-4 py-3 shadow-sm dark:border-gray-800 dark:bg-gray-900/70">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">{{ $title }}</div> <div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">{{ $title }}</div>
<div class="mt-2"> <div class="mt-2">
@if ($url !== null)
<x-filament::link :href="$url" icon="heroicon-m-arrow-top-right-on-square"> <x-filament::link :href="$url" icon="heroicon-m-arrow-top-right-on-square">
{{ $label }} {{ $label }}
</x-filament::link> </x-filament::link>
@else
<x-filament::badge color="gray" size="sm">
{{ __('localization.review.unavailable') }}
</x-filament::badge>
@endif
</div> </div>
@if ($description !== null && trim($description) !== '') @if ($description !== null && trim($description) !== '')
@ -130,9 +137,15 @@
@endif @endif
<div class="space-y-2"> <div class="space-y-2">
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">{{ __('localization.review.publication_readiness') }}</div> <div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
{{ $customerWorkspaceMode ? __('localization.review.released_governance_record') : __('localization.review.publication_readiness') }}
</div>
@if ($publishBlockers === [] && $decisionDirection === 'publishable') @if ($customerWorkspaceMode)
<div class="rounded-md border border-emerald-100 bg-emerald-50 px-3 py-2 text-sm text-emerald-800 dark:border-emerald-900/40 dark:bg-emerald-950/30 dark:text-emerald-200">
{{ __('localization.review.released_governance_record_available') }}
</div>
@elseif ($publishBlockers === [] && $decisionDirection === 'publishable')
<div class="rounded-md border border-emerald-100 bg-emerald-50 px-3 py-2 text-sm text-emerald-800 dark:border-emerald-900/40 dark:bg-emerald-950/30 dark:text-emerald-200"> <div class="rounded-md border border-emerald-100 bg-emerald-50 px-3 py-2 text-sm text-emerald-800 dark:border-emerald-900/40 dark:bg-emerald-950/30 dark:text-emerald-200">
{{ __('localization.review.ready_for_publication') }} {{ __('localization.review.ready_for_publication') }}
</div> </div>

View File

@ -57,7 +57,7 @@
Storage::disk('exports')->put('review-packs/customer-review-workspace-smoke.zip', 'PK-test'); Storage::disk('exports')->put('review-packs/customer-review-workspace-smoke.zip', 'PK-test');
ReviewPack::factory()->ready()->create([ $pack = ReviewPack::factory()->ready()->create([
'tenant_id' => (int) $tenantPublished->getKey(), 'tenant_id' => (int) $tenantPublished->getKey(),
'workspace_id' => (int) $tenantPublished->workspace_id, 'workspace_id' => (int) $tenantPublished->workspace_id,
'tenant_review_id' => (int) $publishedReview->getKey(), 'tenant_review_id' => (int) $publishedReview->getKey(),
@ -67,6 +67,8 @@
'file_disk' => 'exports', 'file_disk' => 'exports',
]); ]);
$publishedReview->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
$this->actingAs($user)->withSession([ $this->actingAs($user)->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenantPublished->workspace_id, WorkspaceContext::SESSION_KEY => (int) $tenantPublished->workspace_id,
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [ WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
@ -83,6 +85,8 @@
->waitForText('Customer-safe review workspace') ->waitForText('Customer-safe review workspace')
->assertSee('Clear filters') ->assertSee('Clear filters')
->assertSee('Open latest review') ->assertSee('Open latest review')
->assertSee('Current review pack available')
->assertSee('Proof summary available')
->assertDontSee('Publish review') ->assertDontSee('Publish review')
->assertDontSee('Refresh review') ->assertDontSee('Refresh review')
->click('Clear filters') ->click('Clear filters')
@ -90,6 +94,8 @@
->assertSee('No published review available yet') ->assertSee('No published review available yet')
->click('Open latest review') ->click('Open latest review')
->waitForText('Outcome summary') ->waitForText('Outcome summary')
->assertSee('Download current review pack')
->assertSee('Released governance record')
->assertDontSee('Publish review') ->assertDontSee('Publish review')
->assertDontSee('Refresh review') ->assertDontSee('Refresh review')
->assertDontSee('Create next review') ->assertDontSee('Create next review')

View File

@ -2,7 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\EvidenceSnapshotResource;
use App\Models\AuditLog; use App\Models\AuditLog;
use App\Models\EvidenceSnapshot;
use App\Support\Audit\AuditActionId; use App\Support\Audit\AuditActionId;
use App\Support\Evidence\EvidenceCompletenessState; use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\Evidence\EvidenceSnapshotStatus; use App\Support\Evidence\EvidenceSnapshotStatus;
@ -31,3 +34,32 @@
->and(AuditLog::query()->where('action', AuditActionId::EvidenceSnapshotExpired->value)->exists())->toBeTrue() ->and(AuditLog::query()->where('action', AuditActionId::EvidenceSnapshotExpired->value)->exists())->toBeTrue()
->and(data_get($expiredAudit?->metadata, 'reason'))->toBe('Evidence basis is obsolete.'); ->and(data_get($expiredAudit?->metadata, 'reason'))->toBe('Evidence basis is obsolete.');
}); });
it('records audit entries when customer review proof is opened explicitly', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'readonly');
$snapshot = EvidenceSnapshot::query()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'status' => EvidenceSnapshotStatus::Active->value,
'completeness_state' => EvidenceCompletenessState::Complete->value,
'summary' => ['finding_count' => 1],
'generated_at' => now(),
]);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'tenant').'?'.http_build_query([
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
]))
->assertOk();
$audit = AuditLog::query()
->where('action', AuditActionId::EvidenceSnapshotOpened->value)
->latest('id')
->first();
expect($audit)->not->toBeNull()
->and($audit?->resource_type)->toBe('evidence_snapshot')
->and(data_get($audit?->metadata, 'evidence_snapshot_id'))->toBe((int) $snapshot->getKey())
->and(data_get($audit?->metadata, 'source_surface'))->toBe(CustomerReviewWorkspace::SOURCE_SURFACE);
});

View File

@ -5,6 +5,7 @@
use App\Filament\Resources\EvidenceSnapshotResource; use App\Filament\Resources\EvidenceSnapshotResource;
use App\Filament\Resources\EvidenceSnapshotResource\Pages\ListEvidenceSnapshots; use App\Filament\Resources\EvidenceSnapshotResource\Pages\ListEvidenceSnapshots;
use App\Filament\Resources\EvidenceSnapshotResource\Pages\ViewEvidenceSnapshot; use App\Filament\Resources\EvidenceSnapshotResource\Pages\ViewEvidenceSnapshot;
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Jobs\GenerateEvidenceSnapshotJob; use App\Jobs\GenerateEvidenceSnapshotJob;
use App\Models\EvidenceSnapshot; use App\Models\EvidenceSnapshot;
use App\Models\EvidenceSnapshotItem; use App\Models\EvidenceSnapshotItem;
@ -419,6 +420,55 @@ function suspendEvidenceSnapshotWorkspace(Tenant $tenant): void
->assertSeeText('Copy JSON'); ->assertSeeText('Copy JSON');
}); });
it('hides evidence refresh, expiry, operation, fingerprint, and raw json in the customer review proof flow', function (): void {
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly');
$run = OperationRun::factory()->forTenant($tenant)->create();
$snapshot = EvidenceSnapshot::query()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'operation_run_id' => (int) $run->getKey(),
'status' => EvidenceSnapshotStatus::Active->value,
'completeness_state' => EvidenceCompletenessState::Complete->value,
'summary' => ['finding_count' => 1],
'fingerprint' => hash('sha256', 'customer-proof-flow'),
'generated_at' => now(),
]);
EvidenceSnapshotItem::query()->create([
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'tenant_id' => (int) $tenant->getKey(),
'dimension_key' => 'findings_summary',
'state' => EvidenceCompletenessState::Complete->value,
'required' => true,
'source_kind' => 'model_summary',
'summary_payload' => ['count' => 1, 'open_count' => 0],
'sort_order' => 10,
]);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'tenant').'?'.http_build_query([
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
]))
->assertOk()
->assertSee('Evidence dimensions')
->assertDontSee('Open the latest evidence refresh operation.')
->assertDontSee('customer-proof-flow')
->assertDontSee('Raw summary JSON')
->assertDontSee('Copy JSON');
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
Livewire::withQueryParams(['source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE])
->actingAs($user)
->test(ViewEvidenceSnapshot::class, ['record' => $snapshot->getKey()])
->assertActionDoesNotExist('refresh_evidence')
->assertActionDoesNotExist('expire_snapshot');
});
it('hides expire actions for expired snapshots on list and detail surfaces', function (): void { it('hides expire actions for expired snapshots on list and detail surfaces', function (): void {
$tenant = Tenant::factory()->create(); $tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');

View File

@ -4,6 +4,7 @@
use App\Models\ReviewPack; use App\Models\ReviewPack;
use App\Models\AuditLog; use App\Models\AuditLog;
use App\Models\OperationRun;
use App\Models\PlatformUser; use App\Models\PlatformUser;
use App\Services\Entitlements\WorkspaceCommercialLifecycleResolver; use App\Services\Entitlements\WorkspaceCommercialLifecycleResolver;
use App\Services\ReviewPackService; use App\Services\ReviewPackService;
@ -67,6 +68,8 @@ function suspendReadyPackWorkspaceForDownloadTest(ReviewPack $pack): void
$signedUrl = app(ReviewPackService::class)->generateDownloadUrl($pack, [ $signedUrl = app(ReviewPackService::class)->generateDownloadUrl($pack, [
'source_surface' => 'customer_review_workspace', 'source_surface' => 'customer_review_workspace',
]); ]);
$packCount = ReviewPack::query()->count();
$operationRunCount = OperationRun::query()->count();
$response = $this->actingAs($user)->get($signedUrl); $response = $this->actingAs($user)->get($signedUrl);
@ -82,7 +85,9 @@ function suspendReadyPackWorkspaceForDownloadTest(ReviewPack $pack): void
expect($audit)->not->toBeNull() expect($audit)->not->toBeNull()
->and($audit?->resource_type)->toBe('review_pack') ->and($audit?->resource_type)->toBe('review_pack')
->and(data_get($audit?->metadata, 'review_pack_id'))->toBe((int) $pack->getKey()) ->and(data_get($audit?->metadata, 'review_pack_id'))->toBe((int) $pack->getKey())
->and(data_get($audit?->metadata, 'source_surface'))->toBe('customer_review_workspace'); ->and(data_get($audit?->metadata, 'source_surface'))->toBe('customer_review_workspace')
->and(ReviewPack::query()->count())->toBe($packCount)
->and(OperationRun::query()->count())->toBe($operationRunCount);
}); });
it('keeps ready pack downloads available while the workspace is suspended read-only', function (): void { it('keeps ready pack downloads available while the workspace is suspended read-only', function (): void {

View File

@ -2,6 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\ReviewPackResource; use App\Filament\Resources\ReviewPackResource;
use App\Filament\Resources\ReviewPackResource\Pages\ListReviewPacks; use App\Filament\Resources\ReviewPackResource\Pages\ListReviewPacks;
use App\Filament\Resources\ReviewPackResource\Pages\ViewReviewPack; use App\Filament\Resources\ReviewPackResource\Pages\ViewReviewPack;
@ -644,6 +645,40 @@ function seedReviewPackEvidence(Tenant $tenant): EvidenceSnapshot
->assertActionVisible('regenerate'); ->assertActionVisible('regenerate');
}); });
it('hides regenerate and raw pack diagnostics in the customer review pack flow', function (): void {
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly');
$pack = ReviewPack::factory()->ready()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'initiated_by_user_id' => (int) $user->getKey(),
'sha256' => hash('sha256', 'customer-pack-flow'),
'fingerprint' => hash('sha256', 'customer-pack-fingerprint'),
]);
$this->actingAs($user)
->get(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant, panel: 'tenant').'?'.http_build_query([
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
]))
->assertOk()
->assertSee('Outcome summary')
->assertDontSee('Regenerate')
->assertDontSee('SHA-256')
->assertDontSee('Fingerprint')
->assertDontSee('Include PII')
->assertDontSee('Include operations');
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
Livewire::withQueryParams(['source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE])
->actingAs($user)
->test(ViewReviewPack::class, ['record' => $pack->getKey()])
->assertActionVisible('download')
->assertActionDoesNotExist('regenerate');
});
// ─── Non-Member Access ─────────────────────────────────────── // ─── Non-Member Access ───────────────────────────────────────
it('returns 404 for non-members on list page', function (): void { it('returns 404 for non-members on list page', function (): void {

View File

@ -3,10 +3,12 @@
declare(strict_types=1); declare(strict_types=1);
use App\Filament\Pages\Reviews\CustomerReviewWorkspace; use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Models\AuditLog;
use App\Models\Tenant; use App\Models\Tenant;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Models\WorkspaceMembership; use App\Models\WorkspaceMembership;
use App\Support\Audit\AuditActionId;
use App\Support\Workspaces\WorkspaceContext; use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
@ -46,6 +48,16 @@
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(CustomerReviewWorkspace::getUrl(panel: 'admin')) ->get(CustomerReviewWorkspace::getUrl(panel: 'admin'))
->assertOk(); ->assertOk();
$audit = AuditLog::query()
->where('action', AuditActionId::CustomerReviewWorkspaceOpened->value)
->latest('id')
->first();
expect($audit)->not->toBeNull()
->and($audit?->resource_type)->toBe('customer_review_workspace')
->and(data_get($audit?->metadata, 'source_surface'))->toBe(CustomerReviewWorkspace::SOURCE_SURFACE)
->and(data_get($audit?->metadata, 'entitled_tenant_count'))->toBe(1);
}); });
it('returns 404 for explicit out-of-scope tenant targeting on the customer review workspace', function (): void { it('returns 404 for explicit out-of-scope tenant targeting on the customer review workspace', function (): void {

View File

@ -136,17 +136,32 @@
'published_by_user_id' => (int) $user->getKey(), 'published_by_user_id' => (int) $user->getKey(),
])->save(); ])->save();
Storage::disk('exports')->put('review-packs/customer-workspace-detail-download.zip', 'PK-test');
$pack = ReviewPack::factory()->ready()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'tenant_review_id' => (int) $review->getKey(),
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
'file_path' => 'review-packs/customer-workspace-detail-download.zip',
'file_disk' => 'exports',
]);
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
setTenantPanelContext($tenant); setTenantPanelContext($tenant);
Livewire::withQueryParams([CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1]) Livewire::withQueryParams([CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1])
->actingAs($user) ->actingAs($user)
->test(ViewTenantReview::class, ['record' => $review->getKey()]) ->test(ViewTenantReview::class, ['record' => $review->getKey()])
->assertSee('Outcome summary') ->assertSee('Outcome summary')
->assertActionVisible('download_current_review_pack')
->assertActionDoesNotExist('publish_review') ->assertActionDoesNotExist('publish_review')
->assertActionDoesNotExist('refresh_review') ->assertActionDoesNotExist('refresh_review')
->assertActionDoesNotExist('create_next_review') ->assertActionDoesNotExist('create_next_review')
->assertActionDoesNotExist('export_executive_pack') ->assertActionDoesNotExist('export_executive_pack')
->assertActionHidden('archive_review'); ->assertActionDoesNotExist('archive_review');
$audit = AuditLog::query() $audit = AuditLog::query()
->where('action', AuditActionId::TenantReviewOpened->value) ->where('action', AuditActionId::TenantReviewOpened->value)

View File

@ -6,12 +6,15 @@
use App\Models\PlatformUser; use App\Models\PlatformUser;
use App\Models\ReviewPack; use App\Models\ReviewPack;
use App\Models\Tenant; use App\Models\Tenant;
use App\Models\User;
use App\Services\Entitlements\WorkspaceCommercialLifecycleResolver; use App\Services\Entitlements\WorkspaceCommercialLifecycleResolver;
use App\Services\Settings\SettingsWriter; use App\Services\Settings\SettingsWriter;
use App\Support\Auth\Capabilities;
use App\Support\Auth\PlatformCapabilities; use App\Support\Auth\PlatformCapabilities;
use App\Support\TenantReviewStatus; use App\Support\TenantReviewStatus;
use App\Support\Workspaces\WorkspaceContext; use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Gate;
use Livewire\Livewire; use Livewire\Livewire;
uses(RefreshDatabase::class); uses(RefreshDatabase::class);
@ -66,7 +69,7 @@ function suspendCustomerReviewWorkspacePackAccessWorkspace(Tenant $tenant): void
->test(CustomerReviewWorkspace::class) ->test(CustomerReviewWorkspace::class)
->assertTableActionVisible('open_latest_review', $tenant) ->assertTableActionVisible('open_latest_review', $tenant)
->assertTableActionVisible('download_review_pack', $tenant) ->assertTableActionVisible('download_review_pack', $tenant)
->assertSee('Available'); ->assertSee('Current review pack available');
}); });
it('keeps customer review workspace and pack actions visible while suspended read-only', function (): void { it('keeps customer review workspace and pack actions visible while suspended read-only', function (): void {
@ -104,7 +107,7 @@ function suspendCustomerReviewWorkspacePackAccessWorkspace(Tenant $tenant): void
->test(CustomerReviewWorkspace::class) ->test(CustomerReviewWorkspace::class)
->assertTableActionVisible('open_latest_review', $tenant) ->assertTableActionVisible('open_latest_review', $tenant)
->assertTableActionVisible('download_review_pack', $tenant) ->assertTableActionVisible('download_review_pack', $tenant)
->assertSee('Available'); ->assertSee('Current review pack available');
}); });
it('shows an unavailable pack state and hides the download action when no current review pack exists', function (): void { it('shows an unavailable pack state and hides the download action when no current review pack exists', function (): void {
@ -128,7 +131,52 @@ function suspendCustomerReviewWorkspacePackAccessWorkspace(Tenant $tenant): void
->test(CustomerReviewWorkspace::class) ->test(CustomerReviewWorkspace::class)
->assertTableActionVisible('open_latest_review', $tenant) ->assertTableActionVisible('open_latest_review', $tenant)
->assertTableActionHidden('download_review_pack', $tenant) ->assertTableActionHidden('download_review_pack', $tenant)
->assertSee('Unavailable'); ->assertSee('No current review pack available yet');
});
it('distinguishes expired and capability-blocked review-pack states on the workspace', function (): void {
$expiredTenant = Tenant::factory()->create(['name' => 'Expired Pack Tenant']);
[$user, $expiredTenant] = createUserWithTenant(tenant: $expiredTenant, role: 'readonly');
$blockedTenant = Tenant::factory()->create([
'workspace_id' => (int) $expiredTenant->workspace_id,
'name' => 'Blocked Pack Tenant',
]);
createUserWithTenant(tenant: $blockedTenant, user: $user, role: 'readonly');
foreach ([$expiredTenant, $blockedTenant] as $tenant) {
$snapshot = seedTenantReviewEvidence($tenant);
$review = composeTenantReviewForTest($tenant, $user, $snapshot);
$review->forceFill([
'status' => TenantReviewStatus::Published->value,
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
])->save();
$pack = ReviewPack::factory()->ready()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'tenant_review_id' => (int) $review->getKey(),
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
'expires_at' => $tenant->is($expiredTenant) ? now()->subDay() : now()->addDay(),
]);
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
}
Gate::define(Capabilities::REVIEW_PACK_VIEW, fn (User $actor, Tenant $tenant): bool => ! $tenant->is($blockedTenant));
$this->actingAs($user);
setAdminPanelContext();
session()->put(WorkspaceContext::SESSION_KEY, (int) $expiredTenant->workspace_id);
Livewire::actingAs($user)
->test(CustomerReviewWorkspace::class)
->assertCanSeeTableRecords([$expiredTenant->fresh(), $blockedTenant->fresh()])
->assertSee('Review pack expired')
->assertSee('Review pack access is unavailable for this actor')
->assertTableActionHidden('download_review_pack', $expiredTenant)
->assertTableActionHidden('download_review_pack', $blockedTenant);
}); });
it('hides review and pack actions for tenants without a published review', function (): void { it('hides review and pack actions for tenants without a published review', function (): void {

View File

@ -4,6 +4,8 @@
use App\Filament\Pages\Reviews\CustomerReviewWorkspace; use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\TenantReviewResource; use App\Filament\Resources\TenantReviewResource;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\Tenant; use App\Models\Tenant;
use App\Models\User; use App\Models\User;
use App\Support\TenantReviewStatus; use App\Support\TenantReviewStatus;
@ -140,6 +142,59 @@
->assertSee(TenantReviewResource::tenantScopedUrl('view', ['record' => $publishedReview->fresh()], $tenantPublished), false); ->assertSee(TenantReviewResource::tenantScopedUrl('view', ['record' => $publishedReview->fresh()], $tenantPublished), false);
}); });
it('summarizes accepted-risk accountability and evidence proof availability in customer-safe workspace rows', function (): void {
$tenant = Tenant::factory()->create(['name' => 'Governed Tenant']);
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly');
$owner = User::factory()->create(['name' => 'Risk Owner']);
$snapshot = seedTenantReviewEvidence($tenant);
$review = composeTenantReviewForTest($tenant, $user, $snapshot);
$review->forceFill([
'status' => TenantReviewStatus::Published->value,
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
'summary' => array_replace_recursive(is_array($review->summary) ? $review->summary : [], [
'risk_acceptance' => [
'status_marked_count' => 1,
'valid_governed_count' => 1,
'warning_count' => 0,
],
]),
])->save();
$finding = Finding::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
]);
FindingException::query()->create([
'workspace_id' => (int) $tenant->workspace_id,
'tenant_id' => (int) $tenant->getKey(),
'finding_id' => (int) $finding->getKey(),
'status' => FindingException::STATUS_ACTIVE,
'current_validity_state' => FindingException::VALIDITY_VALID,
'requested_by_user_id' => (int) $user->getKey(),
'request_reason' => 'Vendor patch window accepted by the customer.',
'owner_user_id' => (int) $owner->getKey(),
'approved_by_user_id' => (int) $owner->getKey(),
'requested_at' => now()->subDays(2),
'approved_at' => now()->subDay(),
'effective_from' => now()->subDay(),
'review_due_at' => now()->addDays(14),
]);
$this->actingAs($user);
setAdminPanelContext();
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Livewire::actingAs($user)
->test(CustomerReviewWorkspace::class)
->assertCanSeeTableRecords([$tenant->fresh()])
->assertSee('1 accepted risks are governed. Accountable: Risk Owner. Re-review by')
->assertSee('Reason: Vendor patch window accepted by the customer.')
->assertSee('Proof summary available');
});
it('defaults the customer review workspace to the remembered tenant when tenant context is available', function (): void { it('defaults the customer review workspace to the remembered tenant when tenant context is available', function (): void {
$tenantA = Tenant::factory()->create(['name' => 'Alpha Tenant']); $tenantA = Tenant::factory()->create(['name' => 'Alpha Tenant']);
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'readonly'); [$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'readonly');

View File

@ -3,8 +3,10 @@
declare(strict_types=1); declare(strict_types=1);
use App\Filament\Pages\Reviews\ReviewRegister; use App\Filament\Pages\Reviews\ReviewRegister;
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\TenantReviewResource; use App\Filament\Resources\TenantReviewResource;
use App\Models\Tenant; use App\Models\Tenant;
use App\Support\OperationRunLinks;
use App\Support\ReasonTranslation\ReasonPresenter; use App\Support\ReasonTranslation\ReasonPresenter;
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthPresenter; use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthPresenter;
use App\Support\Ui\GovernanceArtifactTruth\SurfaceCompressionContext; use App\Support\Ui\GovernanceArtifactTruth\SurfaceCompressionContext;
@ -62,3 +64,35 @@
->assertSee($registerOutcome?->primaryReason ?? '') ->assertSee($registerOutcome?->primaryReason ?? '')
->assertSee($explanation?->nextActionText ?? ''); ->assertSee($explanation?->nextActionText ?? '');
}); });
it('keeps customer-workspace review detail customer-readable by hiding internal reason ownership and fingerprints', function (): void {
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly');
$snapshot = seedTenantReviewEvidence($tenant);
$review = composeTenantReviewForTest($tenant, $user, $snapshot);
$review->forceFill([
'status' => 'published',
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
])->save();
expect($review->operation_run_id)->not->toBeNull();
setTenantPanelContext($tenant);
$this->actingAs($user)
->get(TenantReviewResource::tenantScopedUrl('view', ['record' => $review], $tenant).'?'.http_build_query([
CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1,
]))
->assertOk()
->assertSee('Released governance record')
->assertSee('This released review is available for customer-safe governance consumption.')
->assertSee('Evidence snapshot')
->assertSee('source_surface=customer_review_workspace', false)
->assertDontSee('Reason owner')
->assertDontSee('Platform reason family')
->assertDontSee('Fingerprint')
->assertDontSee(OperationRunLinks::tenantlessView((int) $review->operation_run_id), false)
->assertDontSee('Inspect the latest review composition or refresh run.');
});

View File

@ -3,23 +3,31 @@
declare(strict_types=1); declare(strict_types=1);
use App\Filament\Pages\Reviews\ReviewRegister; use App\Filament\Pages\Reviews\ReviewRegister;
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\TenantReviewResource; use App\Filament\Resources\TenantReviewResource;
use App\Filament\Resources\TenantReviewResource\Pages\ListTenantReviews; use App\Filament\Resources\TenantReviewResource\Pages\ListTenantReviews;
use App\Filament\Resources\TenantReviewResource\Pages\ViewTenantReview; use App\Filament\Resources\TenantReviewResource\Pages\ViewTenantReview;
use App\Models\ReviewPack;
use App\Models\Tenant; use App\Models\Tenant;
use App\Models\TenantReview; use App\Models\TenantReview;
use App\Models\User; use App\Models\User;
use App\Support\TenantReviewStatus;
use App\Services\TenantReviews\TenantReviewLifecycleService; use App\Services\TenantReviews\TenantReviewLifecycleService;
use App\Support\Ui\GovernanceActions\GovernanceActionCatalog; use App\Support\Ui\GovernanceActions\GovernanceActionCatalog;
use App\Support\Workspaces\WorkspaceContext; use App\Support\Workspaces\WorkspaceContext;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Illuminate\Support\Facades\Storage;
use Livewire\Features\SupportTesting\Testable; use Livewire\Features\SupportTesting\Testable;
use Livewire\Livewire; use Livewire\Livewire;
use Tests\Feature\Concerns\BuildsGovernanceArtifactTruthFixtures; use Tests\Feature\Concerns\BuildsGovernanceArtifactTruthFixtures;
uses(BuildsGovernanceArtifactTruthFixtures::class); uses(BuildsGovernanceArtifactTruthFixtures::class);
beforeEach(function (): void {
Storage::fake('exports');
});
function tenantReviewContractHeaderActions(Testable $component): array function tenantReviewContractHeaderActions(Testable $component): array
{ {
$instance = $component->instance(); $instance = $component->instance();
@ -161,6 +169,54 @@ function tenantReviewContractHeaderActions(Testable $component): array
->and($groupLabels)->toBe(['More', 'Danger']); ->and($groupLabels)->toBe(['More', 'Danger']);
}); });
it('uses the current review-pack download as the only customer-workspace detail header action', function (): void {
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly');
$snapshot = seedTenantReviewEvidence($tenant);
$review = composeTenantReviewForTest($tenant, $user, $snapshot);
$review->forceFill([
'status' => TenantReviewStatus::Published->value,
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
])->save();
Storage::disk('exports')->put('review-packs/customer-detail-primary.zip', 'PK-test');
$pack = ReviewPack::factory()->ready()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'tenant_review_id' => (int) $review->getKey(),
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
'file_path' => 'review-packs/customer-detail-primary.zip',
'file_disk' => 'exports',
]);
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
setTenantPanelContext($tenant);
$component = Livewire::withQueryParams([CustomerReviewWorkspace::DETAIL_CONTEXT_QUERY_KEY => 1])
->actingAs($user)
->test(ViewTenantReview::class, ['record' => $review->getKey()])
->assertActionVisible('download_current_review_pack')
->assertActionEnabled('download_current_review_pack')
->assertActionDoesNotExist('publish_review')
->assertActionDoesNotExist('refresh_review')
->assertActionDoesNotExist('create_next_review')
->assertActionDoesNotExist('export_executive_pack')
->assertActionDoesNotExist('archive_review');
$topLevelActionNames = collect(tenantReviewContractHeaderActions($component))
->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null)
->filter()
->values()
->all();
expect($topLevelActionNames)->toBe(['download_current_review_pack']);
});
it('shows publication truth and next-step guidance when a review is not yet publishable', function (): void { it('shows publication truth and next-step guidance when a review is not yet publishable', function (): void {
$tenant = Tenant::factory()->create(); $tenant = Tenant::factory()->create();
[$owner, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); [$owner, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');

View File

@ -1,5 +1,10 @@
# TenantPilot / TenantAtlas — Handover Document # TenantPilot / TenantAtlas — Handover Document
> **Status:** Needs Review
> **Last reviewed:** 2026-04-30
> **Use for:** Handover context, repo snapshot orientation, and migration-era operational notes
> **Do not use for:** Current implementation truth or current branch state without repo verification
> **Generated**: 2026-03-06 · **Branch**: `dev` · **HEAD**: `da1adbd` > **Generated**: 2026-03-06 · **Branch**: `dev` · **HEAD**: `da1adbd`
> **Stack**: Laravel 12 · Filament v5 · Livewire v4 · PostgreSQL 16 · Tailwind v4 · Pest 4 > **Stack**: Laravel 12 · Filament v5 · Livewire v4 · PostgreSQL 16 · Tailwind v4 · Pest 4

View File

@ -1,154 +1,90 @@
# Microsoft Graph API Permissions # Microsoft Graph API Permissions
This document lists all required Microsoft Graph API permissions for TenantPilot to function correctly. > **Status:** Reference
> **Last reviewed:** 2026-04-30
> **Use for:** Current repo-based Microsoft Graph permission reference for implemented platform features
> **Do not use for:** Future roadmap permissions or final tenant-specific grant truth without checking the repo and the live tenant posture
## Required Permissions This document summarizes the permission registry currently defined in:
The Azure AD / Entra ID **App Registration** used by TenantPilot requires the following **Application Permissions** (not Delegated): - `apps/platform/config/intune_permissions.php`
- `apps/platform/config/entra_permissions.php`
### Core Policy Management (Required) These config files are the repo source of truth for currently implemented permission requirements.
- `DeviceManagementConfiguration.Read.All` - Read Intune device configuration policies
- `DeviceManagementConfiguration.ReadWrite.All` - Write/restore Intune policies
- `DeviceManagementApps.Read.All` - Read app configuration policies
- `DeviceManagementApps.ReadWrite.All` - Write app policies
### Scope Tags (Feature 004 - Required for Phase 3) ## Scope Rules
- **`DeviceManagementRBAC.Read.All`** - Read scope tags and RBAC settings
- **Purpose**: Resolve scope tag IDs to display names (e.g., "0" → "Default")
- **Missing**: Backup items will show "Unknown (ID: 0)" instead of scope tag names
- **Impact**: Metadata display only - backups still work without this permission
### Group Resolution (Feature 004 - Required for Phase 2) - The list below describes the current repo-required Microsoft Graph permissions for implemented features.
- `Group.Read.All` - Resolve group IDs to names for assignments - This document does not promote roadmap or research-only permissions to required status.
- `Directory.Read.All` - Batch resolve directory objects (groups, users, devices) - `granted_stub` values in `intune_permissions.php` are display aids for the UI, not the canonical required-permission list.
- Unless stated otherwise, these are application permissions.
## How to Add Permissions ## Current Required Permissions
### Azure Portal (Entra ID) ### Intune Configuration, Backup, Restore, and Drift
1. Go to **Azure Portal****Entra ID** (Azure Active Directory) | Permission | Why the repo requires it |
2. Navigate to **App registrations** → Select your TenantPilot app |---|---|
3. Click **API permissions** in the left menu | `DeviceManagementConfiguration.Read.All` | Read Intune device configuration policies for inventory, backup, settings normalization, and drift flows |
4. Click **+ Add a permission** | `DeviceManagementConfiguration.ReadWrite.All` | Execute restore and other write flows for Intune device configuration policies |
5. Select **Microsoft Graph** → **Application permissions** | `DeviceManagementApps.Read.All` | Read Intune app configuration and assignments for sync and backup |
6. Search for and select the required permissions: | `DeviceManagementApps.ReadWrite.All` | Restore and manage Intune app configuration and assignments |
- `DeviceManagementRBAC.Read.All` | `DeviceManagementServiceConfig.Read.All` | Read enrollment restrictions, Autopilot, ESP, and related service configuration |
- (Add others as needed) | `DeviceManagementServiceConfig.ReadWrite.All` | Restore and manage enrollment restrictions, Autopilot, ESP, and related service configuration |
7. Click **Add permissions** | `DeviceManagementScripts.Read.All` | Read device management scripts and remediations for sync and backup |
8. **IMPORTANT**: Click **Grant admin consent for [Your Organization]** | `DeviceManagementScripts.ReadWrite.All` | Restore and manage device management scripts and remediations |
- ⚠️ Without admin consent, the permissions won't be active!
### PowerShell (Alternative) ### Conditional Access And Policy Coverage
```powershell | Permission | Why the repo requires it |
# Connect to Microsoft Graph |---|---|
Connect-MgGraph -Scopes "Application.ReadWrite.All" | `Policy.Read.All` | Read Conditional Access and related identity policy surfaces used for backup, preview, and versioning |
| `Policy.ReadWrite.ConditionalAccess` | Manage Conditional Access policies for controlled restore or admin-managed write paths |
# Get your app registration ### Directory, Groups, And Intune RBAC Foundations
$appId = "YOUR-APP-CLIENT-ID"
$app = Get-MgApplication -Filter "appId eq '$appId'"
# Add DeviceManagementRBAC.Read.All permission | Permission | Why the repo requires it |
$graphServicePrincipal = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'" |---|---|
$rbacPermission = $graphServicePrincipal.AppRoles | Where-Object {$_.Value -eq "DeviceManagementRBAC.Read.All"} | `Directory.Read.All` | Directory lookups and tenant-health-oriented checks |
| `Group.Read.All` | Assignment name resolution, group mapping, group directory cache, backup metadata enrichment, and drift context |
| `DeviceManagementRBAC.Read.All` | Read Intune RBAC settings and scope tags for metadata enrichment and assignment-aware flows |
| `DeviceManagementRBAC.ReadWrite.All` | Manage scope tags for foundation backup and restore workflows |
$requiredResourceAccess = @{ ### Entra Admin Roles Evidence
ResourceAppId = "00000003-0000-0000-c000-000000000000"
ResourceAccess = @(
@{
Id = $rbacPermission.Id
Type = "Role"
}
)
}
Update-MgApplication -ApplicationId $app.Id -RequiredResourceAccess $requiredResourceAccess | Permission | Why the repo requires it |
|---|---|
| `RoleManagement.Read.Directory` | Read directory role definitions and assignments for Entra admin roles evidence and findings |
# Grant admin consent ## Not Currently Required By Implemented Features
# (Must be done manually or via Graph API with RoleManagement.ReadWrite.Directory scope)
```
## Verification These permissions may appear in research, roadmap ideas, or tenant-specific grants, but they are not part of the current required-permission registry:
After adding permissions and granting admin consent: - `SharePointTenantSettings.Read.All` is a roadmap or research permission until SharePoint tenant settings are actually implemented.
- Exchange Online or Defender for Office 365 PowerShell permissions are not current repo requirements because those integrations are not implemented as production features.
- `DeviceManagementManagedDevices.ReadWrite.All` may appear in fixtures or grant stubs, but it is not listed in the current required-permission registry.
## Grant And Verify
1. In Entra ID, open the TenantPilot app registration.
2. Add the required Microsoft Graph application permissions from the tables above.
3. Grant admin consent for the tenant.
4. In the application, use the required-permissions or permission-posture surfaces to compare granted versus required permissions.
5. If the platform still shows stale permission state, clear caches with:
1. Go to **App registrations** → Your app → **API permissions**
2. Verify status shows **Granted for [Your Organization]** with a green checkmark ✅
3. Clear cache in TenantPilot:
```bash ```bash
php artisan cache:clear cd apps/platform && ./vendor/bin/sail artisan cache:clear
```
4. Test scope tag resolution:
```bash
php artisan tinker
>>> use App\Services\Graph\ScopeTagResolver;
>>> use App\Models\Tenant;
>>> $tenant = Tenant::first();
>>> $resolver = app(ScopeTagResolver::class);
>>> $tags = $resolver->resolve(['0'], $tenant);
>>> dd($tags);
```
Expected output:
```php
[
[
"id" => "0",
"displayName" => "Default"
]
]
``` ```
## Troubleshooting ## Least-Privilege Notes
### Error: "Application is not authorized to perform this operation" - Read-only evaluation or inventory-focused setups can often begin with the read permissions only.
- Any real restore or write lane needs the corresponding `ReadWrite` permission set.
**Symptoms:** - Conditional Access write access should be treated as a higher-risk permission and granted only when the restore or admin-write lane is intentionally enabled.
- Backup items show "Unknown (ID: 0)" for scope tags - Scope-tag restore paths require `DeviceManagementRBAC.ReadWrite.All`, not just the read permission.
- Logs contain: `Application must have one of the following scopes: DeviceManagementRBAC.Read.All`
**Solution:**
1. Add `DeviceManagementRBAC.Read.All` permission (see above)
2. **Grant admin consent** (critical step!)
3. Wait 5-10 minutes for Azure to propagate permissions
4. Clear cache: `php artisan cache:clear`
5. Test again
### Error: "Insufficient privileges to complete the operation"
**Cause:** The user account used to grant admin consent doesn't have sufficient permissions.
**Solution:**
- Use an account with **Global Administrator** or **Privileged Role Administrator** role
- Or have the IT admin grant consent for the organization
### Permissions showing but still getting 403
**Possible causes:**
1. Admin consent not granted (click the button!)
2. Permissions not yet propagated (wait 5-10 minutes)
3. Wrong tenant (check tenant ID in app config)
4. Cached token needs refresh (clear cache + restart)
## Feature Impact Matrix
| Feature | Required Permissions | Without Permission | Impact Level |
|---------|---------------------|-------------------|--------------|
| Basic Policy Backup | `DeviceManagementConfiguration.Read.All` | Cannot backup | 🔴 Critical |
| Policy Restore | `DeviceManagementConfiguration.ReadWrite.All` | Cannot restore | 🔴 Critical |
| Scope Tag Names (004) | `DeviceManagementRBAC.Read.All` | Shows "Unknown (ID: X)" | 🟡 Medium |
| Assignment Names (004) | `Group.Read.All` + `Directory.Read.All` | Shows group IDs only | 🟡 Medium |
| Group Mapping (004) | `Group.Read.All` | Manual ID mapping required | 🟡 Medium |
## Security Notes
- All permissions are **Application Permissions** (app-level, not user-level)
- Requires **admin consent** from Global Administrator
- Use **least privilege principle**: Only add permissions for features you use
- Consider creating separate app registrations for different environments (dev/staging/prod)
- Rotate client secrets regularly (recommended: every 6 months)
## References ## References
- [Microsoft Graph API Permissions](https://learn.microsoft.com/en-us/graph/permissions-reference) - [Microsoft Graph permissions reference](https://learn.microsoft.com/en-us/graph/permissions-reference)
- [Intune Graph API Overview](https://learn.microsoft.com/en-us/graph/api/resources/intune-graph-overview) - [Microsoft Intune Graph overview](https://learn.microsoft.com/en-us/graph/api/resources/intune-graph-overview)
- [App Registration Best Practices](https://learn.microsoft.com/en-us/azure/active-directory/develop/security-best-practices-for-app-registration) - [App registration security best practices](https://learn.microsoft.com/en-us/azure/active-directory/develop/security-best-practices-for-app-registration)

View File

@ -2,6 +2,11 @@
--- ---
> **Status:** Needs Review
> **Last reviewed:** 2026-04-30
> **Use for:** Fast repository orientation, stack overview, and high-level product scope context
> **Do not use for:** Current implementation truth or completion status without repo verification
**Overview:** **Overview:**
- **Purpose:** Intune management tool (backup, restore, policy versioning, safe change management) built with Laravel + Filament. - **Purpose:** Intune management tool (backup, restore, policy versioning, safe change management) built with Laravel + Filament.
- **Primary goals:** policy version control (diff/history/rollback), reliable backup & restore of Microsoft Intune configuration, admin-focused productivity features, auditability and least-privilege operations. - **Primary goals:** policy version control (diff/history/rollback), reliable backup & restore of Microsoft Intune configuration, admin-focused productivity features, auditability and least-privilege operations.

60
docs/README.md Normal file
View File

@ -0,0 +1,60 @@
# TenantPilot Documentation Index
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Navigating current documentation sources and understanding how to maintain them with low overhead
> **Do not use for:** Assuming any document is implementation truth without repo verification
## Current Source of Truth
- `docs/product/roadmap.md` - current product roadmap and prioritization context
- `docs/product/spec-candidates.md` - active spec candidate queue
- `docs/product/principles.md` - product and architecture principles
- `docs/strategy/product-vision.md` - long-term product vision
- `docs/strategy/domain-coverage.md` - domain and coverage strategy
## Product Operations
- `docs/product/discoveries.md`
- `docs/product/implementation-ledger.md`
- `docs/product/prompts/`
- `docs/product/standards/`
## Technical Research
- `docs/research/`
## UI Standards
- `docs/ui/`
## Audits
- `docs/audits/`
## Security And Access References
- `docs/PERMISSIONS.md`
- `docs/security/`
## Historical Or Superseded Material
- `docs/audits/archive/`
- audit-derived candidate documents that are marked `Historical`, `Superseded`, or `Needs Review`
## Lightweight Maintenance Model
- Keep only the current source-of-truth documents actively maintained.
- Update a document when the underlying roadmap, policy, or decision actually changes.
- Mark older material with status headers instead of rewriting it to feel current.
- Prefer archive or superseded markers over deletion.
- Verify implementation claims against repo code, specs, and tests.
## Rules For Agents
- Treat docs as guidance, not implementation truth.
- Verify implementation claims against repo code.
- Specs are not automatically implemented.
- Tests are not automatically executed.
- Historical audits may be outdated.
- Prefer current roadmap and spec-candidates for prioritization.

View File

@ -1,5 +1,10 @@
# Audit-Derived Spec Candidates # Audit-Derived Spec Candidates
> **Status:** Superseded
> **Last reviewed:** 2026-04-30
> **Use for:** Historical context on audit-driven candidate clustering from March 2026
> **Do not use for:** The current candidate queue; use `docs/product/spec-candidates.md` instead
**Date:** 2026-03-15 **Date:** 2026-03-15
**Source audit:** [docs/audits/tenantpilot-architecture-audit-constitution.md](docs/audits/tenantpilot-architecture-audit-constitution.md) plus first-pass repo scan driven by [ .github/prompts/tenantpilot.audit.prompt.md ](.github/prompts/tenantpilot.audit.prompt.md) **Source audit:** [docs/audits/tenantpilot-architecture-audit-constitution.md](docs/audits/tenantpilot-architecture-audit-constitution.md) plus first-pass repo scan driven by [ .github/prompts/tenantpilot.audit.prompt.md ](.github/prompts/tenantpilot.audit.prompt.md)

10
docs/audits/README.md Normal file
View File

@ -0,0 +1,10 @@
# TenantPilot Audits
> **Status:** Reference
> **Last reviewed:** 2026-04-30
> **Use for:** Historical and current audit findings
> **Do not use for:** Current implementation truth without repo verification
Audits are point-in-time assessments. They may be outdated after implementation.
Use current source files, specs, tests, and product roadmap to verify whether a finding still applies.

View File

@ -1,5 +1,10 @@
# Enterprise Architecture Audit — TenantPilot / TenantAtlas # Enterprise Architecture Audit — TenantPilot / TenantAtlas
> **Status:** Historical
> **Last reviewed:** 2026-04-30
> **Use for:** Original architecture diagnosis and historical context for scope, panel, and RBAC design discussions
> **Do not use for:** Current architecture truth without repo verification
**Date:** 2026-03-09 **Date:** 2026-03-09
**Auditor role:** Senior Enterprise SaaS Architect, UX/IA Auditor, Security/RBAC Reviewer **Auditor role:** Senior Enterprise SaaS Architect, UX/IA Auditor, Security/RBAC Reviewer
**Stack:** Laravel 12, Filament v5, Livewire v4, PostgreSQL, Tailwind v4 **Stack:** Laravel 12, Filament v5, Livewire v4, PostgreSQL, Tailwind v4

View File

@ -1,5 +1,10 @@
# Repo-wide Legacy / Orphaned Truth Audit # Repo-wide Legacy / Orphaned Truth Audit
> **Status:** Needs Review
> **Last reviewed:** 2026-04-30
> **Use for:** Cleanup investigations and historical context on legacy truth collisions in the repo
> **Do not use for:** Current implementation truth without repo verification
**Date**: 2026-03-16 **Date**: 2026-03-16
**Scope**: Full codebase — models, migrations, enums, services, jobs, observers, Filament resources, policies, capabilities, badges, tests, factories **Scope**: Full codebase — models, migrations, enums, services, jobs, observers, Filament resources, policies, capabilities, badges, tests, factories
**Method**: Systematic source-of-truth tracing across all layers **Method**: Systematic source-of-truth tracing across all layers

View File

@ -1,5 +1,10 @@
# Semantic Clarity — Spec Candidate Package # Semantic Clarity — Spec Candidate Package
> **Status:** Superseded
> **Last reviewed:** 2026-04-30
> **Use for:** Historical reasoning behind the semantic clarity cleanup program and its original packaging
> **Do not use for:** The current candidate queue, current spec numbering, or current implementation truth without repo verification
**Source audit:** `docs/audits/semantic-clarity-audit.md` **Source audit:** `docs/audits/semantic-clarity-audit.md`
**Date:** 2026-03-21 **Date:** 2026-03-21
**Author role:** Senior Staff Engineer / Enterprise SaaS Product Architect **Author role:** Senior Staff Engineer / Enterprise SaaS Product Architect

View File

@ -1,5 +1,10 @@
# TenantPilot Architecture Audit Constitution # TenantPilot Architecture Audit Constitution
> **Status:** Reference
> **Last reviewed:** 2026-04-30
> **Use for:** Audit standards, architectural review criteria, and repo-level safety review framing
> **Do not use for:** Current implementation truth or roadmap priority without repo verification
## Purpose ## Purpose
This constitution defines the non-negotiable architecture, security, and workflow rules for TenantPilot / TenantAtlas. This constitution defines the non-negotiable architecture, security, and workflow rules for TenantPilot / TenantAtlas.

View File

@ -1,47 +1,25 @@
# Discoveries # Discoveries
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Parking implementation findings and follow-up ideas that are not yet part of the active roadmap or candidate queue
> **Do not use for:** Active priority order once an item is already tracked in the roadmap or spec-candidates
>
> Things found during implementation that don't belong in the current spec. > Things found during implementation that don't belong in the current spec.
> Review weekly. Promote to [spec-candidates.md](spec-candidates.md) or discard. > Review weekly. Promote to [spec-candidates.md](spec-candidates.md) or discard.
Items that are already tracked in [spec-candidates.md](spec-candidates.md) or [roadmap.md](roadmap.md) should not remain here. Items that are already tracked in [spec-candidates.md](spec-candidates.md) or [roadmap.md](roadmap.md) should not remain here.
**Last reviewed**: 2026-03-15 **Last reviewed**: 2026-04-30
--- ---
## 2026-03-15 — Queued execution trust relies too much on dispatch-time authority ## 2026-04-30 — 2026-03-15 architecture hardening cluster moved out of discoveries
- **Source**: architecture audit - **Source**: architecture audit
- **Observation**: Queued jobs still rely too heavily on the actor, tenant, and authorization state captured at dispatch time. Execution-time scope continuity and reauthorization are not yet hardened as a canonical backend contract. - **Observation**: The queued execution, tenant-query-canon, findings-enforcement, and Livewire trust-boundary items from the 2026-03-15 audit are now tracked through promoted specs and the roadmap hardening lane. They no longer belong in `discoveries.md` as open findings.
- **Category**: hardening - **Category**: documentation
- **Priority**: high - **Priority**: low
- **Suggested follow-up**: Track in [../audits/2026-03-15-audit-spec-candidates.md](../audits/2026-03-15-audit-spec-candidates.md) as Candidate A: queued execution reauthorization and scope continuity. - **Suggested follow-up**: Use `roadmap.md` and `spec-candidates.md` for current hardening follow-through; keep `discoveries.md` for new findings that are not yet tracked elsewhere.
---
## 2026-03-15 — Tenant-owned query canon remains too ad hoc
- **Source**: architecture audit
- **Observation**: Tenant isolation is broadly present, but many tenant-owned reads still depend on repeated local `tenant_id` filtering instead of a reusable canonical query path. This increases drift risk and weakens wrong-tenant regression discipline.
- **Category**: hardening
- **Priority**: high
- **Suggested follow-up**: Track in [../audits/2026-03-15-audit-spec-candidates.md](../audits/2026-03-15-audit-spec-candidates.md) as Candidate B: tenant-owned query canon and wrong-tenant guards.
---
## 2026-03-15 — Findings lifecycle truth is stronger in docs than in enforcement
- **Source**: architecture audit
- **Observation**: Findings workflow semantics are well-defined at spec level, but architectural enforcement still depends too much on service-path discipline. Direct or bypassing status mutations remain too plausible.
- **Category**: hardening
- **Priority**: high
- **Suggested follow-up**: Track in [../audits/2026-03-15-audit-spec-candidates.md](../audits/2026-03-15-audit-spec-candidates.md) as Candidate C: findings workflow enforcement and audit backstop.
---
## 2026-03-15 — Livewire trust-boundary hardening is still convention-driven
- **Source**: architecture audit
- **Observation**: Complex Livewire and Filament flows still expose too much ownership-relevant context in public component state. This is not a proven exploit in the repo today, but the hardening standard is not yet explicit or reusable.
- **Category**: hardening
- **Priority**: medium
- **Suggested follow-up**: Track in [../audits/2026-03-15-audit-spec-candidates.md](../audits/2026-03-15-audit-spec-candidates.md) as Candidate D: Livewire context locking and trusted-state reduction.
--- ---

View File

@ -1,5 +1,10 @@
# TenantPilot Implementation Ledger # TenantPilot Implementation Ledger
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Repo-based implementation status and product-surface maturity assessment
> **Do not use for:** Roadmap priority, spec priority, or proof that tests were executed in the current branch
## Purpose ## Purpose
Dieses Dokument beschreibt den aktuellen repo-basierten Implementierungsstand von TenantPilot. Es ergaenzt `roadmap.md` und `spec-candidates.md`, ersetzt sie aber nicht. Dieses Dokument beschreibt den aktuellen repo-basierten Implementierungsstand von TenantPilot. Es ergaenzt `roadmap.md` und `spec-candidates.md`, ersetzt sie aber nicht.
@ -15,7 +20,7 @@ ## Purpose
## Current Product Position ## Current Product Position
TenantPilot ist aktuell ein starkes internes Governance- und Operations-Produkt mit belastbaren Foundations fuer Execution Truth, Baselines/Drift, Findings, Evidence, Reviews, Review Packs, Supportability, Telemetry und Safety Controls und inzwischen repo-real umgesetzten Customer-safe Review Consumption, Risk-Acceptance/Exception-Workflow, Findings-/Governance-Inboxen und einer DE/EN-Locale-Foundation. Die Repo-Wahrheit liegt damit klar ueber einer simplen Lesart von "R1 done / R2 partial". Gleichzeitig ist das Produkt noch nicht voll als kundenseitig konsumierbare Portfolio- und Commercial-Plattform ausgereift: Cross-Tenant-Workflows, Compare/Promotion, Billing-/Lifecycle-Reife und Private-AI-Governance bleiben unvollstaendig. Zusaetzlich zeigt der Repo-Stand weiterhin eine schmale Findings-Cleanup-Lane: sichtbare Lifecycle-Backfill-Runtime-Surfaces, `acknowledged`-Kompatibilitaet und fehlende explizite Creation-Time-Invariant-Absicherung sollten als getrennte Folgespecs behandelt werden. TenantPilot ist aktuell ein starkes internes Governance- und Operations-Produkt mit belastbaren Foundations fuer Execution Truth, Baselines/Drift, Findings, Evidence, Reviews, Review Packs, Supportability, Telemetry und Safety Controls sowie einer repo-real umgesetzten ersten Customer-Review-Surface, Risk-Acceptance/Exception-Workflow, Findings-/Governance-Inboxen und einer DE/EN-Locale-Foundation. Die Repo-Wahrheit liegt damit klar ueber einer simplen Lesart von "R1 done / R2 partial". Gleichzeitig ist das Produkt noch nicht voll als kundenseitig konsumierbare Portfolio- und Commercial-Plattform ausgereift: Die Customer-Review-Surface ist noch eher eine operator-led customer delivery view im Admin-Kontext als eine voll produktisierte, kundensichere Governance-of-Record Consumption-Flache; dazu bleiben Cross-Tenant-Workflows, Compare/Promotion, Billing-/Lifecycle-Reife und Private-AI-Governance unvollstaendig. Zusaetzlich zeigt der Repo-Stand weiterhin eine schmale Findings-Cleanup-Lane: sichtbare Lifecycle-Backfill-Runtime-Surfaces, `acknowledged`-Kompatibilitaet und fehlende explizite Creation-Time-Invariant-Absicherung sollten als getrennte Folgespecs behandelt werden.
## Status Model ## Status Model
@ -41,7 +46,7 @@ ## Roadmap Coverage Summary
| Roadmap Area | Status | Evidence Level | UI Ready | Tested | Sellable | Notes | | Roadmap Area | Status | Evidence Level | UI Ready | Tested | Sellable | Notes |
|---|---|---:|---|---|---|---| |---|---|---:|---|---|---|---|
| R1 Golden Master Governance | adopted | strong | yes | repo tests, not run | yes | Baselines, Drift, Findings und OperationRun-Truth sind breit im Produkt verankert. | | R1 Golden Master Governance | adopted | strong | yes | repo tests, not run | yes | Baselines, Drift, Findings und OperationRun-Truth sind breit im Produkt verankert. |
| R2 Tenant Reviews, Evidence & Control Foundation | adopted | strong | yes | repo tests, not run | yes | Reviews, Evidence, Review Packs, Customer Review Workspace und Control-/Exception-Layer greifen als reale Governance-Surface zusammen. | | R2 Tenant Reviews, Evidence & Control Foundation | adopted | strong | yes | repo tests, not run | almost | Reviews, Evidence, Review Packs, Customer Review Workspace und Control-/Exception-Layer greifen als reale Governance-Surface zusammen, aber die Customer-Consumption-Productization bleibt unvollstaendig. |
| Alert escalation + notification routing | implemented_verified | strong | partial | repo tests, not run | yes | Alert-Regeln, Dispatch, Cooldown und Quiet Hours sind real. | | Alert escalation + notification routing | implemented_verified | strong | partial | repo tests, not run | yes | Alert-Regeln, Dispatch, Cooldown und Quiet Hours sind real. |
| Governance & Architecture Hardening | implemented_partial | strong | partial | repo tests, not run | foundation-only | Viele Hardening-Slices sind bereits im Code, die Lane bleibt aber aktiv. | | Governance & Architecture Hardening | implemented_partial | strong | partial | repo tests, not run | foundation-only | Viele Hardening-Slices sind bereits im Code, die Lane bleibt aber aktiv. |
| UI & Product Maturity Polish | implemented_partial | strong | partial | partial repo tests, not run | no | Empty States, Navigation, Localization und read-only Review-Polish sind real, aber kein geschlossenes Theme-Completion-Signal. | | UI & Product Maturity Polish | implemented_partial | strong | partial | partial repo tests, not run | no | Empty States, Navigation, Localization und read-only Review-Polish sind real, aber kein geschlossenes Theme-Completion-Signal. |
@ -50,12 +55,12 @@ ## Roadmap Coverage Summary
| R1.9 Platform Localization v1 | implemented_verified | strong | yes | repo tests, not run | foundation-only | Locale-Resolver, Override/Praeferenz, Workspace-Default, Fallback und lokalisierte Notifications sind repo-real. | | R1.9 Platform Localization v1 | implemented_verified | strong | yes | repo tests, not run | foundation-only | Locale-Resolver, Override/Praeferenz, Workspace-Default, Fallback und lokalisierte Notifications sind repo-real. |
| Product Scalability & Self-Service Foundation | implemented_partial | strong | yes | repo tests, not run | almost | Onboarding, Support, Help und Entitlements sind weit; Billing, Trial und Demo-Reife fehlen. | | Product Scalability & Self-Service Foundation | implemented_partial | strong | yes | repo tests, not run | almost | Onboarding, Support, Help und Entitlements sind weit; Billing, Trial und Demo-Reife fehlen. |
| R2.0 Canonical Control Catalog Foundation | implemented_verified | strong | partial | repo tests, not run | foundation-only | Bereits implementiert und in Evidence/Reviews referenziert, aber kein eigenstaendiger Kundennutzen-Surface. | | R2.0 Canonical Control Catalog Foundation | implemented_verified | strong | partial | repo tests, not run | foundation-only | Bereits implementiert und in Evidence/Reviews referenziert, aber kein eigenstaendiger Kundennutzen-Surface. |
| R2 Completion: customer review, support, help | adopted | strong | yes | repo tests, not run | yes | Customer Review Workspace, Support Diagnostics/Requests und Help-Katalog sind repo-real. | | R2 Completion: customer review, support, help | implemented_partial | strong | yes | repo tests, not run | almost | Customer Review Workspace, Support Diagnostics/Requests und Help-Katalog sind repo-real, aber die Customer-Review-Consumption ist noch nicht voll productized. |
| Findings Workflow v2 / Execution Layer | adopted | strong | yes | repo tests, not run | almost | Triage, Ownership, My Work, Intake, Governance Inbox, Exceptions und Alerts/Hygiene sind real; Cross-Tenant-Decisioning bleibt spaeter. | | Findings Workflow v2 / Execution Layer | adopted | strong | yes | repo tests, not run | almost | Triage, Ownership, My Work, Intake, Governance Inbox, Exceptions und Alerts/Hygiene sind real; Cross-Tenant-Decisioning bleibt spaeter. |
| Policy Lifecycle / Ghost Policies | specified | weak | no | no | no | Als Richtung sichtbar, aber nicht als repo-verifizierter Workflow. | | Policy Lifecycle / Ghost Policies | specified | weak | no | no | no | Als Richtung sichtbar, aber nicht als repo-verifizierter Workflow. |
| Platform Operations Maturity | implemented_partial | strong | yes | repo tests, not run | almost | System Panel, Control Tower und Ops Controls sind real; CSV/Raw Drilldowns bleiben offen. | | Platform Operations Maturity | implemented_partial | strong | yes | repo tests, not run | almost | System Panel, Control Tower und Ops Controls sind real; CSV/Raw Drilldowns bleiben offen. |
| Product Usage, Customer Health & Operational Controls | adopted | strong | yes | repo tests, not run | almost | Diese Mid-term-Lane ist im Repo bereits substanziell vorhanden. | | Product Usage, Customer Health & Operational Controls | adopted | strong | yes | repo tests, not run | almost | Diese Mid-term-Lane ist im Repo bereits substanziell vorhanden. |
| Private AI Execution & Usage Governance Foundation | planned | none | no | no | no | Keine belastbare AI-Governance-Foundation im Repo. | | Private AI Execution Governance Foundation | planned | none | no | no | no | Keine belastbare AI-Governance-Foundation im Repo. |
| MSP Portfolio & Operations | implemented_partial | medium | partial | repo tests, not run | foundation-only | Portfolio-Triage ist da; Compare/Promotion und Decision Workboard fehlen. | | MSP Portfolio & Operations | implemented_partial | medium | partial | repo tests, not run | foundation-only | Portfolio-Triage ist da; Compare/Promotion und Decision Workboard fehlen. |
| Human-in-the-Loop Autonomous Governance | planned | none | no | no | no | Kein repo-verifizierter Decision-Pack- oder Approval-Workflow jenseits des jetzigen Exception-/Review-Layers. | | Human-in-the-Loop Autonomous Governance | planned | none | no | no | no | Kein repo-verifizierter Decision-Pack- oder Approval-Workflow jenseits des jetzigen Exception-/Review-Layers. |
| Drift & Change Governance | implemented_partial | strong | yes | repo tests, not run | almost | Drift review, accepted-risk governance, exception validity und Governance-Inbox-Surfaces sind repo-real; portfolio-weite Eskalation bleibt offen. | | Drift & Change Governance | implemented_partial | strong | yes | repo tests, not run | almost | Drift review, accepted-risk governance, exception validity und Governance-Inbox-Surfaces sind repo-real; portfolio-weite Eskalation bleibt offen. |
@ -75,7 +80,7 @@ ## Implemented Capabilities
| Evidence snapshots | implemented_verified | yes | yes | repo tests, not run | yes | foundation-only | `app/Models/EvidenceSnapshot.php`; `app/Services/Evidence/EvidenceSnapshotService.php`; `tests/Feature/Evidence/*` | | Evidence snapshots | implemented_verified | yes | yes | repo tests, not run | yes | foundation-only | `app/Models/EvidenceSnapshot.php`; `app/Services/Evidence/EvidenceSnapshotService.php`; `tests/Feature/Evidence/*` |
| Tenant reviews | implemented_verified | yes | yes | repo tests, not run | yes | almost | `app/Models/TenantReview.php`; `app/Services/TenantReviews/TenantReviewService.php`; `tests/Feature/TenantReview/*` | | Tenant reviews | implemented_verified | yes | yes | repo tests, not run | yes | almost | `app/Models/TenantReview.php`; `app/Services/TenantReviews/TenantReviewService.php`; `tests/Feature/TenantReview/*` |
| Review pack generation and export | implemented_verified | yes | yes | repo tests, not run | yes | yes | `app/Models/ReviewPack.php`; `app/Services/ReviewPackService.php`; `tests/Feature/ReviewPack/*` | | Review pack generation and export | implemented_verified | yes | yes | repo tests, not run | yes | yes | `app/Models/ReviewPack.php`; `app/Services/ReviewPackService.php`; `tests/Feature/ReviewPack/*` |
| Customer review workspace | implemented_verified | yes | yes | repo tests, not run | yes | yes | `app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`; `tests/Feature/Reviews/*`; `tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php` | | Customer review workspace | implemented_partial | yes | yes | repo tests, not run | yes | almost | `app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`; `tests/Feature/Reviews/*`; `tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php` |
| Alerts and notification routing | implemented_verified | yes | partial | repo tests, not run | yes | yes | `app/Services/Alerts/AlertDispatchService.php`; `tests/Feature/*Alert*` | | Alerts and notification routing | implemented_verified | yes | partial | repo tests, not run | yes | yes | `app/Services/Alerts/AlertDispatchService.php`; `tests/Feature/*Alert*` |
| Provider health, onboarding readiness and required permissions | adopted | yes | yes | repo tests, not run | yes | almost | `app/Jobs/ProviderConnectionHealthCheckJob.php`; `app/Services/Onboarding/OnboardingLifecycleService.php`; `app/Filament/Pages/TenantRequiredPermissions.php` | | Provider health, onboarding readiness and required permissions | adopted | yes | yes | repo tests, not run | yes | almost | `app/Jobs/ProviderConnectionHealthCheckJob.php`; `app/Services/Onboarding/OnboardingLifecycleService.php`; `app/Filament/Pages/TenantRequiredPermissions.php` |
| Permission posture reporting | implemented_verified | yes | yes | repo tests, not run | yes | yes | `app/Services/PermissionPosture/PermissionPostureFindingGenerator.php`; `tests/Feature/PermissionPosture/*` | | Permission posture reporting | implemented_verified | yes | yes | repo tests, not run | yes | yes | `app/Services/PermissionPosture/PermissionPostureFindingGenerator.php`; `tests/Feature/PermissionPosture/*` |
@ -110,7 +115,7 @@ ## Foundation-Only Capabilities
## Partial Capabilities ## Partial Capabilities
- Customer-facing review consumption: Tenant Reviews, Evidence Snapshots, Review Packs und der Customer Review Workspace sind repo-real, aber portfolio-weite Consumption- und Sharing-Patterns bleiben offen. - Customer-facing review consumption: Tenant Reviews, Evidence Snapshots, Review Packs und der Customer Review Workspace sind repo-real, aber die Surface bleibt noch operator-led im Admin-Kontext; customer-safe wording, evidence summarization boundaries, audit-grade access semantics und calmer consumption states brauchen ein eigenes Productization-Follow-up.
- Findings Workflow v2: Triage, Assignment, My Work, Intake, Governance Inbox, Exceptions und Notifications sind vorhanden; spaetere Cross-Tenant-Decisioning-Layer und Cleanup debt um Lifecycle-Backfill-Surfaces, `acknowledged`-Kompatibilitaet und explizite Creation-Time-Invarianten bleiben offen. - Findings Workflow v2: Triage, Assignment, My Work, Intake, Governance Inbox, Exceptions und Notifications sind vorhanden; spaetere Cross-Tenant-Decisioning-Layer und Cleanup debt um Lifecycle-Backfill-Surfaces, `acknowledged`-Kompatibilitaet und explizite Creation-Time-Invarianten bleiben offen.
- Product scalability and self-service: Onboarding, Support, Help und Entitlements sind weit, Billing-, Trial- und Demo-Reife aber nicht. - Product scalability and self-service: Onboarding, Support, Help und Entitlements sind weit, Billing-, Trial- und Demo-Reife aber nicht.
- MSP portfolio operations: Portfolio-Triage ist vorhanden, Cross-Tenant Compare und Promotion fehlen. - MSP portfolio operations: Portfolio-Triage ist vorhanden, Cross-Tenant Compare und Promotion fehlen.
@ -119,7 +124,7 @@ ## Partial Capabilities
## Planned But Not Implemented ## Planned But Not Implemented
- Private AI Execution & Usage Governance Foundation - Private AI Execution Governance Foundation
- Human-in-the-Loop Autonomous Governance - Human-in-the-Loop Autonomous Governance
- Standardization & Policy Quality / Intune Linting - Standardization & Policy Quality / Intune Linting
- PSA / Ticketing Handoff - PSA / Ticketing Handoff
@ -132,9 +137,10 @@ ## Release Readiness
| Release / Theme | Readiness | Notes | | Release / Theme | Readiness | Notes |
|---|---|---| |---|---|---|
| R1 Golden Master Governance | implemented | Die zentrale Governance- und Execution-Layer ist repo-verifiziert und breit adoptiert. | | R1 Golden Master Governance | implemented | Die zentrale Governance- und Execution-Layer ist repo-verifiziert und breit adoptiert. |
| R2 Tenant Reviews & Evidence Packs | implemented | Reviews, Evidence Snapshots, Review Packs, Customer Review Workspace und Exception-/Accepted-Risk-Workflow sind repo-real; breitere Commercial-Polish-Themen bleiben separat. | | R2 Tenant Reviews & Evidence Packs | implemented | Reviews, Evidence Snapshots, Review Packs, Customer Review Workspace und Exception-/Accepted-Risk-Workflow sind repo-real; die Customer-Review-Productization bleibt aber als sellability follow-up offen. |
| R3 MSP Portfolio OS | foundation only | Portfolio-Triage und Governance-Surfaces sind da, aber Compare/Promotion und portfolio-weite Action-Layer fehlen. | | R3 MSP Portfolio OS | foundation only | Portfolio-Triage und Governance-Surfaces sind da, aber Compare/Promotion und portfolio-weite Action-Layer fehlen. |
| Later Compliance Light | foundation only | Canonical Controls, Evidence und Exceptions existieren als Grundlage; ein Compliance-Produkt ist nicht repo-proven. | | Compliance Evidence Mapping v1 | foundation only | Canonical Controls, Evidence, Stored Reports und Exceptions existieren als Grundlage; eine customer-safe Mapping-Layer ist nicht repo-proven. |
| Governance-as-a-Service Packaging v1 | foundation only | Review Packs, Exports, Evidence und Accepted-Risk-Truth sind repo-real; eine wiederholbare management-taugliche Governance-Verpackung ist nicht repo-proven. |
## Commercial Readiness ## Commercial Readiness
@ -142,14 +148,14 @@ ### Demo-ready
- Baseline compare and drift walkthroughs - Baseline compare and drift walkthroughs
- Review pack generation and export - Review pack generation and export
- Customer-safe review workspace walkthroughs - Customer review workspace walkthroughs with operator guidance
- Provider health, onboarding readiness and required permissions - Provider health, onboarding readiness and required permissions
- Support diagnostics - Support diagnostics
- Permission posture and Entra admin roles reporting - Permission posture and Entra admin roles reporting
### Almost sellable ### Almost sellable
- Review-driven governance workflow rund um Tenant Reviews, Customer Review Workspace, accepted risks und Review Packs - Review-driven governance workflow rund um Tenant Reviews, Customer Review Workspace, accepted risks und Review Packs, aber noch nicht als vollstaendig productisierte customer-safe consumption experience
- Baseline drift and restore governance - Baseline drift and restore governance
- Findings workflow mit persönlicher Inbox, Intake, Governance Inbox und Exception-Handling - Findings workflow mit persönlicher Inbox, Intake, Governance Inbox und Exception-Handling
- Alerting and run visibility for governance operations - Alerting and run visibility for governance operations
@ -174,39 +180,46 @@ ### Foundation-only
### Not sellable yet ### Not sellable yet
- Cross-Tenant Compare and Promotion v1 - Cross-Tenant Compare and Promotion v1
- Compliance Evidence Mapping v1
- Governance-as-a-Service Packaging v1
- Private AI Execution Governance Foundation - Private AI Execution Governance Foundation
- External Support Desk / PSA Handoff - External Support Desk / PSA Handoff
- Compliance Light product layer
## Open Gaps & Blockers ## Open Gaps & Blockers
| Gap | Type | Impact | Roadmap Area | Recommended Spec | | Gap | Type | Impact | Roadmap Area | Recommended Spec |
|---|---|---|---|---| |---|---|---|---|---|
| Customer review productization remains incomplete | Sellability blocker | The repo has a real read-only customer review surface, but it still sits too close to operator/admin semantics and does not yet enforce a fully customer-safe consumption contract for findings, evidence, accepted risks, and audit-grade access/download flows | R2 completion / Customer review | P0 Customer Review Workspace Productization v1 |
| Decisioning still spans multiple repo-real inboxes | UX blocker | My Findings, Intake, Governance Inbox und Exception Queue sind real, aber Operators springen weiter zwischen mehreren Spezial-Surfaces und es gibt noch keinen portfolio-weiten Action-Layer | Findings Workflow / MSP Portfolio | P1 Governance Decision Surface Convergence | | Decisioning still spans multiple repo-real inboxes | UX blocker | My Findings, Intake, Governance Inbox und Exception Queue sind real, aber Operators springen weiter zwischen mehreren Spezial-Surfaces und es gibt noch keinen portfolio-weiten Action-Layer | Findings Workflow / MSP Portfolio | P1 Governance Decision Surface Convergence |
| Findings lifecycle backfill runtime surfaces remain productized | Cleanup blocker | Runbooks, commands, capabilities and tenant actions still expose a pre-production repair path that should not ship as product truth | Findings Workflow / Legacy Removal | P1 Remove Findings Lifecycle Backfill Runtime Surfaces | | Findings lifecycle backfill runtime surfaces remain productized | Cleanup blocker | Runbooks, commands, capabilities and tenant actions still expose a pre-production repair path that should not ship as product truth | Findings Workflow / Legacy Removal | P1 Remove Findings Lifecycle Backfill Runtime Surfaces |
| Legacy `acknowledged` status compatibility still survives | Semantics blocker | Status helpers, filters, badges, capability aliases and tests keep non-canonical workflow semantics alive | Findings Workflow / RBAC | P1 Remove Legacy Acknowledged Finding Status Compatibility | | Legacy `acknowledged` status compatibility still survives | Semantics blocker | Status helpers, filters, badges, capability aliases and tests keep non-canonical workflow semantics alive | Findings Workflow / RBAC | P1 Remove Legacy Acknowledged Finding Status Compatibility |
| Creation-time finding invariants are implied but not explicitly protected | Integrity blocker | Future finding generators could regress into partial lifecycle writes and recreate the need for repair tooling | Findings Workflow / Data Integrity | P1 Enforce Creation-Time Finding Invariants | | Creation-time finding invariants are implied but not explicitly protected | Integrity blocker | Future finding generators could regress into partial lifecycle writes and recreate the need for repair tooling | Findings Workflow / Data Integrity | P1 Enforce Creation-Time Finding Invariants |
| Cross-tenant compare and promotion is not repo-proven | Release blocker | MSP portfolio story remains partial | MSP Portfolio & Operations | P1 Cross-Tenant Compare and Promotion v1 | | Cross-tenant compare and promotion is not repo-proven | Release blocker | MSP portfolio story remains partial | MSP Portfolio & Operations | P1 Cross-Tenant Compare and Promotion v1 |
| Entitlements stop short of full commercial lifecycle | Commercialization blocker | Plan gating exists, but trial, grace and suspension semantics remain incomplete | Product Scalability & Self-Service Foundation | P2 Commercial Entitlements and Billing-State Maturity | | Entitlements stop short of full commercial lifecycle | Commercialization blocker | Plan gating exists, but trial, grace and suspension semantics remain incomplete | Product Scalability & Self-Service Foundation | P2 Commercial Entitlements and Billing-State Maturity |
| Compliance-oriented control mapping is not productized | Moat blocker | Canonical controls and evidence exist, but the product still lacks a bounded customer-safe layer that maps technical truth into control/readiness language | Compliance Evidence Mapping | P2 Compliance Evidence Mapping v1 |
| Review truth is not yet packaged as a repeatable MSP deliverable | Sellability blocker | Review packs and evidence are real, but recurring management-ready governance packaging still depends on manual interpretation and presentation | Governance-as-a-Service Packaging | P2 Governance-as-a-Service Packaging v1 |
| Support requests do not hand off to an external desk | Commercialization blocker | Support operations still depend on manual follow-through outside the product | R2 completion / Support | P2 External Support Desk / PSA Handoff | | Support requests do not hand off to an external desk | Commercialization blocker | Support operations still depend on manual follow-through outside the product | R2 completion / Support | P2 External Support Desk / PSA Handoff |
| AI governance foundation is absent | Architecture blocker | Future AI features would risk trust and policy drift if added directly | Private AI Execution & Usage Governance | P3 Private AI Execution Governance Foundation | | AI governance foundation is absent | Architecture blocker | Future AI features would risk trust and policy drift if added directly | Private AI Execution Governance | P3 Private AI Execution Governance Foundation |
| Roadmap understates current repo truth | Architecture blocker | Prioritization can drift because strategy docs still lag neuere Review-, Findings- und Localization-Surfaces | Product planning / roadmap maintenance | none - docs alignment | | Roadmap understates current repo truth | Architecture blocker | Prioritization can drift because strategy docs still lag neuere Review-, Findings- und Localization-Surfaces | Product planning / roadmap maintenance | none - docs alignment |
| Test files were not executed for this ledger update | Testing blocker | This document relies on code plus test presence, not live runtime validation | all areas | none - run targeted suites | | Test files were not executed for this ledger update | Testing blocker | This document relies on code plus test presence, not live runtime validation | all areas | none - run targeted suites |
## Recommended Next Specs ## Recommended Next Specs
- `P0 Customer Review Workspace Productization v1`: turns the existing admin-plane handoff into a more explicit customer-safe review consumption contract with calmer wording, progressive disclosure, explicit access states, and auditable download/view semantics.
- `P1 Governance Decision Surface Convergence`: verbindet My Findings, Intake, Governance Inbox, Customer Review Workspace und Exception Queue zu weniger Operator-Journeys und bereitet die Portfolio-Ebene vor. - `P1 Governance Decision Surface Convergence`: verbindet My Findings, Intake, Governance Inbox, Customer Review Workspace und Exception Queue zu weniger Operator-Journeys und bereitet die Portfolio-Ebene vor.
- `P1 Remove Findings Lifecycle Backfill Runtime Surfaces`: removes visible pre-production repair tooling from runbooks, commands, actions, capabilities and deploy/runtime hooks. - `P1 Remove Findings Lifecycle Backfill Runtime Surfaces`: removes visible pre-production repair tooling from runbooks, commands, actions, capabilities and deploy/runtime hooks.
- `P1 Remove Legacy Acknowledged Finding Status Compatibility`: collapses findings workflow semantics onto the canonical `triaged` model and removes stale RBAC/query aliases. - `P1 Remove Legacy Acknowledged Finding Status Compatibility`: collapses findings workflow semantics onto the canonical `triaged` model and removes stale RBAC/query aliases.
- `P1 Enforce Creation-Time Finding Invariants`: proves that new findings are lifecycle-ready at write time so no repair backfill has to return later. - `P1 Enforce Creation-Time Finding Invariants`: proves that new findings are lifecycle-ready at write time so no repair backfill has to return later.
- `P1 Cross-Tenant Compare and Promotion v1`: needed to move from portfolio visibility to portfolio action. - `P1 Cross-Tenant Compare and Promotion v1`: needed to move from portfolio visibility to portfolio action.
- `P2 Commercial Entitlements and Billing-State Maturity`: extends the already real entitlement substrate into a usable commercial lifecycle. - `P2 Commercial Entitlements and Billing-State Maturity`: extends the already real entitlement substrate into a usable commercial lifecycle.
- `P2 Compliance Evidence Mapping v1`: should start as one bounded versioned overlay that maps existing technical truth into one customer-safe control/readiness view and one reuse path into review or export surfaces.
- `P2 Governance-as-a-Service Packaging v1`: should start as one on-demand management-ready governance package built from existing review-pack, evidence, and accepted-risk truth rather than a broad recurring reporting suite.
- `P2 External Support Desk / PSA Handoff`: extends support requests beyond internal persistence. - `P2 External Support Desk / PSA Handoff`: extends support requests beyond internal persistence.
- `P3 Private AI Execution Governance Foundation`: should exist before feature-level AI adoption, not after it. - `P3 Private AI Execution Governance Foundation`: should exist before feature-level AI adoption, not after it.
## Roadmap Drift Notes ## Roadmap Drift Notes
- `roadmap.md` understates current R2 completion. Customer Review Workspace, published review handoff, review-pack downloads und der Finding-Exception-/Risk-Acceptance-Workflow sind bereits repo-real. - `roadmap.md` understates current R2 implementation depth, but the ledger had overstated sellability. Customer Review Workspace, published review handoff, review-pack downloads und der Finding-Exception-/Risk-Acceptance-Workflow sind repo-real; the remaining gap is customer-safe productization, not review-foundation absence.
- `roadmap.md` understates findings workflow maturity. My Findings, Intake, Governance Inbox und Exception Queue existieren bereits im Repo. - `roadmap.md` understates findings workflow maturity. My Findings, Intake, Governance Inbox und Exception Queue existieren bereits im Repo.
- `roadmap.md` understates localization maturity. Locale resolution order, Workspace-Default, User-Praeferenz, lokalisierte Notifications und Fallback-Tests sind implementiert. - `roadmap.md` understates localization maturity. Locale resolution order, Workspace-Default, User-Praeferenz, lokalisierte Notifications und Fallback-Tests sind implementiert.
- `roadmap.md` understates the current R2 control foundation. Canonical controls, stored reports, permission posture and Entra admin roles are already repo-real, not just near-term ideas. - `roadmap.md` understates the current R2 control foundation. Canonical controls, stored reports, permission posture and Entra admin roles are already repo-real, not just near-term ideas.
@ -214,7 +227,7 @@ ## Roadmap Drift Notes
- `roadmap.md` understates operational maturity. Product telemetry, customer health and operational controls are already implemented and wired into the system panel. - `roadmap.md` understates operational maturity. Product telemetry, customer health and operational controls are already implemented and wired into the system panel.
- `roadmap.md` understates commercial foundations. A workspace entitlement resolver, plan profiles and enforcement points already exist, even though full billing-state maturity does not. - `roadmap.md` understates commercial foundations. A workspace entitlement resolver, plan profiles and enforcement points already exist, even though full billing-state maturity does not.
- The roadmap is now better at describing still-missing portfolio- und commercial-Layer than the current state of review/findings/localization implementation. Cross-Tenant Compare and Promotion, full billing-state maturity, external PSA handoff and AI Governance still look genuinely unimplemented. - The roadmap is now better at describing still-missing portfolio- und commercial-Layer than the current state of review/findings/localization implementation. Cross-Tenant Compare and Promotion, full billing-state maturity, external PSA handoff and AI Governance still look genuinely unimplemented.
- The main drift pattern is underestimation, not overestimation. Customer-facing review consumption is no longer the clearest missing piece; portfolio action and commercial lifecycle are. - The main drift pattern is still underestimation, but customer-review sellability now needs a more precise reading: the missing piece is no longer basic review read-only access, but the final customer-safe productization layer over an already real surface.
## Evidence Sources ## Evidence Sources

View File

@ -1,5 +1,10 @@
# Operator Semantic Taxonomy # Operator Semantic Taxonomy
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Canonical operator-facing vocabulary for lifecycle, outcome, evidence, and actionability states
> **Do not use for:** Inventing local synonyms or assuming every product surface already fully conforms without repo verification
>
> Canonical operator-facing state reference for the first implementation slice. > Canonical operator-facing state reference for the first implementation slice.
> Downstream specs and badge mappings must reuse this vocabulary instead of inventing local synonyms. > Downstream specs and badge mappings must reuse this vocabulary instead of inventing local synonyms.

View File

@ -1,5 +1,10 @@
# Product Principles # Product Principles
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Stable product and architecture principles that should shape specs, UX, and implementation choices
> **Do not use for:** Assuming every principle is already enforced everywhere in code without repo verification
>
> Permanent product principles that govern every spec, every UI decision, and every architectural choice. > Permanent product principles that govern every spec, every UI decision, and every architectural choice.
> New specs must align with these. If a principle needs to change, update this file first. > New specs must align with these. If a principle needs to change, update this file first.

View File

@ -0,0 +1,11 @@
# Product & Roadmap Prompts
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Reusable roadmap, productization, and portfolio audit prompts
> **Do not use for:** Direct implementation without converting outputs into specs or verified planning artifacts
This folder contains reusable prompts for roadmap analysis, productization audits, and product strategy reviews.
Prompts are not specs.
Prompts are tools to generate, validate, or refine roadmap and spec-candidate decisions.

View File

@ -1,9 +1,16 @@
# Product Roadmap # Product Roadmap
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Current product roadmap, release themes, and prioritization context
> **Do not use for:** Implementation truth, spec completion status, or delivery guarantees without repo verification
>
> Strategic thematic blocks and release trajectory. > Strategic thematic blocks and release trajectory.
> This is the "big picture" — not individual specs. > This is the "big picture" — not individual specs.
>
> Queue boundary: the active candidate queue lives in `spec-candidates.md`; older audit-derived candidate packages are historical inputs only.
**Last updated**: 2026-04-25 **Last updated**: 2026-04-30
--- ---
@ -16,6 +23,26 @@ ## Release History
| **R2 "Tenant Reviews, Evidence & Control Foundation"** | Evidence packs, stored reports, canonical control catalog, permission posture, alerts | **Partial** | | **R2 "Tenant Reviews, Evidence & Control Foundation"** | Evidence packs, stored reports, canonical control catalog, permission posture, alerts | **Partial** |
| **R2 cont.** | Alert escalation + notification routing | **Done** | | **R2 cont.** | Alert escalation + notification routing | **Done** |
## Current Productization & Moat Priorities
This is the repo-based prioritization overlay for the next sellable lanes. The bottleneck is no longer raw backend truth alone. The next roadmap slices should make existing governance foundations customer-safe, decision-centered, auditable, and MSP-sellable before opening more backend-only islands.
| Order | Theme | Repo truth | Product posture | Why now | Candidate posture |
|---|---|---|---|---|---|
| 1 | Customer Review Workspace Productization v1 | Reviews, Evidence Snapshots, Review Packs, Customer Review Workspace, and accepted-risk foundations are repo-real | fast sellable | clearest sellability blocker between current repo truth and a customer-safe governance-of-record surface | active P0 candidate |
| 2 | Risk Acceptance & Accountability productization | Exception / risk-acceptance workflow is repo-verified, but customer-safe accountability presentation is not fully productized | fast sellable | strong MSP and German midmarket moat around documented decisions, expiry, reviewability, and audit trail | fold into Customer Review Workspace Productization and review/reporting follow-through, not a new greenfield foundation |
| 3 | Governance Decision Surface Convergence | Governance Inbox, My Findings, Intake, and Exception Queue are repo-real, but convergence is not | almost | reduces admin-tool sprawl and turns multiple queue surfaces into calmer decision work | active P1 candidate |
| 4 | Compliance Evidence Mapping v1 | Canonical controls, evidence, stored reports, reviews, and findings foundations are repo-real; customer-safe compliance mapping is not | foundation-only | strong governance moat for compliance-oriented MSP and Mittelstand reviews without certification claims | active P2 candidate |
| 5 | Governance-as-a-Service Packaging v1 | Review packs, exports, evidence, and accepted-risk foundations are repo-real; recurring executive/MSP packaging is not | foundation-only | turns governance truth into a repeatable MSP deliverable instead of one-off manual reporting | active P2 candidate |
| 6 | Cross-Tenant Compare & Promotion v1 | Portfolio triage exists; compare and promotion are not repo-proven | not implemented | strongest MSP multiplier after customer-safe review and decision workflows are calmer | active P1 candidate |
| 7 | Private AI Execution Governance Foundation | Spec 248 exists, but no repo-real governed AI execution layer is proven | only spec / not implemented | strategic moat later, but not ahead of current productization and portfolio-action gaps | keep as later strategic lane, not near-term blocker |
Explicit anti-sprawl boundaries for this priority set:
- Do not reopen risk acceptance as a broad new foundation theme; reuse the existing exception/risk-acceptance workflow and productize its customer-safe accountability trail.
- Do not reopen private AI as a fresh roadmap idea; the foundation already exists at spec level and should remain behind current customer-facing and MSP-facing sellability gaps.
- Do not prioritize Tenant Trust Score / public governance profile, insurance connectors, Copilot shadow-IT governance, local-first/on-prem proxy, or a standalone Betriebsrat mode before customer-safe review consumption, decision convergence, compliance mapping, governance packaging, and compare/promotion are materially clearer.
--- ---
## Active / Near-term ## Active / Near-term
@ -51,6 +78,8 @@ ### R1.9 Platform Localization v1 (DE/EN)
UI-Sprache umschaltbar (`de`, `en`) mit sauberem Locale-Foundation-Layer. UI-Sprache umschaltbar (`de`, `en`) mit sauberem Locale-Foundation-Layer.
Goal: Konsistente, durchgängige Lokalisierung aller Governance-Oberflächen — ohne Brüche in Export, Audit oder Maschinenformaten. Goal: Konsistente, durchgängige Lokalisierung aller Governance-Oberflächen — ohne Brüche in Export, Audit oder Maschinenformaten.
Repo reality: Die Locale-Foundation ist bereits repo-real. Der verbleibende Gap ist kein greenfield Localization-Foundation-Spec mehr, sondern Surface-Adoption, Copy-/Glossary-Vervollständigung und Regression-Hardening auf customer- und governance-nahen Oberflächen.
- Locale-Priorität: expliziter Override → User Preference → Workspace Default → System Default - Locale-Priorität: expliziter Override → User Preference → Workspace Default → System Default
- Workspace Default Language für neue Nutzer, User kann persönliche Sprache überschreiben - Workspace Default Language für neue Nutzer, User kann persönliche Sprache überschreiben
- Core-Surfaces zuerst: Navigation, Dashboard, Tenant Views, Findings, Baseline Compare, Risk Exceptions, Alerts, Operations, Audit-nahe Grundtexte - Core-Surfaces zuerst: Navigation, Dashboard, Tenant Views, Findings, Baseline Compare, Risk Exceptions, Alerts, Operations, Audit-nahe Grundtexte
@ -64,7 +93,7 @@ ### R1.9 Platform Localization v1 (DE/EN)
- Search/Sort/Filter auf kritischen Listen für locale-sensitives Verhalten prüfen - Search/Sort/Filter auf kritischen Listen für locale-sensitives Verhalten prüfen
- QA/Foundation: Missing-Key Detection, Locale Regression Tests, Pseudolocalization Smoke Tests für kritische Flows - QA/Foundation: Missing-Key Detection, Locale Regression Tests, Pseudolocalization Smoke Tests für kritische Flows
**Active specs**: — (not yet specced) **Queue status**: no standalone active candidate right now; remaining localization work should be folded into customer-facing productization and UI-maturity follow-through unless a narrower repo-real gap emerges.
### Product Scalability & Self-Service Foundation ### Product Scalability & Self-Service Foundation
Self-service and supportability foundation that keeps TenantPilot operable as a low-headcount, AI-assisted SaaS instead of drifting into manual onboarding, manual support, and founder-dependent customer operations. Self-service and supportability foundation that keeps TenantPilot operable as a low-headcount, AI-assisted SaaS instead of drifting into manual onboarding, manual support, and founder-dependent customer operations.
@ -110,10 +139,10 @@ ### R1.x Foundation Hardening — Governance Platform Anti-Drift
### R2 Completion — Evidence & Exception Workflows ### R2 Completion — Evidence & Exception Workflows
- Review pack export (Spec 109 — done) - Review pack export (Spec 109 — done)
- Exception/risk-acceptance workflow for Findings → Spec 154 (draft) - Exception/risk-acceptance workflow for Findings → Spec 154 (repo-real foundation; the next product gap is accountability-trail productization in customer-safe review, expiry/re-review visibility, and management-ready reporting)
- Formal evidence/review-pack entity foundation → Spec 153 (evidence snapshots, draft) + Spec 155 (tenant review layer / review packs, draft) - Formal evidence/review-pack entity foundation → Spec 153 (evidence snapshots, draft) + Spec 155 (tenant review layer / review packs, draft)
- Workspace-level PII override for review packs → deferred from 109 - Workspace-level PII override for review packs → deferred from 109
- Customer Review Workspace / Read-only View v1 → sharpen customer-facing review consumption: baseline status, latest reviews, findings, accepted risks, evidence/review-pack downloads, customer-safe redaction, and no admin/remediation actions - Customer Review Workspace Productization v1 → sharpen customer-facing review consumption: baseline status, latest reviews, findings, accepted risks, evidence/review-pack downloads, customer-safe redaction, calmer access states, and no admin/remediation actions
- Support Diagnostic Pack → connect tenant/review/finding/report/operation contexts into a reusable support bundle before support demand scales - Support Diagnostic Pack → connect tenant/review/finding/report/operation contexts into a reusable support bundle before support demand scales
- In-App Support Request with Context → attach the relevant diagnostic pack and ticket reference to support workflows without creating a separate support data model - In-App Support Request with Context → attach the relevant diagnostic pack and ticket reference to support workflows without creating a separate support data model
- Product Knowledge & Contextual Help → reuse canonical glossary, outcome/reason semantics, and report/finding terminology as the product-help source layer - Product Knowledge & Contextual Help → reuse canonical glossary, outcome/reason semantics, and report/finding terminology as the product-help source layer
@ -127,10 +156,24 @@ ### Findings Workflow v2 / Execution Layer
- Reuse the existing alerting foundation for assignment, reopen, due-soon, and overdue notification flows - Reuse the existing alerting foundation for assignment, reopen, due-soon, and overdue notification flows
- Keep comments, external ticket handoff, and cross-tenant workboards as later slices instead of forcing them into the first workflow iteration - Keep comments, external ticket handoff, and cross-tenant workboards as later slices instead of forcing them into the first workflow iteration
### Policy Lifecycle / Ghost Policies ### Workspace, Tenant & Managed Object Lifecycle Governance
Soft delete detection, automatic restore, "Deleted" badge, restore from backup. Strategic lifecycle taxonomy for workspaces, tenants, managed provider objects, evidence, backups, restoreability, export, retention, and purge.
Draft exists (Spec 900). Needs spec refresh and prioritization. **Goal**: Prevent local lifecycle fixes such as “Ghost Policies” from introducing inconsistent deletion semantics before TenantPilot has one enterprise-grade lifecycle model.
**Risk**: Ghost policies create confusion for backup item references.
TenantPilot must distinguish at least these lifecycle dimensions:
- Local record lifecycle: active, archived, locally removed, purge scheduled, purged
- Provider presence lifecycle: present, missing from provider, provider deleted, reappeared
- Operator suppression lifecycle: visible, ignored / suppressed, restored to visibility
- Commercial / workspace lifecycle: trial, active, grace, suspended read-only, closed
- Retention / compliance lifecycle: retained, export requested, deletion requested, deletion scheduled, legal hold / retention hold, purge due, purged
- Restoreability lifecycle: restorable, metadata only, blocked by dependency, not restorable, expired by retention
**Roadmap posture**: Strategic P2 enterprise-trust candidate, not immediate implementation. This should not block Customer Review Workspace Productization, Governance Decision Surface Convergence, or Cross-Tenant Compare & Promotion.
**Important boundary**: Do not implement a narrow policy-only ghost lifecycle patch, Laravel `SoftDeletes` rollout, workspace deletion flow, tenant deletion flow, purge engine, or retention framework before this lifecycle taxonomy is agreed.
**Spec candidate**: `Workspace, Tenant & Managed Object Lifecycle Governance v1` in `docs/product/spec-candidates.md`.
### Platform Operations Maturity ### Platform Operations Maturity
- CSV export for filtered run metadata (deferred from Spec 114) - CSV export for filtered run metadata (deferred from Spec 114)
@ -166,7 +209,7 @@ ### Additional Solo-Founder Scale Guardrails
- Vendor Questionnaire Answer Bank: reusable security/procurement answers aligned with the Security Trust Pack, product data model, Microsoft permissions, hosting, AI usage, subprocessors, retention, backup, deletion, and incident handling - Vendor Questionnaire Answer Bank: reusable security/procurement answers aligned with the Security Trust Pack, product data model, Microsoft permissions, hosting, AI usage, subprocessors, retention, backup, deletion, and incident handling
- Product Intake & No-Customization Governance: feature-request intake, roadmap-fit classification, no-custom-work policy, customer exception handling, productization rules, and a clear path from request → candidate → spec → release or rejection - Product Intake & No-Customization Governance: feature-request intake, roadmap-fit classification, no-custom-work policy, customer exception handling, productization rules, and a clear path from request → candidate → spec → release or rejection
- Support Severity Matrix & Runbooks: P1P4 definitions, incident vs support vs bug vs feature request distinction, response expectations by plan, escalation rules, known-issue handling, and internal support runbooks - Support Severity Matrix & Runbooks: P1P4 definitions, incident vs support vs bug vs feature request distinction, response expectations by plan, escalation rules, known-issue handling, and internal support runbooks
- Data Retention, Export & Deletion Self-Service: customer-facing and operator-facing flows for export, archive, deletion request handling, trial data expiry, workspace deactivation, and evidence/report retention visibility - Data Retention, Export & Deletion Self-Service: customer-facing and operator-facing flows for export, archive, deletion request handling, trial data expiry, workspace deactivation, and evidence/report retention visibility; depends on the shared Workspace, Tenant & Managed Object Lifecycle Governance taxonomy before destructive or retention-sensitive flows are implemented
- Business Continuity / Founder Backup Plan: access documentation, secret management, emergency contacts, deployment and restore runbooks, incident templates, DNS/domain/hosting ownership, billing access, and vacation/sickness fallback - Business Continuity / Founder Backup Plan: access documentation, secret management, emergency contacts, deployment and restore runbooks, incident templates, DNS/domain/hosting ownership, billing access, and vacation/sickness fallback
**Active specs**: — (not yet specced; guardrail track, only product-impacting items should become specs) **Active specs**: — (not yet specced; guardrail track, only product-impacting items should become specs)
@ -182,12 +225,13 @@ ### Product Usage, Customer Health & Operational Controls
**Depends on**: Self-Service Tenant Onboarding & Connection Readiness, Support Diagnostic Pack, Plans / Entitlements & Billing Readiness, ProviderConnection health, OperationRun truth, Findings workflow, StoredReports / EvidenceItems, and audit log foundation. **Depends on**: Self-Service Tenant Onboarding & Connection Readiness, Support Diagnostic Pack, Plans / Entitlements & Billing Readiness, ProviderConnection health, OperationRun truth, Findings workflow, StoredReports / EvidenceItems, and audit log foundation.
**Scope direction**: Start with privacy-aware product telemetry, derived customer/workspace health indicators, and a minimal operational controls registry. Avoid building a full analytics platform, CRM, or customer-success suite in the first slice. **Scope direction**: Start with privacy-aware product telemetry, derived customer/workspace health indicators, and a minimal operational controls registry. Avoid building a full analytics platform, CRM, or customer-success suite in the first slice.
### Private AI Execution & Usage Governance Foundation ### Private AI Execution Governance Foundation
Strategic AI platform foundation for using AI inside TenantPilot without hard-coding public cloud AI calls, leaking tenant data, losing cost control, or forcing later rewrites. Strategic AI platform foundation for using AI inside TenantPilot without hard-coding public cloud AI calls, leaking tenant data, losing cost control, or forcing later rewrites.
**Goal**: Make AI local/private-first, explicitly governed, budgeted, cacheable, auditable, and human-approved. External public AI providers are disabled by default and only usable through workspace-level opt-in, data classification, redaction, usage limits, and approval gates. **Goal**: Make AI local/private-first, explicitly governed, budgeted, cacheable, auditable, and human-approved. External public AI providers are disabled by default and only usable through workspace-level opt-in, data classification, redaction, usage limits, and approval gates.
**Why it matters**: TenantPilot sells governance, compliance readiness, evidence, and tenant trust. AI cannot be bolted on through direct feature-level API calls. The platform needs a reusable execution boundary so support summaries, finding explanations, review packs, decision packs, and customer communications can use AI later without rebuilding privacy, cost, provider, approval, and audit controls each time. **Why it matters**: TenantPilot sells governance, compliance readiness, evidence, and tenant trust. AI cannot be bolted on through direct feature-level API calls. The platform needs a reusable execution boundary so support summaries, finding explanations, review packs, decision packs, and customer communications can use AI later without rebuilding privacy, cost, provider, approval, and audit controls each time.
**Depends on**: Product Knowledge & Contextual Help, Support Diagnostic Pack, Decision Pack Contract & Approval Workflow, Product Usage & Adoption Telemetry, Plans / Entitlements & Billing Readiness, Operational Controls & Feature Flags, Security Trust Pack Light, audit log foundation, and workspace/RBAC isolation. **Depends on**: Product Knowledge & Contextual Help, Support Diagnostic Pack, Decision Pack Contract & Approval Workflow, Product Usage & Adoption Telemetry, Plans / Entitlements & Billing Readiness, Operational Controls & Feature Flags, Security Trust Pack Light, audit log foundation, and workspace/RBAC isolation.
**Scope direction**: Build the foundation before broad AI features: AI use case registry, AI provider registry, workspace AI policy, AI data classification, AI context builders, AI policy gate, AI budget gate, AI result store/cache, AI usage ledger, and AI audit trail. Start with local/private and customer-hosted model compatibility; keep external provider support optional and explicit. **Scope direction**: Build the foundation before broad AI features: AI use case registry, AI provider registry, workspace AI policy, AI data classification, AI context builders, AI policy gate, AI budget gate, AI result store/cache, AI usage ledger, and AI audit trail. Start with local/private and customer-hosted model compatibility; keep external provider support optional and explicit.
**Priority note**: This remains strategic, but it should stay behind current customer-review productization, decision convergence, compliance mapping, governance packaging, and compare/promotion gaps.
**Core principles**: **Core principles**:
- AI is never called directly from feature code; every AI action goes through governed use cases, policy gates, budget gates, context builders, provider adapters, cache/result storage, and audit trails - AI is never called directly from feature code; every AI action goes through governed use cases, policy gates, budget gates, context builders, provider adapters, cache/result storage, and audit trails
@ -212,7 +256,7 @@ ### AI-Assisted Customer Operations
AI-assisted customer operations layer for support, reviews, summaries, release communication, and customer-facing explanations, explicitly bounded by private AI execution policy, human approval, and product auditability. AI-assisted customer operations layer for support, reviews, summaries, release communication, and customer-facing explanations, explicitly bounded by private AI execution policy, human approval, and product auditability.
**Goal**: Use AI to prepare, summarize, classify, and draft customer operations work while keeping tenant-changing actions, customer commitments, legal statements, and external communications under human approval. **Goal**: Use AI to prepare, summarize, classify, and draft customer operations work while keeping tenant-changing actions, customer commitments, legal statements, and external communications under human approval.
**Why it matters**: TenantPilot can stay lean only if support, customer reviews, release communication, and diagnostics are structured enough for AI assistance without becoming ungoverned automation or uncontrolled public-model data processing. **Why it matters**: TenantPilot can stay lean only if support, customer reviews, release communication, and diagnostics are structured enough for AI assistance without becoming ungoverned automation or uncontrolled public-model data processing.
**Depends on**: Private AI Execution & Usage Governance Foundation, Product Knowledge & Contextual Help, Support Diagnostic Pack, In-App Support Request, StoredReports / EvidenceItems, Findings workflow maturity, release-note discipline, and company support-desk structure. **Depends on**: Private AI Execution Governance Foundation, Product Knowledge & Contextual Help, Support Diagnostic Pack, In-App Support Request, StoredReports / EvidenceItems, Findings workflow maturity, release-note discipline, and company support-desk structure.
**Scope direction**: Start with AI-generated support summaries, finding explanations, tenant review summaries, diagnostic summaries, release-note drafts, and support-response drafts. Prefer local/private execution for tenant/customer data. Avoid autonomous tenant remediation, automatic risk acceptance, automatic legal commitments, or customer-facing messages without review. **Scope direction**: Start with AI-generated support summaries, finding explanations, tenant review summaries, diagnostic summaries, release-note drafts, and support-response drafts. Prefer local/private execution for tenant/customer data. Avoid autonomous tenant remediation, automatic risk acceptance, automatic legal commitments, or customer-facing messages without review.
### Decision-Based Operating Foundations ### Decision-Based Operating Foundations
@ -221,12 +265,14 @@ ### Decision-Based Operating Foundations
**Why it matters**: Governance inboxes, actionable alerts, and later autonomous-governance features will fail if they land on top of detail-heavy, entity-first navigation. This is the UX/product prerequisite layer for the later MSP Portfolio OS direction. **Why it matters**: Governance inboxes, actionable alerts, and later autonomous-governance features will fail if they land on top of detail-heavy, entity-first navigation. This is the UX/product prerequisite layer for the later MSP Portfolio OS direction.
**Depends on**: Current constitution and action-surface hardening, operator-truth work, existing navigation/context specs. **Depends on**: Current constitution and action-surface hardening, operator-truth work, existing navigation/context specs.
**Scope direction**: First the constitution/rule delta, then a surface / IA classification of current product surfaces, then bounded retrofits that demote detail-first flows behind progressive disclosure instead of creating more top-level pages. **Scope direction**: First the constitution/rule delta, then a surface / IA classification of current product surfaces, then bounded retrofits that demote detail-first flows behind progressive disclosure instead of creating more top-level pages.
**Concrete next slice**: `Governance Decision Surface Convergence` is the repo-based follow-up. Do not reopen the first Governance Inbox as a greenfield concept.
### MSP Portfolio & Operations (Multi-Tenant) ### MSP Portfolio & Operations (Multi-Tenant)
Multi-tenant health dashboard, SLA/compliance reports (PDF), cross-tenant troubleshooting center. Multi-tenant health dashboard, SLA/compliance reports (PDF), cross-tenant troubleshooting center.
**Source**: 0800-future-features brainstorming, identified as highest priority pillar. **Source**: 0800-future-features brainstorming, identified as highest priority pillar.
**Prerequisites**: Decision-Based Operating Foundations, Cross-tenant compare (Spec 043 — draft only). **Prerequisites**: Decision-Based Operating Foundations, Cross-tenant compare (Spec 043 — draft only).
**Later expansion**: portfolio operations should eventually include a cross-tenant findings workboard once tenant-level inbox and intake flows are stable. **Later expansion**: portfolio operations should eventually include a cross-tenant findings workboard once tenant-level inbox and intake flows are stable.
**Concrete next slice**: `Cross-Tenant Compare & Promotion v1` is the repo-based move from portfolio visibility toward portfolio action.
### Human-in-the-Loop Autonomous Governance (Microsoft-first, Provider-extensible Decision-Based Operating) ### Human-in-the-Loop Autonomous Governance (Microsoft-first, Provider-extensible Decision-Based Operating)
Continuous detection, triage, decision drafting, approval-driven execution, and closed-loop evidence for governance actions across the Microsoft-first workspace portfolio, while keeping the decision model provider-extensible for later non-Microsoft domains. Continuous detection, triage, decision drafting, approval-driven execution, and closed-loop evidence for governance actions across the Microsoft-first workspace portfolio, while keeping the decision model provider-extensible for later non-Microsoft domains.
@ -260,12 +306,12 @@ ### PSA / Ticketing Handoff
Outbound handoff from findings into external service-desk or PSA systems with visible `ticket_ref` linkage and auditable "ticket created/linked" events. Outbound handoff from findings into external service-desk or PSA systems with visible `ticket_ref` linkage and auditable "ticket created/linked" events.
**Scope direction**: start with one-way handoff and internal visibility, not full bidirectional sync or full ITSM modeling. **Scope direction**: start with one-way handoff and internal visibility, not full bidirectional sync or full ITSM modeling.
### Compliance Readiness & Executive Review Packs ### Compliance Evidence Mapping v1
On-demand review packs that combine governance findings, accepted risks, evidence, baseline/drift posture, canonical control coverage, and key security signals into one coherent deliverable. CIS-aligned baseline libraries plus NIS2-/BSI-oriented readiness views depend on the Canonical Control Catalog and Evidence-to-Control mapping and remain explicitly without certification claims. Executive / CISO / customer-facing report surfaces alongside operator-facing detail views. Exportable auditor-ready and management-ready outputs. Versioned mapping layer that connects technical findings, accepted risks, evidence, and review outcomes to customer-safe control and readiness views without certification claims.
**Goal**: Make TenantPilot sellable as an MSP-facing governance and review platform for German midmarket and compliance-oriented customers who want structured tenant reviews and management-ready outputs on demand. **Goal**: Translate existing governance truth into control- and audit-ready language for German midmarket and compliance-oriented customers while keeping technical findings clearly separate from regulatory interpretation.
**Why it matters**: Turns existing governance data into a clear customer-facing value proposition. Strengthens MSP sales story beyond backup and restore. Creates a repeatable "review on demand" workflow for quarterly reviews, security health checks, and audit preparation. **Why it matters**: Canonical controls and evidence are already repo-real foundations. The missing value is not another control catalog, but a customer-safe mapping layer that explains why a finding matters, what evidence exists, and which control or readiness statement it supports.
**Depends on**: Canonical Control Catalog Foundation, Evidence-to-Control mapping, StoredReports / EvidenceItems foundation, Tenant Review runs, Customer Review Workspace / Read-only View, Findings + Risk Acceptance workflow, evidence / signal ingestion, export pipeline maturity. **Depends on**: Canonical Control Catalog Foundation, Evidence-to-Control mapping, StoredReports / EvidenceItems foundation, Tenant Review runs, Customer Review Workspace Productization, Findings + Risk Acceptance workflow, and export maturity.
**Scope direction**: Start as compliance readiness and review packaging. Avoid formal certification language or promises. Position as governance evidence, management reporting, and audit preparation. **Scope direction**: Start with versioned control interpretations plus one bounded overlay layer. Avoid certification promises, legal guarantees, or framework-specific deep branching inside the platform core.
**Modeling principle**: Compliance and governance requirements are modeled through a framework-neutral canonical control catalog plus technical interpretations and versioned framework overlays, not as separate technical object worlds per framework. Readiness views, evidence packs, baseline libraries, and auditor outputs are generated from that shared domain model. **Modeling principle**: Compliance and governance requirements are modeled through a framework-neutral canonical control catalog plus technical interpretations and versioned framework overlays, not as separate technical object worlds per framework. Readiness views, evidence packs, baseline libraries, and auditor outputs are generated from that shared domain model.
**Layering**: **Layering**:
@ -280,6 +326,13 @@ ### Compliance Readiness & Executive Review Packs
- Keep ISO / COBIT semantics in governance-assurance and ISMS-oriented overlays rather than introducing a second technical control universe - Keep ISO / COBIT semantics in governance-assurance and ISMS-oriented overlays rather than introducing a second technical control universe
- Avoid framework-specific one-off reports that bypass the common evidence, findings, exception, and export pipeline - Avoid framework-specific one-off reports that bypass the common evidence, findings, exception, and export pipeline
### Governance-as-a-Service Packaging v1
Recurring governance deliverables for MSPs and customer stakeholders built on review packs, accepted risks, evidence, and control mapping.
**Goal**: Let MSPs deliver monthly or quarterly governance packages without manual screenshot decks, Excel exports, or ad-hoc PowerPoint work.
**Why it matters**: This is the commercial layer that turns already-strong review, evidence, and accepted-risk foundations into a repeatable MSP revenue surface. TenantPilot should sell not only truth capture, but calm customer-safe governance reporting.
**Depends on**: Customer Review Workspace Productization, Compliance Evidence Mapping v1, Review Packs, StoredReports / EvidenceItems, Findings + Risk Acceptance workflow, export pipeline maturity, localization, and entitlements.
**Scope direction**: Start with executive summary, top findings, accepted risks, open decisions, evidence links, management-ready review-pack export, and bounded MSP branding. Avoid CRM/newsletter tooling, PSA replacement, or raw operator-data dumps as the default output.
### Entra Role Governance ### Entra Role Governance
Expand TenantPilot's governance coverage into Microsoft Entra role definitions and assignments as a first-class identity administration surface. Expand TenantPilot's governance coverage into Microsoft Entra role definitions and assignments as a first-class identity administration surface.
**What it means**: Inventory and visibility for built-in and custom role definitions. Visibility into role assignments and governance-relevant changes. Review-ready representation of identity administration posture. **What it means**: Inventory and visibility for built-in and custom role definitions. Visibility into role assignments and governance-relevant changes. Review-ready representation of identity administration posture.
@ -331,15 +384,15 @@ ## Infrastructure & Platform Debt
| Item | Risk | Status | | Item | Risk | Status |
|------|------|--------| |------|------|--------|
| No explicit company automation roadmap linkage | Risk that sales, support, billing, legal, and customer communication become founder-only manual work | Covered by Solo-Founder SaaS Automation & Operating Readiness | | No explicit company automation roadmap linkage | Risk that sales, support, billing, legal, and customer communication become founder-only manual work | Covered by Solo-Founder SaaS Automation & Operating Readiness |
| No product-level entitlement foundation yet | Later pricing, trial, retention, export, user, and tenant limits may require invasive retrofits | Covered by Product Scalability & Self-Service Foundation | | No shared lifecycle taxonomy for workspace, tenant, managed-object, retention, export, purge, and restoreability states | Local fixes such as ghost-policy handling, workspace deactivation, tenant removal, retention, or purge can create inconsistent deletion semantics and audit gaps | Covered by Workspace, Tenant & Managed Object Lifecycle Governance candidate |
| No structured support diagnostic bundle yet | Support cases require manual context gathering across tenants, runs, findings, providers, and reports | Covered by Product Scalability & Self-Service Foundation | | No structured support diagnostic bundle yet | Support cases require manual context gathering across tenants, runs, findings, providers, and reports | Covered by Product Scalability & Self-Service Foundation |
| No formal security trust pack yet | Enterprise sales and customer security reviews require repeated manual explanations | Covered by Solo-Founder SaaS Automation & Operating Readiness | | No formal security trust pack yet | Enterprise sales and customer security reviews require repeated manual explanations | Covered by Solo-Founder SaaS Automation & Operating Readiness |
| No product usage/adoption telemetry yet | Founder cannot see onboarding drop-off, feature adoption, trial health, or support-triggering surfaces without manual investigation | Covered by Additional Solo-Founder Scale Guardrails | | No product usage/adoption telemetry yet | Founder cannot see onboarding drop-off, feature adoption, trial health, or support-triggering surfaces without manual investigation | Covered by Additional Solo-Founder Scale Guardrails |
| No customer health score yet | Churn, inactive customers, stale reviews, unhealthy provider connections, and unresolved high-risk findings may be noticed too late | Covered by Additional Solo-Founder Scale Guardrails | | No customer health score yet | Churn, inactive customers, stale reviews, unhealthy provider connections, and unresolved high-risk findings may be noticed too late | Covered by Additional Solo-Founder Scale Guardrails |
| No explicit operational controls / feature flags lane | Incidents or risky features may require code changes or manual database intervention instead of safe operator controls | Covered by Additional Solo-Founder Scale Guardrails | | No explicit operational controls / feature flags lane | Incidents or risky features may require code changes or manual database intervention instead of safe operator controls | Covered by Additional Solo-Founder Scale Guardrails |
| No private AI execution foundation yet | Future AI features may call model providers directly, leak tenant context, become hard to audit, or require rewrites to support local/private models | Covered by Private AI Execution & Usage Governance Foundation | | No private AI execution foundation yet | Future AI features may call model providers directly, leak tenant context, become hard to audit, or require rewrites to support local/private models | Covered by Private AI Execution Governance Foundation |
| No AI usage budgeting / cost governance yet | AI-assisted summaries, decision packs, reviews, and support workflows may create uncontrolled compute/API costs and queue pressure | Covered by Private AI Execution & Usage Governance Foundation | | No AI usage budgeting / cost governance yet | AI-assisted summaries, decision packs, reviews, and support workflows may create uncontrolled compute/API costs and queue pressure | Covered by Private AI Execution Governance Foundation |
| No AI data classification / context-builder boundary yet | Raw provider payloads, personal data, or customer-confidential tenant context could be over-shared with models instead of sanitized purpose-specific context | Covered by Private AI Execution & Usage Governance Foundation | | No AI data classification / context-builder boundary yet | Raw provider payloads, personal data, or customer-confidential tenant context could be over-shared with models instead of sanitized purpose-specific context | Covered by Private AI Execution Governance Foundation |
| No no-customization governance yet | Customer-specific requests can silently turn the product into consulting work and create hidden maintenance obligations | Covered by Additional Solo-Founder Scale Guardrails | | No no-customization governance yet | Customer-specific requests can silently turn the product into consulting work and create hidden maintenance obligations | Covered by Additional Solo-Founder Scale Guardrails |
| No business-continuity / founder-backup plan yet | Solo-founder operations create continuity risk for incidents, illness, vacation, access recovery, and customer trust | Covered by Additional Solo-Founder Scale Guardrails | | No business-continuity / founder-backup plan yet | Solo-founder operations create continuity risk for incidents, illness, vacation, access recovery, and customer trust | Covered by Additional Solo-Founder Scale Guardrails |
| No `.env.example` in repo | Onboarding friction | Open | | No `.env.example` in repo | Onboarding friction | Open |
@ -356,7 +409,7 @@ ## Priority Ranking (from Product Brainstorming)
1. Product Scalability & Self-Service Foundation 1. Product Scalability & Self-Service Foundation
2. Product Usage, Customer Health & Operational Controls 2. Product Usage, Customer Health & Operational Controls
3. Private AI Execution & Usage Governance Foundation 3. Private AI Execution Governance Foundation
4. Decision-Based Operating / Governance Inbox 4. Decision-Based Operating / Governance Inbox
5. MSP Portfolio + Alerting 5. MSP Portfolio + Alerting
6. Drift + Approval Workflows 6. Drift + Approval Workflows
@ -373,6 +426,7 @@ ## How to use this file
- **Big product and operating themes** live here. - **Big product and operating themes** live here.
- **Concrete spec candidates** → see [spec-candidates.md](spec-candidates.md) - **Concrete spec candidates** → see [spec-candidates.md](spec-candidates.md)
- **Lifecycle / deletion / retention work must be taxonomy-first**: do not promote narrow ghost-policy, workspace deletion, tenant deletion, purge, or retention specs until the shared Workspace, Tenant & Managed Object Lifecycle Governance candidate defines the platform semantics.
- **Company automation / solo-founder operating items** live here as strategic tracks first; only product-impacting or repeatable engineering work should become spec candidates. - **Company automation / solo-founder operating items** live here as strategic tracks first; only product-impacting or repeatable engineering work should become spec candidates.
- **Solo-founder guardrails** should remain visible even when they are not immediate product specs, because they define what must become measurable, controllable, delegable, or documented before customer volume grows. - **Solo-founder guardrails** should remain visible even when they are not immediate product specs, because they define what must become measurable, controllable, delegable, or documented before customer volume grows.
- **Governance positioning is Microsoft-first, provider-extensible**: roadmap language should keep the initial product scope focused on Microsoft tenant governance while avoiding unnecessary Microsoft-only coupling in platform-level abstractions. - **Governance positioning is Microsoft-first, provider-extensible**: roadmap language should keep the initial product scope focused on Microsoft tenant governance while avoiding unnecessary Microsoft-only coupling in platform-level abstractions.

View File

@ -1,9 +1,14 @@
# Spec Candidates # Spec Candidates
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** The active repo-based queue of spec candidates that may still need new or refreshed specs
> **Do not use for:** Proof that a candidate is already specced, implemented, or prioritized above the roadmap without repo verification
>
> Repo-based next-spec queue for TenantPilot. > Repo-based next-spec queue for TenantPilot.
> This file is not a wishlist. It tracks only open gaps that are still worth turning into new or refreshed specs. > This file is not a wishlist. It tracks only open gaps that are still worth turning into new or refreshed specs.
> **Last reviewed**: 2026-04-28 > **Last reviewed**: 2026-04-30
> **Basis**: `implementation-ledger.md`, `roadmap.md`, current `specs/` truth > **Basis**: `implementation-ledger.md`, `roadmap.md`, current `specs/` truth
--- ---
@ -19,70 +24,115 @@ ## Candidate Rules
- P3 is for later platform ambitions after current release blockers close. - P3 is for later platform ambitions after current release blockers close.
- Existing candidate history is preserved through `Promoted to Spec`, `Deferred`, and `Superseded / Removed` notes rather than silent deletion. - Existing candidate history is preserved through `Promoted to Spec`, `Deferred`, and `Superseded / Removed` notes rather than silent deletion.
## Current Source-Of-Truth Boundary
- This file is the active candidate queue.
- `roadmap.md` provides strategic themes and release framing, not the canonical candidate queue.
- `discoveries.md` is a staging area for findings that may later be promoted here.
- `implementation-ledger.md` is maturity evidence, not a prioritization queue.
- Audit-derived candidate packages under `docs/audits/` are historical inputs only unless they are explicitly promoted into this file.
## Active Candidate Queue ## Active Candidate Queue
### P0 — Release Blockers ### P0 — Release Blockers
### Customer Review Workspace v1 ### Customer Review Workspace Productization v1
- **Priority**: P0 - **Priority**: P0
- **Why this stays active**: The repo already has strong internal review foundations: tenant reviews, evidence snapshots, review packs, redaction paths, entitlements, audit, and RBAC-aware surfaces. What is still missing is the customer-safe read-only consumption layer that turns those internal assets into a clearly sellable review product. - **Candidate type**: Productization / customer-safe consumption.
- **Roadmap relationship**: R2 completion / customer-facing review consumption. - **Why this stays active**: The repo already has strong review foundations plus a repo-real `CustomerReviewWorkspace` page from Spec 249. What remains open is productization: the current surface still behaves more like an operator-led customer delivery view inside the admin plane than a fully customer-safe governance-of-record consumption experience.
- **Roadmap relationship**: R2 completion / customer-facing review consumption and sellability polish.
- **Existing implementation context**: Spec 249 (`customer-review-workspace`) delivered the first read-only workspace handoff. This candidate is the bounded follow-up that hardens the existing surface into a clearer customer-safe product contract instead of reopening review foundations from scratch.
- **Goal**: Turn the existing customer review surface into a customer-safe, read-only review consumption experience for customer reviewers, customer admins, and auditors that answers what was reviewed, what is critical, what was accepted, what evidence exists, and what the next sensible step is.
- **Dependencies**: - **Dependencies**:
- `TenantReview` - `TenantReview`
- `EvidenceSnapshot`
- `ReviewPack` - `ReviewPack`
- `EvidenceSnapshot`
- `Finding`
- accepted risks / exceptions workflow
- existing redaction behavior - existing redaction behavior
- stored reports and canonical control catalog foundations
- workspace entitlements - workspace entitlements
- tenant/workspace RBAC and audit foundations - tenant/workspace RBAC, audit, localization, and workspace-isolation foundations
- **Scope**: - **Scope**:
- customer-safe read-only workspace or view for latest review state - productize the existing customer review workspace into a clearer customer-safe read-only review consumption surface
- latest findings and accepted risks in customer-safe form - keep the primary surface centered on customer review workspace, review detail, findings summary, accepted-risk summary, evidence summary, and review-pack download areas
- review-pack download surface with existing redaction rules - visible findings semantics with severity, status, reason, impact, and recommendation in customer-safe language
- explicit absence of admin or remediation actions - accepted risks / exceptions shown as understandable governance decisions rather than internal workflow residue, including decision reason, accountable person or role, decision timing, expiry / re-review state, evidence linkage, and review context in customer-safe language
- clear authorization boundaries for customer and read-only viewers - evidence snapshots shown as narrative summaries and proof pointers, not raw JSON or provider payloads
- review-pack download area with existing redaction and entitlement rules
- clearer control / baseline context and next-step guidance for customer reviewers, customer admins, and auditors
- explicit audit events for workspace access, review detail access, evidence summary access, and pack downloads
- explicit empty, permission, expired, and unavailable states
- DE/EN-ready labels for customer-facing review text
- progressive disclosure for technical detail instead of operator-default density
- **Non-scope**: - **Non-scope**:
- admin settings - a new customer portal, separate identity plane, or broader customer product shell
- remediation actions - policy remediation, restore, or admin actions for customers
- raw operator diagnostics - a new review engine, evidence engine, or report-generation engine
- a broader customer portal rewrite - AI-generated summaries
- billing or contract workflows - public-link sharing without authentication or RBAC
- raw operator diagnostics, provider-debug data, or raw evidence payloads as default-visible content
- **Decision workflow**:
- customer reviewers can read released reviews, evidence summaries, and review packs
- customer acknowledgement can return later as a narrower v1.1 or v2 follow-up
- no technical remediation or admin mutation lives in this workspace
- **Capability review**:
- validate or introduce customer-facing capability boundaries such as `reviews.customer.view`, `reviews.customer.download_pack`, `evidence.customer.view`, `findings.customer.view`, and `risks.customer.view`
- existing operator capabilities must not automatically imply customer access
- **Export posture**:
- review packs remain the primary export and proof artifact
- evidence stays narrative and customer-safe by default rather than raw-payload first
- downloads must remain auditable
- **Acceptance criteria**: - **Acceptance criteria**:
- an authorized customer or read-only actor can open the review workspace - the customer-facing review workspace does not expose internal run, debug, provider, or raw JSON details by default
- latest review status, accepted risks, and key findings are visible without exposing admin controls - findings are shown with severity, status, reason, impact, and recommendation in customer-safe wording
- review-pack downloads respect existing redaction and entitlement rules - accepted risks / exceptions are visible and understandable for non-operator consumers, including accountable role or person, decision timing, expiry or review status, and evidence linkage where product truth exists
- evidence is summarized without exposing raw payloads by default
- review packs are downloadable when entitlement and capability checks pass
- each relevant view and download action produces audit evidence
- tenant and workspace isolation are enforced and tested - tenant and workspace isolation are enforced and tested
- audit-sensitive or operator-only data is not exposed through this surface - permission gaps and expired or unavailable access states are explicit and calm
- **Notes**: This is the clearest repo-derived blocker between current internal review strength and a cleaner sellable release. - customer-facing labels are localization-ready
- global search does not leak customer review or evidence artifacts into unintended discovery paths
- **Notes**: This is still the clearest repo-derived P0 blocker between today's operator-strong review foundations and a cleaner customer-safe sellable release. Do not split a second broad liability / accountability foundation candidate out of this unless a narrower internal expiry-cockpit or portfolio-risk gap proves separate product value.
### P1 — Enterprise Maturity ### P1 — Enterprise Maturity
### Decision-Based Governance Inbox v1 ### Governance Decision Surface Convergence
- **Priority**: P1 - **Priority**: P1
- **Why this stays active**: Findings, alerts, operation runs, review-pack generation, and portfolio triage already exist, but operators still work across several surfaces. The next maturity step is a single decision-oriented work surface, not more raw detail pages. - **Why this stays active**: The repo already has Governance Inbox, My Findings, Intake, Exception Queue, alerts, and review-linked action entry points. The open gap is no longer the first governance inbox; it is convergence across these repo-real surfaces so operators stop hopping between adjacent work queues.
- **Roadmap relationship**: Findings workflow maturity; later MSP Portfolio OS prerequisite. - **Roadmap relationship**: Findings workflow maturity; later MSP Portfolio OS prerequisite.
- **Existing implementation context**:
- Spec 250 (`decision-governance-inbox`) delivered the first decision-oriented governance inbox surface.
- Specs 221, 222, 224, 225, 230, and 231 already cover major inbox, intake, notification, and workflow-adoption slices.
- `CustomerReviewWorkspace` and `FindingExceptionsQueue` now act as adjacent decision surfaces that should converge around one calmer operator journey instead of multiplying parallel entry points.
- **Dependencies**: - **Dependencies**:
- findings workflow semantics and inbox foundations from Specs 219, 221, 222, 224, 225, 230, 231 - findings workflow semantics and inbox foundations from Specs 219, 221, 222, 224, 225, 230, 231
- alert routing foundation - alert routing foundation
- `OperationRun` truth - `OperationRun` truth
- portfolio triage continuity - portfolio triage continuity
- customer review and exception governance surfaces where decision work overlaps
- contextual help and reason-code surfaces where helpful - contextual help and reason-code surfaces where helpful
- **Scope**: - **Scope**:
- one operator-facing inbox for high-signal governance work - one decision-centered operator entry model across more than one existing queue or signal family
- grouping or prioritization across findings, alerts, stale runs, and related attention signals - reduce surface-hopping between My Findings, Intake, Governance Inbox, Exception Queue, and adjacent high-signal attention states
- direct action links into compare, finding review, review-pack generation, or triage paths - preserve direct action links into compare, finding review, review-pack generation, exception handling, or triage paths instead of duplicating domain state
- auditable state changes such as snooze, assign, or acknowledge where already supported - add convergence rules, prioritization, and clearer routing before inventing more list surfaces
- auditable state changes such as snooze, assign, or acknowledge only where those state mutations already exist as product truth
- **Non-scope**: - **Non-scope**:
- rebuilding the first governance inbox from scratch
- autonomous remediation - autonomous remediation
- AI-generated recommendations - AI-generated recommendations
- customer-facing inboxes - customer-facing inboxes
- full cross-tenant workboard redesign - full cross-tenant workboard redesign
- **Acceptance criteria**: - **Acceptance criteria**:
- one surface shows prioritized governance work from more than one underlying signal family - operators can start from one decision-centered surface or convergence model that spans more than one existing signal family or queue
- actions route to existing product truth rather than duplicating state - existing surfaces keep one consistent routing model instead of growing more parallel queue concepts
- actions route to existing product truth rather than creating duplicate state or duplicate work ownership
- visibility is capability-aware and workspace-safe - visibility is capability-aware and workspace-safe
- auditable state changes are recorded where the inbox mutates work state - auditable state changes are recorded where the inbox mutates work state
- tests prove signal grouping and authorization boundaries - tests prove signal grouping, routing, and authorization boundaries
- **Notes**: Important, but not a P0 release blocker while Customer Review Workspace is still missing. - **Notes**: This is a follow-up to the existing Governance Inbox, not a greenfield inbox foundation.
### Cross-Tenant Compare and Promotion v1 ### Cross-Tenant Compare and Promotion v1
- **Priority**: P1 - **Priority**: P1
@ -112,32 +162,6 @@ ### Cross-Tenant Compare and Promotion v1
- audit trail exists for compare and promotion entry points - audit trail exists for compare and promotion entry points
- the slice refreshes or narrows Spec 043 instead of reopening it as a vague ambition - the slice refreshes or narrows Spec 043 instead of reopening it as a vague ambition
### Localization v1
- **Priority**: P1
- **Why this stays active**: The repo and roadmap both indicate this is still absent. It is not a backend foundation gap; it is a product maturity gap that will get more expensive as the governance surface grows.
- **Roadmap relationship**: R1.9 Platform Localization v1.
- **Dependencies**:
- existing status and terminology catalogs
- contextual help boundaries
- notification and UI copy inventory on critical surfaces
- locale resolution rules for workspace, user, and system context
- **Scope**:
- `de` and `en` on core governance surfaces
- locale resolution order and fallback behavior
- locale-aware formatting for dates, times, and numbers
- stable machine and export formats that remain non-localized
- **Non-scope**:
- public website localization
- broad documentation translation
- retrospective translation of every legacy free-text record
- marketing copy systems
- **Acceptance criteria**:
- core navigation, dashboard, findings, baseline compare, alerts, and operations surfaces support `de` and `en`
- no raw translation keys appear on critical UI paths
- fallback to English is controlled and predictable
- locale-aware formatting does not affect audit or export truth
- targeted regression coverage exists for fallback and key critical flows
### Remove Findings Lifecycle Backfill Runtime Surfaces ### Remove Findings Lifecycle Backfill Runtime Surfaces
- **Priority**: P1 - **Priority**: P1
- **Why this stays active**: Repo audit shows visible runtime surfaces for a pre-production findings lifecycle repair path even though active finding generators already write the relevant lifecycle fields directly. The remaining path is not just ballast; it appears partially detached from current operational-control truth and keeps internal repair tooling productized. - **Why this stays active**: Repo audit shows visible runtime surfaces for a pre-production findings lifecycle repair path even though active finding generators already write the relevant lifecycle fields directly. The remaining path is not just ballast; it appears partially detached from current operational-control truth and keeps internal repair tooling productized.
@ -256,6 +280,63 @@ ### Commercial Entitlements and Billing-State Maturity
- changes and overrides are audited - changes and overrides are audited
- tests cover blocked and allowed paths - tests cover blocked and allowed paths
### Compliance Evidence Mapping v1
- **Priority**: P2
- **Why this stays active**: Canonical control catalog, evidence snapshots, stored reports, review packs, findings, and accepted-risk foundations are already repo-real. The missing gap is a versioned mapping layer from technical governance truth to customer-safe control or readiness views, not another control foundation rewrite.
- **Roadmap relationship**: Compliance moat / executive review follow-through.
- **Dependencies**:
- canonical control catalog foundation
- evidence snapshots and stored reports
- findings and accepted-risk workflow
- tenant reviews and review-pack export
- customer review productization and export maturity
- **Scope**:
- one versioned control interpretation layer and one bounded overlay for a first customer-safe readiness/control view
- map findings, evidence, and accepted risks to customer-safe control views without certification claims
- show control, evidence, and recommendation linkage in one primary review or export surface before broad multi-surface rollout
- keep framework overlays downstream from the shared canonical control model
- **Non-scope**:
- certification claims or legal guarantees
- hard-coded BSI, NIS2, CIS, or ISO semantics deep in the platform core
- separate technical control object models per framework
- full GRC suite or lawyer-facing workflow
- **Acceptance criteria**:
- one bounded overlay maps existing technical truth to a control or readiness view
- one concrete review or export surface can show control status, evidence linkage, and recommended action from shared foundations
- mapping versions are explicit and auditable
- the product clearly separates technical findings from regulatory interpretation
- no framework-specific one-off output bypasses the common evidence, findings, exception, and export pipeline
- **Smallest useful v1**: start with one overlay family and one customer-safe output path. Do not start by modeling multiple frameworks, multiple customer profiles, and multiple output surfaces at once.
### Governance-as-a-Service Packaging v1
- **Priority**: P2
- **Why this stays active**: Review packs, evidence snapshots, stored reports, customer review foundations, and accepted-risk workflow are repo-real. The missing gap is repeatable MSP/customer-safe packaging, not raw reporting substrate.
- **Roadmap relationship**: MSP sellability / recurring governance service.
- **Dependencies**:
- customer review workspace productization
- compliance evidence mapping
- review packs, evidence snapshots, and stored reports
- findings and accepted-risk workflow
- localization, entitlements, and export maturity
- **Scope**:
- one on-demand management-ready governance package built from the existing review-pack and evidence pipeline
- executive summary with customer-safe language
- top findings, accepted risks, open decisions, and evidence links
- bounded MSP branding and packaging rules
- no scheduling, batching, or report-program engine in the first slice
- **Non-scope**:
- CRM, newsletter, or marketing automation
- PSA replacement or service-desk workflow
- raw operator-data dumps as the default deliverable
- a separate reporting engine that bypasses existing review/evidence/export truth
- **Acceptance criteria**:
- an MSP can generate one repeatable on-demand governance package from existing review, evidence, and accepted-risk artifacts
- the output is customer-safe and management-readable by default
- top findings, accepted risks, open decisions, and evidence links are clearly represented
- packaging reuses shared review/evidence/export foundations instead of creating a parallel report domain
- bounded branding or presentation options do not weaken auditability or customer-safe defaults
- **Smallest useful v1**: one management-ready package for one review context, generated on demand from existing artifacts. Leave recurring schedules, multi-pack campaigns, and broader customer-communications automation out of scope.
### External Support Desk / PSA Handoff ### External Support Desk / PSA Handoff
- **Priority**: P2 - **Priority**: P2
- **Why this stays active**: In-app support requests are already repo-real. The remaining gap is external handoff and visible ticket linkage, not support-request creation itself. - **Why this stays active**: In-app support requests are already repo-real. The remaining gap is external handoff and visible ticket linkage, not support-request creation itself.
@ -292,7 +373,55 @@ ## Deferred / Existing Drafts Outside the Current Queue
These items are still useful, but they are not the next best open specs from the current repo state. These items are still useful, but they are not the next best open specs from the current repo state.
- `Policy Lifecycle / Ghost Policies`: still a valid gap, but not ahead of Customer Review Workspace or Cross-Tenant Compare. ### Workspace, Tenant & Managed Object Lifecycle Governance v1
- **Priority**: P2 — Important hardening / enterprise trust
- **Status**: Strategic candidate, not ready for immediate implementation
- **Do not prep before**: Customer Review Workspace, Cross-Tenant Compare & Promotion, Governance Decision Convergence, and current sellability/productization follow-through are materially closed.
- **Why this replaces `Policy Lifecycle / Ghost Policies`**: A policy-only ghost lifecycle spec risks introducing local deletion semantics, Laravel `SoftDeletes`, or overloaded `ignored_at` behavior before TenantPilot has a clear platform lifecycle taxonomy. The real roadmap-fit problem is broader: TenantPilot needs consistent lifecycle truth for workspaces, tenants, managed provider objects, evidence, backups, restoreability, export, retention, and purge.
- **Problem**: Lifecycle concerns currently appear across separate product areas such as policies, restore flows, commercial state, workspace entitlements, backup history, evidence snapshots, audit, support, and workspace administration. Without one shared taxonomy, local fixes can collapse different meanings into the same field or UI label: provider object missing, local TenantPilot record deleted, operator ignored the item, workspace suspended, data retained for compliance, data eligible for purge, or restore no longer possible.
- **Product goal**: Define an enterprise-grade lifecycle model before implementing destructive or retention-sensitive workflows. TenantPilot must distinguish at least these dimensions:
- **Local record lifecycle**: active, archived, locally removed, purge scheduled, purged
- **Provider presence lifecycle**: present, missing from provider, provider deleted, reappeared
- **Operator suppression lifecycle**: visible, ignored / suppressed, restored to visibility
- **Commercial / workspace lifecycle**: trial, active, grace, suspended read-only, closed
- **Retention / compliance lifecycle**: retained, export requested, deletion requested, deletion scheduled, legal hold / retention hold, purge due, purged
- **Restoreability lifecycle**: restorable, metadata only, blocked by dependency, not restorable, expired by retention
- **Smallest useful v1**: Do not implement deletion flows immediately. First define the lifecycle taxonomy, naming rules, state boundaries, audit expectations, OperationRun expectations, retention boundaries, and implementation guardrails for future specs.
- **Questions v1 must answer**:
- What does “deleted” mean in TenantPilot?
- What does “missing from provider” mean?
- What does “ignored” mean?
- What happens when a tenant is removed from a workspace?
- What happens when a workspace is suspended or closed?
- What data remains visible in read-only or suspended states?
- What data must be exportable before deletion?
- What data is retained for audit, evidence, or legal reasons?
- What can be purged, and what must never be purged automatically?
- Which lifecycle transitions require explicit human confirmation?
- Which transitions require audit events?
- Which transitions require OperationRun truth?
- Which transitions affect restore eligibility?
- **Explicit non-goals for v1**:
- no immediate workspace deletion implementation
- no immediate tenant deletion implementation
- no purge engine
- no hard-delete workflow
- no policy-only ghost lifecycle patch
- no Laravel `SoftDeletes` rollout
- no migration that reinterprets existing `ignored_at` data
- no new lifecycle dashboard or workboard
- no new restore engine
- no payment-provider or billing integration
- **Expected follow-up specs after taxonomy approval**:
1. `Provider-Missing Managed Object Truth v1` — explicit provider-missing state for policies and later other managed objects, no local deletion semantics, restore continuity where backup-backed history exists.
2. `Workspace & Tenant Closure Lifecycle v1` — close workspace, remove tenant from workspace, define read-only / suspended / closed behavior, no destructive purge yet.
3. `Data Export Before Deletion v1` — export customer-owned evidence, reports, audit-relevant artifacts, restore metadata, and tenant/workspace records before deletion.
4. `Retention & Purge Governance v1` — retention periods, legal hold, purge eligibility, irreversible deletion confirmation, and audit trail.
5. `Restoreability Expiry & Evidence Retention v1` — distinguish restorable backup payloads from retained evidence/audit metadata and define when restore is no longer possible but evidence remains retained.
- **Roadmap fit**: This is not a P0 sales feature. It is a P2 enterprise trust and compliance hardening candidate that becomes important before serious production customer offboarding, destructive data operations, or regulated retention commitments. It must not block Customer Review Workspace Productization, Governance Decision Surface Convergence, or Cross-Tenant Compare & Promotion.
- **Candidate decision**: Keep as strategic candidate. Do not implement a narrow Ghost Policy spec until the lifecycle taxonomy is agreed. If provider-missing policy behavior becomes an immediate product bug, create a smaller follow-up spec named `Provider-Missing Policy Visibility & Restore Continuity v1`; that smaller spec must use `provider_deleted_at`, `missing_from_provider_at`, or an equivalent provider-presence field and must not use Laravel `SoftDeletes` or local deletion semantics.
- `Workspace-level PII override for review packs`: bounded deferred follow-up from Spec 109. - `Workspace-level PII override for review packs`: bounded deferred follow-up from Spec 109.
- `CSV export for filtered run metadata`: valid system-console follow-up, but not near the top of the queue. - `CSV export for filtered run metadata`: valid system-console follow-up, but not near the top of the queue.
- `Raw error/context drilldowns for system console`: useful operator enhancement, but not ahead of current P0-P2 gaps. - `Raw error/context drilldowns for system console`: useful operator enhancement, but not ahead of current P0-P2 gaps.
@ -312,6 +441,8 @@ ## Promoted to Spec
- In-App Support Request with Context -> Spec 246 (`support-request-context`) - In-App Support Request with Context -> Spec 246 (`support-request-context`)
- Plans, Entitlements & Billing Readiness -> Spec 247 (`plans-entitlements-billing-readiness`) - Plans, Entitlements & Billing Readiness -> Spec 247 (`plans-entitlements-billing-readiness`)
- Private AI Execution & Policy Foundation -> Spec 248 (`private-ai-policy-foundation`) - Private AI Execution & Policy Foundation -> Spec 248 (`private-ai-policy-foundation`)
- Customer Review Workspace v1 -> Spec 249 (`customer-review-workspace`)
- Decision-Based Governance Inbox v1 -> Spec 250 (`decision-governance-inbox`)
- Queued Execution Reauthorization and Scope Continuity -> Spec 149 (`queued-execution-reauthorization`) - Queued Execution Reauthorization and Scope Continuity -> Spec 149 (`queued-execution-reauthorization`)
- Livewire Context Locking and Trusted-State Reduction -> Spec 152 (`livewire-context-locking`) - Livewire Context Locking and Trusted-State Reduction -> Spec 152 (`livewire-context-locking`)
- Evidence Domain Foundation -> Spec 153 (`evidence-domain-foundation`) - Evidence Domain Foundation -> Spec 153 (`evidence-domain-foundation`)
@ -353,4 +484,5 @@ ## Superseded / Removed From Active Queue
- `In-App Support Request with Context`: remove from active candidates because it is already Spec 246 and repo-implemented. - `In-App Support Request with Context`: remove from active candidates because it is already Spec 246 and repo-implemented.
- `Plans, Entitlements & Billing Readiness`: remove as a broad active candidate because Spec 247 already exists and the remaining open gap is narrower commercial lifecycle maturity. - `Plans, Entitlements & Billing Readiness`: remove as a broad active candidate because Spec 247 already exists and the remaining open gap is narrower commercial lifecycle maturity.
- `Private AI Execution & Policy Foundation`: remove from the active queue because Spec 248 already exists. - `Private AI Execution & Policy Foundation`: remove from the active queue because Spec 248 already exists.
- `Localization v1`: remove as a broad active candidate because the locale foundation is already repo-real; the remaining work is surface adoption, copy/glossary completion, and customer-facing polish inside narrower productization or UI-maturity follow-ups.
- Company-ops items such as `Lead Capture & CRM Pipeline`, `AVV / DPA / TOM / Legal Pack`, `Vendor Questionnaire Answer Bank`, `Business Continuity / Founder Backup Plan`, and similar operating artifacts should remain outside the active product-spec queue unless a concrete product slice emerges. - Company-ops items such as `Lead Capture & CRM Pipeline`, `AVV / DPA / TOM / Legal Pack`, `Vendor Questionnaire Answer Bank`, `Business Continuity / Founder Backup Plan`, and similar operating artifacts should remain outside the active product-spec queue unless a concrete product slice emerges.

View File

@ -1,5 +1,10 @@
# Admin Canonical Tenant Rollout # Admin Canonical Tenant Rollout
> **Status:** Historical
> **Last reviewed:** 2026-04-30
> **Use for:** Historical rollout notes for the admin canonical tenant transition
> **Do not use for:** Current implementation truth without checking the corresponding specs and code
## Purpose ## Purpose
Spec 136 completes the workspace-admin canonical tenant rule across admin-visible and admin-reachable shared surfaces. Workspace-admin requests under `/admin/...` resolve tenant context through `App\Support\OperateHub\OperateHubShell::activeEntitledTenant(request())`. Tenant-panel requests under `/admin/t/{tenant}/...` keep panel-native tenant semantics. Spec 136 completes the workspace-admin canonical tenant rule across admin-visible and admin-reachable shared surfaces. Workspace-admin requests under `/admin/...` resolve tenant context through `App\Support\OperateHub\OperateHubShell::activeEntitledTenant(request())`. Tenant-panel requests under `/admin/t/{tenant}/...` keep panel-native tenant semantics.

View File

@ -1,5 +1,10 @@
# Canonical Tenant Context Resolution # Canonical Tenant Context Resolution
> **Status:** Historical
> **Last reviewed:** 2026-04-30
> **Use for:** Historical context for the canonical tenant resolution rule and exception model
> **Do not use for:** Current path truth or current panel behavior without repo verification
## Canonical Rule ## Canonical Rule
- Tenant-panel and tenant-scoped flows keep panel-native tenant semantics through `Filament::getTenant()` / `Tenant::current()`. - Tenant-panel and tenant-scoped flows keep panel-native tenant semantics through `Filament::getTenant()` / `Tenant::current()`.

View File

@ -1,5 +1,10 @@
# SECTION A — FILAMENT V5 NOTES (BULLETS + SOURCES) # SECTION A — FILAMENT V5 NOTES (BULLETS + SOURCES)
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Filament v5 reference notes and framework-specific decision checks when local behavior is uncertain
> **Do not use for:** Assuming local implementation already follows every note without repo verification
## Versioning & Base Requirements ## Versioning & Base Requirements
- Rule: Filament v5 requires Livewire v4.0+. - Rule: Filament v5 requires Livewire v4.0+.
When: Always when installing/upgrading Filament v5. When NOT: Never target Livewire v3 in a v5 codebase. When: Always when installing/upgrading Filament v5. When NOT: Never target Livewire v3 in a v5 codebase.

View File

@ -1,5 +1,10 @@
# Golden Master / Baseline Drift — Deep Settings-Drift (Content-Fidelity) Analysis # Golden Master / Baseline Drift — Deep Settings-Drift (Content-Fidelity) Analysis
> **Status:** Reference
> **Last reviewed:** 2026-04-30
> **Use for:** Deep drift-engine research, architectural rationale, and fidelity trade-off analysis
> **Do not use for:** Current implementation truth or roadmap priority without repo verification
>
> Enterprise Research Report for TenantAtlas / TenantPilot > Enterprise Research Report for TenantAtlas / TenantPilot
> Date: 2025-07-15 > Date: 2025-07-15
> Scope: Architecture, code evidence, implementation proposal > Scope: Architecture, code evidence, implementation proposal

View File

@ -1,5 +1,10 @@
# TenantPilot M365 Policy Coverage Gap Analysis # TenantPilot M365 Policy Coverage Gap Analysis
> **Status:** Reference
> **Last reviewed:** 2026-04-30
> **Use for:** Coverage expansion planning and M365 policy gap research
> **Do not use for:** Current productization priority order without roadmap review
**Date:** 2026-03-07 **Date:** 2026-03-07
**Author:** Gap Analysis (Automated Deep Research) **Author:** Gap Analysis (Automated Deep Research)
**Scope:** Security, Governance & Baseline-relevante Policy-Familien über Microsoft 365 hinweg **Scope:** Security, Governance & Baseline-relevante Policy-Familien über Microsoft 365 hinweg

View File

@ -1,5 +1,10 @@
# Redaction / Masking / Sanitizing — Codebase Audit Report # Redaction / Masking / Sanitizing — Codebase Audit Report
> **Status:** Needs Review
> **Last reviewed:** 2026-04-30
> **Use for:** Security and data-integrity audit findings around redaction behavior and masking risks
> **Do not use for:** Assuming every finding is still open without verifying the current codebase
**Auditor:** Security + Data-Integrity Codebase Auditor **Auditor:** Security + Data-Integrity Codebase Auditor
**Date:** 2026-03-06 **Date:** 2026-03-06
**Scope:** Entire TenantAtlas repo (excluding `vendor/`, `node_modules/`, compiled views) **Scope:** Entire TenantAtlas repo (excluding `vendor/`, `node_modules/`, compiled views)

View File

@ -1,5 +1,10 @@
# Domain Coverage Map # Domain Coverage Map
> **Status:** Reference
> **Last reviewed:** 2026-04-30
> **Use for:** Domain classification, planning boundaries, and evaluating which Microsoft domains fit which TenantPilot product primitives
> **Do not use for:** Current release priority or implementation truth without roadmap, spec, and code verification
>
> Canonical classification of Microsoft domains for TenantPilot platform planning. > Canonical classification of Microsoft domains for TenantPilot platform planning.
> This document defines which domains receive which product primitives and why. > This document defines which domains receive which product primitives and why.

View File

@ -0,0 +1,27 @@
# TenantPilot Product Vision
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Long-term product direction and roadmap alignment
> **Do not use for:** Implementation truth without repo verification
TenantPilot is a Governance-of-Record platform for Microsoft tenant governance, evidence-first reviews, MSP portfolio operations, Intune backup/restore, auditability, customer-safe review consumption, and decision-based governance workflows.
TenantPilot is not a generic Microsoft admin mirror.
## Core Principles
- OperationRun Truth Layer
- Evidence-first Reporting
- Customer-safe Review Consumption
- Decision-first, diagnostics-second, evidence-third UX
- Capability-first RBAC
- Workspace-first Multi-Tenancy
- Provider-extensible Architecture
- Auditability
- MSP Portfolio Governance
- Enterprise SaaS UX over admin-tool sprawl
## Strategic Direction
The platform should prioritize productization of existing foundations before adding isolated technical coverage. New coverage should strengthen reviews, evidence, findings, baselines, governance inbox, or MSP portfolio workflows.

View File

@ -1,5 +1,10 @@
# Website Working Contract # Website Working Contract
> **Status:** Reference
> **Last reviewed:** 2026-04-30
> **Use for:** Guardrails around keeping `apps/website` an intentionally independent track
> **Do not use for:** Introducing new runtime coupling without explicit contract changes and repo verification
>
> Guardrails for evolving `apps/website` as an independently evolvable track in the current repository. > Guardrails for evolving `apps/website` as an independently evolvable track in the current repository.
> This document is repo-truth-based and describes the currently verified state, not a speculative future architecture. > This document is repo-truth-based and describes the currently verified state, not a speculative future architecture.

View File

@ -1,5 +1,10 @@
# Action surface contract # Action surface contract
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Action placement and affordance rules for Filament resources, pages, and relation managers
> **Do not use for:** Skipping authorization, confirmation, or resource-specific UX review
This project enforces a small “action surface contract” for Filament Resources / Pages / RelationManagers to keep table UIs consistent, quiet, and safe. This project enforces a small “action surface contract” for Filament Resources / Pages / RelationManagers to keep table UIs consistent, quiet, and safe.
## Inspect affordance (required) ## Inspect affordance (required)

View File

@ -1,5 +1,10 @@
# Filament Table Standard # Filament Table Standard
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Standard list-surface rules for production Filament tables
> **Do not use for:** Overriding product-specific needs without an explicit documented exception
## Standard ## Standard
TenantPilot standardizes production Filament list surfaces with a convention-first model: TenantPilot standardizes production Filament list surfaces with a convention-first model:

View File

@ -1,5 +1,10 @@
# Operator UX & Surface Standards # Operator UX & Surface Standards
> **Status:** Active
> **Last reviewed:** 2026-04-30
> **Use for:** Audience, language, disclosure, and operator-surface rules for product UX
> **Do not use for:** Treating internal implementation structure as product IA or redefining constitution terms locally
This document defines the binding audience-and-surface contract for TenantPilot. This document defines the binding audience-and-surface contract for TenantPilot.
It establishes: It establishes:

View File

@ -1,5 +1,10 @@
# Shared Diff Presentation Foundation # Shared Diff Presentation Foundation
> **Status:** Reference
> **Last reviewed:** 2026-04-30
> **Use for:** Reusable presentation rules for simple before/after comparison surfaces
> **Do not use for:** Domain diff logic, data fetching, or token-level compare behavior
## Purpose ## Purpose
Use the shared diff presentation foundation when a screen already has simple before/after data and needs: Use the shared diff presentation foundation when a screen already has simple before/after data and needs:

View File

@ -1,5 +1,10 @@
# Feature Specification: TenantPilot v1 # Feature Specification: TenantPilot v1
> **Status:** Historical
> **Last reviewed:** 2026-04-30
> **Use for:** Early product-history context and original v1 framing
> **Do not use for:** Current implementation truth, current roadmap priority, or current spec structure without repo verification
**Feature Branch**: `tenantpilot-v1` **Feature Branch**: `tenantpilot-v1`
**Created**: 2025-12-10 **Created**: 2025-12-10
**Status**: Draft **Status**: Draft

View File

@ -1,5 +1,10 @@
# Feature 003: Implementation Status Report # Feature 003: Implementation Status Report
> **Status:** Needs Review
> **Last reviewed:** 2026-04-30
> **Use for:** Historical implementation-progress context for Spec 003
> **Do not use for:** Proof that the remaining manual verification or tests were completed in the current repo state
## Executive Summary ## Executive Summary
**Status**: ✅ **Core Implementation Complete** (Phases 1-5) **Status**: ✅ **Core Implementation Complete** (Phases 1-5)

View File

@ -1,5 +1,10 @@
# Feature 003: Manual Testing Checklist # Feature 003: Manual Testing Checklist
> **Status:** Reference
> **Last reviewed:** 2026-04-30
> **Use for:** Manual verification scenarios for Spec 003 if that surface needs targeted re-checks
> **Do not use for:** Proof that manual testing was executed or passed in the current branch
## Prerequisites ## Prerequisites
1. **Start the application:** 1. **Start the application:**

View File

@ -0,0 +1,54 @@
# Preparation Review Checklist: Customer Review Workspace Productization v1
**Purpose**: Validate repo-fit preparation quality after `spec.md`, `plan.md`, and `tasks.md` are complete
**Reviewed**: 2026-04-30
**Feature**: [spec.md](../spec.md)
**Supporting artifacts**: [plan.md](../plan.md), [tasks.md](../tasks.md), [research.md](../research.md), [data-model.md](../data-model.md), [quickstart.md](../quickstart.md), [customer-review-productization.openapi.yaml](../contracts/customer-review-productization.openapi.yaml)
## Candidate Fit
- [x] The selected candidate still matches the active P0 queue in `docs/product/spec-candidates.md`, the current priority order in `docs/product/roadmap.md`, and the open-gap wording in `docs/product/implementation-ledger.md`
- [x] Existing `specs/` coverage was checked so this package stays a new productization follow-up rather than a duplicate of Specs 249, 253, 254, 255, or 257
- [x] The scope stays on the customer-review productization delta over the existing workspace and released-review detail flow instead of reopening review foundations
- [x] Broader baseline/control overlays and management-packaging follow-through are explicitly deferred rather than hidden inside this slice
## Constitution Fit
- [x] The package stays on the existing Filament v5 + Livewire v4 admin plane and does not introduce panel/provider-registration work beyond the current `bootstrap/providers.php` truth
- [x] No new persistence, customer identity plane, portal shell, authoring flow, publication engine, remediation flow, or destructive action surface is introduced
- [x] Workspace/tenant isolation and capability-first RBAC remain explicit, including `404` for non-members and optional capability gating only for secondary access paths
- [x] One dominant safe action per changed surface is explicitly described, with secondary proof affordances demoted out of peer header-action status
- [x] Global-search safety is preserved without introducing a new searchable resource or widening existing review/evidence discovery across tenant boundaries
- [x] Asset strategy remains unchanged; if later implementation unexpectedly registers assets, deployment still uses the existing `cd apps/platform && php artisan filament:assets` step
## Artifact Consistency
- [x] `spec.md`, `plan.md`, and `tasks.md` all target the same workspace-summary plus released-review-detail follow-up
- [x] The likely repo surfaces and plan structure match the current repository layout, including `apps/platform/lang` rather than a fictional app-local language directory
- [x] Tasks directly cover RBAC, auditability, disclosure hierarchy, localization, access/unavailable states, and global-search safety
- [x] Supporting artifacts exist, no unresolved template markers remain, and the package stays implementation-ready without touching application code
## Test Governance
- [x] Validation lanes remain explicitly bounded to `confidence` plus one existing `browser` smoke
- [x] The package reuses the existing reviews test family instead of creating a new heavy-governance or browser family
- [x] Reviewer proof commands remain explicit and minimal for the touched workspace, detail, pack, and proof surfaces
- [x] The close-out path records the review outcome, guardrail status, and any `document-in-feature` vs `follow-up-spec` decision inside the spec package
## Notes
- Reviewed after artifact alignment on 2026-04-30.
- This repository's preparation artifacts are intentionally implementation-oriented, so concrete routes, classes, affected surfaces, and validation commands are expected rather than treated as leakage.
- No application implementation was performed while preparing or reviewing this package.
- Implementation close-out on 2026-04-30 passed the focused feature checks, bounded browser smoke, and Pint. Audit gaps were handled with bounded additive action IDs for workspace entry and proof-open events; global-search and asset strategy remained unchanged.
## Review Outcome
- **Outcome**: `keep`
- **Reason**: The package remains the narrow customer-review productization follow-up, explicitly records the baseline/control deferral, aligns the detail-page action hierarchy, and adds direct task coverage for global-search safety.
- **Workflow result**: Ready for `/speckit.implement` after this preparation review.
## Implementation Outcome
- **Outcome**: `implemented`
- **Workflow result**: Ready for manual review after the implementation loop. No confirmed in-scope findings remain after the focused confidence checks, browser smoke, formatting, and post-implementation analysis.

View File

@ -0,0 +1,299 @@
openapi: 3.0.3
info:
title: TenantPilot Customer Review Workspace Productization v1 (Conceptual)
version: 0.1.0
description: |
Conceptual contract for the customer-safe productization follow-up in Spec 258.
NOTE: These paths describe existing admin and tenant-scoped routes reused by
the implementation. The schemas document expected derived page/view behavior
for planning purposes only; they do not require a new public REST API.
servers:
- url: /
paths:
/admin/reviews/workspace:
get:
summary: View the productized customer review workspace
description: |
Existing canonical admin-plane workspace page for customer-safe review
consumption. The route stays read-only and reuses current tenant review,
finding, evidence, review-pack, localization, RBAC, and audit truth.
parameters:
- in: query
name: tenant
required: false
schema:
type: string
description: |
Optional tenant prefilter using the existing tenant id or external id
pattern already accepted by the workspace page.
responses:
'200':
description: Workspace page rendered
content:
text/html:
schema:
type: string
application/json:
schema:
$ref: '#/components/schemas/CustomerReviewWorkspacePageModel'
'404':
description: Not found for non-members, actors without entitled tenants, or explicit out-of-scope tenant targeting
/admin/t/{tenant}/reviews/{review}:
get:
summary: Open the released review detail from the customer review workspace
description: |
Existing tenant-scoped released-review detail route reused as the
secondary context surface from the workspace page. The customer-workspace
flow uses the existing `customer_workspace=1` query flag to keep the
detail read-only and customer-safe.
parameters:
- in: path
name: tenant
required: true
schema:
type: integer
- in: path
name: review
required: true
schema:
type: integer
- in: query
name: customer_workspace
required: false
schema:
type: boolean
description: Existing query-context flag that suppresses operator lifecycle actions on the detail surface.
responses:
'200':
description: Released review detail rendered
content:
text/html:
schema:
type: string
application/json:
schema:
$ref: '#/components/schemas/CustomerReviewDetailModel'
'403':
description: Forbidden for an in-scope actor missing the record-level review permission
'404':
description: Not found for non-members, tenant mismatches, or out-of-scope review targets
/admin/t/{tenant}/evidence/{evidenceSnapshot}:
get:
summary: Open an evidence proof route from the customer review flow
description: |
Existing tenant-scoped evidence detail route reused only when the actor
explicitly asks for proof and has the required capability.
parameters:
- in: path
name: tenant
required: true
schema:
type: integer
- in: path
name: evidenceSnapshot
required: true
schema:
type: integer
- in: query
name: source_surface
required: false
schema:
type: string
description: Optional source-surface metadata if proof access is audited through the shared audit pipeline.
responses:
'200':
description: Evidence proof detail rendered
content:
text/html:
schema:
type: string
'403':
description: Forbidden for an in-scope actor missing evidence capability
'404':
description: Not found for non-members, mismatched tenant scope, or unavailable proof targets
/admin/review-packs/{reviewPack}/download:
get:
summary: Download the current review pack
description: |
Existing signed download route reused by the productized customer review
flow. The pack must already exist, be ready, and not be expired.
parameters:
- in: path
name: reviewPack
required: true
schema:
type: integer
- in: query
name: source_surface
required: false
schema:
type: string
description: Existing download metadata hook used by the shared audit path.
responses:
'200':
description: Review pack download stream
content:
application/zip:
schema:
type: string
format: binary
'403':
description: Forbidden because of missing signature or invalid signed URL
'404':
description: Review pack not found, not ready, expired, or out of accessible tenant scope
components:
schemas:
CustomerReviewWorkspacePageModel:
type: object
required:
- workspace_id
- entries
properties:
workspace_id:
type: integer
tenant_filter_id:
type: integer
nullable: true
entries:
type: array
items:
$ref: '#/components/schemas/CustomerReviewWorkspaceEntry'
empty_state_message:
type: string
nullable: true
audit_expectation:
type: string
nullable: true
description: |
Planning-only note describing whether workspace-open auditing is
already covered or requires a bounded shared-audit extension.
CustomerReviewWorkspaceEntry:
type: object
required:
- tenant_id
- tenant_name
- review_access
- review_pack_access
- evidence_proof_access
properties:
tenant_id:
type: integer
tenant_name:
type: string
latest_published_review_id:
type: integer
nullable: true
latest_review_published_at:
type: string
format: date-time
nullable: true
outcome_summary:
type: string
nullable: true
findings_summary:
type: string
nullable: true
accepted_risk_accountability_summary:
$ref: '#/components/schemas/AcceptedRiskAccountabilitySummary'
review_access:
$ref: '#/components/schemas/AccessState'
review_pack_access:
$ref: '#/components/schemas/AccessState'
evidence_proof_access:
$ref: '#/components/schemas/AccessState'
redaction_note:
type: string
nullable: true
absence_note:
type: string
nullable: true
CustomerReviewDetailModel:
type: object
required:
- review_id
- tenant_id
- launched_from_customer_workspace
- operator_actions_hidden
properties:
review_id:
type: integer
tenant_id:
type: integer
launched_from_customer_workspace:
type: boolean
operator_actions_hidden:
type: boolean
narrative_outcome_summary:
type: string
nullable: true
findings_summary:
type: string
nullable: true
accepted_risk_accountability_summary:
$ref: '#/components/schemas/AcceptedRiskAccountabilitySummary'
evidence_summary:
type: string
nullable: true
review_pack_access:
$ref: '#/components/schemas/AccessState'
evidence_proof_access:
$ref: '#/components/schemas/AccessState'
secondary_diagnostics_collapsed:
type: boolean
nullable: true
AcceptedRiskAccountabilitySummary:
type: object
nullable: true
properties:
summary_text:
type: string
accountable_party:
type: string
nullable: true
decision_reason:
type: string
nullable: true
review_due_at:
type: string
format: date-time
nullable: true
expires_at:
type: string
format: date-time
nullable: true
completeness_note:
type: string
nullable: true
AccessState:
type: object
required:
- state
properties:
state:
type: string
enum:
- available
- absent
- unavailable
- expired
- redacted
- partial
message:
type: string
nullable: true
url:
type: string
nullable: true
audit_action_id:
type: string
nullable: true
description: Existing or bounded-additive shared audit action id for the explicit access moment.

View File

@ -0,0 +1,273 @@
# Data Model — Customer Review Workspace Productization v1
**Spec**: [spec.md](spec.md)
No new persisted tables, projections, or customer-review entities are required for this follow-up. The feature reuses current tenant-owned review, finding-exception, evidence, review-pack, membership, and audit truth, then tightens the derived workspace and detail presentation contracts.
## Persisted Truth Reused
### Workspace / Tenant Entitlement Context
**Purpose**: Establish the active workspace boundary and the entitled tenant set before any workspace rows, proof links, or review detail routes are composed.
**Persisted carriers**:
- existing workspace membership records
- existing tenant membership pivot rows and role assignments
- existing capability registry and role-capability map
**Relevant fields / contracts**:
- `workspace_id`
- `tenant_id`
- tenant membership role
- capability grants derived from [../../apps/platform/app/Support/Auth/Capabilities.php](../../apps/platform/app/Support/Auth/Capabilities.php)
- current workspace and remembered tenant context from the existing workspace context/session model
**Validation rules**:
- current actor must be a member of the current workspace or the route resolves as not found
- workspace rows and explicit tenant filters may only resolve for entitled tenants in that current workspace
- out-of-scope tenant targets remain `404` and must not leak draft/review existence
### TenantReview
**Purpose**: Canonical source for the released governance record, current outcome summary, findings summary, accepted-risk summary, proof pointers, and review-detail inspect target.
**Persisted carrier**: existing `tenant_reviews` rows via [../../apps/platform/app/Models/TenantReview.php](../../apps/platform/app/Models/TenantReview.php)
**Relevant fields / relationships**:
- `id`
- `workspace_id`
- `tenant_id`
- `status`
- `generated_at`
- `published_at`
- `summary`
- `evidence_snapshot_id`
- `current_export_review_pack_id`
- `published_by_user_id`
- `tenant`
- `evidenceSnapshot`
- `currentExportReviewPack`
- `sections`
**Embedded summary payload currently reused**:
- `finding_count`
- `finding_outcomes`
- `risk_acceptance.status_marked_count`
- `risk_acceptance.valid_governed_count`
- `risk_acceptance.warning_count`
- `publish_blockers`
**Validation / usage rules**:
- the workspace default path continues to use the latest published review per entitled tenant only
- internal-only review states remain off the customer-safe default path
- the customer-workspace drilldown stays on the existing review detail route under the existing query-context flag
- productization may refine how summary data is explained, but it must not move that truth into a new stored model
### FindingException
**Purpose**: Existing accepted-risk and accountability truth used to explain who accepted risk, why it is on record, and whether it needs follow-up.
**Persisted carrier**: existing `finding_exceptions` rows via [../../apps/platform/app/Models/FindingException.php](../../apps/platform/app/Models/FindingException.php)
**Relevant fields / relationships**:
- `id`
- `workspace_id`
- `tenant_id`
- `finding_id`
- `status`
- `current_validity_state`
- `requested_at`
- `approved_at`
- `effective_from`
- `expires_at`
- `review_due_at`
- `owner_user_id`
- `approved_by_user_id`
- `current_decision_id`
- `evidence_summary`
- `owner`
- `approver`
- `currentDecision`
- `evidenceReferences`
**Validation / usage rules**:
- accountability summaries should derive from existing owner/approver/current-decision truth where present
- missing accountable-person or accountable-role truth must surface as partial/unavailable disclosure, not fabricated customer-safe copy
- accepted-risk visibility remains read-only in this slice; no edit, renew, revoke, or approval behavior moves into the customer-safe path
### EvidenceSnapshot
**Purpose**: Existing proof artifact for evidence freshness, completeness, and optional supporting detail reached only after explicit user intent.
**Persisted carrier**: existing `evidence_snapshots` rows via [../../apps/platform/app/Models/EvidenceSnapshot.php](../../apps/platform/app/Models/EvidenceSnapshot.php)
**Relevant fields / relationships**:
- `id`
- `workspace_id`
- `tenant_id`
- `status`
- `completeness_state`
- `generated_at`
- `expires_at`
- `summary`
- `items`
**Validation / usage rules**:
- evidence proof remains optional, lower-priority, and capability-gated by the current evidence-view path
- raw payloads and unrestricted diagnostics remain out of the default-visible workspace and review detail path
- if implementation adds explicit proof-access auditing, it should stay on the shared audit pipeline
### ReviewPack
**Purpose**: Existing packaged governance artifact for current downloadable review output.
**Persisted carrier**: existing `review_packs` rows via [../../apps/platform/app/Models/ReviewPack.php](../../apps/platform/app/Models/ReviewPack.php)
**Relevant fields / relationships**:
- `id`
- `workspace_id`
- `tenant_id`
- `tenant_review_id`
- `status`
- `generated_at`
- `expires_at`
- `summary`
- `file_path`
- `file_disk`
- `sha256`
- `operation_run_id`
- `tenantReview`
- `evidenceSnapshot`
**Validation / usage rules**:
- only current ready, unexpired packs remain available in the customer-safe flow
- review-pack access continues to use the existing signed download route and current capability check
- the feature must not surface generate/regenerate flows, even when a pack is unavailable
### Audit Log Event Family
**Purpose**: Existing auditable truth for explicit customer-review consumption moments.
**Persisted carrier**: existing `audit_logs` rows via [../../apps/platform/app/Services/Audit/WorkspaceAuditLogger.php](../../apps/platform/app/Services/Audit/WorkspaceAuditLogger.php)
**Relevant current action IDs**:
- `tenant_review.opened`
- `review_pack.downloaded`
**Potential bounded extensions only if implementation confirms a gap**:
- workspace access open event for the customer review workspace route
- evidence proof access open event for proof routes launched from the customer review flow
**Validation / usage rules**:
- auditable access remains on the shared audit path only
- no new audit store or mirror analytics stream is justified
- workspace, tenant, source-surface, and artifact identifiers stay in stable audit metadata when a new access moment is added
## Derived Read Models
### CustomerReviewWorkspaceEntry
**Purpose**: Derived row-level presentation contract for one entitled tenant on the existing workspace page.
**Persistence**: none; computed at request time
**Fields**:
- `workspace_id`
- `tenant_id`
- `tenant_name`
- `latest_published_review_id` (nullable)
- `latest_review_published_at` (nullable)
- `outcome_summary`
- `findings_summary`
- `accepted_risk_accountability_summary`
- `evidence_proof_state`
- `review_pack_state`
- `primary_review_url` (nullable)
- `review_pack_download_url` (nullable)
- `proof_detail_url` (nullable)
- `absence_note` (nullable)
- `unavailable_note` (nullable)
- `redaction_note` (nullable)
**Derivation rules**:
- exactly one derived entry exists per entitled tenant visible in the current workspace scope
- if a published review exists, the entry derives its customer-safe summary from that released record only
- if no published review exists, the entry surfaces an explicit absence note and omits deep links that depend on a released review
- if optional proof or pack access is blocked by capability or artifact state, the review remains readable while the secondary path becomes explicitly unavailable
**Validation rules**:
- entries may only be built for entitled tenants in the active workspace
- `review_pack_download_url` is present only when a current pack exists and the actor can consume it
- `proof_detail_url` is present only when the actor can open the proof route
- raw payloads, unrestricted diagnostics, provider IDs, and copied support context are never part of the default entry model
### CustomerReviewDetailPresentation
**Purpose**: Derived section contract for the existing released-review detail page when it is launched from the customer review workspace.
**Persistence**: none; computed from the existing review record and current query-context flag
**Fields**:
- `review_id`
- `tenant_id`
- `launched_from_customer_workspace` (boolean)
- `narrative_outcome_summary`
- `findings_summary`
- `accepted_risk_accountability_summary`
- `evidence_summary`
- `proof_pointer_state`
- `review_pack_state`
- `operator_actions_hidden` (boolean)
- `secondary_diagnostics_collapsed` (boolean)
**Derivation rules**:
- only the existing `customer_workspace` query context activates this productized secondary presentation mode
- the detail remains readable even when optional pack/evidence capabilities are absent
- management actions remain suppressed in this context
**Validation rules**:
- this derived model must not create a second review detail route or a second stored summary object
- secondary proof and support detail remain lower-priority than the narrative governance record
- duplicate equal-priority summary blocks between workspace and detail should be removed or reduced
### CustomerReviewPageState
**Purpose**: Request/query/session-backed page state already required for tenant-prefilter, remembered scope, and launch context continuity.
**Persistence**: request, URL query, and existing session-backed table state only
**Fields**:
- `tenant` prefilter (nullable)
- remembered tenant id in workspace context (nullable)
- `customer_workspace` detail context flag (boolean on the detail route)
- navigation context metadata when launched from other canonical pages (nullable)
**Validation rules**:
- explicit tenant prefilters must resolve to an entitled tenant or the request fails as not found
- any state required after Livewire interaction must remain hydrated via public/query/session-backed state
- no private property may own the control path for disclosure or filter restore
## Derived Disclosure States
This feature introduces no new persisted lifecycle or enum family. It does require explicit derived disclosure outcomes on existing surfaces:
- `available`: the actor can open the review/proof/pack path now
- `absent`: the underlying released artifact does not exist for this tenant yet
- `unavailable`: the artifact exists conceptually but is not currently consumable because of capability, readiness, or redaction limits
- `expired`: the artifact exists and was previously consumable, but time-based or release-lifecycle rules now block access while the surface still needs to explain why
- `redacted`: the route or surface remains visible, but protected details stay hidden behind existing redaction rules
- `partial`: the governance record is readable, but accountability/proof detail is incomplete in current source truth
These remain derived page semantics only and must not become stored status families.
## State Transition Summary
No new persisted lifecycle is added. Only derived surface transitions are expected:
- workspace open -> entitled tenant rows or truthful empty/absence state
- remembered tenant or explicit tenant query -> tenant-prefiltered workspace view
- workspace row with released review -> existing review detail route available
- workspace row without released review -> explicit absence state and no review-open action
- released review detail with optional proof/pack capability missing -> review remains readable and secondary path becomes unavailable
- released review detail with an expired pack/proof artifact -> review remains readable and secondary path becomes explicitly expired
- explicit workspace/review/proof/pack consumption -> shared audit event when covered by the current audit registry or a bounded additive action ID

View File

@ -0,0 +1,301 @@
# Implementation Plan: Customer Review Workspace Productization v1
**Branch**: `258-customer-review-productization` | **Date**: 2026-04-30 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from [spec.md](spec.md)
## Summary
Productize the existing customer review workspace into a calmer, customer-safe governance-of-record surface by tightening the current [../../apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php](../../apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php) page and the existing released-review drilldown in [../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php](../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php). The implementation should reuse current review, finding, accepted-risk, evidence, review-pack, localization, RBAC, and audit truth rather than adding a portal shell, new persistence, or a second presentation framework.
This is a bounded follow-up to Spec 249, not a fresh workspace foundation. Filament remains on Livewire v4 under v5, panel-provider registration stays where it is today in [../../apps/platform/bootstrap/providers.php](../../apps/platform/bootstrap/providers.php), no new panel or provider work is planned, no new globally searchable scope is introduced, no destructive actions are in scope, and no new asset registration strategy is expected.
## Technical Context
**Language/Version**: PHP 8.4, Laravel 12
**Primary Dependencies**: Filament v5, Livewire v4, Pest v4, existing review/evidence/review-pack services, capability helpers, localization copy, and workspace audit infrastructure
**Storage**: PostgreSQL via existing `tenant_reviews`, `review_packs`, `evidence_snapshots`, `finding_exceptions`, memberships, and `audit_logs`; no new persistence planned
**Testing**: Pest v4 feature coverage plus one bounded browser smoke slice on the existing workspace flow
**Validation Lanes**: confidence, browser
**Target Platform**: Laravel monolith in `apps/platform`, existing admin plane only (`/admin` plus existing tenant-scoped `/admin/t/{tenant}` reuse)
**Project Type**: Web application (Laravel monolith with Filament pages/resources)
**Performance Goals**: keep workspace and detail rendering DB-only and scope-safe, reuse eager-loaded existing review/pack/evidence relations, and avoid any new Graph calls, queue starts, or heavy asset work on render
**Constraints**: no new page shell, no new persistence, no review publishing engine, no remediation flow, no new customer identity plane, no new global-search scope, no new heavy asset strategy, and no destructive action exposure
**Scale/Scope**: 1 existing workspace page, 1 existing released-review detail page, 2 existing proof/detail resources, 2 localization files, 1 shared audit pipeline, and the existing `tests/Feature/Reviews/*` plus 1 existing browser smoke
## Likely Affected Repo Surfaces
- [../../apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php](../../apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php) for calmer workspace copy, derived summary semantics, explicit access or absence states, and table action hierarchy.
- [../../apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php](../../apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php) for the page intro and disclosure framing.
- [../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php](../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php) for the released-review secondary-context contract, customer-workspace query-flag behavior, and audit handoff.
- [../../apps/platform/app/Filament/Resources/TenantReviewResource.php](../../apps/platform/app/Filament/Resources/TenantReviewResource.php) for existing released-review detail sections, proof links, and current pack/evidence affordances.
- [../../apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php](../../apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php) for workspace membership, entitled tenant scoping, and latest-published review composition.
- [../../apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php](../../apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php) plus `SurfaceCompressionContext` for outcome, freshness, and publication wording already used by review and pack surfaces.
- [../../apps/platform/app/Filament/Resources/ReviewPackResource.php](../../apps/platform/app/Filament/Resources/ReviewPackResource.php), [../../apps/platform/app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php](../../apps/platform/app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php), and [../../apps/platform/app/Http/Controllers/ReviewPackDownloadController.php](../../apps/platform/app/Http/Controllers/ReviewPackDownloadController.php) for current pack availability, safe deep links, and signed download auditing.
- [../../apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php](../../apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php) and [../../apps/platform/app/Filament/Resources/EvidenceSnapshotResource/Pages/ViewEvidenceSnapshot.php](../../apps/platform/app/Filament/Resources/EvidenceSnapshotResource/Pages/ViewEvidenceSnapshot.php) for proof-pointer routing and explicit unavailable states.
- [../../apps/platform/app/Services/Audit/WorkspaceAuditLogger.php](../../apps/platform/app/Services/Audit/WorkspaceAuditLogger.php) and [../../apps/platform/app/Support/Audit/AuditActionId.php](../../apps/platform/app/Support/Audit/AuditActionId.php) for auditable workspace access, review access, proof access, and pack download behavior through the shared audit path.
- [../../apps/platform/app/Services/Auth/RoleCapabilityMap.php](../../apps/platform/app/Services/Auth/RoleCapabilityMap.php) and [../../apps/platform/app/Support/Auth/Capabilities.php](../../apps/platform/app/Support/Auth/Capabilities.php) for capability-first RBAC and workspace/tenant-safe omission rules.
- [../../apps/platform/lang/en/localization.php](../../apps/platform/lang/en/localization.php) and [../../apps/platform/lang/de/localization.php](../../apps/platform/lang/de/localization.php) for calmer customer-safe wording without introducing a second vocabulary system.
- [../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php](../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php), [../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceAuthorizationTest.php](../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceAuthorizationTest.php), [../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceNavigationContextTest.php](../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceNavigationContextTest.php), [../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php](../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php), [../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php](../../apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php), and [../../apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php](../../apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php) for the bounded proof surface already in the repo.
## UI / Filament & Livewire Fit
- Keep [../../apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php](../../apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php) as the canonical customer-safe landing surface. This follow-up productizes the existing page instead of adding a new page class, a new Resource, or a new panel.
- Keep [../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php](../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php) as the secondary context surface reached from the workspace via the existing `customer_workspace` query flag. The drilldown should deepen the governance record, not reopen operator lifecycle controls.
- Preserve the current Livewire-safe filter and remembered-tenant behavior already implemented on the workspace page. Any added state must remain public, query-backed, or session-backed; no private state should control postback-critical disclosure.
- Retain one dominant next action per surface. On the workspace page that remains `Open released review`; pack download or proof routes stay secondary and capability-gated. On the detail page, review-pack access remains the dominant safe action while evidence proof stays lower priority.
- Keep the entire feature in native Filament primitives plus the existing review/evidence shared seams. No custom shell, no heavy asset registration, and no new global-search scope are planned.
## RBAC / Policy Fit
- Workspace membership remains the first isolation boundary through the existing workspace context and `TenantReviewRegisterService::canAccessWorkspace(...)` path.
- Entitled-tenant composition remains capability-first: page entry and rows continue to derive from the current role-capability map and `TENANT_REVIEW_VIEW` path rather than new customer-only roles or raw role-string checks.
- Proof pointers and safe secondary actions continue to reuse existing gates: `REVIEW_PACK_VIEW` for current pack download, `EVIDENCE_VIEW` for proof detail, `TENANT_FINDINGS_VIEW` and `FINDING_EXCEPTION_VIEW` for deeper review content when surfaced, and existing policy checks on review/evidence resources.
- Non-members and explicit out-of-scope tenant targets remain `404`. Member actors who can read the review surface but lack an optional deep-link capability should still see the review with an explicit unavailable state for that optional path.
- No new panel, tenant plane, customer portal plane, or identity model is introduced. This remains an admin-plane follow-up only.
## Audit / Logging Fit
- Reuse [../../apps/platform/app/Services/Audit/WorkspaceAuditLogger.php](../../apps/platform/app/Services/Audit/WorkspaceAuditLogger.php) and [../../apps/platform/app/Support/Audit/AuditActionId.php](../../apps/platform/app/Support/Audit/AuditActionId.php) for all auditable moments. No new audit store, no telemetry sidecar, and no page-local logging subsystem are justified.
- Current review access from the customer-workspace drilldown already logs `TenantReviewOpened` with `source_surface=customer_review_workspace`, and current pack downloads already log `ReviewPackDownloaded` through the signed download route.
- Planning should explicitly account for two remaining audit moments required by this spec: workspace access itself and evidence-summary or proof access when the actor opens an explicit proof route from the customer-safe flow. If those moments are not already covered, the narrowest acceptable change is additive stable action IDs on the existing audit pipeline.
- Passive rendering should still avoid noisy event spam. The auditable boundary is explicit workspace entry or explicit artifact/proof consumption, not every Livewire repaint.
## Data & Query Fit
- Keep the base row query on the existing `customerWorkspaceTenantQuery(...)` seam in [../../apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php](../../apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php). This feature productizes the presentation contract over that query; it does not replace it with a new projection.
- Findings and accepted-risk summaries remain derived from the current `TenantReview.summary` payload already used by the workspace page, including `finding_count`, `finding_outcomes`, and `risk_acceptance` substructures.
- Accepted-risk accountability follow-through should reuse existing `FindingException` and current decision truth where that data already exists. Missing accountable-person or accountable-role truth must surface as explicit partial or unavailable disclosure, not invented copy.
- Evidence proof semantics should stay anchored to existing `EvidenceSnapshot`, related context entries, and `ArtifactTruthPresenter` output. The feature may reorder or reword disclosure, but it should not create a second evidence summary model.
- Access, absence, unavailable, expired, and redacted states remain derived UI or route-state semantics only. They must not become new persisted lifecycle fields or a new presentation enum family.
## UI / Surface Guardrail Plan
- **Guardrail scope**: changed surfaces
- **Native vs custom classification summary**: native Filament
- **Shared-family relevance**: status messaging, evidence/report viewers, action links, navigation entry points, access-state messaging, and review-pack access affordances
- **State layers in scope**: page, detail, URL-query, table/session restore
- **Audience modes in scope**: customer/read-only, customer-admin, auditor-read-only, operator-MSP
- **Decision/diagnostic/raw hierarchy plan**: decision-first, diagnostics-second, support-raw-third
- **Raw/support gating plan**: collapsed and capability-gated on reused detail/proof routes only
- **One-primary-action / duplicate-truth control**: `Open released review` remains the workspace primary action; review-pack access is the detail primary action; equal-priority duplicate summary blocks across workspace and detail are out of scope
- **Handling modes by drift class or surface**: review-mandatory
- **Repository-signal treatment**: review-mandatory
- **Special surface test profiles**: standard-native-filament, shared-detail-family
- **Required tests or manual smoke**: functional-core, state-contract, bounded-browser-smoke
- **Exception path and spread control**: none planned; any new presenter/taxonomy/customer-shell proposal becomes exception-required drift
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes
- **Systems touched**: `CustomerReviewWorkspace`, `ViewTenantReview`, `TenantReviewResource`, `ReviewPackResource`, `EvidenceSnapshotResource`, `TenantReviewRegisterService`, `ArtifactTruthPresenter`, `SurfaceCompressionContext`, `ActionSurfaceDeclaration`, `ReviewPackDownloadController`, `WorkspaceAuditLogger`, `AuditActionId`, and review localization copy
- **Shared abstractions reused**: `TenantReviewRegisterService`, `ArtifactTruthPresenter`, `SurfaceCompressionContext`, existing resource URL helpers, existing action-surface declarations, `ReviewPackService`, and the shared audit logger
- **New abstraction introduced? why?**: none planned. If implementation discovers a small copy or disclosure helper is needed, it should stay inside the existing review surface family instead of becoming a new reusable framework
- **Why the existing abstraction was sufficient or insufficient**: the repo already has the page, detail route, truth envelopes, pack download path, and audit seams; what is insufficient today is the product contract over those seams, not the underlying domain model
- **Bounded deviation / spread control**: none planned. This slice should tighten the current path rather than add a parallel customer-review language, mirror page, or publication layer
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: no
- **Central contract reused**: `N/A`
- **Delegated UX behaviors**: `N/A`
- **Surface-owned behavior kept local**: read-only workspace and detail rendering only; any existing operation-run links remain secondary diagnostics on reused detail surfaces
- **Queued DB-notification policy**: `N/A`
- **Terminal notification path**: `N/A`
- **Exception path**: none
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: no
- **Provider-owned seams**: `N/A`
- **Platform-core seams**: existing workspace, tenant, review, evidence, risk acceptance, review pack, and audit vocabulary only
- **Neutral platform terms / contracts preserved**: `workspace`, `tenant`, `review`, `evidence`, `review pack`, `accepted risk`, `proof`, and existing artifact-truth wording
- **Retained provider-specific semantics and why**: none new
- **Bounded extraction or follow-up path**: `N/A`
## Constitution Check
*GATE: Must pass before implementation preparation continues. Re-check after Phase 1 design artifacts.*
- Inventory-first / snapshot truth: PASS. The slice consumes existing review, pack, and evidence artifacts as read-only truth.
- Read/write separation: PASS. No new create, publish, regenerate, refresh, remediation, or destructive flow is introduced.
- Graph contract path: PASS. No new Graph work or provider contract work is part of this slice.
- Deterministic capabilities: PASS. Existing capability registries and role maps remain authoritative.
- Workspace and tenant isolation: PASS. Workspace membership and tenant entitlement remain non-negotiable `404` boundaries.
- RBAC-UX plane separation: PASS. Everything stays in the existing `/admin` plane and current tenant-scoped detail routes.
- Destructive confirmation standard: PASS by non-use. Destructive actions are out of scope.
- Global search safety: PASS. No new globally searchable resource or search scope is added; any mention of search remains tenant-safe reuse only.
- OperationRun / Ops-UX: PASS by non-use. The productization slice starts no runs and changes no run lifecycle UX.
- Data minimization: PASS. Default-visible content remains decision-first; raw payloads and unrestricted diagnostics stay gated.
- Test governance (TEST-GOV-001): PASS. Planned proof stays in focused feature coverage plus one bounded browser smoke.
- Proportionality / no premature abstraction: PASS. The feature productizes existing surfaces instead of adding persistence, a shell, or a second presenter framework.
- Persisted truth (PERSIST-001): PASS. No new table, artifact, or cache is planned.
- Behavioral state (STATE-001): PASS. Access, absence, unavailable, expired, and redacted conditions remain derived presentation semantics.
- UI semantics / shared pattern first / Filament-native UI: PASS. Native Filament pages/resources and existing truth abstractions remain the default path.
- Provider boundary (PROV-001): PASS. No provider/platform seam widens.
- Filament / Laravel planning contract: PASS. Filament v5 stays on Livewire v4, provider registration remains in [../../apps/platform/bootstrap/providers.php](../../apps/platform/bootstrap/providers.php), no new panel/provider work is planned, no new global-search scope is created, and asset handling stays unchanged (`cd apps/platform && php artisan filament:assets` remains deploy-only if future registered assets are ever added).
**Gate evaluation**: PASS.
- The feature stays in the existing admin plane and current workspace/tenant membership model.
- The canonical entry surface remains the existing customer review workspace, not a new shell.
- Existing truth seams are sufficient if implementation resists adding a mirror presenter or publication engine.
**Post-design re-check**: PASS (design artifacts: [research.md](research.md), [data-model.md](data-model.md), [quickstart.md](quickstart.md), [contracts/customer-review-productization.openapi.yaml](contracts/customer-review-productization.openapi.yaml)).
## Test Governance Check
- **Test purpose / classification by changed surface**: Feature for workspace rows, access/absence/unavailable states, navigation context, pack access, and audit metadata; Browser for one bounded end-to-end calm disclosure path on the existing workspace handoff
- **Affected validation lanes**: confidence, browser
- **Why this lane mix is the narrowest sufficient proof**: the repo already has the exact workspace feature family and a single smoke harness; expanding those files is cheaper and more honest than adding new browser families or generalized helpers
- **Narrowest proving command(s)**:
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspaceAuthorizationTest.php tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspaceNavigationContextTest.php tests/Feature/TenantReview/TenantReviewUiContractTest.php tests/Feature/TenantReview/TenantReviewExplanationSurfaceTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php tests/Feature/ReviewPack/ReviewPackDownloadTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php tests/Feature/Evidence/EvidenceSnapshotResourceTest.php tests/Feature/Evidence/EvidenceSnapshotAuditLogTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php`
- **Fixture / helper / factory / seed / context cost risks**: moderate but contained; reuse existing workspace membership, entitled-tenant, published review, finding-exception, evidence snapshot, review pack, and audit fixtures
- **Expensive defaults or shared helper growth introduced?**: no; any new helper should stay explicit and inside the reviews family
- **Heavy-family additions, promotions, or visibility changes**: none beyond the already-existing single browser smoke
- **Surface-class relief / special coverage rule**: standard-native-filament relief on the workspace page, shared-detail-family coverage on the released-review handoff
- **Closing validation and reviewer handoff**: rerun the focused commands above, verify customer-safe default visibility, verify `404` on out-of-scope tenant targeting, verify optional proof paths show explicit unavailable states instead of leaking content, and verify audit metadata stays on the shared logger path
- **Budget / baseline / trend follow-up**: none expected beyond small feature-local assertions in the existing reviews suite
- **Review-stop questions**: lane fit, hidden fixture growth, browser sprawl, duplicate-truth regressions, audit-gap drift
- **Escalation path**: `document-in-feature` for contained audit metadata placement notes; `reject-or-split` for any drift into new persistence, portal scope, or expanded browser coverage
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage
- **Why no dedicated follow-up spec is needed**: this is already the bounded follow-up to Spec 249; remaining work stays inside this productization lane unless it tries to become publishing/remediation/portal scope
## Rollout & Risk Controls
- Keep the canonical entry surface on the existing workspace page and the canonical secondary surface on the existing released-review detail route.
- Keep all proof and packaged artifact flows on the existing tenant review, review-pack, and evidence routes. Do not add a new proof viewer or download endpoint.
- Treat missing accountability truth or missing proof availability as explicit partial or unavailable disclosure, never as fabricated customer-safe copy.
- Prefer localization-key updates in the existing review language namespace over page-local inline wording.
- Keep browser validation bounded to the existing smoke harness before considering any wider UI rollout.
## Guardrail / Smoke Coverage Close-Out
- **Close-out date**: 2026-04-30
- **Confidence lane**: PASS via the focused customer-workspace, TenantReview detail, ReviewPack, EvidenceSnapshot, audit, capability, and download feature suites listed in [quickstart.md](quickstart.md).
- **Browser lane**: PASS via the bounded customer-review workspace smoke test. Tested path: `/admin/reviews/workspace` as a readonly-capable actor, released review row visibility, customer-safe pack/proof availability labels, workspace-to-detail handoff, and customer-safe released-review detail text.
- **Audit-gap outcome**: bounded additive action IDs were required for explicit workspace entry and proof-open events (`customer_review_workspace.opened`, `evidence_snapshot.opened`). Existing `tenant_review.opened` and `review_pack.downloaded` paths were reused with `source_surface=customer_review_workspace`.
- **Localization / copy outcome**: contained to the existing review localization namespace in English and German; no new vocabulary framework or page-local copy layer was introduced.
- **Global-search safety outcome**: no new globally searchable resource or search scope was introduced. Touched review, pack, and evidence resources remain on their existing tenant-scoped resource paths and customer-workspace query context.
- **Follow-up decision**: no `follow-up-spec` is required for the implemented scope. Broader portal, publication, remediation, baseline/control overlays, and management-packaging expansion remain outside this feature.
## Project Structure
### Documentation (this feature)
```text
specs/258-customer-review-productization/
├── checklists/
│ └── requirements.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── customer-review-productization.openapi.yaml
└── tasks.md # Created later by /speckit.tasks, not by this plan step
```
### Source Code (repository root)
```text
apps/platform/
├── app/
│ ├── Filament/
│ │ ├── Pages/Reviews/
│ │ │ └── CustomerReviewWorkspace.php
│ │ └── Resources/
│ │ ├── TenantReviewResource.php
│ │ ├── TenantReviewResource/Pages/ViewTenantReview.php
│ │ ├── ReviewPackResource.php
│ │ ├── ReviewPackResource/Pages/ViewReviewPack.php
│ │ ├── EvidenceSnapshotResource.php
│ │ └── EvidenceSnapshotResource/Pages/ViewEvidenceSnapshot.php
│ ├── Http/Controllers/ReviewPackDownloadController.php
│ ├── Models/
│ │ ├── TenantReview.php
│ │ ├── ReviewPack.php
│ │ ├── EvidenceSnapshot.php
│ │ └── FindingException.php
│ ├── Services/
│ │ ├── Audit/WorkspaceAuditLogger.php
│ │ └── TenantReviews/TenantReviewRegisterService.php
│ ├── Support/
│ │ ├── Audit/AuditActionId.php
│ │ ├── Auth/Capabilities.php
│ │ └── Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php
├── lang/
│ ├── de/localization.php
│ └── en/localization.php
├── bootstrap/providers.php
├── resources/views/filament/pages/reviews/customer-review-workspace.blade.php
└── tests/
├── Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php
├── Feature/ReviewPack/ReviewPackDownloadTest.php
└── Feature/Reviews/
```
**Structure Decision**: Laravel monolith. The implementation stays inside the existing `apps/platform` reviews, review-pack, evidence, localization, and audit surfaces, with no new panel/provider locations and no new persistence layer.
## Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| None expected at planning time | The intended implementation is a productization pass over existing pages, routes, copy, and audit seams | Adding a portal, new presenter layer, or persisted customer-review projection would import unnecessary structure |
## Proportionality Review
- **Current operator problem**: the repo already has customer-review truth, but the current workspace and drilldown still feel too operator-led and under-explain accountability, proof, and unavailable states for a customer-safe governance record.
- **Existing structure is insufficient because**: Spec 249 created the canonical entry route, but the product contract across workspace summary, released-review detail, proof pointers, pack access, and auditable access semantics is still incomplete.
- **Narrowest correct implementation**: tighten the existing workspace page, released-review detail, proof affordances, localization copy, and shared audit metadata without adding a new page shell, persistence, or customer-specific presenter family.
- **Ownership cost created**: limited copy/disclosure maintenance on existing surfaces, a small extension to focused tests, and at most bounded additive audit action IDs if current coverage is incomplete.
- **Alternative intentionally rejected**: a new portal, publication engine, remediation flow, or second customer-review explanation framework was rejected because the repo already has the required read-only truth seams.
- **Release truth**: current-release productization follow-up to Spec 249.
## Phase 0 — Research (output: research.md)
Research resolves the remaining implementation-shaping decisions:
- keep the existing `CustomerReviewWorkspace` page as the canonical customer-safe landing surface
- keep `ViewTenantReview` as the secondary detail surface under the current `customer_workspace` query flag
- reuse existing localization, artifact-truth, and accepted-risk seams instead of adding a second vocabulary
- keep workspace and tenant isolation on the current capability-first RBAC paths
- reuse the existing audit pipeline and identify only the bounded missing access moments that may need additive action IDs
- keep browser coverage bounded to the existing workspace smoke path and focused feature tests
**Output**: [research.md](research.md)
## Phase 1 — Design (outputs: data-model.md, contracts/, quickstart.md)
Design artifacts capture the narrow productization shape:
- no new persistence; reused truth stays in tenant reviews, finding exceptions, review packs, evidence snapshots, memberships, and audit logs
- one derived workspace presentation contract and one derived released-review disclosure contract document the existing surfaces without becoming stored entities
- the conceptual contract documents current workspace, review detail, proof, and pack-download route expectations plus explicit access/absence/unavailable semantics
- quickstart records the intended implementation order, bounded validation commands, Filament v5 / Livewire v4 posture, provider-registration location, and no-new-assets expectation
**Artifacts**:
- [data-model.md](data-model.md)
- [contracts/customer-review-productization.openapi.yaml](contracts/customer-review-productization.openapi.yaml)
- [quickstart.md](quickstart.md)
## Phase 2 — Planning (for tasks.md)
Dependency-ordered implementation outline for the later `tasks.md` step:
1. Tighten the existing workspace page and Blade intro so the default-visible path is calm, customer-safe, and explicit about absence/unavailable states.
2. Tighten the existing released-review detail flow under the `customer_workspace` context flag so it remains read-only and deepens understanding without exposing operator lifecycle actions.
3. Reuse existing review summary, finding outcome, accepted-risk, proof, and pack truth to improve explanation quality and customer-safe disclosure hierarchy without adding a second presenter or new persistence.
4. Align proof pointers and review-pack affordances so optional deep links are capability-gated and unavailable states are explicit.
5. Reuse the shared audit pipeline for workspace access, review access, proof access, and pack downloads, adding only bounded audit registry entries if the current actions do not cover required moments.
6. Expand the focused review feature suite and keep the single existing browser smoke as the only browser proof for this slice.
## Planning Guardrail Notes
- Planning guardrail result: PASS. Filament remains v5 on Livewire v4, panel providers remain in [../../apps/platform/bootstrap/providers.php](../../apps/platform/bootstrap/providers.php), no new global-search scope is introduced, no destructive action is added, and no new asset bundle is planned.
- Shared seam result: the plan stays on existing page/resource/service/audit seams, not a new customer-review framework.
- Smoke plan: the existing [../../apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php](../../apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php) remains the single bounded browser proof.
- Agent context update: intentionally skipped during this plan pass because the feature introduces no new technology and the user requested preparation-artifact-only changes.

View File

@ -0,0 +1,55 @@
# Quickstart — Customer Review Workspace Productization v1
## Preconditions
- Docker is running and the Sail stack for `apps/platform` is available.
- The feature remains inside the existing Laravel monolith and existing admin plane.
- The canonical entry surface already exists at `/admin/reviews/workspace`; this slice productizes it instead of adding a new shell.
- No new persistence, no review publishing engine, no remediation flow, no new identity plane, and no heavy new asset strategy are part of this work.
## Intended Implementation Order
1. Review the current workspace page, Blade intro, and feature/browser tests so the productization pass stays inside the existing reviews family.
2. Tighten the workspace page wording, disclosure order, and explicit access/absence/unavailable states using the existing localization namespace in [../../apps/platform/lang/en/localization.php](../../apps/platform/lang/en/localization.php) and [../../apps/platform/lang/de/localization.php](../../apps/platform/lang/de/localization.php).
3. Tighten the released-review detail flow in [../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php](../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php) under the existing `customer_workspace` context flag so it remains read-only and customer-safe.
4. Reuse the current `TenantReview.summary`, `FindingException`, `ArtifactTruthPresenter`, review-pack, and evidence seams to improve accountability/proof framing without creating a new presenter or persistence layer.
5. Align secondary proof and pack affordances so the workspace still has one dominant next action and optional proof paths show explicit unavailable or expired states when blocked.
6. Reuse the shared audit pipeline for workspace access, review access, proof access, and pack download moments, adding only bounded action IDs if the current registry does not already cover the required events.
7. Expand the focused `tests/Feature/Reviews/*` family and keep the existing browser smoke as the only browser proof for this slice.
8. Run the targeted tests and Pint after implementation.
## Targeted Validation Commands (after implementation)
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspaceAuthorizationTest.php tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspaceNavigationContextTest.php tests/Feature/TenantReview/TenantReviewUiContractTest.php tests/Feature/TenantReview/TenantReviewExplanationSurfaceTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php tests/Feature/ReviewPack/ReviewPackDownloadTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php tests/Feature/Evidence/EvidenceSnapshotResourceTest.php tests/Feature/Evidence/EvidenceSnapshotAuditLogTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
## Planned Smoke Checklist (after implementation)
1. Sign in to `/admin` as a readonly-capable actor with workspace scope and open `/admin/reviews/workspace`.
2. Confirm the page stays calm and customer-safe: current governance record first, no mutation actions, and explicit absence/unavailable states where appropriate.
3. Launch the workspace from an existing released review or related context and confirm tenant prefilter and customer-safe drilldown continuity still hold.
4. Open the released review and confirm the detail stays read-only, highlights findings/accepted-risk/accountability/proof clearly, and does not expose publish/refresh/create-next/regenerate/archive controls.
5. Use the pack action for a tenant with a current pack and confirm the existing signed download path still works; for tenants without a current or still-valid pack, confirm the UI shows a truthful unavailable or expired state instead of a generation action.
6. Follow an optional proof path and confirm the route is capability-gated, auditable when required, and explicit when proof is unavailable or redacted.
7. Attempt an explicit out-of-scope tenant target and confirm the result remains not found without leaking tenant presence.
## Notes
- Filament v5 already runs on Livewire v4 in this repo.
- Panel providers remain registered through [../../apps/platform/bootstrap/providers.php](../../apps/platform/bootstrap/providers.php); this slice does not add or move providers.
- No new globally searchable resource or search scope is part of this productization pass.
- No destructive action belongs on the workspace surface or the customer-workspace drilldown. If implementation accidentally exposes one, it must stay out of scope and use confirmation.
- No new registered asset bundle is expected. If future implementation unexpectedly registers a Filament asset, deployment still requires `cd apps/platform && php artisan filament:assets`.
- This remains a customer-safe consumption/productization slice only. Review creation, publication, regeneration, remediation, and broader portal behavior stay outside this spec.
## Implementation Close-Out
- **Completed**: 2026-04-30
- **Targeted feature checks**: PASS
- **Browser smoke**: PASS, covering `/admin/reviews/workspace`, released-review row visibility, customer-safe pack/proof labels, workspace-to-detail handoff, and released-governance-record detail text.
- **Formatting**: PASS via Pint dirty-file run.
- **Audit result**: used bounded additive action IDs only for the confirmed gaps (`customer_review_workspace.opened`, `evidence_snapshot.opened`); reused existing tenant-review open and review-pack download audit events with `source_surface=customer_review_workspace`.
- **Global-search result**: unchanged; this implementation added no global-search surface.
- **Assets / deploy result**: unchanged; no new Filament assets were registered.

View File

@ -0,0 +1,156 @@
# Research — Customer Review Workspace Productization v1
**Date**: 2026-04-30
**Spec**: [spec.md](spec.md)
This document resolves the planning decisions for the smallest safe productization follow-up to Spec 249.
## Decision 1 — Keep the existing customer review workspace as the canonical landing surface
**Decision**: Productize the existing [../../apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php](../../apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php) page instead of creating a second customer-review page, new Resource, new panel, or customer portal shell.
**Rationale**:
- The repo already has the canonical admin-plane route, current tenant prefilter behavior, and bounded test family for this page.
- The gap is productization of the current disclosure contract, not missing routing or missing persistence.
- Reusing the existing page keeps the follow-up aligned with Spec 249 and avoids a second customer-review vocabulary.
**Alternatives considered**:
- Add a second customer-facing page or shell.
- Rejected: duplicates the existing workspace route and widens scope into shell-level IA.
- Convert the workspace into a new Resource.
- Rejected: this is still a read-only workspace report, not a new persisted object family.
## Decision 2 — Keep the existing released-review detail route as the only secondary context surface
**Decision**: Continue to use [../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php](../../apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php) as the secondary context surface reached from the workspace by the existing `customer_workspace` query flag.
**Rationale**:
- The current detail page already suppresses management actions when launched from the customer-workspace flow.
- The current route already writes customer-workspace review-open audit metadata.
- Productization should deepen understanding on the current drilldown path instead of inventing a second detail page.
**Alternatives considered**:
- Add a customer-only review detail page.
- Rejected: would duplicate detail truth and drift from the current policy/audit path.
- Push all new explanation back onto the workspace page only.
- Rejected: would keep the first drilldown operator-heavy and incomplete.
## Decision 3 — Reuse the existing review summary and artifact-truth seams for findings, accepted-risk, and proof framing
**Decision**: Reuse the current `TenantReview.summary` payload, [../../apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php](../../apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php), and current review/evidence/pack relationships for customer-safe findings, accepted-risk, and proof framing.
**Rationale**:
- The workspace page already derives `finding_outcomes` and `risk_acceptance` summary content from the current review payload.
- `ArtifactTruthPresenter` already normalizes review and pack freshness/publication semantics.
- The productization gap is wording, priority, and explicit unavailable-state behavior, not missing domain truth.
**Alternatives considered**:
- Introduce a customer-review presenter or new derived persistence layer.
- Rejected: adds structure without new source-of-truth need.
- Inline a second page-local taxonomy for findings and proof states.
- Rejected: creates shared-language drift across workspace, review, and pack surfaces.
## Decision 4 — Keep accepted-risk accountability rooted in existing finding-exception truth
**Decision**: Accepted-risk accountability remains derived from existing `FindingException` and current decision truth where available; missing accountable-person or accountable-role data must surface as partial/unavailable disclosure rather than a fabricated customer-safe summary.
**Rationale**:
- The spec explicitly forbids new persistence and new decision stores.
- Existing `FindingException` truth already includes owner, approver, current decision, validity, review due, and evidence reference relationships.
- Productization requires better accountability framing, but not a parallel accepted-risk model.
**Alternatives considered**:
- Add a new customer-accountability projection.
- Rejected: violates the no-new-persistence goal.
- Hide accepted-risk accountability when details are incomplete.
- Rejected: weakens the governance-of-record objective and obscures truthful partiality.
## Decision 5 — Keep workspace and tenant isolation on the current capability-first seams
**Decision**: Reuse [../../apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php](../../apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php), [../../apps/platform/app/Services/Auth/RoleCapabilityMap.php](../../apps/platform/app/Services/Auth/RoleCapabilityMap.php), and [../../apps/platform/app/Support/Auth/Capabilities.php](../../apps/platform/app/Support/Auth/Capabilities.php) as the isolation and entitlement seams.
**Rationale**:
- The current workspace page already uses `canAccessWorkspace(...)`, `authorizedTenants(...)`, and the current workspace context.
- The existing test family already proves `404` semantics for out-of-scope tenant targeting on the workspace route.
- Capability-first reuse avoids new role families or customer-only policy forks.
**Alternatives considered**:
- Add new customer-review roles or customer-specific policy branches.
- Rejected: outside scope and unnecessary for the current admin-plane audience.
- Resolve optional proof access by hiding the entire review.
- Rejected: the review should remain readable even when optional proof is unavailable.
## Decision 6 — Treat access, absence, unavailable, expired, and redacted conditions as derived disclosure states only
**Decision**: The follow-up should make access, absence, unavailable, expired, and redacted conditions explicit across workspace, review, evidence, and pack paths, but these remain derived view semantics rather than new persisted statuses.
**Rationale**:
- The current workspace page already distinguishes `No published review available yet` and pack availability from persisted review lifecycle state.
- The spec explicitly rejects new state families and new persistence.
- Explicit customer-safe disclosure is the sellability gap; a new persisted taxonomy is not.
**Alternatives considered**:
- Add a new customer-availability enum family.
- Rejected: presentation-only distinction with no new lifecycle consequence.
- Leave absence and unavailable states implicit.
- Rejected: that is the current productization gap.
## Decision 7 — Reuse the current review-pack and evidence proof routes instead of adding new proof viewers
**Decision**: Keep [../../apps/platform/app/Http/Controllers/ReviewPackDownloadController.php](../../apps/platform/app/Http/Controllers/ReviewPackDownloadController.php), [../../apps/platform/app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php](../../apps/platform/app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php), and [../../apps/platform/app/Filament/Resources/EvidenceSnapshotResource/Pages/ViewEvidenceSnapshot.php](../../apps/platform/app/Filament/Resources/EvidenceSnapshotResource/Pages/ViewEvidenceSnapshot.php) as the only proof/detail routes reused by this feature.
**Rationale**:
- Current pack download already enforces tenant access, capability checks, readiness, expiry, and audit logging.
- Current evidence resource routing already exists and is referenced by review and pack truth presenters.
- The follow-up only needs clearer proof-pointer semantics and explicit unavailable states, not a new viewer family.
**Alternatives considered**:
- Add a new customer-proof page or customer-only download endpoint.
- Rejected: duplicates existing review-pack and evidence seams.
- Surface raw evidence payloads directly on the workspace page.
- Rejected: violates customer-safe disclosure hierarchy.
## Decision 8 — Reuse the existing audit pipeline and extend it only where access moments are still missing
**Decision**: Keep all auditing on [../../apps/platform/app/Services/Audit/WorkspaceAuditLogger.php](../../apps/platform/app/Services/Audit/WorkspaceAuditLogger.php) and [../../apps/platform/app/Support/Audit/AuditActionId.php](../../apps/platform/app/Support/Audit/AuditActionId.php). Existing review-open and pack-download actions stay authoritative, and only bounded additive action IDs should be considered if workspace access or proof access moments are not yet covered.
**Rationale**:
- The current customer-workspace review handoff already logs `TenantReviewOpened`.
- The current signed pack download route already logs `ReviewPackDownloaded`.
- The repo does not currently show a matching evidence-proof-open audit on the customer-workspace flow, so that is the bounded gap to evaluate.
**Alternatives considered**:
- Create a new customer-review audit table or analytics stream.
- Rejected: unnecessary persistence and duplication.
- Leave workspace/proof access unaudited.
- Rejected: the spec explicitly requires auditability for those consumption moments.
## Decision 9 — Keep browser coverage bounded to the existing smoke harness
**Decision**: Reuse [../../apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php](../../apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php) as the single browser smoke proof and expand only the existing `tests/Feature/Reviews/*` family for the rest.
**Rationale**:
- The repo already has a browser harness for the review-detail-to-workspace handoff and calm disclosure checks.
- The rest of the productization contract is better proven in focused feature tests for omission rules, access states, and audit metadata.
- Adding a broader browser family would expand governance cost without clearer business proof.
**Alternatives considered**:
- Add multiple new browser tests for each proof path.
- Rejected: too heavy for a bounded productization follow-up.
- Rely on browser testing alone.
- Rejected: feature tests remain the narrower proof for RBAC and disclosure-state coverage.
## Decision 10 — Keep the feature asset-light and non-search-expanding
**Decision**: Do not add a new asset bundle, new panel assets, or any new global-search scope as part of this follow-up.
**Rationale**:
- The work is a content/disclosure/productization pass over existing Filament surfaces.
- Existing review, review-pack, and evidence resources already avoid global-search exposure in this customer-safe path.
- The user explicitly scoped out heavy asset strategy and new global-search work.
**Alternatives considered**:
- Add new visual infrastructure or customer-safe asset loading.
- Rejected: no product need for this slice.
- Add a new search entry point for the customer workspace.
- Rejected: outside the tenant-safe reuse boundary and unnecessary for the current route.

View File

@ -0,0 +1,348 @@
# Feature Specification: Customer Review Workspace Productization v1
**Feature Branch**: `258-customer-review-productization`
**Created**: 2026-04-30
**Status**: Draft
**Input**: User description: "Customer Review Workspace Productization v1 as the smallest follow-up slice that hardens the existing customer review workspace into a calmer customer-safe review consumption surface with clearer findings, accepted-risk, evidence-summary, and auditable pack-access semantics without adding a portal, new persistence, or write paths."
## Spec Candidate Check *(mandatory — SPEC-GATE-001)*
- **Problem**: TenantPilot already has repo-real review, evidence, review-pack, redaction, RBAC, and audit foundations plus an existing customer review workspace, but the current surface still reads more like an operator-led admin handoff than a fully customer-safe governance-of-record consumption path.
- **Today's failure**: Authorized customer reviewers, customer admins, and auditors can reach review truth, but they still have to infer meaning from operator-oriented wording, incomplete accepted-risk/accountability framing, uneven evidence proof semantics, and unclear access or absence states. That keeps the surface harder to sell and less trustworthy than the underlying repo truth.
- **User-visible improvement**: An authorized reader can open one existing workspace review surface and understand what was reviewed, what matters, what risk was accepted, what evidence exists, and what can be safely accessed or downloaded without seeing mutation paths, operator residue, or raw diagnostics.
- **Smallest enterprise-capable version**: Productize the existing customer review workspace and related released-review detail flow inside the current admin plane as a clearer read-only governance-of-record surface with calmer wording, findings and accepted-risk/accountability summaries, evidence proof pointers, explicit unavailable states, and auditable access/download semantics.
- **Explicit non-goals**: No new panel, no new provider registration path, no portal, no new identity plane, no new persistence, no review authoring or publishing engine, no remediation or mutation flow, no new review generation or regeneration flow, no AI summaries, no new global-searchable resource, no heavy new assets, and no destructive actions.
- **Permanent complexity imported**: One bounded productization pass over existing workspace and released-review surfaces, refined disclosure and access-state rules, explicit audit coverage for access/download behavior, focused feature-test expansion, and one bounded browser smoke check. No new models, enums, persisted artifacts, provider seams, or presentation frameworks are introduced.
- **Why now**: This remains the highest-priority active candidate in the roadmap overlay, and the implementation ledger still marks customer review productization as incomplete even though the underlying review foundations are already repo-real.
- **Why not local**: Isolated copy fixes or one-off detail-page tweaks would not establish a coherent customer-safe contract across workspace summary, released review detail, accepted-risk accountability, evidence proof pointers, and access/download semantics.
- **Approval class**: Core Enterprise
- **Red flags triggered**: Multi-surface disclosure contract and customer-facing wording inside an existing admin-plane surface. Defense: the slice stays read-only, derived, in-panel, and reuses current review/evidence/review-pack/RBAC/redaction/audit truth without adding new persistence or authoring behavior.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12**
- **Decision**: approve
## Spec Scope Fields *(mandatory)*
- **Scope**: canonical-view
- **Primary Routes**:
- existing admin-plane customer review workspace at `/admin/reviews/workspace`
- existing tenant-scoped released review detail route for the selected review
- existing review-pack access/download surface reached from released review context
- existing evidence summary/proof routes reached from released review context when the actor is entitled
- **Data Ownership**: All visible truth remains derived from existing tenant-owned review, finding, accepted-risk, evidence, review-pack, and audit records in the current workspace. No new workspace-owned or tenant-owned persisted artifact, projection table, or publication store is introduced.
- **RBAC**:
- this remains an admin-plane follow-up, not a new panel or plane
- workspace membership remains the first isolation boundary
- page entry requires an established workspace scope plus at least one entitled tenant the actor may read through the existing capability registry
- proof pointers, evidence summaries, and review-pack downloads remain capability-gated through current review/evidence/pack authorization paths
- non-members or out-of-scope tenant requests resolve as deny-as-not-found
- member actors lacking an optional gated capability receive capability denial only for the gated deep link or access path
- no new customer-only role family or identity model is introduced
For canonical-view specs, the spec MUST define:
- **Default filter behavior when tenant-context is active**: When the workspace is launched from a tenant-scoped review or related review context, it prefilters to that tenant and foregrounds the latest released review for that tenant. Without an incoming tenant context, the page shows only entitled tenants in the current workspace.
- **Explicit entitlement checks preventing cross-tenant leakage**: Aggregated lists, review detail entry, proof pointers, and pack access only resolve for tenants the actor is entitled to in the current workspace. Inaccessible tenant targets are omitted from aggregated lists and resolve as not found when directly targeted.
## 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)**: status messaging, evidence/report viewers, action links, navigation entry points, access-state messaging, and review-pack access/download affordances
- **Systems touched**: existing `CustomerReviewWorkspace`, existing released review detail surfaces, existing review-pack access/download surface, existing evidence summary/proof presentation, existing redaction messaging, existing localization review copy, and existing audit infrastructure
- **Existing pattern(s) to extend**: the current customer review workspace, existing released review detail path, existing artifact-truth presentation, existing redaction notes, and current review-pack access semantics
- **Shared contract / presenter / builder / renderer to reuse**: `ArtifactTruthPresenter`, `ArtifactTruthEnvelope`, `SurfaceCompressionContext`, `ActionSurfaceDeclaration`, existing review/evidence/review-pack disclosure surfaces, and the current audit logging path
- **Why the existing shared path is sufficient or insufficient**: The underlying review, evidence, pack, and audit truth is already present and authoritative. What is insufficient is the current customer-safe product contract over that truth, not the underlying data model or access seams.
- **Allowed deviation and why**: none. This follow-up must tighten the existing shared path rather than introduce a second customer-review vocabulary or mirror presenter.
- **Consistency impact**: Review outcome wording, findings severity and status language, accepted-risk accountability phrasing, evidence proof terminology, pack availability states, redaction notes, and audit action labels must stay aligned across workspace and released-review detail flows.
- **Review focus**: Reviewers must block any new page-local taxonomy, raw-payload viewer, duplicate proof summary, or portal-only terminology that drifts from current review/evidence/review-pack truth.
## 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`)*
- **Touches OperationRun start/completion/link UX?**: no
- **Shared OperationRun UX contract/layer reused**: `N/A`
- **Delegated start/completion UX behaviors**: `N/A`
- **Local surface-owned behavior that remains**: This slice stays read-only and does not add new run start, queue, resume, or completion behavior. Any existing deep provider or run diagnostics remain outside the default customer-safe path.
- **Queued DB-notification policy**: `N/A`
- **Terminal notification path**: `N/A`
- **Exception required?**: none
## 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`)*
N/A - no shared provider/platform boundary touched. The slice consumes existing governance review truth and does not widen provider-specific semantics, identity scope, or shared platform taxonomy.
## 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 |
|---|---|---|---|---|---|---|
| Customer Review Workspace page | yes | Native Filament page plus shared review/evidence primitives | status messaging, evidence/report viewers, access-state messaging, pack access | page, table/filter, disclosure state | no | Existing page is materially productized rather than replaced |
| Released Customer Review detail | yes | Native Filament resource/detail surface plus shared review/evidence primitives | status messaging, proof pointers, pack access, progressive disclosure | detail sections, disclosure state | no | Existing detail flow becomes the secondary customer-safe context surface |
## 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 |
|---|---|---|---|---|---|---|---|
| Customer Review Workspace page | Primary Decision Surface | Customer reviewer, customer admin, or auditor decides whether the released review answers the current governance question or needs a follow-up conversation with the workspace operator team | released review state, calm outcome summary, key findings summary, accepted-risk/accountability summary, evidence/proof availability, and pack access state | released review detail, review-pack metadata, and proof pointers only after explicit open | Primary because it is the first truthful customer-safe route and should answer the high-level question without requiring a detail drilldown | Follows review-consumption workflow, not storage-object or operator-workbench navigation | Replaces cross-surface reconstruction with one calm starting point |
| Released Customer Review detail | Secondary Context Surface | Reader inspects the chosen released review to understand why the current governance record looks the way it does and what proof or packaged artifact is available | narrative outcome summary, findings summary, accepted-risk/accountability explanation, evidence summary, proof pointers, and pack availability | redacted proof references, release history, and gated secondary diagnostics only after explicit expansion | Not primary because it is entered from the workspace summary and should deepen understanding rather than replace the decision-first landing surface | Keeps the operator journey centered on one review case after the workspace summary selects it | Preserves a focused second step instead of exposing every proof or diagnostic on the first page |
## 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 |
|---|---|---|---|---|---|---|---|
| Customer Review Workspace page | customer-read-only, customer-admin, auditor-read-only, operator-MSP | what was reviewed, current outcome, key findings, accepted risks, evidence/proof availability, pack availability, and clear access/unavailable states | release timing, review lineage, and secondary freshness detail only after opening the review | raw payloads, provider IDs, run internals, and unrestricted diagnostics stay out of the default path | `Open released review` | raw/support detail, deep diagnostics, and unavailable secondary access paths stay hidden until explicitly requested and entitled | Workspace summary states each tenant review truth once; the detail surface elaborates instead of restating the same overview blocks |
| Released Customer Review detail | customer-read-only, customer-admin, auditor-read-only, operator-MSP | calm narrative outcome, findings summary, accepted-risk/accountability context, evidence summary with proof pointers, pack access state, and explicit redaction/access explanations | release history, evidence freshness detail, and secondary metadata only in collapsible or separately gated sections | raw evidence payloads, provider-debug detail, and unrestricted audit internals remain hidden or capability-gated | `Download current review pack` | raw/support detail, broad diagnostics, and any operator-only sections remain excluded from the customer-safe default view | Workspace page provides the overview; detail adds explanation and proof pointers without duplicating full summary cards at equal priority |
## 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Customer Review Workspace page | List / Table / Read-only workspace report | Read-only registry report | Open the released review for the selected tenant | full-row open to released review detail | required | one safe inline pack-access affordance only when already available and entitled | none | `/admin/reviews/workspace` | `/admin/t/{tenant}/reviews/{record}` | workspace context, tenant prefilter, release state, pack availability | Customer review | what was reviewed, the released outcome, key findings, accepted-risk/accountability, proof availability, and access state | none |
| Released Customer Review detail | Detail / Report / Evidence | Read-only detail report | Download the current review pack or inspect proof pointers | sectioned detail page with one dominant safe header action | forbidden | proof pointers live in secondary evidence sections and capability-gated safe surfaces after the pack action | none | `/admin/reviews/workspace` | `/admin/t/{tenant}/reviews/{record}` | workspace, tenant, review release state, evidence summary, pack availability | Customer review | why this is the current governance record and what proof or packaged artifact is available | 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| Customer Review Workspace page | Customer reviewer, customer admin, or auditor with read access | Decide whether the released review is sufficient for current governance consumption or whether human follow-up is needed | Read-only workspace review overview | What was reviewed for my tenant, what matters now, what was accepted, and what can I safely open or download? | released outcome, key findings, accepted-risk/accountability summary, evidence/proof availability, pack access state, and explicit absence/unavailable messaging | release lineage, deeper evidence freshness, and secondary access metadata only after drilldown | review outcome, findings severity mix, accepted-risk lifecycle, evidence completeness, pack availability, access state | none | Open released review | none |
| Released Customer Review detail | Customer reviewer, customer admin, or auditor with read access | Understand the current governance record and consume proof or packaged artifacts safely | Read-only detail report | Why does the review say this, what evidence supports it, and what packaged artifact is available? | calm narrative summary, findings and recommendations, accepted-risk/accountability context, evidence summary and proof pointers, pack access state | release history, deeper proof metadata, and gated secondary diagnostics | review outcome, evidence freshness/completeness, accepted-risk timing, pack availability, redaction/access state | none | Download current review pack | none |
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no
- **New persisted entity/table/artifact?**: no
- **New abstraction?**: no
- **New enum/state/reason family?**: no
- **New cross-domain UI framework/taxonomy?**: no
- **Current operator problem**: The repo already holds released review truth, but the current surface still makes customer-safe governance consumption harder than it should be by leaving too much operator framing and too little explicit accountability/proof language in the default path.
- **Existing structure is insufficient because**: The current workspace and detail contract does not yet consistently separate customer-readable review summary from secondary diagnostics, nor does it make access, absence, and unavailable states explicit enough for a sellable governance-of-record surface.
- **Narrowest correct implementation**: Tighten the existing workspace and released-review detail surfaces, reusing current review/evidence/review-pack/redaction/RBAC/audit truth and adding no new persistence, panel, or workflow engine.
- **Ownership cost**: A bounded copy and disclosure pass, focused audit assertions, targeted feature-test expansion, and one bounded browser smoke.
- **Alternative intentionally rejected**: A separate portal, customer-specific persistence layer, or new review publication framework was rejected because the repo already has the necessary read-only review truth and access seams.
- **Release truth**: current-release sellability blocker, not future-release preparation
### Compatibility posture
This feature assumes a pre-production environment.
Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec.
Canonical replacement is preferred over preservation.
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
- **Test purpose / classification**: Feature, Browser
- **Validation lane(s)**: confidence, browser
- **Why this classification and these lanes are sufficient**: Focused feature tests are the narrowest sufficient proof for entitlement boundaries, progressive disclosure, explicit absence/access states, localization-ready copy expectations, and auditable access/download semantics. One bounded browser smoke remains justified to prove the calm default-visible path and the one-dominant-action flow under real UI rendering.
- **New or expanded test families**: expand the existing `Reviews/CustomerReviewWorkspace` feature family; keep exactly one bounded browser smoke around the same surface
- **Fixture / helper cost impact**: low to moderate. Reuse existing workspace membership, tenant entitlement, released review, findings, accepted-risk/exception, evidence snapshot, review pack, redaction, and audit fixtures instead of adding new provider or queue-heavy defaults.
- **Heavy-family visibility / justification**: exactly one browser smoke stays explicit because this slice is mostly about customer-safe wording, disclosure, and safe action placement. No broader browser family is introduced.
- **Special surface test profile**: shared-detail-family
- **Standard-native relief or required special coverage**: ordinary Filament feature coverage is sufficient for routing, authorization, disclosure, and unavailable states; the existing bounded smoke is the only required browser proof.
- **Reviewer handoff**: Reviewers must confirm that the customer-safe default view never exposes operator-only or mutation actions, that unauthorized tenant targets do not leak presence, that access/download events stay auditable, that review/evidence/pack absence states are explicit, and that no new global search leakage is introduced.
- **Budget / baseline / trend impact**: low feature-local increase only
- **Escalation needed**: none
- **Active feature PR close-out entry**: Smoke Coverage
- **Planned validation commands**:
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspaceAuthorizationTest.php tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspaceNavigationContextTest.php tests/Feature/TenantReview/TenantReviewUiContractTest.php tests/Feature/TenantReview/TenantReviewExplanationSurfaceTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php tests/Feature/ReviewPack/ReviewPackDownloadTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php tests/Feature/Evidence/EvidenceSnapshotResourceTest.php tests/Feature/Evidence/EvidenceSnapshotAuditLogTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php`
## Scope Boundaries
### In Scope
- productize the existing admin-plane customer review workspace into a clearer customer-safe read-only governance-of-record surface
- tighten the released-review detail flow so it remains a customer-safe secondary context surface rather than an operator-heavy detail page
- findings summary in calm customer-safe language, including severity, status, impact, and recommendation
- accepted-risk/accountability summary using existing risk-acceptance truth, including decision reason, accountable person or role when available, timing, and expiry or re-review state
- evidence summary and proof pointers using existing evidence truth without raw payload default visibility
- explicit access, absence, unavailable, expired, and redaction states across workspace, review, evidence, and pack access
- auditable workspace access, review access, evidence-summary/proof access, and review-pack download behavior
- progressive disclosure boundaries that keep the default path calm and customer-readable
- reuse of existing localization, redaction, RBAC, audit, review-pack, and evidence truth
### Non-Goals
- a new panel, portal, standalone customer shell, or separate identity plane
- new persistence, new publication state families, or a customer-specific projection store
- review authoring, review publishing, review generation, pack generation/regeneration, remediation, or other write paths
- risk acceptance editing, finding triage, owner reassignment, or admin mutation flows
- AI-generated review summaries or AI-generated recommendation layers
- raw evidence payload viewers, provider-debug views, or new operator diagnostics in the customer-safe default path
- a new global-searchable review or evidence resource
- broader baseline/control overlays or cross-surface "next sensible step" orchestration beyond the existing released-review contract
- new heavy frontend assets or a standalone asset-loading strategy
## Dependencies
- existing `CustomerReviewWorkspace` route and navigation entry point
- existing released `TenantReview` detail surface and release-truth semantics
- existing `ReviewPack` access and download behavior
- existing `EvidenceSnapshot` summaries and proof context
- existing finding and accepted-risk/exception truth
- existing redaction behavior and safe review-pack disclosure
- existing workspace and tenant RBAC plus entitlement enforcement
- existing audit logging for access and artifact events
- existing DE/EN localization posture for review-facing copy
## Assumptions
- The existing Filament v5 and Livewire v4 admin-plane customer review workspace remains the canonical entry surface for v1; no new panel, portal shell, or provider registration change is required.
- Released review truth already exists and remains authoritative for what is customer-safe to consume.
- Accepted-risk/accountability summaries can be derived from existing risk-acceptance or exception truth without inventing a new customer-facing decision store.
- Review packs remain the primary packaged export/proof artifact, and this slice only clarifies access and wording around them.
- Existing panel assets are sufficient; this slice does not justify heavy new asset registration or on-demand asset infrastructure.
- Existing tenant-safe search behavior remains unchanged; this slice does not depend on introducing a new global search surface.
## Risks
- Some released reviews may not yet carry fully populated accountable-person or accountable-role data, which could force partial accountability summaries until the underlying product truth is present.
- If productization changes only the workspace page and not the released-review detail follow-through, the customer-safe contract could still feel inconsistent after drilldown.
- Over-eager implementation could try to smuggle in publication, regeneration, or remediation behavior because the surrounding review foundations already exist; this spec must block that scope growth.
- Access auditing for read-only views can be under-specified if the implementation focuses only on download events; the slice must treat access and download behavior as separate auditable moments.
- If access, absence, and unavailable states are not differentiated clearly, users may misread missing proof as a system error or hidden operator state rather than a truthful product condition.
## Candidate Selection Rationale
- **Selected candidate**: Customer Review Workspace Productization v1
- **Source locations**:
- `docs/product/spec-candidates.md` active P0 candidate
- `docs/product/roadmap.md` priority order item 1
- `docs/product/implementation-ledger.md` open gap `Customer review productization remains incomplete`
- `specs/249-customer-review-workspace/spec.md` as the immediate predecessor spec
- existing repo surface and tests centered on the current customer review workspace
- **Why selected**: This is the highest-priority active unspecced follow-up that converts already repo-real review foundations into a more sellable customer-safe product surface without reopening foundations or requiring new persistence.
- **Why this is the smallest viable implementation slice**: The repo already has the workspace page, review detail, evidence, review-pack, redaction, RBAC, and audit truth. The missing piece is productization of wording, accountability summary, proof framing, and access-state semantics, not a new review engine.
- **Intentional narrowing from source candidate**: This slice deliberately defers broader baseline/control context overlays and richer cross-surface next-step guidance from the roadmap candidate. Those remain follow-up work for `Compliance Evidence Mapping v1` and `Governance-as-a-Service Packaging v1` after the customer-safe review contract is stable.
- **Why close alternatives are deferred**:
- Governance Decision Surface Convergence already has `specs/257-governance-decision-convergence` and addresses a different operator convergence lane.
- Remove Findings Lifecycle Backfill Runtime Surfaces already has `specs/253-remove-findings-backfill-runtime-surfaces` and is a cleanup lane, not the current customer-safe sellability blocker.
- Remove Legacy Acknowledged Finding Status Compatibility already has `specs/254-remove-acknowledged-compat` and is a workflow semantics cleanup lane.
- Enforce Creation-Time Finding Invariants already has `specs/255-enforce-finding-creation-invariants` and is a data-integrity hardening lane.
- Cross-Tenant Compare and Promotion v1 already has `specs/043-cross-tenant-compare-and-promotion` and remains a separate refresh track rather than the next unspecced customer-review productization slice.
## Follow-up Candidates
- Governance-as-a-Service Packaging v1 once released reviews, proof pointers, and accepted-risk summaries are stable enough to package as a repeatable management deliverable
- Compliance Evidence Mapping v1 once the customer-safe review surface needs a stronger control/readiness and baseline/control-context overlay than this released-review productization slice intentionally provides
- Cross-Tenant Compare and Promotion v1 as the next MSP multiplier after customer-safe review consumption is calmer
- Broader risk acceptance and accountability reporting follow-through if customer-safe accountability views need portfolio or management-level rollups beyond the review surface
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Understand the latest released review at a glance (Priority: P1)
A customer reviewer, customer admin, or auditor wants one calm workspace route that shows the latest released review for each entitled tenant so they can understand the current governance record without reconstructing it from operator-oriented screens.
**Why this priority**: This is the core sellability gap. If the reader still has to search across internal review, evidence, and pack surfaces to understand the current state, the productization slice fails.
**Independent Test**: Sign in as an entitled read-only actor, open the customer review workspace, and confirm that each visible tenant shows a released review summary with findings, accepted-risk/accountability, evidence/proof, and pack availability in customer-safe wording.
**Acceptance Scenarios**:
1. **Given** an entitled actor has access to one or more tenants with released reviews, **When** they open the customer review workspace, **Then** they see only entitled tenants and only released review summaries in the default path.
2. **Given** a tenant has released findings, accepted risks, and proof-backed evidence, **When** the actor scans the workspace row or card, **Then** they can understand what was reviewed, what matters, and what proof or pack is available without opening a second page first.
3. **Given** the actor has no entitled tenant with a released review, **When** they open the workspace, **Then** they see a truthful absence state rather than leaked draft or hidden internal review states.
---
### User Story 2 - Understand why the review says what it says (Priority: P1)
A customer reviewer, customer admin, or auditor wants the released review detail to explain findings, accepted-risk/accountability, and evidence proof in calmer customer-safe wording so they can trust the review as the governance record without seeing admin or debug residue.
**Why this priority**: Productization is not complete if the workspace summary is calmer but the first drilldown still feels like an operator surface.
**Independent Test**: Open a released review from the workspace and verify that the default detail view shows summary, findings, accepted-risk/accountability, and evidence proof pointers while hiding mutation actions and raw diagnostics.
**Acceptance Scenarios**:
1. **Given** a tenant has a released review with findings and accepted risks, **When** the actor opens the released review detail, **Then** the page explains the outcome, recommendations, accepted-risk accountability, and proof pointers in customer-safe language.
2. **Given** deeper evidence or proof metadata exists, **When** the actor stays on the default detail view, **Then** raw payloads, provider-debug context, and broad diagnostics remain hidden until explicitly requested and entitled.
3. **Given** the actor lacks an optional capability for a secondary proof or pack action, **When** they view the released review detail, **Then** the review still remains readable while the gated access path is explicitly unavailable.
---
### User Story 3 - Safely consume packaged proof and understand unavailable states (Priority: P2)
A customer reviewer, customer admin, or auditor wants to access the current review pack or understand why it is unavailable so they can consume the released artifact without operator assistance or confusion.
**Why this priority**: The review workspace is more trustworthy when access and absence states are explicit instead of silent or operator-only.
**Independent Test**: Open the workspace and released review detail for tenants with and without available packs or proof access, then verify explicit access, unavailable, expired, or redacted states plus auditable access/download behavior.
**Acceptance Scenarios**:
1. **Given** a tenant has a current review pack and the actor is entitled to access it, **When** they choose the pack action, **Then** they can access or download the existing artifact without triggering any generation or mutation flow.
2. **Given** a tenant lacks a current review pack or proof access, **When** the actor views the workspace or released review detail, **Then** the surface shows a truthful unavailable state instead of implying a hidden operator path.
3. **Given** the actor tries to target a tenant outside their scope, **When** they open the workspace with that tenant context or a direct review link, **Then** the system resolves as not found and reveals no review or pack presence.
### Edge Cases
- What happens when a tenant has findings and accepted risks but no released review yet? The customer-safe workspace shows an explicit `No released review available yet` style state rather than leaking internal review lifecycle.
- What happens when a released review exists but accountability data is only partially populated? The summary shows only confirmed accountability truth and does not invent a placeholder owner or decision role.
- What happens when a review pack exists but access is unavailable because of entitlement or redaction posture? The UI shows a clear access or unavailable state and does not offer a generation or recovery action.
- What happens when a user enters through a saved filter or tenant-prefilter for an inaccessible tenant? The filter resolves safely without exposing the tenant or its review artifacts.
## Requirements *(mandatory)*
**Constitution alignment (required):** This feature introduces no Microsoft Graph calls, no write/change behavior, no new queue or scheduled behavior, and no new persistence. It changes customer-safe disclosure, authorization boundaries on read-only surfaces, and audit expectations for access/download behavior.
**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** This follow-up must stay derived. It must not introduce new persistence, new presentation frameworks, new customer state families, or speculative abstractions.
**Constitution alignment (XCUT-001):** The feature must extend existing review, evidence, review-pack, localization, redaction, and audit paths rather than invent a page-local customer-review semantic layer.
**Constitution alignment (DECIDE-AUD-001 / OPSURF-001):** The default path must separate customer-readable decision content from deeper diagnostics and keep raw/support detail hidden by default.
**Constitution alignment (TEST-GOV-001):** The implementation must stay with focused feature coverage plus one bounded browser smoke and avoid creating a broader heavy family.
**Constitution alignment (RBAC-UX):** Workspace and tenant membership remain deny-as-not-found boundaries. Optional secondary disclosure remains capability-gated inside an established scope. No new role strings or raw capability strings are introduced by this spec.
**Constitution alignment (UI-FIL-001 / UI-NAMING-001 / DECIDE-001 / ACTSURF-001):** The slice must remain a native Filament read-only reporting flow with one dominant safe inspect action and no destructive or mutation actions.
### Functional Requirements
- **FR-001**: The system MUST keep this slice inside the existing admin-plane customer review workspace and released-review detail flow rather than creating a new panel, portal, or identity surface.
- **FR-002**: The system MUST derive every visible summary, access state, and proof affordance from existing review, evidence, review-pack, accepted-risk, redaction, entitlement, and audit truth without creating new persisted customer-review artifacts.
- **FR-003**: The default workspace path MUST show only entitled tenants and only released or otherwise customer-safe reviews as the governance-of-record path.
- **FR-004**: The default-visible workspace summary MUST answer, in calm customer-safe language, what was reviewed, the current outcome, key findings, accepted risks, available evidence or proof, and pack availability.
- **FR-005**: Findings shown through this slice MUST present severity, status, impact, and recommendation in customer-safe wording and MUST not depend on operator-only vocabulary to be understood.
- **FR-006**: Accepted-risk content shown through this slice MUST summarize decision reason, accountable person or role when product truth exists, decision timing, current expiry or re-review state, and linked proof context without exposing internal workflow residue.
- **FR-007**: Evidence shown through this slice MUST present a narrative summary and proof pointers and MUST not expose raw payloads, provider-debug data, or unrestricted diagnostics by default.
- **FR-008**: The workspace and released-review detail surfaces MUST make access, absence, expired, unavailable, and redaction states explicit and understandable without implying a hidden write path or missing internal admin permission.
- **FR-009**: The feature MUST use progressive disclosure so that default-visible content remains customer-readable while deeper review detail, evidence context, and any gated secondary diagnostics appear only after explicit user intent and capability checks.
- **FR-010**: The primary workspace action MUST be opening the released review, and any secondary safe proof or pack action MUST never compete with write, remediation, generation, or admin actions.
- **FR-011**: Review-pack access and downloads MUST reuse existing entitlement, redaction, and access rules and MUST never trigger generation, regeneration, publication, or any other mutation from this slice.
- **FR-012**: Every explicit workspace access, released-review access, evidence summary or proof access, and review-pack download exposed by this slice MUST remain auditable through the current audit infrastructure.
- **FR-013**: Non-members or out-of-scope workspace or tenant requests MUST resolve as deny-as-not-found, while actors inside an established scope MUST receive explicit capability denial only for gated secondary actions or deep links they are not allowed to use.
- **FR-014**: When launched from tenant-scoped context, the workspace MUST preserve a safe tenant prefilter and context return path without broadening discovery beyond entitled tenants.
- **FR-015**: The slice MUST NOT introduce a new global-searchable resource or broaden existing search discovery in a way that reveals review or evidence artifacts across tenant boundaries.
- **FR-016**: Any new or revised status, severity, availability, or redaction labels in this slice MUST stay aligned with existing centralized semantics rather than page-local mappings.
- **FR-017**: Customer-facing labels and guidance introduced by this slice MUST remain localization-ready for the existing DE/EN product language posture.
- **FR-018**: The slice MUST expose no destructive, remediation, authoring, publishing, generation, or admin-only actions.
## 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| Customer Review Workspace | existing `App\Filament\Pages\Reviews\CustomerReviewWorkspace` | `Clear filters` only | `recordUrl()` or full-row open to the released review | `Open released review`, `Download current review pack` when already available and entitled | none | `Clear filters` only when filters are active; otherwise explanatory no-data text with no create CTA | `N/A` | `N/A` | yes | Action Surface Contract satisfied: exactly one primary inspect model, no redundant view action, no empty action groups, no destructive actions |
| Released Customer Review detail | existing tenant-scoped released review detail surface | none by default | `N/A` | `N/A` | none | `N/A` | `Download current review pack` only; evidence summary remains in secondary in-body proof sections when entitled | `N/A` | yes | Customer-safe launch mode hides publish, refresh, generate, archive, remediation, and other operator-only actions while keeping one dominant safe header action |
Action Surface Contract is satisfied for this slice. Each affected surface keeps one primary inspect/open model, no empty `ActionGroup` or `BulkActionGroup` placeholder, and no destructive-action placement rules are needed because destructive actions are out of scope. `UI-FIL-001` and `UX-001` are satisfied by staying inside native Filament read-only surfaces, using explicit empty states, and keeping status emphasis aligned to shared review semantics rather than page-local visual language.
### Key Entities *(include if feature involves data)*
- **Customer Review Workspace Summary**: A derived workspace-scoped summary for one entitled tenant that combines the current released review, findings overview, accepted-risk/accountability summary, evidence proof availability, and pack access state without becoming a persisted entity.
- **TenantReview**: The existing released review artifact that anchors what was reviewed, the current governance outcome, and the released detail path.
- **Finding**: The existing issue-level governance truth that feeds the customer-safe findings summary and recommendation framing.
- **Accepted Risk Decision**: The existing accepted-risk or exception truth that explains why a risk was accepted, who is accountable when product truth exists, and when the decision should be revisited.
- **EvidenceSnapshot**: The existing supporting proof artifact that informs evidence summaries and proof pointers.
- **ReviewPack**: The existing packaged review artifact that remains the primary downloadable proof bundle.
- **AuditLog**: The existing audit trail used to record explicit access and download behavior without introducing a new audit store.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: An entitled read-only actor can answer what was reviewed, what matters, what was accepted, what evidence exists, and what artifact is available from the customer review workspace in two interactions or fewer.
- **SC-002**: In 100% of validated customer-safe scenarios, the default-visible workspace and released-review detail path shows no mutation, remediation, publication, or operator-debug actions.
- **SC-003**: In 100% of validated unauthorized workspace or tenant access scenarios, the feature reveals no cross-tenant review, evidence, or pack presence.
- **SC-004**: For tenants with a released review and available pack, entitled users can open the released review or access the pack on their first attempt without operator assistance.
- **SC-005**: For tenants without released review truth, proof access, or a current pack, the surface explains the absence or unavailability explicitly rather than showing a blank state or generic error.

View File

@ -0,0 +1,205 @@
---
description: "Task list for Customer Review Workspace Productization v1"
---
# Tasks: Customer Review Workspace Productization v1
**Input**: Design documents from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/`
**Prerequisites**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/plan.md` (required), `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/spec.md` (required), `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/checklists/requirements.md` (required), `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/research.md`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/data-model.md`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/contracts/customer-review-productization.openapi.yaml`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/quickstart.md`
**Tests**: Required (Pest) for runtime behavior changes. Keep proof in the narrow `confidence` lane plus one bounded `browser` smoke only because this slice changes customer-safe wording, disclosure order, and safe action hierarchy on existing workspace and detail surfaces.
**Operations**: No new `OperationRun`, queue, remote call, publication flow, remediation flow, or background processing is introduced. Auditability stays on the current shared audit pipeline only.
**RBAC**: Workspace membership remains the first boundary. Non-members or out-of-scope tenant targets remain `404`; in-scope actors missing an optional capability get explicit unavailability or `403` only on the gated secondary path. Reuse `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Services/Auth/RoleCapabilityMap.php` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Auth/Capabilities.php`; do not add raw capability strings or role-string checks.
**Shared Pattern Reuse**: Reuse `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Ui/GovernanceArtifactTruth/SurfaceCompressionContext.php`, existing review-pack and evidence resources, and the shared audit logger rather than creating a new customer shell, presenter family, or persistence layer.
**Organization**: Tasks are grouped by user story so workspace productization, released-review detail hardening, and packaged-proof access remain independently testable after shared seams are settled.
## Test Governance Notes
- Lane assignment: `confidence` plus one explicit `browser` smoke remain the narrowest sufficient proof for capability-first RBAC, workspace and tenant isolation, calmer disclosure hierarchy, explicit unavailable states, and auditable pack or proof consumption.
- Keep new coverage inside `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Reviews/CustomerReviewWorkspace*.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/TenantReview/TenantReviewUiContractTest.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/TenantReview/TenantReviewExplanationSurfaceTest.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/ReviewPack/ReviewPackDownloadTest.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Evidence/EvidenceSnapshotResourceTest.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Evidence/EvidenceSnapshotAuditLogTest.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php`; do not widen this slice into a new browser family or a broader cross-tenant workboard suite.
- Reuse existing workspace membership, entitled-tenant, released review, finding exception, evidence snapshot, review pack, localization, and audit fixtures; any helper added during implementation must stay explicit and cheap by default.
- If implementation finds that workspace-open or proof-open auditing is already fully covered, close the corresponding audit tasks as reuse-only and record the outcome as `document-in-feature` instead of adding new action IDs.
---
## Phase 1: Setup (Shared Context)
**Purpose**: Lock the bounded productization delta, current proof lanes, and exact repo anchors before runtime edits begin.
- [x] T001 Review the bounded slice, non-goals, guardrail outcomes, and required customer-safe behaviors in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/spec.md`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/plan.md`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/checklists/requirements.md`
- [x] T002 [P] Review route reuse, derived disclosure states, audit expectations, and no-new-persistence constraints in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/research.md`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/data-model.md`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/contracts/customer-review-productization.openapi.yaml`
- [x] T003 [P] Confirm the focused Sail/Pest commands, the single bounded browser-smoke requirement, and the existing review test family in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/quickstart.md`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Reviews/`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Browser/Reviews/`
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Settle the shared RBAC, isolation, audit, presenter, and localization seams that every user story depends on.
**⚠️ CRITICAL**: No user story work should begin until this phase is complete.
- [x] T004 [P] Add shared authorization coverage for workspace membership, entitled-tenant omission, deny-as-not-found tenant targeting, and in-scope optional-capability denial in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceAuthorizationTest.php`
- [x] T005 Reuse or minimally tighten capability-first workspace and tenant isolation in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Services/TenantReviews/TenantReviewRegisterService.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Auth/Capabilities.php`, and existing capability maps so later summary, proof, and pack work stays on one existing seam
- [x] T006 [P] Verify and extend the shared audit pipeline only where required in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Audit/AuditActionId.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Http/Controllers/ReviewPackDownloadController.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource/Pages/ViewEvidenceSnapshot.php` for workspace entry, review open, proof open, and pack download moments
- [x] T007 [P] Confirm the shared customer-safe truth presentation seams to reuse in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Ui/GovernanceArtifactTruth/SurfaceCompressionContext.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php` before story-specific disclosure changes begin
- [x] T008 [P] Inventory the bounded copy, localization, and tenant-safe global-search seams for calmer wording, customer-safe disclosure hierarchy, explicit access-state labels, and unchanged review/evidence discoverability in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/en/localization.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/de/localization.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php`
**Checkpoint**: Shared RBAC, isolation, audit, presenter, and localization seams are fixed before workspace or detail productization begins.
---
## Phase 3: User Story 1 - Understand The Latest Released Review At A Glance (Priority: P1) 🎯 MVP
**Goal**: Let an entitled customer reviewer, customer admin, or auditor open one existing workspace route and immediately understand the current released governance record per entitled tenant.
**Independent Test**: Open `/admin/reviews/workspace` as an entitled actor and confirm each visible tenant shows only released review truth, customer-safe summary wording, one dominant `Open released review` action, and explicit absence or unavailable states without leaking internal review lifecycle.
### Tests for User Story 1
- [x] T009 [P] [US1] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php` for latest released review-only rows, calmer at-a-glance summaries, accepted-risk accountability visibility, evidence or pack availability summaries, and truthful no-released-review states
- [x] T010 [P] [US1] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php` for launches into `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php` from existing review, evidence, and review-pack detail paths with safe tenant prefilter and no broadened tenant discovery
### Implementation for User Story 1
- [x] T011 [US1] Compose one workspace entry per entitled tenant from released review truth only in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php` and the existing tenant-review register query seam, reusing existing `TenantReview`, current pack, and evidence relationships without draft or internal fallback
- [x] T012 [US1] Reuse or minimally tighten launch and prefilter handoffs in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php` so review, evidence, and review-pack detail paths enter the workspace with preserved tenant context and no new shell
- [x] T013 [US1] Rework the default-visible workspace disclosure hierarchy in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php` so outcome, key findings, accepted-risk accountability, evidence availability, and review-pack state appear as decision-first content with exactly one dominant `Open released review` action
- [x] T014 [US1] Implement explicit workspace absence, unavailable, partial, expired, and redaction-safe messaging in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php` without introducing a new persisted state family
- [x] T015 [US1] Update calmer workspace wording and DE or EN localization keys in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/en/localization.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/de/localization.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php` so operator-led language is removed from the customer-safe entry surface
**Checkpoint**: User Story 1 is independently functional when the workspace truthfully shows only released review summaries for entitled tenants with clear customer-safe wording and explicit access-state handling.
---
## Phase 4: User Story 2 - Understand Why The Review Says What It Says (Priority: P1)
**Goal**: Let the same actor open the released review detail from the workspace and understand findings, accepted-risk accountability, and proof context without seeing operator or mutation residue.
**Independent Test**: Open a released review from the workspace and confirm the detail stays read-only, keeps customer-safe disclosure first, preserves tenant and workspace context, and makes optional proof or pack unavailability explicit instead of hiding or leaking content.
### Tests for User Story 2
- [x] T016 [P] [US2] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceNavigationContextTest.php` for workspace-to-review handoff, preserved tenant context, safe return semantics, and deny-as-not-found behavior when `customer_workspace=1` targets an out-of-scope tenant or review
- [x] T017 [P] [US2] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/TenantReview/TenantReviewUiContractTest.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/ReviewPack/ReviewPackResourceTest.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Evidence/EvidenceSnapshotResourceTest.php` for customer-workspace read-only mode, one dominant safe action, hidden publish or remediation controls, explicit unavailable states for optional secondary actions, and preserved tenant-safe global-search posture across the touched review/evidence/pack resources
- [x] T018 [P] [US2] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/TenantReview/TenantReviewExplanationSurfaceTest.php` for calmer findings language, accepted-risk accountability framing, evidence summary ordering, and hidden raw or support detail by default when the detail is launched from the workspace
- [x] T019 [P] [US2] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php` for the calm workspace-to-detail handoff, one dominant primary action, and truthful optional-action unavailable states while keeping browser proof bounded to this single smoke slice
### Implementation for User Story 2
- [x] T020 [US2] Tighten the existing customer-workspace detail mode in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource.php` so the released review remains read-only, capability-aware, and context-preserving without creating a second detail surface
- [x] T021 [US2] Reuse `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Ui/GovernanceArtifactTruth/SurfaceCompressionContext.php`, and the current review and finding-exception relationships to present calmer findings, accepted-risk accountability, and evidence-summary disclosure on the existing released-review detail surface
- [x] T022 [US2] Reuse the related detail surfaces in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource/Pages/ViewEvidenceSnapshot.php` so secondary pack and proof paths preserve customer-safe wording, source context, and explicit unavailable messaging after drilldown
- [x] T023 [US2] Update customer-safe detail, pack, and proof copy in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/en/localization.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/de/localization.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php` so calmer wording and the customer-safe disclosure hierarchy stay aligned across the flow
**Checkpoint**: User Story 2 is independently functional when the released review detail deepens understanding without exposing operator controls, duplicate summaries, or raw support detail by default.
---
## Phase 5: User Story 3 - Safely Consume Packaged Proof And Understand Unavailable States (Priority: P2)
**Goal**: Let the actor access the current review pack or proof route when entitled, or understand exactly why it is unavailable, expired, absent, or redacted.
**Independent Test**: From the workspace and released-review detail, verify that current review-pack download and proof routes stay capability-gated, explicit when unavailable, auditable, and never trigger generation, regeneration, publication, or remediation behavior.
### Tests for User Story 3
- [x] T024 [P] [US3] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php` for available, unavailable, expired, absent, and redacted pack or proof states plus explicit customer-safe messaging on the workspace and released-review detail surfaces
- [x] T025 [P] [US3] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/ReviewPack/ReviewPackDownloadTest.php` for signed current-pack download reuse, `source_surface=customer_review_workspace` audit metadata, and the absence of generate, regenerate, or publication behavior on this path
- [x] T026 [P] [US3] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Evidence/EvidenceSnapshotResourceTest.php` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Evidence/EvidenceSnapshotAuditLogTest.php` for proof-route capability gating, explicit unavailable or redacted handling, and shared audit logging when proof is opened from the customer review flow
### Implementation for User Story 3
- [x] T027 [US3] Reuse `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Http/Controllers/ReviewPackDownloadController.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php` so current-pack access stays capability-first, signed, auditable, and explicit when absent, unavailable, expired, or redacted
- [x] T028 [US3] Reuse `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource/Pages/ViewEvidenceSnapshot.php` so proof pointers remain customer-safe, capability-gated, and explicit when proof is absent, unavailable, or redacted instead of silently omitted
- [x] T029 [US3] Finalize shared audit wiring in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/Audit/AuditActionId.php` for workspace entry, review open, proof open, and pack download only where T006 confirmed a real gap, preserving stable tenant, workspace, and `source_surface` metadata on the existing pipeline
- [x] T030 [US3] Align pack and proof access wording in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/en/localization.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/de/localization.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php` so access, absence, unavailable, expired, and redacted states stay distinct and customer-safe
**Checkpoint**: User Story 3 is independently functional when current pack and proof access remain bounded, auditable, and explicit under all supported access states.
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Run the narrow validation set, keep formatting clean, and record bounded reviewer outcomes without widening scope.
- [x] T031 Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspaceAuthorizationTest.php tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php`
- [x] T032 Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspaceNavigationContextTest.php tests/Feature/TenantReview/TenantReviewUiContractTest.php tests/Feature/TenantReview/TenantReviewExplanationSurfaceTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php tests/Feature/ReviewPack/ReviewPackDownloadTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php tests/Feature/Evidence/EvidenceSnapshotResourceTest.php tests/Feature/Evidence/EvidenceSnapshotAuditLogTest.php`
- [x] T033 Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php`
- [x] T034 Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- [x] T035 Record the final `Guardrail / Smoke Coverage` close-out, lane results, audit-gap outcome (`reuse-only` vs bounded additive action IDs), localization or copy scope outcome, global-search safety outcome, and any `document-in-feature` or `follow-up-spec` decision in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/plan.md`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/quickstart.md`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/258-customer-review-productization/checklists/requirements.md`
---
## Dependencies & Execution Order
### Phase Dependencies
- **Phase 1 (Setup)**: no dependencies; start immediately.
- **Phase 2 (Foundational)**: depends on Phase 1 and blocks all user stories until RBAC, isolation, audit, presenter, and localization seams are settled.
- **Phase 3 (US1)**: depends on Phase 2 and delivers the MVP workspace productization slice.
- **Phase 4 (US2)**: depends on Phase 2 and should follow US1 because it deepens the same review-consumption flow on shared surfaces.
- **Phase 5 (US3)**: depends on Phase 2 and is safest after US1 or US2 because pack and proof actions build on the same workspace and detail context.
- **Phase 6 (Polish)**: depends on all implemented stories.
### User Story Dependencies
- **US1 (P1)**: first independently shippable increment once Phase 2 is complete.
- **US2 (P1)**: independently testable after Phase 2, but should merge after US1 because the same workspace and detail surfaces are shared hotspots.
- **US3 (P2)**: independently testable after Phase 2, but should merge after US1 because explicit access-state handling depends on the final workspace and detail wording contract.
### Within Each User Story
- Write the listed Pest coverage first and make it fail for the intended gap before runtime implementation.
- Reuse shared RBAC, audit, presenter, and localization seams before introducing any local helper or new copy mapping.
- Re-run the narrowest relevant proof command after each story checkpoint before moving to the next story.
---
## Parallel Execution Examples
### Phase 1
- T002 and T003 can run in parallel after T001 confirms the bounded slice.
### Phase 2
- T004, T006, T007, and T008 can run in parallel while T005 settles the shared capability-first control path.
### User Story 1
- T009 and T010 can run in parallel before runtime edits begin.
- After T011 settles row composition, T012 can proceed before T013 through T015 finalize disclosure, states, and copy.
### User Story 2
- T016, T017, T018, and T019 can run in parallel because they cover different proof surfaces in the same flow.
- After the tests exist, T020 through T023 should land in order because they touch the same released-review detail family.
### User Story 3
- T024, T025, and T026 can run in parallel.
- After the tests exist, T027 and T028 can proceed in parallel before T029 and T030 finalize audit and wording alignment.
---
## Implementation Strategy
### Suggested MVP Scope
- MVP = **Phase 2 + User Story 1** only. That delivers the calmer customer-safe workspace entry surface, capability-first tenant isolation, explicit access-state handling, and safe launch continuity without yet deepening the released-review detail surface.
### Incremental Delivery
1. Complete Phase 1 and Phase 2.
2. Deliver US1 and validate the workspace productization contract.
3. Deliver US2 and validate the released-review detail follow-through.
4. Deliver US3 and validate packaged-proof access, unavailable states, and audit reuse.
5. Finish with Phase 6 validation, formatting, and reviewer close-out notes.
### Team Strategy
1. Settle the shared capability, audit, presenter, and localization seams first.
2. Parallelize test authoring inside each story before converging on the shared workspace and detail files.
3. Serialize merges around `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/TenantReviewResource/Pages/ViewTenantReview.php` because they are the primary conflict hotspots for this slice.

View File

@ -1,26 +0,0 @@
# Implementation Plan: BaselineCompareRun model bugfix
**Branch**: `feat/700-bugfix` | **Date**: 2026-02-20 | **Spec**: `specs/feat/700-bugfix/spec.md`
## Summary
Fix runtime crash caused by a missing Eloquent model referenced by a Filament dashboard widget.
## Technical Context
- PHP 8.4.x, Laravel 12
- Filament v5, Livewire v4
- PostgreSQL (Sail locally)
- Tests: Pest v4 (`vendor/bin/sail artisan test --compact`)
## Approach
1. Identify intended storage for baseline compare runs:
- If a `baseline_compare_runs` table already exists, implement `App\Models\BaselineCompareRun` mapped to it.
- If not, align the widget to an existing persistence type (likely `OperationRun`) without changing UX.
2. Add a regression test that exercises the tenant dashboard route and asserts a successful response.
3. Run Pint on dirty files and run the focused test.
## Risks
- Introducing a new model without an existing table could still fail at runtime. Prefer minimal, compatibility-first changes.

View File

@ -1,29 +0,0 @@
# Bugfix Specification: BaselineCompareRun missing model
**Branch**: `feat/700-bugfix`
**Created**: 2026-02-20
**Status**: Ready
## Problem
Navigating to the tenant dashboard (`/admin/t/{tenant}`) throws an Internal Server Error:
- `Class "App\Models\BaselineCompareRun" not found`
The stack trace points to the dashboard widget `app/Filament/Widgets/Dashboard/BaselineCompareNow.php`.
## Goal
- Tenant dashboard loads successfully.
- Baseline compare widget can safely query baseline compare run state without a fatal error.
## Non-Goals
- No UX redesign.
- No new baseline-compare workflow features beyond restoring runtime stability.
## Acceptance Criteria
- Visiting `/admin/t/{tenant}` does not throw a 500.
- The widget renders even when there are no baseline compare runs.
- A focused automated test covers the regression.

View File

@ -1,20 +0,0 @@
---
description: "Tasks for feat/700-bugfix (BaselineCompareRun missing model)"
---
# Tasks: feat/700-bugfix
**Input**: `specs/feat/700-bugfix/spec.md` and `specs/feat/700-bugfix/plan.md`
## Setup
- [X] T001 Confirm whether baseline compare runs table exists
## Tests (TDD)
- [X] T010 Add regression test for tenant dashboard (no 500)
## Core
- [X] T020 Fix missing BaselineCompareRun reference (model or widget)
## Validation
- [X] T030 Run Pint (dirty)
- [X] T040 Run focused tests via Sail