Merge branch 'platform-dev' of git.cloudarix.de:ahmido/TenantAtlas into platform-dev

This commit is contained in:
Ahmed Darrazi 2026-05-15 14:55:38 +02:00
commit adb3737298
12 changed files with 1274 additions and 22 deletions

View File

@ -279,6 +279,9 @@ private function executeReviewDerivedGeneration(
$fingerprint = app(ReviewPackService::class)->computeFingerprintForReview($review, $options);
$reviewSummary = is_array($review->summary) ? $review->summary : [];
$governancePackage = is_array($reviewSummary['governance_package'] ?? null)
? $this->redactReportPayload($reviewSummary['governance_package'], $includePii)
: [];
$summary = [
'environment_review_id' => (int) $review->getKey(),
'review_status' => (string) $review->status,
@ -289,6 +292,7 @@ private function executeReviewDerivedGeneration(
'operation_count' => $includeOperations ? (int) ($reviewSummary['operation_count'] ?? 0) : 0,
'highlights' => is_array($reviewSummary['highlights'] ?? null) ? $reviewSummary['highlights'] : [],
'publish_blockers' => is_array($reviewSummary['publish_blockers'] ?? null) ? $reviewSummary['publish_blockers'] : [],
'governance_package' => $governancePackage,
'delivery_bundle' => $this->deliveryBundleSummary($review),
'evidence_resolution' => [
'outcome' => 'resolved',
@ -541,7 +545,24 @@ private function redactReportPayload(array $payload, bool $includePii): array
*/
private function redactArrayPii(array $data): array
{
$piiKeys = ['displayName', 'display_name', 'userPrincipalName', 'user_principal_name', 'email', 'mail'];
$piiKeys = [
'displayName',
'display_name',
'userPrincipalName',
'user_principal_name',
'email',
'mail',
'tenant_name',
'tenant_label',
'customer_name',
'owner_label',
'owner_name',
'actor_label',
'actor_name',
'initiator_name',
'requested_by',
'approved_by',
];
foreach ($data as $key => $value) {
if (is_string($key) && in_array($key, $piiKeys, true)) {
@ -825,7 +846,10 @@ private function buildExecutiveEntrypoint(
$tenantName = $includePii ? $tenant->name : '[REDACTED]';
$topFindings = is_array($package['top_findings'] ?? null) ? $package['top_findings'] : [];
$acceptedRisks = is_array($package['accepted_risks'] ?? null) ? $package['accepted_risks'] : [];
$governanceDecisions = is_array($package['governance_decisions'] ?? null) ? $package['governance_decisions'] : [];
$decisionSummary = is_array($package['decision_summary'] ?? null) ? $package['decision_summary'] : [];
$governanceDecisions = is_array($decisionSummary['entries'] ?? null)
? $decisionSummary['entries']
: (is_array($package['governance_decisions'] ?? null) ? $package['governance_decisions'] : []);
$nextActions = is_array($reviewSummary['recommended_next_actions'] ?? null) ? $reviewSummary['recommended_next_actions'] : [];
$lines = [
@ -857,7 +881,7 @@ private function buildExecutiveEntrypoint(
'',
'## Governance decisions requiring awareness',
'',
...$this->entryBullets($governanceDecisions, 'No governance decisions require awareness in this released review.'),
...$this->decisionSummaryLines($decisionSummary, $governanceDecisions),
'',
'## Next actions',
'',
@ -876,6 +900,35 @@ private function buildExecutiveEntrypoint(
return implode("\n", $lines);
}
/**
* @param array<string, mixed> $decisionSummary
* @param array<int, mixed> $entries
* @return list<string>
*/
private function decisionSummaryLines(array $decisionSummary, array $entries): array
{
$lines = [];
$summary = $this->plainText($decisionSummary['summary'] ?? null, '');
$nextAction = $this->plainText($decisionSummary['next_action'] ?? null, '');
if ($summary !== '') {
$lines[] = $summary;
}
if ($nextAction !== '') {
$lines[] = 'Next action: '.$nextAction;
}
if ($lines !== []) {
$lines[] = '';
}
return [
...$lines,
...$this->entryBullets($entries, 'No governance decisions require awareness in this released review.'),
];
}
/**
* @param array<int, mixed> $entries
* @return list<string>

View File

@ -148,6 +148,11 @@ private function governancePackageSummary(
->map(fn (array $entry): array => $this->packageGovernanceDecisionEntry($entry))
->values()
->all();
$decisionSummary = $this->packageDecisionSummary(
snapshot: $snapshot,
acceptedRisksSection: $acceptedRisksSection,
governanceDecisions: $governanceDecisions,
);
return [
'delivery_artifact_family' => 'review_pack',
@ -163,6 +168,7 @@ private function governancePackageSummary(
'top_findings' => $openRiskEntries,
'accepted_risks' => $stableAcceptedRiskEntries->all(),
'governance_decisions' => $governanceDecisions,
'decision_summary' => $decisionSummary,
'evidence_basis_summary' => $this->governancePackageEvidenceBasisSummary(
snapshot: $snapshot,
controlInterpretationSummary: $controlInterpretationSummary,
@ -236,15 +242,118 @@ private function packageGovernanceDecisionEntry(array $entry): array
'finding_id' => $entry['finding_id'] ?? null,
'title' => $entry['title'] ?? 'Governance decision',
'governance_state' => $governanceState,
'awareness_reason' => $this->governanceDecisionAwarenessReason($governanceState),
'summary' => match ($governanceState) {
'expired_exception' => 'The accepted-risk exception has expired and needs follow-up before stakeholder delivery.',
'revoked_exception' => 'The accepted-risk exception was revoked and needs follow-up before stakeholder delivery.',
'risk_accepted_without_valid_exception' => 'The accepted-risk entry has no currently valid exception basis and needs follow-up before stakeholder delivery.',
default => 'This governance decision needs follow-up before stakeholder delivery.',
},
'next_action' => $this->governanceDecisionNextAction($governanceState),
'evidence_basis' => 'Included in the anchored released-review evidence basis.',
];
}
/**
* @param array<string, mixed> $acceptedRisksSection
* @param list<array<string, mixed>> $governanceDecisions
* @return array<string, mixed>
*/
private function packageDecisionSummary(
EvidenceSnapshot $snapshot,
array $acceptedRisksSection,
array $governanceDecisions,
): array {
$totalCount = count($governanceDecisions);
$evidenceState = is_string($acceptedRisksSection['completeness_state'] ?? null)
? (string) $acceptedRisksSection['completeness_state']
: (string) $snapshot->completeness_state;
$decisionDataState = $totalCount > 0 || $this->decisionEvidenceIsAvailable($evidenceState)
? 'available'
: 'incomplete';
$status = match (true) {
$totalCount > 0 => 'requires_awareness',
$decisionDataState === 'incomplete' => 'unavailable',
default => 'none',
};
return [
'customer_safe' => true,
'status' => $status,
'decision_data_state' => $decisionDataState,
'evidence_state' => $evidenceState,
'total_count' => $totalCount,
'requires_awareness' => $totalCount > 0,
'summary' => $this->decisionSummaryText($status, $totalCount),
'empty_state' => $this->decisionSummaryEmptyState($status),
'next_action' => $this->decisionSummaryNextAction($status),
'evidence_basis' => sprintf(
'Anchored to evidence snapshot #%d with %s decision-evidence completeness.',
(int) $snapshot->getKey(),
$evidenceState,
),
'source_section_state' => is_string($acceptedRisksSection['completeness_state'] ?? null)
? $acceptedRisksSection['completeness_state']
: null,
'entries' => $governanceDecisions,
];
}
private function decisionEvidenceIsAvailable(string $evidenceState): bool
{
return in_array($evidenceState, ['complete'], true);
}
private function decisionSummaryText(string $status, int $totalCount): string
{
return match ($status) {
'requires_awareness' => sprintf(
'%d governance decision%s require%s customer awareness before relying on this released review.',
$totalCount,
$totalCount === 1 ? '' : 's',
$totalCount === 1 ? 's' : '',
),
'unavailable' => 'Decision evidence is incomplete for this released review; no customer-aware decisions can be confirmed from the current evidence basis.',
default => 'No governance decisions require customer awareness in this released review.',
};
}
private function decisionSummaryEmptyState(string $status): string
{
return match ($status) {
'unavailable' => 'Decision evidence is incomplete in the current released-review basis.',
'requires_awareness' => '',
default => 'No governance decisions require awareness in this released review.',
};
}
private function decisionSummaryNextAction(string $status): string
{
return match ($status) {
'requires_awareness' => 'Review the accepted-risk decision basis before customer delivery.',
'unavailable' => 'Open the review evidence before treating the decision register summary as complete.',
default => 'No customer action is needed for Decision Register follow-up from this review.',
};
}
private function governanceDecisionAwarenessReason(string $governanceState): string
{
return match ($governanceState) {
'expired_exception' => 'The accepted-risk approval has expired and needs customer awareness before the review is relied on.',
'revoked_exception' => 'The accepted-risk approval was revoked and needs customer awareness before the review is relied on.',
'risk_accepted_without_valid_exception' => 'The accepted risk has no valid governance backing in the released review evidence.',
default => 'This accepted-risk governance decision needs customer awareness before the review is relied on.',
};
}
private function governanceDecisionNextAction(string $governanceState): string
{
return match ($governanceState) {
'expired_exception', 'revoked_exception', 'risk_accepted_without_valid_exception' => 'Confirm whether this accepted risk should be renewed, remediated, or removed before relying on the review.',
default => 'Confirm the accepted-risk decision before relying on the review.',
};
}
/**
* @param array<string, mixed> $executiveSummaryPayload
* @param array<string, mixed> $executiveRenderPayload

View File

@ -50,8 +50,8 @@ public function collect(ManagedEnvironment $tenant): array
'finding_type' => (string) $finding->finding_type,
'severity' => (string) $finding->severity,
'status' => (string) $finding->status,
'title' => $finding->title,
'description' => $finding->description,
'title' => $finding->resolvedSubjectDisplayName(),
'description' => null,
'created_at' => $finding->created_at?->toIso8601String(),
'updated_at' => $finding->updated_at?->toIso8601String(),
'verification_state' => $outcome['verification_state'],

View File

@ -371,6 +371,7 @@
'download_governance_package' => 'Governance-Paket herunterladen',
'governance_package' => 'Governance-Paket',
'governance_decisions' => 'Governance-Entscheidungen',
'governance_decisions_requiring_awareness' => 'Governance-Entscheidungen mit Aufmerksamkeitsbedarf',
'governance_package_delivery_note' => 'Dieses Governance-Paket wird über das aktuelle Export-Review-Pack des veröffentlichten Reviews ausgeliefert.',
'executive_entrypoint' => 'Executive-Einstieg',
'executive_entrypoint_description' => 'Beginnen Sie im heruntergeladenen Paket mit executive-summary.md.',

View File

@ -371,6 +371,7 @@
'download_governance_package' => 'Download governance package',
'governance_package' => 'Governance package',
'governance_decisions' => 'Governance decisions',
'governance_decisions_requiring_awareness' => 'Governance decisions requiring awareness',
'governance_package_delivery_note' => 'This governance package is delivered through the current export review pack for the released review.',
'executive_entrypoint' => 'Executive entrypoint',
'executive_entrypoint_description' => 'Start with executive-summary.md in the downloaded package.',

View File

@ -16,7 +16,13 @@
$packageAvailability = is_array($governancePackage['availability'] ?? null) ? $governancePackage['availability'] : [];
$packageTopFindings = is_array($governancePackage['top_findings'] ?? null) ? $governancePackage['top_findings'] : [];
$packageAcceptedRisks = is_array($governancePackage['accepted_risks'] ?? null) ? $governancePackage['accepted_risks'] : [];
$packageGovernanceDecisions = is_array($governancePackage['governance_decisions'] ?? null) ? $governancePackage['governance_decisions'] : [];
$packageDecisionSummary = is_array($governancePackage['decision_summary'] ?? null) ? $governancePackage['decision_summary'] : [];
$packageGovernanceDecisions = is_array($packageDecisionSummary['entries'] ?? null)
? $packageDecisionSummary['entries']
: (is_array($governancePackage['governance_decisions'] ?? null) ? $governancePackage['governance_decisions'] : []);
$packageDecisionSummaryText = is_string($packageDecisionSummary['summary'] ?? null) ? trim((string) $packageDecisionSummary['summary']) : null;
$packageDecisionNextAction = is_string($packageDecisionSummary['next_action'] ?? null) ? trim((string) $packageDecisionSummary['next_action']) : null;
$packageDecisionEmptyState = is_string($packageDecisionSummary['empty_state'] ?? null) ? trim((string) $packageDecisionSummary['empty_state']) : null;
$controlControls = is_array($controlInterpretation['controls'] ?? null) ? $controlInterpretation['controls'] : [];
$controlVersion = is_string($controlInterpretation['version_key'] ?? null) ? $controlInterpretation['version_key'] : null;
$controlDisclosure = is_string($controlInterpretation['non_certification_disclosure'] ?? null)
@ -205,24 +211,47 @@
</div>
@endif
@if ($packageGovernanceDecisions !== [])
@if ($packageDecisionSummary !== [] || $packageGovernanceDecisions !== [])
<div class="space-y-2">
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">{{ __('localization.review.governance_decisions') }}</div>
<ul class="space-y-1 text-sm text-amber-800 dark:text-amber-200">
@foreach ($packageGovernanceDecisions as $decision)
@php
$decisionTitle = is_string($decision['title'] ?? null) ? $decision['title'] : __('localization.review.governance_decisions');
$decisionSummary = is_string($decision['summary'] ?? null) ? $decision['summary'] : null;
@endphp
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">{{ __('localization.review.governance_decisions_requiring_awareness') }}</div>
<li class="rounded-md border border-amber-100 bg-amber-50 px-3 py-2 dark:border-amber-900/40 dark:bg-amber-950/30">
<div class="font-medium">{{ $decisionTitle }}</div>
@if ($decisionSummary !== null && trim($decisionSummary) !== '')
<div class="mt-1 text-xs">{{ $decisionSummary }}</div>
@endif
</li>
@endforeach
</ul>
@if ($packageDecisionSummaryText !== null && $packageDecisionSummaryText !== '')
<div class="rounded-md border border-gray-100 bg-gray-50 px-3 py-2 text-sm text-gray-700 dark:border-gray-800 dark:bg-gray-950/60 dark:text-gray-300">
{{ $packageDecisionSummaryText }}
</div>
@endif
@if ($packageDecisionNextAction !== null && $packageDecisionNextAction !== '')
<div class="rounded-md border border-primary-100 bg-primary-50 px-3 py-2 text-sm text-primary-900 dark:border-primary-900/40 dark:bg-primary-950/30 dark:text-primary-100">
{{ $packageDecisionNextAction }}
</div>
@endif
@if ($packageGovernanceDecisions !== [])
<ul class="space-y-1 text-sm text-amber-800 dark:text-amber-200">
@foreach ($packageGovernanceDecisions as $decision)
@php
$decisionTitle = is_string($decision['title'] ?? null) ? $decision['title'] : __('localization.review.governance_decisions');
$decisionSummary = is_string($decision['summary'] ?? null) ? $decision['summary'] : null;
$decisionNextAction = is_string($decision['next_action'] ?? null) ? $decision['next_action'] : null;
@endphp
<li class="rounded-md border border-amber-100 bg-amber-50 px-3 py-2 dark:border-amber-900/40 dark:bg-amber-950/30">
<div class="font-medium">{{ $decisionTitle }}</div>
@if ($decisionSummary !== null && trim($decisionSummary) !== '')
<div class="mt-1 text-xs">{{ $decisionSummary }}</div>
@endif
@if ($decisionNextAction !== null && trim($decisionNextAction) !== '')
<div class="mt-2 text-xs">{{ $decisionNextAction }}</div>
@endif
</li>
@endforeach
</ul>
@elseif ($packageDecisionEmptyState !== null && $packageDecisionEmptyState !== '')
<div class="rounded-md border border-gray-100 bg-gray-50 px-3 py-2 text-sm text-gray-700 dark:border-gray-800 dark:bg-gray-950/60 dark:text-gray-300">
{{ $packageDecisionEmptyState }}
</div>
@endif
</div>
@endif
</div>

View File

@ -5,7 +5,13 @@
use App\Filament\Resources\EnvironmentReviewResource;
use App\Filament\Widgets\ManagedEnvironment\ManagedEnvironmentReviewPackCard;
use App\Jobs\GenerateReviewPackJob;
use App\Models\Finding;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Findings\FindingExceptionService;
use App\Services\Findings\FindingRiskGovernanceResolver;
use App\Services\ReviewPackService;
use App\Support\EnvironmentReviewStatus;
use Illuminate\Support\Facades\Storage;
use Livewire\Livewire;
@ -13,6 +19,40 @@
Storage::fake('exports');
});
function spec308SeedExpiredDecisionFinding(ManagedEnvironment $tenant, User $requester, string $title): Finding
{
$approver = User::factory()->create(['name' => 'Spec 308 Approver']);
createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner', workspaceRole: 'manager');
/** @var FindingExceptionService $exceptionService */
$exceptionService = app(FindingExceptionService::class);
$finding = Finding::factory()->for($tenant)->riskAccepted()->create([
'fingerprint' => 'spec-308-raw-fingerprint-'.$title,
'evidence_jsonb' => [
'display_name' => $title,
'internal_url' => 'https://tenantpilot.test/admin/operations/raw-run',
],
]);
$requested = $exceptionService->request($finding, $tenant, $requester, [
'owner_user_id' => (int) $requester->getKey(),
'request_reason' => 'Temporary exception for staged remediation.',
'review_due_at' => now()->addDays(5)->toDateTimeString(),
'expires_at' => now()->addDays(14)->toDateTimeString(),
]);
$exceptionService->approve($requested, $approver, [
'effective_from' => now()->subDays(10)->toDateTimeString(),
'expires_at' => now()->subDay()->toDateTimeString(),
'approval_reason' => 'Approved with customer controls.',
]);
app(FindingRiskGovernanceResolver::class)->syncExceptionState($finding->findingException()->firstOrFail());
return $finding->refresh();
}
it('renders an executive-ready environment review and exports a pack with matching section order and summary truth', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$review = composeEnvironmentReviewForTest($tenant, $user);
@ -88,3 +128,93 @@
->test(ManagedEnvironmentReviewPackCard::class, ['record' => $tenant])
->assertSee('View review');
});
it('builds an explicit customer-safe decision summary for released review consumption', function (): void {
[$user, $tenant] = createUserWithTenant(
tenant: ManagedEnvironment::factory()->create(['name' => 'Visible Customer Environment']),
role: 'owner',
);
spec308SeedExpiredDecisionFinding($tenant, $user, 'Privileged role exception');
$hiddenTenant = ManagedEnvironment::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'name' => 'Hidden Customer Environment',
]);
[$hiddenUser, $hiddenTenant] = createUserWithTenant(tenant: $hiddenTenant, role: 'owner');
spec308SeedExpiredDecisionFinding($hiddenTenant, $hiddenUser, 'Hidden tenant exception');
$snapshot = seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
])->save();
$decisionSummary = data_get($review->fresh(), 'summary.governance_package.decision_summary');
expect($decisionSummary)->toBeArray()
->and($decisionSummary['status'] ?? null)->toBe('requires_awareness')
->and($decisionSummary['total_count'] ?? null)->toBe(1)
->and($decisionSummary['summary'] ?? null)->toContain('1 governance decision requires customer awareness')
->and($decisionSummary['next_action'] ?? null)->toBe('Review the accepted-risk decision basis before customer delivery.')
->and(data_get($decisionSummary, 'entries.0.title'))->toBe('Privileged role exception')
->and(data_get($decisionSummary, 'entries.0.awareness_reason'))->toContain('expired')
->and(data_get($decisionSummary, 'entries.0.next_action'))->toBe('Confirm whether this accepted risk should be renewed, remediated, or removed before relying on the review.')
->and(json_encode($decisionSummary, JSON_THROW_ON_ERROR))->not->toContain(
'Hidden tenant exception',
'spec-308-raw-fingerprint',
'raw-run',
'OperationRun',
'Reason owner',
'Platform reason family',
);
$this->actingAs($user)
->get(EnvironmentReviewResource::tenantScopedUrl('view', ['record' => $review], $tenant).'?customer_workspace=1&source_surface=customer_review_workspace')
->assertOk()
->assertSee('Governance decisions requiring awareness')
->assertSee('Privileged role exception')
->assertSee('Review the accepted-risk decision basis before customer delivery.')
->assertDontSee('Hidden tenant exception')
->assertDontSee('raw-run')
->assertDontSeeText('Approve exception')
->assertDontSeeText('Reject exception')
->assertDontSeeText('Renew exception')
->assertDontSeeText('Revoke exception');
});
it('distinguishes no decision awareness from incomplete decision evidence', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$reviewWithNoDecisions = composeEnvironmentReviewForTest(
tenant: $tenant,
user: $user,
snapshot: seedEnvironmentReviewEvidence($tenant, findingCount: 2, driftCount: 0),
);
$noDecisionSummary = data_get($reviewWithNoDecisions, 'summary.governance_package.decision_summary');
expect($noDecisionSummary)->toBeArray()
->and($noDecisionSummary['status'] ?? null)->toBe('none')
->and($noDecisionSummary['total_count'] ?? null)->toBe(0)
->and($noDecisionSummary['summary'] ?? null)->toBe('No governance decisions require customer awareness in this released review.');
$partialTenant = ManagedEnvironment::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'name' => 'Partial Evidence Environment',
]);
createUserWithTenant(tenant: $partialTenant, user: $user, role: 'owner');
$partialReview = composeEnvironmentReviewForTest(
tenant: $partialTenant,
user: $user,
snapshot: seedPartialEnvironmentReviewEvidence($partialTenant, findingCount: 0, driftCount: 0),
);
$unavailableSummary = data_get($partialReview, 'summary.governance_package.decision_summary');
expect($unavailableSummary)->toBeArray()
->and($unavailableSummary['status'] ?? null)->toBe('unavailable')
->and($unavailableSummary['total_count'] ?? null)->toBe(0)
->and($unavailableSummary['summary'] ?? null)->toContain('Decision evidence is incomplete');
});

View File

@ -4,7 +4,13 @@
use App\Filament\Resources\ReviewPackResource;
use App\Jobs\GenerateReviewPackJob;
use App\Models\Finding;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Findings\FindingExceptionService;
use App\Services\Findings\FindingRiskGovernanceResolver;
use App\Services\ReviewPackService;
use App\Support\EnvironmentReviewStatus;
use App\Support\ReviewPackStatus;
use Illuminate\Support\Facades\Storage;
@ -12,6 +18,40 @@
Storage::fake('exports');
});
function spec308SeedPackDecisionFinding(ManagedEnvironment $tenant, User $requester, string $title): Finding
{
$approver = User::factory()->create(['name' => 'Risk Owner']);
createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner', workspaceRole: 'manager');
/** @var FindingExceptionService $exceptionService */
$exceptionService = app(FindingExceptionService::class);
$finding = Finding::factory()->for($tenant)->riskAccepted()->create([
'fingerprint' => 'spec-308-pack-fingerprint',
'evidence_jsonb' => [
'display_name' => $title,
'internal_url' => 'https://tenantpilot.test/admin/operations/raw-review-pack-run',
],
]);
$requested = $exceptionService->request($finding, $tenant, $requester, [
'owner_user_id' => (int) $approver->getKey(),
'request_reason' => 'Customer owner approved temporary exception.',
'review_due_at' => now()->addDays(5)->toDateTimeString(),
'expires_at' => now()->addDays(14)->toDateTimeString(),
]);
$exceptionService->approve($requested, $approver, [
'effective_from' => now()->subDays(10)->toDateTimeString(),
'expires_at' => now()->subDay()->toDateTimeString(),
'approval_reason' => 'Approved with customer controls.',
]);
app(FindingRiskGovernanceResolver::class)->syncExceptionState($finding->findingException()->firstOrFail());
return $finding->refresh();
}
it('generates a review-derived executive pack with environment-review metadata and filtered sections', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$review = composeEnvironmentReviewForTest($tenant, $user);
@ -75,3 +115,69 @@
->assertSee('#'.$review->getKey())
->assertSee('Review status');
});
it('includes the customer-safe decision summary in review-derived pack JSON and markdown', function (): void {
[$user, $tenant] = createUserWithTenant(
tenant: ManagedEnvironment::factory()->create(['name' => 'Contoso Decision Tenant']),
role: 'owner',
);
spec308SeedPackDecisionFinding($tenant, $user, 'Privileged access accepted risk');
$snapshot = seedEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
$review->forceFill([
'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
])->save();
$pack = app(ReviewPackService::class)->generateFromReview($review, $user, [
'include_pii' => false,
'include_operations' => false,
]);
$job = new GenerateReviewPackJob(
reviewPackId: (int) $pack->getKey(),
operationRunId: (int) $pack->operation_run_id,
);
app()->call([$job, 'handle']);
$pack->refresh();
expect(data_get($pack->summary, 'governance_package.decision_summary.status'))->toBe('requires_awareness')
->and(data_get($pack->summary, 'governance_package.decision_summary.total_count'))->toBe(1)
->and(data_get($pack->summary, 'delivery_bundle.contract'))->toBe(ReviewPackService::REVIEW_DERIVED_DELIVERY_CONTRACT);
$zipContent = Storage::disk('exports')->get((string) $pack->file_path);
$tempFile = tempnam(sys_get_temp_dir(), 'review-derived-pack-decisions-');
file_put_contents($tempFile, $zipContent);
$zip = new ZipArchive;
$zip->open($tempFile);
$filenames = collect(range(0, $zip->numFiles - 1))
->map(fn (int $index): string => (string) $zip->getNameIndex($index))
->values()
->all();
$metadata = json_decode((string) $zip->getFromName('metadata.json'), true, 512, JSON_THROW_ON_ERROR);
$summary = json_decode((string) $zip->getFromName('summary.json'), true, 512, JSON_THROW_ON_ERROR);
$executiveEntrypoint = (string) $zip->getFromName(ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME);
$summaryJson = json_encode($summary, JSON_THROW_ON_ERROR);
expect($filenames)->toContain('metadata.json', 'summary.json', 'sections.json', ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME)
->and(collect($filenames)->filter(fn (string $filename): bool => str_starts_with($filename, 'executive-'))->values()->all())->toBe([ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME])
->and(data_get($metadata, 'delivery_bundle.contract'))->toBe(ReviewPackService::REVIEW_DERIVED_DELIVERY_CONTRACT)
->and(data_get($summary, 'delivery_bundle.contract'))->toBe(ReviewPackService::REVIEW_DERIVED_DELIVERY_CONTRACT)
->and(data_get($summary, 'governance_package.decision_summary.status'))->toBe('requires_awareness')
->and(data_get($summary, 'governance_package.decision_summary.total_count'))->toBe(1)
->and(data_get($summary, 'governance_package.decision_summary.entries.0.title'))->toBe('Privileged access accepted risk')
->and($executiveEntrypoint)->toContain('## Governance decisions requiring awareness')
->and($executiveEntrypoint)->toContain('1 governance decision requires customer awareness')
->and($executiveEntrypoint)->toContain('Privileged access accepted risk')
->and($executiveEntrypoint)->toContain('Review the accepted-risk decision basis before customer delivery.')
->and($summaryJson)->not->toContain('Contoso Decision Tenant', 'Risk Owner', 'spec-308-pack-fingerprint', 'raw-review-pack-run')
->and($executiveEntrypoint)->not->toContain('Contoso Decision Tenant', 'Risk Owner', 'spec-308-pack-fingerprint', 'raw-review-pack-run', 'OperationRun URL');
$zip->close();
unlink($tempFile);
});

View File

@ -0,0 +1,44 @@
# Requirements Checklist: Decision Register Customer-Safe Summary & Review-Pack Inclusion
**Purpose**: Validate that the preparation artifacts are clear, bounded, testable, and aligned with TenantPilot constitution guardrails before implementation.
**Created**: 2026-05-15
**Feature**: [spec.md](../spec.md)
## Requirement Quality
- [x] CHK001 The selected candidate is explicitly sourced from the user request, roadmap priority ranking, and manual-promotion backlog.
- [x] CHK002 The scope is narrowed to existing Decision Register context, Environment Review summaries, Customer Review Workspace/review detail, and review-derived Review Pack export.
- [x] CHK003 Functional requirements are testable and avoid implementation-only wording where user behavior is the point.
- [x] CHK004 Acceptance criteria cover positive, empty/none, unavailable/incomplete, redaction, and cross-scope scenarios.
- [x] CHK005 Out-of-scope boundaries explicitly reject new decision persistence, new workflow engines, new customer approval actions, new OperationRun lifecycle, and broad export redesign.
## Constitution And Product Truth
- [x] CHK006 The spec identifies the source of truth for decisions, reviews, review packs, evidence, and operation runs.
- [x] CHK007 The proportionality review states no new persisted entity, enum/status family, public framework, or artifact family is planned.
- [x] CHK008 Existing completed specs 265, 306, 307, 109, 258, and 260 are treated as context only and are not modified.
- [x] CHK009 Provider-boundary requirements avoid spreading Microsoft/provider-specific semantics into platform-core review/export truth.
- [x] CHK010 Customer-safe disclosure requirements preserve non-certification wording and avoid raw/debug/internal detail.
## UI, RBAC, And Operations
- [x] CHK011 The affected surfaces carry consistent native/shared Filament classification and do not require ad-hoc styling or new assets.
- [x] CHK012 Filament v5 / Livewire v4, provider registration, global search, destructive action confirmation, and asset strategy requirements are explicit.
- [x] CHK013 RBAC requirements preserve workspace/environment isolation, non-member 404, and missing-capability 403 semantics.
- [x] CHK014 OperationRun impact is explicitly bounded to existing review-pack generation behavior with no new run UX.
- [x] CHK015 Auditability requirements preserve existing review and review-pack telemetry/audit semantics and avoid new audit action IDs unless scope changes.
## Test Governance
- [x] CHK016 Planned Pest coverage uses existing focused EnvironmentReview, ReviewPack, and Reviews feature families.
- [x] CHK017 Browser coverage is bounded to the existing CustomerReviewWorkspace smoke only if rendered UI changes.
- [x] CHK018 Fixture/helper cost risks are identified and kept feature-local.
- [x] CHK019 Validation commands are concrete and scoped.
- [x] CHK020 The task list includes explicit Filament/RBAC/asset/no-migration contract review tasks.
## Preparation Outcome
- [x] Outcome class: acceptable-special-case.
- [x] Workflow outcome: keep.
- [x] Final note location: active feature PR close-out entry `Guardrail / Smoke Coverage`.
- [x] Spec Readiness Gate passes for preparation: `spec.md`, `plan.md`, `tasks.md`, and this checklist exist, contain no placeholders, and keep implementation scope bounded to Spec 308.

View File

@ -0,0 +1,287 @@
# Implementation Plan: Decision Register Customer-Safe Summary & Review-Pack Inclusion
**Branch**: `308-decision-register-summary-review-pack` | **Date**: 2026-05-15 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/308-decision-register-summary-review-pack/spec.md`
## Summary
Add a bounded customer-safe Decision Register summary to existing released-review governance-package truth and review-derived Review Pack exports. The implementation should extend current Environment Review and Review Pack composition paths, not create a new decision store, export subsystem, customer approval workflow, or OperationRun lifecycle.
## Technical Context
**Language/Version**: PHP 8.4.15, Laravel 12.52.0
**Primary Dependencies**: Filament 5.2.1, Livewire 4.1.4, Pest 4.3.1, Laravel Sail
**Storage**: PostgreSQL; existing JSONB fields on `environment_reviews.summary`, `review_packs.summary`, and generated files on the existing `exports` disk
**Testing**: Pest 4 feature tests and optional bounded browser smoke
**Validation Lanes**: confidence; browser only if rendered UI changes
**Target Platform**: Laravel Sail locally; Dokploy container deployment for staging/production
**Project Type**: single Laravel app under `apps/platform`
**Performance Goals**: Derived summary composition remains ordinary review/pack work; no new query-heavy global scan
**Constraints**: no migrations expected; no new assets; no new operation type; no customer-safe raw diagnostic leakage
**Scale/Scope**: one managed-environment released-review and review-pack flow at a time
## UI / Surface Guardrail Plan
- **Guardrail scope**: changed customer-safe review/package surfaces and exported artifact content.
- **Native vs custom classification summary**: native/shared existing Filament surfaces plus existing export files.
- **Shared-family relevance**: customer review consumption, governance-package summary, Review Pack artifact truth, evidence/report disclosure, Decision Register context.
- **State layers in scope**: page, detail, artifact content; no shell change.
- **Audience modes in scope**: customer/read-only and operator-MSP.
- **Decision/diagnostic/raw hierarchy plan**: customer-safe decision summary first, existing detail/sections second, raw/support diagnostics absent from default customer paths.
- **Raw/support gating plan**: no raw OperationRun URLs, fingerprints, platform reason families, or provider payloads in customer-safe summary/export.
- **One-primary-action / duplicate-truth control**: preserve existing `Open review`, `Download governance package`, and `Download` actions; summary content must not add peer operator-only actions.
- **Handling modes by drift class or surface**: review-mandatory for customer-safe copy/export changes; report-only for unchanged Decision Register page.
- **Repository-signal treatment**: review-mandatory because this touches customer-safe review consumption and exported artifacts.
- **Special surface test profiles**: shared-detail-family plus standard-native-filament.
- **Required tests or manual smoke**: focused feature tests; existing bounded browser smoke if rendered customer workspace/review detail changes.
- **Exception path and spread control**: none planned.
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage.
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes.
- **Systems touched**: `EnvironmentReviewComposer`, `EnvironmentReviewSectionFactory`, `CustomerReviewWorkspace`, `EnvironmentReviewResource`, `ReviewPackService`, `GenerateReviewPackJob`, `ReviewPackResource`, review/review-pack tests.
- **Shared abstractions reused**: existing `governance_package` summary, existing `auditor_ready_executive_export.v1` delivery contract, existing `ReviewPackService::generateFromReview()`, existing `executive-summary.md` generation, existing BADGE-001 rendering if UI labels need badges.
- **New abstraction introduced? why?**: none planned. Private methods in existing classes are acceptable if needed to keep summary derivation readable.
- **Why the existing abstraction was sufficient or insufficient**: Existing review and pack composition paths already own customer-safe summary and export truth; they lack explicit Decision Register follow-through proof. Existing operator register builder is intentionally too operator-focused for direct customer export.
- **Bounded deviation / spread control**: no public framework or new artifact family. Any new summary keys stay nested under existing review/pack summary payloads.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: no new start/completion/link UX. Existing review-pack generation run behavior remains.
- **Central contract reused**: existing `ReviewPackService`, `GenerateReviewPackJob`, `OperationRunService`, and existing `OperationRunLinks` usage where already present.
- **Delegated UX behaviors**: existing Review Pack generation / run feedback only.
- **Surface-owned behavior kept local**: customer-safe copy may mention evidence/package availability, not run diagnostics.
- **Queued DB-notification policy**: N/A.
- **Terminal notification path**: existing Review Pack terminal handling remains.
- **Exception path**: none.
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: no.
- **Provider-owned seams**: none.
- **Platform-core seams**: review/governance-package summary and review-pack export artifact content.
- **Neutral platform terms / contracts preserved**: governance decision, accepted risk, evidence basis, review, review pack, managed environment, workspace.
- **Retained provider-specific semantics and why**: existing finding/evidence titles may include provider context; this feature does not add provider coupling.
- **Bounded extraction or follow-up path**: none.
## Constitution Check
- Inventory-first: PASS. The feature uses existing evidence/review snapshot truth; it does not query Graph or change inventory behavior.
- Read/write separation: PASS. Customer-safe summary is derived/read-only. Existing review-pack generation remains the only write path and already uses confirmation/authorization semantics where applicable.
- Graph contract path: PASS. No Graph calls or contracts are touched.
- Deterministic capabilities: PASS. Existing review/review-pack capabilities remain.
- RBAC-UX: PASS. Existing workspace/environment membership and review/review-pack policy behavior remain server-side boundaries.
- Workspace isolation: PASS. Summary derivation must scope to the released review workspace.
- Tenant/environment isolation: PASS. Summary derivation must scope to the managed environment under review.
- Run observability: PASS. Existing Review Pack `OperationRun` remains; no new run type or local run UX.
- TEST-GOV-001: PASS with focused feature coverage and optional bounded browser smoke.
- PROP-001 / BLOAT-001: PASS. No new table, enum family, service framework, or export family.
- PERSIST-001: PASS. Existing review and review-pack artifacts receive derived content; no new persisted truth.
- STATE-001: PASS. No new lifecycle/status family.
- UI-SEM-001: PASS. Customer-safe copy stays a derived summary, not a UI framework.
- XCUT-001: PASS. Reuses existing review and review-pack shared paths.
- PROV-001: PASS. No provider seam changes.
- UI-FIL-001: PASS. Existing Filament-native surfaces stay native/shared-primitives first; no ad-hoc CSS or new assets expected.
- DECIDE-AUD-001 / OPSURF-001: PASS. Customer-safe summary excludes raw/support/debug content by default.
## Test Governance Check
- **Test purpose / classification by changed surface**: Feature tests for review summary composition, review-pack ZIP content, customer workspace/review-pack access, redaction, and isolation. Browser smoke only if rendered UI changes.
- **Affected validation lanes**: confidence; browser optional/bounded.
- **Why this lane mix is the narrowest sufficient proof**: Export content can be verified by reading generated ZIP entries; rendered summary and access boundaries can be verified through existing feature tests. A broad suite or new browser family is not justified.
- **Narrowest proving command(s)**:
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/EnvironmentReview/EnvironmentReviewExecutivePackTest.php tests/Feature/EnvironmentReview/EnvironmentReviewCreationTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ReviewPack/EnvironmentReviewDerivedReviewPackTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php` if rendered UI changes
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `git diff --check`
- **Fixture / helper / factory / seed / context cost risks**: use existing helpers such as `composeEnvironmentReviewForTest()` and review-pack factories; avoid widening defaults.
- **Expensive defaults or shared helper growth introduced?**: no.
- **Heavy-family additions, promotions, or visibility changes**: none.
- **Surface-class relief / special coverage rule**: standard-native-filament plus shared-detail-family review/export checks.
- **Closing validation and reviewer handoff**: verify summary content, redaction, no raw diagnostics, no cross-scope leakage, unchanged destructive action confirmation, and unchanged asset strategy.
- **Budget / baseline / trend follow-up**: none expected.
- **Review-stop questions**: Are customer-safe summaries derived from the correct source? Are hidden records excluded? Is raw diagnostic detail absent? Did tests stay focused?
- **Escalation path**: none.
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage.
- **Why no dedicated follow-up spec is needed**: This is a bounded productization pass over existing review/pack paths; broader localization, packaging cadence, and artifact lifecycle remain separate candidates.
## Project Structure
### Documentation (this feature)
```text
specs/308-decision-register-summary-review-pack/
|-- spec.md
|-- plan.md
|-- tasks.md
`-- checklists/
`-- requirements.md
```
### Source Code (likely affected later)
```text
apps/platform/app/Services/EnvironmentReviews/
|-- EnvironmentReviewComposer.php
`-- EnvironmentReviewSectionFactory.php
apps/platform/app/Jobs/
`-- GenerateReviewPackJob.php
apps/platform/app/Services/
`-- ReviewPackService.php
apps/platform/app/Filament/Pages/Reviews/
`-- CustomerReviewWorkspace.php
apps/platform/app/Filament/Resources/
|-- EnvironmentReviewResource.php
`-- ReviewPackResource.php
apps/platform/tests/Feature/EnvironmentReview/
apps/platform/tests/Feature/ReviewPack/
apps/platform/tests/Feature/Reviews/
apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php
```
**Structure Decision**: Single Laravel/Filament application under `apps/platform`. Later implementation should modify only existing review, review-pack, and focused test surfaces unless repo inspection proves a smaller private helper is necessary.
## Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| None planned | N/A | N/A |
## Technical Approach
1. Inspect current `EnvironmentReviewComposer::governancePackageSummary()` and `GenerateReviewPackJob::buildExecutiveEntrypoint()` behavior.
2. Add or refine a customer-safe decision-summary shape under existing `EnvironmentReview.summary['governance_package']`.
3. Keep existing `governance_decisions` entries aligned with accepted-risk / exception decision truth and add explicit count/empty/unavailable semantics if missing.
4. Ensure `GenerateReviewPackJob` includes the same summary in `summary.json` and readable markdown in `executive-summary.md`.
5. Update existing customer workspace/review detail presentation only if current surfaces do not expose the summary clearly.
6. Preserve redaction through existing `redactReportPayload()` behavior and add assertions for `include_pii=false`.
7. Add focused tests for positive, none, incomplete, cross-scope, and redaction scenarios.
## Data / Model Implications
- No migration expected.
- No new model expected.
- No new enum/status family expected.
- Existing JSONB summary fields may receive additive keys:
- `governance_package.decision_summary`
- or a clarified `governance_package.governance_decisions` shape
- Existing ZIP entries remain preferred:
- `summary.json`
- `executive-summary.md`
- `sections.json`
If implementation finds a new table, new artifact file family, or new public contract is necessary, stop and update the spec/plan before coding further.
## UI / Filament Implications
- Filament v5.2.1 and Livewire v4.1.4 remain the target.
- No panel provider changes; Laravel 12 provider registration remains in `bootstrap/providers.php`.
- No new globally searchable resource.
- No new destructive actions.
- Existing Review Pack destructive-like actions such as expire/regenerate must keep `->requiresConfirmation()` and existing authorization.
- No new assets; deployment `filament:assets` unchanged.
- If UI changes are needed, prefer existing Filament sections/infolists/table entries and shared badge primitives.
## Livewire Implications
- Existing Filament/Livewire pages may render updated summary content.
- No new Livewire component is expected.
- Avoid server-driven reactivity changes unless current page state already requires it.
## RBAC / Policy Implications
- Reuse current review and review-pack policies/capabilities.
- No new capability constant expected.
- Customer-safe content must derive only after workspace and managed-environment scope are established.
- Non-member/not-entitled remains `404`; member missing capability remains existing `403`.
- Tests must cover at least one hidden/cross-scope omission path.
## Audit / Logging / Evidence Implications
- No new audit action ID expected.
- Existing review pack request/export/download audit and telemetry remain.
- No secrets, raw JSON, fingerprints, internal reason ownership, or OperationRun URLs in customer-safe default content.
- Existing `ReviewPack`, `EnvironmentReview`, `EvidenceSnapshot`, and `OperationRun` links remain traceability for operators.
## Rollout Considerations
- No migration.
- No new env vars.
- Queue workers already process review-pack jobs; no new worker class expected beyond current job changes.
- Staging validation should generate at least one review-derived pack with decisions requiring awareness and one with no decisions.
- Production/Dokploy deploy impact is ordinary app code plus queue restart if job code changes.
## Risk Controls
- Keep customer-safe summary derived and additive.
- Keep operator Decision Register unchanged unless a test proves a small source alignment issue.
- Reject raw OperationRun/proof URL exposure in customer-safe summary.
- Preserve non-certification disclosure in `executive-summary.md`.
- Keep tests focused on current review, pack, and customer workspace families.
## Implementation Phases
### Phase 1 - Discovery and Failing Tests
Confirm current summary/export behavior, then add focused tests for missing customer-safe decision summary, export inclusion, redaction, and cross-scope omission.
### Phase 2 - Summary Composition
Extend existing Environment Review governance-package summary with bounded customer-safe decision summary content.
### Phase 3 - Review Pack Inclusion
Ensure review-derived Review Pack `summary.json` and `executive-summary.md` include matching customer-safe decision summary content.
### Phase 4 - Customer Surface Presentation
Expose the summary on existing customer-safe review surfaces only where needed and only through native/shared Filament patterns.
### Phase 5 - Validation and Close-Out
Run focused Pest commands, optional bounded browser smoke, Pint dirty, and `git diff --check`. Record no new assets, no migrations, and no application implementation outside scope.
## Spec Readiness Gate
- `spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md` exist.
- Scope is bounded to existing Decision Register context, Environment Review summary, Customer Review Workspace/review detail, and review-derived Review Pack export.
- No open question blocks implementation.
- No new persisted entity, new status family, new public framework, new OperationRun type, or new asset bundle is planned.
- RBAC, isolation, auditability, OperationRun semantics, evidence/result truth, customer-safe disclosure, and test governance are addressed.
## Implementation Close-Out
- **Implementation status**: Completed for Spec 308.
- **Changed application files**:
- `apps/platform/app/Services/EnvironmentReviews/EnvironmentReviewComposer.php`
- `apps/platform/app/Services/Evidence/Sources/FindingsSummarySource.php`
- `apps/platform/app/Jobs/GenerateReviewPackJob.php`
- `apps/platform/resources/views/filament/infolists/entries/environment-review-summary.blade.php`
- `apps/platform/lang/en/localization.php`
- `apps/platform/lang/de/localization.php`
- **Changed tests**:
- `apps/platform/tests/Feature/EnvironmentReview/EnvironmentReviewExecutivePackTest.php`
- `apps/platform/tests/Feature/ReviewPack/EnvironmentReviewDerivedReviewPackTest.php`
- **No-migration status**: No database migration, new model, new enum/status family, new persisted decision source, or new Review Pack status was introduced.
- **No-asset status**: No frontend asset registration was added; existing deployment `filament:assets` expectations are unchanged.
- **OperationRun / audit status**: No new OperationRun type, run-link surface, or audit action ID was added. Existing review-pack generation/download traceability remains unchanged.
- **Validation results**:
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/EnvironmentReview/EnvironmentReviewExecutivePackTest.php tests/Feature/EnvironmentReview/EnvironmentReviewCreationTest.php` - passed, 5 tests / 68 assertions.
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ReviewPack/EnvironmentReviewDerivedReviewPackTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php` - passed, 22 tests / 142 assertions.
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php` - passed, 12 tests / 77 assertions.
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php` - passed, 1 test / 46 assertions.
- Additional regression lane: `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/EnvironmentReview/EnvironmentReviewComposerTest.php tests/Feature/Evidence/ExceptionValidityEvidenceIntegrationTest.php tests/Feature/ReviewPack/ReviewPackValidRiskAcceptanceTest.php` - passed, 6 tests / 27 assertions.
- Additional RBAC/download lane: `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ReviewPack/ReviewPackDownloadTest.php tests/Feature/ReviewPack/ReviewPackRbacTest.php` - passed, 20 tests / 49 assertions.
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - passed.
- `git diff --check` - passed.
- **Browser smoke result**: Required because rendered customer review detail UI changed. Existing bounded customer workspace browser smoke passed.
- **Post-implementation analysis**: No confirmed in-scope findings remain after the first implementation/fix iteration.
- **Remaining gaps**: None inside Spec 308 scope. Localization adoption, artifact lifecycle/retention, governance-service packaging cadence, and AI-assisted review drafting remain separate follow-up candidates and were not implemented.

View File

@ -0,0 +1,382 @@
# Feature Specification: Decision Register Customer-Safe Summary & Review-Pack Inclusion
**Feature Branch**: `308-decision-register-summary-review-pack`
**Created**: 2026-05-15
**Status**: Ready for implementation
**Input**: User description: "308 - Decision Register Customer-Safe Summary & Review-Pack Inclusion"
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
- **Problem**: The operator Decision Register is now repo-real through Specs 265, 306, and 307, and Review Pack / Customer Review Workspace delivery is repo-real through Specs 109, 258, 260, and 263. The remaining gap is that customer-safe review consumption does not yet clearly carry the Decision Register accountability story into released-review summaries and review-derived Review Packs.
- **Today's failure**: Operators can inspect decisions and proof internally, and review packs can include accepted-risk and governance-package content, but stakeholder-facing summaries can still miss the current decision-register framing: which governance decisions need customer awareness, why they matter, what evidence basis supports them, and what next action is expected without exposing raw operator diagnostics.
- **User-visible improvement**: A released customer review and its review-derived Review Pack include a calm customer-safe decision summary that explains accepted-risk / exception decision follow-through, uses existing governance-package truth, preserves redaction, and avoids raw OperationRun, payload, fingerprint, or internal reason-family detail.
- **Smallest enterprise-capable version**: Extend the existing Environment Review governance-package summary and review-derived Review Pack export path with one derived customer-safe decision-summary projection. Reuse existing `EnvironmentReview.summary`, existing Review Pack `summary.json`, existing `executive-summary.md`, existing customer workspace/review detail surfaces, and existing Decision Register truth. No new table, no new approval workflow, no new customer portal, no new operation lifecycle, and no new export subsystem.
- **Explicit non-goals**: No new `governance_decisions` table; no new `ReviewPack` status; no generic decision framework; no inline customer approval; no Decision Register redesign; no new Review Pack storage family; no raw evidence payload export; no OperationRun URLs in customer-safe exported content; no PSA/ticket handoff; no AI summary generation; no broad localization pass.
- **Permanent complexity imported**: One bounded customer-safe summary shape inside existing review/governance-package payloads, focused feature tests, and one bounded browser smoke update if UI copy changes. No new persisted entity, enum family, public service framework, route family, queue family, or asset bundle.
- **Why now**: `docs/product/roadmap.md` ranks "Decision Register customer-safe summary / review-pack inclusion" as the first current manual-promotion item. `docs/product/implementation-ledger.md` says Decision Register proof/run links are now repo-backed while customer-safe summary and review-pack inclusion remain separate follow-ups.
- **Why not local**: A one-off label on the Decision Register would not reach customer-facing review consumption or exported Review Packs. A broad review-pack rewrite would be too large. The narrow correct point is the existing review/governance-package composition and review-derived pack export path.
- **Approval class**: Workflow Compression
- **Red flags triggered**: Cross-surface review/export productization and customer-safe disclosure. Defense: the slice derives from existing truth, extends existing artifacts only, and explicitly forbids new persistence, new lifecycle states, and raw diagnostic leakage.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 12/12**
- **Decision**: approve
## Spec Scope Fields *(mandatory)*
- **Scope**: canonical-view
- **Primary Routes**:
- existing `/admin/governance/decisions` Decision Register page as operator context only
- existing `EnvironmentReviewResource` view route for released-review detail
- existing `CustomerReviewWorkspace` route for customer-safe review consumption
- existing `ReviewPackResource` view and signed download route for review-derived pack delivery
- **Data Ownership**:
- `FindingException` and `FindingExceptionDecision` remain the decision source of truth.
- `FindingExceptionEvidenceReference`, `EvidenceSnapshot`, and `StoredReport` remain evidence/report truth.
- `EnvironmentReview.summary` remains released-review summary truth.
- `ReviewPack.summary` and the generated ZIP files remain review-pack artifact truth.
- `OperationRun` remains execution truth and is not copied into customer-safe decision content beyond safe count/source statements already present in review summaries.
- **RBAC**:
- Workspace membership remains the first boundary for customer review and review-pack surfaces.
- Environment/managed-environment entitlement remains required before any released review or Review Pack is visible.
- Existing review and review-pack capabilities remain authoritative: no new capability is introduced.
- Non-members or out-of-scope workspace/environment actors get deny-as-not-found (`404`).
- Members missing review or review-pack capability get existing `403` capability denial where policies already define it.
For canonical-view specs:
- **Default filter behavior when tenant-context is active**: Customer Review Workspace and released-review detail preserve existing managed-environment prefilter behavior. This feature does not change Decision Register default filters.
- **Explicit entitlement checks preventing cross-tenant leakage**: Decision summary entries are derived only from the same workspace and managed environment as the released review / review pack. Cross-workspace and cross-environment decision, proof, and operation records must not affect counts, summaries, links, or unavailable-state copy.
## Cross-Cutting / Shared Pattern Reuse *(mandatory)*
- **Cross-cutting feature?**: yes.
- **Interaction class(es)**: customer-safe review summaries, governance package summaries, review-pack export content, evidence/report disclosure, action links, and status messaging.
- **Systems touched**: `EnvironmentReviewComposer`, `EnvironmentReviewSectionFactory`, `CustomerReviewWorkspace`, `EnvironmentReviewResource`, `ReviewPackService`, `GenerateReviewPackJob`, `ReviewPackResource`, existing Review Pack download route, existing Decision Register builder/page as source context only, and focused review/review-pack tests.
- **Existing pattern(s) to extend**: existing `governance_package` payload in `EnvironmentReview.summary`, existing review-derived Review Pack contract `auditor_ready_executive_export.v1`, existing `executive-summary.md`, existing customer workspace package availability states, existing BADGE-001-backed status display.
- **Shared contract / presenter / builder / renderer to reuse**: Reuse existing review composition and review-pack export paths. Reuse `BadgeRenderer` / `BadgeCatalog` for status-like UI if UI display changes. Reuse existing resource URL helpers for review/pack navigation. Do not create a new decision-summary presenter framework unless implementation proves the current composer cannot safely host the projection.
- **Why the existing shared path is sufficient or insufficient**: Existing review/package paths already carry executive summary, accepted risks, governance decisions, evidence basis, and package metadata. They are sufficient for v1 if they get an explicit customer-safe Decision Register summary shape and tests. The operator Decision Register builder is not sufficient by itself because it intentionally exposes operator-only proof and OperationRun affordances.
- **Allowed deviation and why**: A small private helper inside the existing review composer or review-pack generation job is allowed to keep customer-safe copy bounded. A public reusable framework is not allowed in v1.
- **Consistency impact**: `Decision register`, `Governance decisions requiring awareness`, `Accepted risks`, `Evidence basis`, `Next action`, and `Review pack` wording must stay aligned across review detail, customer workspace, and review-pack export.
- **Review focus**: Block raw payload/fingerprint/internal reason export, fake customer links, duplicate lifecycle truth, second approval surfaces, and any new persistence or workflow engine.
## OperationRun UX Impact *(mandatory)*
- **Touches OperationRun start/completion/link UX?**: yes, indirectly and read-only. Existing review-derived Review Pack generation already creates an `OperationRun`; this feature must not create a new run type or local start UX. Customer-safe summaries must not expose OperationRun URLs or raw run diagnostics by default.
- **Shared OperationRun UX contract/layer reused**: Existing `ReviewPackService`, `GenerateReviewPackJob`, `OperationRunService`, and existing `OperationRunLinks` usage remain the only run paths.
- **Delegated start/completion UX behaviors**: Existing review-pack generation queue/toast/run behavior remains delegated to current Review Pack and OperationRun infrastructure. No new queued toast, browser event, DB notification, dedupe messaging, or terminal notification is introduced.
- **Local surface-owned behavior that remains**: Customer-safe summary may state that evidence was generated or that a review pack is available, but must not render raw run status, run identifiers, or operation links in customer/read-only default paths.
- **Queued DB-notification policy**: N/A - no new notification policy.
- **Terminal notification path**: Existing review-pack terminal notification behavior remains unchanged.
- **Exception required?**: none.
## Provider Boundary / Platform Core Check *(mandatory)*
- **Shared provider/platform boundary touched?**: no new provider seam.
- **Boundary classification**: platform-core review/export summary over existing governance truth.
- **Seams affected**: Existing review and review-pack summary payloads; no Graph contract, provider connection, or provider dispatch seam changes.
- **Neutral platform terms preserved or introduced**: decision, governance decision, accepted risk, evidence basis, review, review pack, managed environment, workspace.
- **Provider-specific semantics retained and why**: Existing evidence and finding content may originate from Microsoft/Intune data because those records already own that truth. The customer-safe summary must not add Microsoft-specific platform-core language unless existing evidence titles already contain it.
- **Why this does not deepen provider coupling accidentally**: The feature derives from TenantPilot governance decision and review artifacts rather than Graph payloads or provider contracts.
- **Follow-up path**: none.
## UI / Surface Guardrail Impact *(mandatory)*
| 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 governance-package summary | yes | Existing Filament page / shared view primitives | customer-safe review consumption, package availability | page, table/detail context | no | Add bounded customer-safe decision summary only if current surface has a package summary location |
| Environment Review detail governance-package disclosure | yes | Native Filament resource/infolist | released-review detail, package export action | detail | no | Existing view remains the action owner; no new mutation action |
| Review Pack detail/export | yes | Native Filament resource plus existing ZIP export | review-pack artifact truth and signed download | detail, artifact content | no | Export content changes; no new asset or route |
| Decision Register page | no direct UI change expected | Existing native Filament page | operator-only context | none | no | Source context only; do not redesign register |
Implementation intent: use existing Filament-native sections/infolists/table display and existing export files. Do not add local CSS, ad-hoc cards, custom badges, hover affordances, or new assets. Filament remains v5 with Livewire v4.1.4+.
## Decision-First Surface Role *(mandatory)*
| 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 | Primary customer-safe consumption surface | Customer/stakeholder understands which governance decisions require awareness | customer-safe decision count, short summary, evidence basis, next action wording | released-review detail and downloadable review pack | Primary for customer consumption because it avoids sending stakeholders to operator register pages | Follows released-review delivery, not internal queue work | Reduces operator translation before sharing review status |
| Environment Review detail | Secondary context | Operator/customer-safe reviewer inspects one released review before export | decision-summary section, package availability, export action | sections, evidence basis, audit context where allowed | Secondary because it deepens the chosen review and starts export | Keeps review lifecycle ownership on existing detail page | Prevents rebuilding summary from Decision Register rows |
| Review Pack export | Tertiary evidence / delivery artifact | Stakeholder consumes the exported summary offline | executive decision-awareness section and structured summary JSON | appendix files in ZIP | Delivery artifact, not an interactive decision surface | Carries the released-review truth into the customer artifact | Avoids separate manual deck or email summary |
| Decision Register page | Operator primary decision surface, unchanged | Operator decides internal follow-up | existing register row proof/link state | existing detail/proof/operation links | Not customer-facing in this slice | Remains internal action and proof surface | Avoids duplicating operator workflow in customer surfaces |
## Audience-Aware Disclosure *(mandatory)*
| 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 | customer-read-only, operator-MSP | decision summary, evidence-basis wording, readiness/follow-up state | review detail and existing diagnostics remain secondary | raw payloads, fingerprints, platform reason families, run identifiers hidden | `Open review` or `Download review pack` depending current context | raw evidence and operation details | Summary states customer impact once; detail/export add evidence rather than restating internal state |
| Environment Review detail | customer-read-only, operator-MSP | released-review decision summary and package availability | review sections, evidence/source context | low-level evidence and run detail stays secondary/capability-gated | `Download governance package` when ready | raw JSON/fingerprints/internal reason ownership | Review summary remains the source for exported pack |
| Review Pack export | customer-read-only | executive story, accepted risks, governance decisions requiring awareness, next actions, non-certification disclosure | structured appendix only | raw payloads, fingerprints, internal reason families, OperationRun URLs absent | read executive entrypoint first | raw/support diagnostics absent from default entrypoint | `summary.json` carries structured truth; `executive-summary.md` carries readable summary |
## UI/UX Surface Classification *(mandatory)*
| 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 | List / Review Workspace | Customer-safe released-review list/detail handoff | Open review or download current pack | Existing row/detail handoff | current behavior preserved | package download remains contextual | none | existing customer workspace route | existing EnvironmentReview view route | workspace and managed-environment scope | Customer reviews | whether governance package and decision summary are available | none |
| Environment Review detail | Detail / Reviewable governance artifact | Released-review detail | Download governance package | Existing detail page | N/A | export action remains detail-owned | none | existing EnvironmentReview list/register | existing EnvironmentReview view | managed environment and review status | Environment review | decision summary, evidence basis, export readiness | none |
| Review Pack detail/export | Detail / Artifact delivery | Review-pack artifact | Download pack | Existing ReviewPack view/download | N/A | regenerate remains operator-only outside customer flow | existing expire/regenerate confirmation unchanged | existing ReviewPack list | existing ReviewPack view | managed environment, review link, status | Review pack | generated artifact truth and summary | none |
## Operator Surface Contract *(mandatory)*
| 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 | Customer-safe reader / MSP operator | Understand released review and choose whether to open or download | Customer-safe review workspace | What does the latest released review mean for governance follow-up? | executive summary, decision-awareness count/summary, evidence-basis copy, package availability | raw evidence and run detail remain elsewhere | review status, completeness, package availability, decision-awareness state | none | Open review / Download review pack | none |
| Environment Review detail | MSP operator / customer-safe reviewer | Validate released-review summary and export package | Detail page | Is this review ready to share and what decisions require awareness? | decision summary, package availability, evidence basis, next action | sections, evidence source detail, audit context | review status, completeness, governance-package availability | TenantPilot artifact export only | Download governance package | none in this slice |
| Review Pack detail/export | MSP operator / customer-safe reader | Consume or deliver review-pack artifact | Artifact detail/export | What is included in this package and is it safe to share? | status, customer-safe summary, review link, evidence snapshot completeness | fingerprints and operation links hidden in customer flow | pack status, evidence completeness, review status | existing review-pack generation only | Download | existing regenerate/expire only, unchanged |
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no. Decision truth remains `FindingException` / `FindingExceptionDecision`; review truth remains `EnvironmentReview.summary`; pack truth remains `ReviewPack.summary` and generated files.
- **New persisted entity/table/artifact?**: no new entity/table/artifact family. Existing review and review-pack artifacts receive derived customer-safe summary content.
- **New abstraction?**: no public framework. A private helper in the existing composer/job is allowed only if it keeps the derived summary readable and bounded.
- **New enum/state/reason family?**: no. Any customer-safe state labels are derived presentation strings from existing governance states.
- **New cross-domain UI framework/taxonomy?**: no.
- **Current operator problem**: Operators need customer-ready decision accountability to travel with released reviews and review packs without manual translation.
- **Existing structure is insufficient because**: Current operator Decision Register and proof links are internal; current review-pack content has governance-package concepts but does not yet explicitly prove the customer-safe Decision Register follow-through requirement.
- **Narrowest correct implementation**: Extend existing governance-package summary and review-derived export content; add focused tests that prove safe inclusion and no raw diagnostic leakage.
- **Ownership cost**: A small payload contract inside existing review summary, focused review/review-pack tests, and one bounded smoke if rendered customer-safe UI changes.
- **Alternative intentionally rejected**: New customer decision portal, new decision persistence, new export file family, and generic decision-summary service were rejected as broader than current-release truth.
- **Release truth**: Current-release productization over existing governance-of-record truth, not future workflow-platform 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. Existing review-pack ZIP consumers are internal/pre-production; additive structured summary keys are allowed, but the implementation should avoid breaking existing file names unless a test proves the changed contract.
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
- **Test purpose / classification**: Feature plus one bounded Browser smoke if rendered customer-review UI changes. Unit tests are optional only if implementation extracts non-trivial summary logic.
- **Validation lane(s)**: confidence for focused Pest feature tests; browser only for existing `CustomerReviewWorkspaceSmokeTest` or equivalent bounded smoke.
- **Why this classification and these lanes are sufficient**: The behavior is primarily review composition, exported ZIP content, customer-safe rendering, RBAC boundaries, and redaction. Existing feature tests can verify payloads and export files without broad suite cost. One existing browser smoke is enough if UI rendering changes.
- **New or expanded test families**: expand existing `tests/Feature/EnvironmentReview/*`, `tests/Feature/ReviewPack/*`, and `tests/Feature/Reviews/*` families. Do not create a new heavy-governance family.
- **Fixture / helper cost impact**: moderate, feature-local. Tests need released reviews, evidence snapshots, risk-accepted findings / finding exceptions, review-derived packs, and customer workspace actors. Helpers must stay opt-in and not make workspace/environment setup heavier by default.
- **Heavy-family visibility / justification**: none beyond one bounded browser smoke when rendered UI changes.
- **Special surface test profile**: shared-detail-family plus standard-native-filament.
- **Standard-native relief or required special coverage**: Existing native Filament resources/pages should need ordinary feature assertions plus one smoke path. Custom export content must be verified by reading generated ZIP entries.
- **Reviewer handoff**: Reviewers must verify the customer-safe summary excludes raw JSON, fingerprints, internal reason ownership, platform reason families, OperationRun URLs, and cross-scope decision records.
- **Budget / baseline / trend impact**: low feature-local increase only.
- **Escalation needed**: none.
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage.
- **Planned validation commands**:
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/EnvironmentReview/EnvironmentReviewExecutivePackTest.php tests/Feature/EnvironmentReview/EnvironmentReviewCreationTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ReviewPack/EnvironmentReviewDerivedReviewPackTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php` if rendered customer workspace or review detail UI changes
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `git diff --check`
## Summary
Prepare a narrow follow-up that carries Decision Register accountability into customer-safe review consumption and review-derived Review Packs.
The feature should make current governance decisions visible to stakeholders as a bounded summary, not as an operator workflow. It should answer:
- Which accepted-risk / exception decisions require customer awareness?
- Why does the decision matter in the released review?
- What evidence basis supports the statement?
- What next action should the customer or MSP understand?
It must not expose raw proof details, operation links, run diagnostics, internal reason families, or cross-tenant state in customer-safe defaults.
## Scope Boundaries
### In Scope
- customer-safe decision-summary content inside existing released-review governance-package summary
- review-derived Review Pack inclusion through existing `summary.json` and `executive-summary.md`
- customer workspace / review detail display only where existing package-summary surfaces already exist
- redaction behavior for `include_pii=false`
- RBAC and scope isolation for customer review and pack access
- focused tests proving content, omission, redaction, and no raw diagnostic leakage
### Non-Goals
- new persisted decision table or projection store
- new customer approval workflow or customer-side mutation
- new Review Pack status, lifecycle, or retention behavior
- new OperationRun type, notification policy, or run-link surface
- broad Decision Register page redesign
- full customer-facing localization adoption
- AI-generated summaries
- generic governance artifact lifecycle runtime
## Assumptions
- Existing Environment Review summary composition is the correct source for customer-safe released-review content.
- Existing Review Pack generation from an Environment Review is the correct export path for stakeholder packages.
- Existing `governance_package.governance_decisions` content is close to the target and should be extended or made explicit rather than replaced.
- Customer-safe summaries may omit proof links where exposing a direct link would leak operator-only context.
- The current product is pre-production, so additive payload-key changes do not require compatibility shims.
## Risks
- Reusing operator Decision Register row data directly could leak proof URLs, OperationRun links, or internal state.
- Adding a new public summary service too early could violate proportionality and create another presentation layer.
- Review-pack inclusion could drift into a broad export redesign if it adds new files, statuses, or delivery workflows.
- Customer-safe wording could overstate compliance or certification if non-certification disclosure is not preserved.
## Candidate Selection Rationale
- **Selected candidate**: `308 - Decision Register Customer-Safe Summary & Review-Pack Inclusion`.
- **Source locations**:
- explicit user request
- `docs/product/roadmap.md` priority ranking item 1
- `docs/product/spec-candidates.md` manual-promotion backlog for `decision-register-review-pack-inclusion` / `decision-register-customer-safe-summary`
- `docs/product/implementation-ledger.md` open gap for Decision Register customer-safe/review-pack inclusion
- **Why selected**: The repo already proves the operator register and proof/run link polish. The highest-priority remaining productization gap is customer-safe consumption and review-pack inclusion, not another operator register pass.
- **Why this is the smallest viable implementation slice**: It reuses current review/governance-package and review-pack export infrastructure, adds only derived customer-safe content, and leaves all lifecycle actions and proof inspection on existing operator surfaces.
- **Intentional narrowing from source candidate**: This spec combines customer-safe summary and review-pack inclusion only for existing accepted-risk / exception decision truth. It does not add customer approvals, multi-family decision workflows, broad localization, artifact lifecycle runtime, or new packaging cadence.
## Completed-Spec Guardrail Result
Related existing specs are context only and must not be rewritten:
- `specs/265-decision-register-approval/` defines the operator register and is not a refresh target.
- `specs/306-decision-register-reconciliation/` carries reconciliation evidence and validation summaries.
- `specs/307-decision-register-evidence-operationrun-link-polish/` has completed task markers and browser-smoke close-out signals.
- `specs/109-review-pack-export/`, `specs/258-customer-review-productization/`, and `specs/260-governance-service-packaging/` carry completed or validation/checklist signals and provide only implementation context.
## Follow-up Candidates
- Customer-Facing Localization Adoption v1 for translated customer-safe labels and glossary discipline.
- Governance Artifact Lifecycle & Retention v1 for broader artifact hold/export/delete semantics.
- Governance Service Packaging v1 for repeatable MSP package cadence and stakeholder mapping.
- First governed AI runtime consumer for later AI-assisted review drafting only after governed runtime requirements are promoted.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - See a customer-safe decision summary in released review consumption (Priority: P1)
As an MSP operator or customer-safe reader, I want the released review to summarize governance decisions requiring awareness so stakeholders understand accepted-risk follow-up without opening the internal Decision Register.
**Why this priority**: This is the visible productization gap. Without it, operators still manually translate internal decision-register truth into customer language.
**Independent Test**: Create a released Environment Review with accepted-risk / exception decision data, open customer-safe review surfaces, and verify the decision summary appears with customer-safe wording and no raw diagnostic detail.
**Acceptance Scenarios**:
1. **Given** a released review includes accepted-risk entries that require governance follow-up, **When** the customer-safe review surface renders, **Then** it shows a concise decision-awareness summary with count, impact, and next-action wording.
2. **Given** a released review has no governance decisions requiring customer awareness, **When** the surface renders, **Then** it shows a calm empty/none state and does not imply hidden risk.
3. **Given** a decision has proof or operation context internally, **When** the customer-safe summary renders, **Then** it does not expose raw proof URLs, OperationRun URLs, fingerprints, internal reason ownership, or platform reason families by default.
### User Story 2 - Include the decision summary in review-derived Review Packs (Priority: P1)
As an MSP operator, I want generated review packs to include the customer-safe Decision Register summary so the exported package can be shared without a separate manual explanation.
**Why this priority**: Review Pack inclusion is the explicit roadmap follow-through and makes the feature useful outside the interactive UI.
**Independent Test**: Generate a review-derived Review Pack from a released review with governance decisions, inspect `summary.json` and `executive-summary.md`, and verify structured and readable decision-summary content is present and redacted correctly.
**Acceptance Scenarios**:
1. **Given** a review-derived pack is generated with decisions requiring awareness, **When** the ZIP is inspected, **Then** `summary.json` includes the structured customer-safe decision summary.
2. **Given** the same pack is inspected, **When** `executive-summary.md` is read, **Then** it includes a readable `Governance decisions requiring awareness` section that matches the structured summary.
3. **Given** `include_pii=false`, **When** the pack is generated, **Then** tenant names, actor names, owner labels, and other PII-bearing summary fields are redacted or omitted according to existing redaction behavior.
### User Story 3 - Preserve security, scope, and lifecycle boundaries (Priority: P1)
As a platform owner, I need customer-safe decision summaries to preserve workspace/environment isolation, RBAC, auditability, and lifecycle ownership so a trust feature does not become a leak or a second workflow.
**Why this priority**: Customer-facing governance summaries are high-trust artifacts; leakage or duplicate lifecycle controls would be worse than omission.
**Independent Test**: Seed same-workspace and cross-workspace/environment decision data, generate reviews and packs, and verify only in-scope decision content appears while lifecycle actions remain on existing operator surfaces.
**Acceptance Scenarios**:
1. **Given** hidden environment decisions exist, **When** a visible environment review or pack is generated, **Then** hidden decisions do not affect counts, copy, or exported content.
2. **Given** a user lacks review-pack view capability, **When** they try to access a pack or download URL, **Then** existing `404`/`403` behavior remains unchanged.
3. **Given** the customer-safe summary renders, **When** actions are inspected, **Then** approve, reject, renew, revoke, and closure actions are not introduced on customer-safe surfaces.
## Edge Cases
- A review has accepted-risk entries but no current exception decision.
- A decision was revoked or expired after the review evidence snapshot was captured.
- A review pack is generated from a partial or stale evidence basis.
- Multiple decisions reference the same finding or accepted risk.
- Decision proof exists internally but is not customer-safe to link.
- `include_pii=false` is set and owner labels or tenant names would otherwise appear.
- A review is published but no ready review pack exists yet.
- A generated pack is expired, blocked by entitlement/commercial lifecycle, or unavailable.
- Cross-workspace/environment decisions exist with similar titles or finding IDs.
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: Released-review governance-package summaries MUST expose a customer-safe decision summary when in-scope accepted-risk / exception decisions require awareness.
- **FR-002**: The decision summary MUST include a total count, readable summary text, customer-safe entry titles, governance state or awareness reason, and one next-action statement where current review truth supports it.
- **FR-003**: Decision summary entries MUST derive from existing review/evidence/decision truth and MUST NOT create or persist a second decision source of truth.
- **FR-004**: Customer-safe summaries MUST distinguish "no decisions requiring awareness" from "decision data unavailable or incomplete" where current evidence completeness supports that distinction.
- **FR-005**: Review-derived Review Packs MUST include the decision summary in `summary.json`.
- **FR-006**: Review-derived Review Packs MUST include readable decision-awareness content in `executive-summary.md`.
- **FR-007**: Review Pack export MUST preserve the existing `auditor_ready_executive_export.v1` contract unless the implementation records a bounded additive version note in the existing delivery metadata.
- **FR-008**: Customer-safe default surfaces and exported executive summaries MUST NOT expose raw JSON, source fingerprints, internal reason ownership, platform reason families, raw OperationRun IDs, OperationRun URLs, debug payloads, or provider payload dumps.
- **FR-009**: `include_pii=false` MUST redact or omit PII-bearing decision summary fields using existing Review Pack redaction behavior.
- **FR-010**: Customer-safe summary generation MUST be scoped to the released review's workspace and managed environment before counts and copy are derived.
- **FR-011**: The feature MUST NOT introduce customer-facing approval, rejection, renewal, revocation, closure, or escalation actions.
- **FR-012**: Existing Review Pack generation, download, expire, regenerate, entitlement, and commercial-lifecycle behavior MUST remain unchanged except for included summary content.
### Non-Functional Requirements
- **NFR-001**: The implementation MUST reuse existing review and review-pack composition paths instead of adding a new export subsystem.
- **NFR-002**: The implementation MUST remain deterministic for the same released review and options.
- **NFR-003**: Generated summary content MUST be concise enough for first-read review consumption and must not turn exported markdown into a raw appendix.
- **NFR-004**: No new frontend assets are registered. Deployment `filament:assets` requirements remain unchanged.
- **NFR-005**: No database migration is expected. If implementation discovers a migration is necessary, stop and update the spec before continuing.
### UX Requirements
- **UX-001**: Customer-safe decision content MUST be default-visible only at a summary level; diagnostics and raw evidence remain secondary or absent in customer paths.
- **UX-002**: The customer-safe summary MUST preserve one dominant next action per surface (`Open review` or `Download review pack`) and must not make operator-only links visually equal.
- **UX-003**: Any status-like display MUST use existing BADGE-001 badge rendering or plain supporting text; no ad-hoc color mapping or custom status UI.
- **UX-004**: Empty states MUST be truthful: no decisions, unavailable evidence, blocked pack, and expired pack are separate meanings.
### RBAC / Security Requirements
- **SEC-001**: Existing workspace membership, managed-environment entitlement, and review/review-pack capabilities remain server-side authorization boundaries.
- **SEC-002**: Non-member or not-entitled workspace/environment access remains `404`.
- **SEC-003**: Member-but-missing-capability behavior remains existing `403` where current policies define it.
- **SEC-004**: Customer-safe summaries MUST NOT leak hidden-environment existence through counts, empty-state wording, links, or exported content.
### Auditability / Observability Requirements
- **AUD-001**: Existing review-open, review-pack generation, download, and export audit/telemetry semantics remain unchanged.
- **AUD-002**: No new audit action ID is required unless implementation introduces a new user-triggered mutation, which is out of scope.
- **AUD-003**: Review Pack generated artifacts remain traceable through existing `ReviewPack`, `EnvironmentReview`, `EvidenceSnapshot`, and `OperationRun` links.
### Data / Truth-Source Requirements
- **DATA-001**: Decision truth remains `FindingException` and `FindingExceptionDecision`.
- **DATA-002**: Released-review summary truth remains `EnvironmentReview.summary`.
- **DATA-003**: Review-pack artifact truth remains `ReviewPack.summary` and the generated ZIP file.
- **DATA-004**: The customer-safe summary is a derived snapshot at review composition / pack generation time, not a live Decision Register view.
## Acceptance Criteria
- **AC-001**: A released review with governance decisions requiring awareness exposes customer-safe summary content on the in-scope review/customer surface.
- **AC-002**: A released review with no decisions requiring awareness exposes a calm no-decision state.
- **AC-003**: A review-derived Review Pack ZIP includes matching structured and readable customer-safe decision summary content.
- **AC-004**: `include_pii=false` prevents tenant names and actor/owner labels from leaking into exported decision-summary content.
- **AC-005**: Customer-safe summaries contain no raw JSON, fingerprints, internal reason-family labels, or OperationRun URLs.
- **AC-006**: Cross-workspace and cross-environment decision records do not influence summary counts or exported content.
- **AC-007**: Existing review-pack generation/download/regenerate/expire authorization and confirmation behavior is unchanged.
- **AC-008**: Focused Pest feature tests pass, and browser smoke is completed or explicitly documented if rendered UI changes.
## Success Criteria
- **SC-001**: In focused test scenarios, 100% of generated review-derived packs include decision-summary content when source review truth contains decisions requiring awareness.
- **SC-002**: In focused negative scenarios, 100% of customer-safe paths omit raw operation/debug/proof internals.
- **SC-003**: In cross-scope test scenarios, 100% of hidden decisions are excluded from rendered and exported content.
- **SC-004**: The implementation introduces no new persisted entity, status family, global search resource, asset bundle, or operation type.
## Filament v5 Blueprint Contract
- **Livewire v4.0+ compliance**: Required. Current app uses Livewire 4.1.4.
- **Provider registration location**: No panel provider changes expected. Laravel 12 panel providers remain in `bootstrap/providers.php`.
- **Global search**: No new globally searchable resource is introduced. Existing resources keep their current View/Edit/global-search posture.
- **Destructive actions**: No new destructive action is introduced. Existing Review Pack `Expire` / regenerate confirmation behavior remains unchanged and must keep `->requiresConfirmation()` and policy/capability enforcement.
- **Asset strategy**: No new assets. Deployment `cd apps/platform && php artisan filament:assets` remains the existing deploy step only when registered assets change; this spec expects none.
- **Testing plan**: Use Pest 4 focused Feature coverage for review, review-pack, and customer workspace paths; use existing bounded Browser smoke only if rendered customer-review UI changes.
## Open Questions
None blocking. The implementation may choose whether to extend the existing `governance_package.governance_decisions` shape directly or add a nested `decision_summary` key, but it must preserve the requirements above and avoid new persistence or a new framework.

View File

@ -0,0 +1,110 @@
# Tasks: Decision Register Customer-Safe Summary & Review-Pack Inclusion
**Input**: Design documents from `/specs/308-decision-register-summary-review-pack/`
**Prerequisites**: `spec.md`, `plan.md`, `checklists/requirements.md`
**Tests**: Required. Use Pest 4 focused Feature coverage and the existing bounded Browser smoke only if rendered customer-review UI changes.
## Test Governance Checklist
- [x] Lane assignment is named and is the narrowest sufficient proof for the changed behavior.
- [x] New or changed tests stay in the smallest honest family, and any browser addition is limited to the existing bounded customer-review smoke.
- [x] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default.
- [x] Planned validation commands cover the change without pulling unrelated suite cost.
- [x] The declared `shared-detail-family` / `standard-native-filament` surface profile is explicit.
- [x] Any material budget, baseline, trend, or escalation note is recorded in the active feature close-out.
## Phase 1: Preparation and Current-Truth Verification
- [x] T001 Review `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/308-decision-register-summary-review-pack/spec.md`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/308-decision-register-summary-review-pack/plan.md`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/308-decision-register-summary-review-pack/checklists/requirements.md`.
- [x] T002 Inspect current `governance_package` summary generation in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Services/EnvironmentReviews/EnvironmentReviewComposer.php`.
- [x] T003 Inspect current review-derived Review Pack generation in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Jobs/GenerateReviewPackJob.php` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Services/ReviewPackService.php`.
- [x] T004 Inspect current customer-safe review/package presentation 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/EnvironmentReviewResource.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/ReviewPackResource.php`.
- [x] T005 Confirm no migration, new model, new capability, new OperationRun type, new global-search resource, or new asset registration is needed; stop and update the spec if this is false.
## Phase 2: Tests First - Customer-Safe Review Summary (US1)
- [x] T006 [US1] Add or extend a focused feature test in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/EnvironmentReview/EnvironmentReviewCreationTest.php` or `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/EnvironmentReview/EnvironmentReviewExecutivePackTest.php` proving a released review summary includes customer-safe decision-summary content when governance decisions require awareness.
- [x] T007 [US1] Add a no-decisions scenario proving the summary renders a calm none state rather than implying hidden risk.
- [x] T008 [US1] Add an incomplete/unavailable evidence scenario proving the summary distinguishes unavailable decision data from no decisions when current evidence completeness supports the distinction.
- [x] T009 [US1] Add assertions that customer-safe review summary content does not include raw JSON, fingerprints, internal reason ownership, platform reason families, raw OperationRun IDs, or OperationRun URLs.
## Phase 3: Tests First - Review Pack Inclusion (US2)
- [x] T010 [US2] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/ReviewPack/EnvironmentReviewDerivedReviewPackTest.php` to generate a review-derived Review Pack with governance decisions and assert `summary.json` includes structured customer-safe decision-summary content.
- [x] T011 [US2] Extend the same test to assert `executive-summary.md` includes readable `Governance decisions requiring awareness` content matching the structured summary.
- [x] T012 [US2] Add a redaction assertion for `include_pii=false` proving tenant names, actor/owner labels, and other PII-bearing decision fields are redacted or omitted.
- [x] T013 [US2] Add an assertion that existing review-derived pack metadata still carries `auditor_ready_executive_export.v1`, `metadata.json`, `summary.json`, `sections.json`, and `executive-summary.md` without introducing a new top-level export family.
## Phase 4: Tests First - Scope, RBAC, and Lifecycle Boundaries (US3)
- [x] T014 [US3] Extend `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php` or adjacent review workspace tests to prove hidden workspace/environment decisions do not affect visible customer-safe summary counts or copy.
- [x] T015 [US3] Add or extend review-pack access assertions proving existing non-member `404` and missing-capability `403` behavior remains unchanged for view/download paths.
- [x] T016 [US3] Add assertions that customer-safe surfaces do not introduce approve, reject, renew, revoke, closure, or escalation actions.
## Phase 5: Implement Customer-Safe Summary Derivation
- [x] T017 [US1] Extend existing summary composition in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Services/EnvironmentReviews/EnvironmentReviewComposer.php` with a bounded customer-safe decision summary derived from current review/evidence/decision truth.
- [x] T018 [US1] Keep the summary additive under existing `governance_package` payloads, preferring `decision_summary` and/or clarified `governance_decisions` keys over new persistence or public framework classes.
- [x] T019 [US1] Ensure summary entries include customer-safe title, awareness reason/governance state, summary, count, and next-action wording where available.
- [x] T020 [US1] Ensure no customer-safe summary field copies raw proof URLs, raw OperationRun URLs, source fingerprints, platform reason families, or provider payload dumps.
- [x] T021 [US1] Preserve existing non-certification / interpretation disclosure when decision-summary content is present.
## Phase 6: Implement Review Pack Inclusion
- [x] T022 [US2] Update `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Jobs/GenerateReviewPackJob.php` so review-derived `summary.json` includes the customer-safe decision summary from the released review.
- [x] T023 [US2] Update `executive-summary.md` generation in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Jobs/GenerateReviewPackJob.php` to render a concise decision-awareness section from the same structured summary.
- [x] T024 [US2] Ensure `include_pii=false` flows through the existing redaction path for any tenant, actor, owner, or customer-identifying decision summary field.
- [x] T025 [US2] Preserve existing ZIP file names and delivery metadata unless a bounded additive metadata note is required and tested.
## Phase 7: Implement Customer Surface Presentation If Needed
- [x] T026 [US1] If current customer workspace/review detail surfaces do not expose the summary clearly, add a native/shared Filament presentation in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php` or `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Resources/EnvironmentReviewResource.php`.
- [x] T027 [US1] Keep exactly one dominant next action on each changed surface (`Open review`, `Download governance package`, or existing `Download`), with decision summary as supporting content.
- [x] T028 [US1] Use existing Filament sections/infolists/table text and BADGE-001-backed badges where status-like display is needed; do not add ad-hoc CSS, custom cards, local color mappings, or new assets.
- [x] T029 [US3] Confirm `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Governance/DecisionRegister.php` remains an operator surface and is not redesigned for this feature.
## Phase 8: Filament, RBAC, and Asset Contract Review
- [x] T030 Confirm Filament v5 / Livewire v4 compliance for any changed Filament page/resource and do not introduce Livewire v3 references.
- [x] T031 Confirm panel provider registration remains unchanged in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/bootstrap/providers.php`.
- [x] T032 Confirm no globally searchable resource is added or changed; if a resource is unexpectedly changed, verify View/Edit/global-search posture per Filament v5 rules.
- [x] T033 Confirm no new destructive action is introduced and existing Review Pack destructive-like actions still use `->requiresConfirmation()` plus existing authorization.
- [x] T034 Confirm no frontend assets are registered; deployment `filament:assets` requirements are unchanged.
## Phase 9: Validation
- [x] T035 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/EnvironmentReview/EnvironmentReviewExecutivePackTest.php tests/Feature/EnvironmentReview/EnvironmentReviewCreationTest.php`.
- [x] T036 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ReviewPack/EnvironmentReviewDerivedReviewPackTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php`.
- [x] T037 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php`.
- [x] T038 If rendered customer workspace or review detail UI changed, run `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php`.
- [x] T039 Run `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`.
- [x] T040 Run `git diff --check`.
## Phase 10: Close-Out
- [x] T041 Record implementation close-out notes in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/308-decision-register-summary-review-pack/plan.md`, including changed files, no-migration status, no-asset status, test results, browser smoke result or reason not run, and remaining gaps.
- [x] T042 Confirm customer-safe summary and Review Pack inclusion remain in scope and follow-up candidates are not implemented here.
- [x] T043 Confirm no application implementation happened outside the selected review/review-pack/customer-safe summary surfaces and focused tests.
## Dependency Order
1. T001-T005 before implementation.
2. T006-T016 before or alongside implementation changes.
3. T017-T025 before UI presentation work.
4. T026-T029 only if rendered UI needs adjustment after summary/export changes.
5. T030-T040 before close-out.
## Parallel Work Guidance
- T006-T009 can run in parallel with T010-T013 after Phase 1.
- T014-T016 can run in parallel with summary/export implementation.
- T030-T034 can be reviewed after implementation before validation.
## Non-Goals Checklist
- [x] No new decision persistence.
- [x] No new review-pack status or operation type.
- [x] No customer approval/mutation workflow.
- [x] No raw OperationRun or proof links in customer-safe default content.
- [x] No new asset bundle or ad-hoc custom styling.