feat: implement decision register summary in environment review packs #363

Merged
ahmido merged 3 commits from 308-decision-register-summary-review-pack into platform-dev 2026-05-15 12:54:43 +00:00
15 changed files with 1323 additions and 47 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

@ -1,7 +1,7 @@
# TenantPilot Implementation Ledger
> **Status:** Active
> **Last reviewed:** 2026-05-12
> **Last reviewed:** 2026-05-15
> **Use for:** Repo-based implementation status and product-surface maturity assessment
> **Do not use for:** Roadmap priority, spec priority, or proof that tests were executed in the current branch
> **Scoped maintenance:** 2026-05-15 Decision Register proof-link implementation update after Spec 307; 2026-05-15 Decision Register reconciliation update after Spec 306; 2026-05-15 Tenant Panel dead-code retirement guardrail update after Spec 304; 2026-05-12 roadmap/ledger alignment after the admin workspace navigation and tenant-owned surface repair candidate intake from the repo-verified navigation/panel audit; 2026-05-06 ledger conflict cleanup plus alignment with `docs/product/roadmap.md` and `docs/product/spec-candidates.md` after the cross-domain indicator candidate intake and the current manual-promotion backlog review.
@ -230,7 +230,7 @@ ## Open Gaps & Blockers
| Decision Register customer-safe/review-pack inclusion is still missing | Productization gap | The operator register now exposes scoped proof/run links, but customer-safe consumption and review-pack inclusion remain separate product choices | Decision-based operating | `decision-register-review-pack-inclusion` or `decision-register-customer-safe-summary` |
| Governance-artifact lifecycle runtime is still missing | Trust / auditability blocker | Lifecycle taxonomy and point retention rules exist, but governance artifacts still lack immutable-reference, hold, export, delete, and suspended/read-only runtime semantics | Lifecycle governance / enterprise trust | `Governance Artifact Lifecycle & Retention v1` |
| Cross-domain progress and indicator semantics guardrail is still missing | UX / trust guardrail | Bars, percentages, scores, readiness, risk, usage, and generation-state hints still lack one shared taxonomy and standards layer above the OperationRun-specific rules | UI semantics / product trust | `Cross-Domain Progress / Indicator Semantics candidate group` |
| Admin workspace navigation and tenant-owned surface contract drift remains open | UX / IA repair blocker | Inventory and adjacent tenant-owned admin surfaces still conflict between workspace-home clean-sidebar rules and valid environment-bound admin access, leaving active product breaks and stale hide-first assumptions in place | UI maturity / admin runtime contract | `admin-inventory-navigation-cutover` from the `Admin Workspace Navigation & Tenant-owned Surface Repair candidate group` |
| Residual admin workspace navigation contract drift may remain | UX / IA repair follow-through | Specs 301-304 now cover Inventory cutover, the tenant-owned surface audit, Entra Groups cutover, and tenant-panel dead-code retirement. The remaining risk is shared contract drift between workspace-home cleanliness and environment-bound admin visibility if new regressions appear. | UI maturity / admin runtime contract | `navigation-contract-split`, only if post-Spec 301-304 drift remains |
| Customer-facing localization adoption is incomplete | Productization blocker | Locale groundwork is repo-real, but customer-safe adoption remains incomplete | Localization / review productization | `Customer-Facing Localization Adoption v1` |
| Billing and subscription truth is missing | Commercial blocker | Entitlements and lifecycle state handling stop short of a durable billing/subscription truth layer | Commercial readiness | `Billing & Subscription Truth Layer v1` |
| Stored reports still lack a clear product surface | Product blocker | Retained evidence and review artifacts remain harder to consume than they should be | Reports / evidence consumption | `Stored Reports Surface v1` |
@ -241,7 +241,7 @@ ## Open Gaps & Blockers
## Recommended Manual Promotions
- `Cross-Domain Progress / Indicator Semantics candidate group` -> anchored by `specs/268-operationrun-activity-feedback/spec.md`, `specs/270-operationrun-progress-contract/spec.md`, `specs/271-counted-progress-rollout/spec.md`, `specs/272-operationrun-phase-composite-progress/spec.md`, `docs/ui/tenantpilot-enterprise-ui-standards.md`, and the current progress-like UI seams called out in `docs/product/spec-candidates.md`
- `Admin Workspace Navigation & Tenant-owned Surface Repair candidate group` -> anchored by `apps/platform/app/Filament/Clusters/Inventory/InventoryCluster.php`, `apps/platform/app/Filament/Pages/InventoryCoverage.php`, `apps/platform/app/Filament/Resources/InventoryItemResource.php`, `apps/platform/app/Filament/Resources/EntraGroupResource.php`, `apps/platform/app/Filament/Concerns/WorkspaceScopedTenantRoutes.php`, `apps/platform/app/Support/OperateHub/OperateHubShell.php`, and the navigation/runtime tests called out in `docs/product/spec-candidates.md`; promote `admin-inventory-navigation-cutover` first, then keep the route audit, groups cutover, contract split, and tenant-panel dead-code retirement as separate sequenced follow-through
- `Admin Workspace Navigation & Tenant-owned Surface Repair candidate group` -> anchored by `apps/platform/app/Filament/Clusters/Inventory/InventoryCluster.php`, `apps/platform/app/Filament/Pages/InventoryCoverage.php`, `apps/platform/app/Filament/Resources/InventoryItemResource.php`, `apps/platform/app/Filament/Resources/EntraGroupResource.php`, `apps/platform/app/Filament/Concerns/WorkspaceScopedTenantRoutes.php`, `apps/platform/app/Support/OperateHub/OperateHubShell.php`, and the navigation/runtime tests called out in `docs/product/spec-candidates.md`; Specs 301-304 now cover Inventory cutover, route-audit prep, groups cutover, and tenant-panel dead-code retirement, so only `navigation-contract-split` remains as a conditional follow-through if drift persists
- `Workspace-first / ManagedEnvironment Core Cutover` pack -> anchored by `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, `docs/product/implementation-ledger.md`, and the tenant-centric platform seams already visible across review, support, portfolio, and governance surfaces; keep it as a clean development-stage cutover pack rather than a compatibility-layer program
- `decision-register-review-pack-inclusion` / `decision-register-customer-safe-summary` -> anchored by `specs/265-decision-register-approval/spec.md`, `specs/306-decision-register-reconciliation/decision-register-reconciliation.md`, `specs/307-decision-register-evidence-operationrun-link-polish/spec.md`, `apps/platform/app/Filament/Pages/Governance/DecisionRegister.php`, `apps/platform/app/Support/GovernanceDecisions/GovernanceDecisionRegisterBuilder.php`, and the focused Decision Register tests
- `Governance Artifact Lifecycle & Retention v1` -> anchored by `specs/158-artifact-truth-semantics/spec.md`, `specs/262-lifecycle-governance-taxonomy/spec.md`, and `docs/product/standards/lifecycle-governance.md`
@ -254,7 +254,7 @@ ## Recommended Manual Promotions
## Roadmap Drift Notes
- `docs/product/roadmap.md` and `docs/product/spec-candidates.md` are aligned through 2026-05-12, including the admin workspace navigation / tenant-owned surface repair candidate intake, the earlier cross-domain indicator candidate intake, the current manual-promotion backlog, and the resolved ledger conflict state.
- `docs/product/roadmap.md` and `docs/product/spec-candidates.md` are aligned through 2026-05-15, including Spec 304 tenant-panel dead-code retirement, Spec 306 Decision Register reconciliation, Spec 307 proof-link polish, the earlier admin workspace navigation / tenant-owned surface repair candidate intake, the cross-domain indicator candidate intake, the current manual-promotion backlog, and the resolved ledger conflict state.
- The remaining documentation risk is no longer queue drift alone; it is understating or overstating still-open follow-through slices such as admin workspace navigation repair, auditor-ready export, promotion execution, governance decision workflow, cross-domain indicator semantics, billing/subscription truth, stored reports surface, and the first governed AI runtime consumer.
- This ledger therefore treats review-driven governance and portfolio preparation as `fast sellable` or `implemented but not productized`, not `sellable`, until those explicit manual-promotion slices land.
- Tests referenced here remain repo-present only. They were not executed for this ledger update.

View File

@ -1,10 +1,10 @@
# Product Roadmap
> **Status:** Active
> **Last reviewed:** 2026-05-12
> **Last reviewed:** 2026-05-15
> **Use for:** Current product roadmap, release themes, and prioritization context
> **Do not use for:** Implementation truth, spec completion status, or delivery guarantees without repo verification
> **Scoped maintenance:** 2026-05-15 Decision Register reconciliation update after Spec 306; 2026-05-12 roadmap alignment after the admin workspace navigation and tenant-owned surface repair candidate intake from the repo-verified navigation/panel audit; 2026-05-06 roadmap cleanup after ledger conflict resolution and cross-domain progress / indicator semantics candidate intake; 2026-05-02 repo-based roadmap drift correction, manual-promotion backlog alignment, and enterprise-SaaS deep-research calibration against current specs, standards, and product-truth docs.
> **Scoped maintenance:** 2026-05-15 Decision Register proof-link update after Spec 307; 2026-05-15 Decision Register reconciliation update after Spec 306; 2026-05-15 tenant-owned admin surface follow-through sync after Specs 301-304; 2026-05-12 roadmap alignment after the admin workspace navigation and tenant-owned surface repair candidate intake from the repo-verified navigation/panel audit; 2026-05-06 roadmap cleanup after ledger conflict resolution and cross-domain progress / indicator semantics candidate intake; 2026-05-02 repo-based roadmap drift correction, manual-promotion backlog alignment, and enterprise-SaaS deep-research calibration against current specs, standards, and product-truth docs.
>
> Strategic thematic blocks and release trajectory.
> This is the "big picture" — not individual specs.
@ -29,7 +29,7 @@ ## Current Productization & Moat Priorities
| Order | Theme | Alignment status | Repo truth | Why now | Queue posture |
|---|---|---|---|---|---|
| 1 | Customer Review Workspace Productization v1 | repo-verified, productization gap | Customer-safe review consumption is repo-real through Specs 249, 258, 259, 260, and 263, but the calm sellable surface still needs final productization discipline | clearest sellability lever for Governance-of-Record without creating a parallel customer portal | spec-backed follow-through |
| 2 | Decision-Based Governance Inbox + Decision Register follow-up | repo-verified, productization gap, roadmap recommendation | Governance inbox, findings queues, alerts, review follow-up, Specs 250/257, and the Spec 265 operator Decision Register are repo-verified; only narrow proof-link and customer-safe follow-through remain open | biggest remaining operator workflow gap and the cleanest defense against admin-tool sprawl | no Greenfield v1; manual promotion only for the narrow follow-up |
| 2 | Decision-Based Governance Inbox + Decision Register follow-up | repo-verified, productization gap, roadmap recommendation | Governance inbox, findings queues, alerts, review follow-up, Spec 265 operator Decision Register, Spec 306 reconciliation, and Spec 307 proof-link polish are repo-verified; only customer-safe/review-pack follow-through remains open | biggest remaining operator workflow gap and the cleanest defense against admin-tool sprawl | no Greenfield v1; manual promotion only for customer-safe/review-pack follow-through |
| 3 | Governance Artifact Lifecycle & Retention v1 | foundation-only, roadmap recommendation, spec candidate | Spec 262 and the lifecycle-governance standard provide taxonomy-first guardrails, but governance-artifact runtime semantics are not yet productized | new trust, auditability, export, and retention lever for evidence snapshots, stored reports, review packs, and decision records | manual promotion only |
| 4 | Commercial Entitlements & Billing-State Lifecycle v1 | repo-verified, foundation-only, productization gap | Specs 247 and 251 already resolve plan and lifecycle posture, but broader commercial-state-to-artifact-access rules and later billing truth remain open | SaaS trust and lifecycle maturity matter before broader packaging, scale, or AI work | spec-backed follow-through plus narrower billing follow-through |
| 5 | External Support Desk / PSA Handoff v1 | repo-verified, productization gap | Spec 256 and the current bounded handoff service already exist, but the portfolio-safe handoff story is still not fully productized | MSP integration should compress follow-through work without turning TenantPilot into a helpdesk | spec-backed follow-through |
@ -67,7 +67,7 @@ ### UI & Product Maturity Polish
Parallel manual-promotion guardrail: `docs/product/spec-candidates.md` now carries a dedicated Cross-Domain Progress / Indicator Semantics candidate group so progress, coverage, readiness, risk, usage, score, and generation-state surfaces do not keep drifting behind OperationRun-specific rules. Spec 278 is the docs-first audit slice; its follow-up lanes stay split into standards patch, metric/indicator contract foundation, shared indicator component system, quality gate, and domain migration.
Parallel immediate repair lane: `docs/product/spec-candidates.md` now also carries an Admin Workspace Navigation & Tenant-owned Surface Repair candidate group so workspace-home clean-sidebar rules stay separate from valid environment-bound admin navigation. The first promotion target is `admin-inventory-navigation-cutover`; the tenant-owned surface route audit, groups cutover, contract split, and tenant-panel dead-code retirement remain sequenced manual follow-through rather than one bundled migration.
Parallel immediate repair lane: `docs/product/spec-candidates.md` still carries the Admin Workspace Navigation & Tenant-owned Surface Repair candidate group as the sequencing record for workspace-home clean-sidebar versus environment-bound admin navigation. Specs 301, 302, 303, and 304 now cover the Inventory cutover, tenant-owned surface audit, Entra Groups cutover, and tenant-panel dead-code retirement. Only `navigation-contract-split` remains as a conditional manual follow-through if fresh repo evidence still shows shared contract drift.
**Active specs**: 122, 121, 112
@ -444,8 +444,8 @@ ## Infrastructure & Platform Debt
| No formal security trust pack yet | Enterprise sales and customer security reviews require repeated manual explanations | Covered by Solo-Founder SaaS Automation & Operating Readiness |
| Auditor-ready executive export is not yet productized | Review truth still stops short of calm auditor-/executive-ready delivery even though the spec package now exists | Covered by `specs/263-auditor-pack-executive-export/spec.md` |
| Cross-tenant promotion execution is missing | Compare preview and preflight stop short of the actual portfolio action even though the execution spec package now exists on this branch | Covered by `specs/264-cross-tenant-promotion-execution/spec.md` |
| Admin workspace navigation contract drift remains open | Inventory and adjacent tenant-owned admin surfaces can still conflict between workspace-home clean-sidebar rules and valid environment-bound admin routing, which risks product breaks and stale hide-first test assumptions | Covered by the Admin Workspace Navigation & Tenant-owned Surface Repair candidate group in `docs/product/spec-candidates.md` |
| Decision Register proof-link productization is still pending | The Spec 265 operator register exists; direct evidence/report, `OperationRun`, and review-pack proof links are not fully productized on the register path | Covered by the narrow manual-promotion follow-up in `docs/product/spec-candidates.md` |
| Residual admin workspace navigation contract drift may remain | Specs 301-304 closed the Inventory cutover, route audit, groups cutover, and tenant-panel dead-code cleanup; only a later contract split may still be needed if new repo evidence shows workspace-home and environment-bound navigation rules colliding again | Covered by the conditional `navigation-contract-split` follow-up in `docs/product/spec-candidates.md` |
| Decision Register customer-safe/review-pack follow-through is still pending | The Spec 265 operator register, Spec 306 reconciliation, and Spec 307 proof/run-link polish exist, but customer-safe consumption and explicit review-pack inclusion remain unproductized | Covered by the narrow manual-promotion follow-up in `docs/product/spec-candidates.md` |
| Customer-facing localization adoption is incomplete | Repo-real locale groundwork is not yet fully productized across customer-safe governance surfaces | Covered by the manual-promotion backlog in `docs/product/spec-candidates.md` |
| Billing and subscription truth is missing | Commercial readiness still stops short of a durable billing/subscription truth layer | Covered by the manual-promotion backlog in `docs/product/spec-candidates.md` |
| Stored reports still lack a clear product surface | Retained evidence and review artifacts remain harder to consume than they should be | Covered by the manual-promotion backlog in `docs/product/spec-candidates.md` |
@ -469,9 +469,9 @@ ## Priority Ranking (Current Manual Promotion Order)
Parallel immediate guardrail lane: the Cross-Domain Progress / Indicator Semantics candidate group in `docs/product/spec-candidates.md` should be promoted alongside OperationRun maturity when UI semantic drift is the active concern. Spec 278 provides the audit inventory and standards-delta input; the remaining follow-up remains split across contract, component, quality-gate, and migration lanes. It stays outside the main sellability ordering below because it is a cross-cutting semantics and standards package rather than a standalone customer-facing delivery lane.
Parallel immediate repair lane: the Admin Workspace Navigation & Tenant-owned Surface Repair candidate group in `docs/product/spec-candidates.md` should be promoted when Inventory or adjacent tenant-owned admin surfaces drift behind stale hide-first navigation contracts. `admin-inventory-navigation-cutover` is the immediate repair slice; the repo-wide route audit, groups cutover, navigation-contract split, and tenant-panel dead-code retirement stay sequenced manual follow-through outside the main sellability ordering.
Parallel immediate repair lane: the Admin Workspace Navigation & Tenant-owned Surface Repair candidate group in `docs/product/spec-candidates.md` is now mostly historical sequencing context after Specs 301-304. Promote only `navigation-contract-split`, and only when fresh repo evidence shows residual shared-contract drift between workspace-home cleanliness and environment-bound admin visibility.
1. Decision Register evidence / OperationRun link polish
1. Decision Register customer-safe summary / review-pack inclusion
2. Governance Artifact Lifecycle & Retention v1
3. Billing & Subscription Truth Layer v1
4. Customer-Facing Localization Adoption v1

View File

@ -1,8 +1,8 @@
# Spec Candidates
> **Status:** Active
> **Last reviewed:** 2026-05-12
> **Use for:** The active repo-based queue of spec candidates that may still need new or refreshed specs
> **Status:** Active
> **Last reviewed:** 2026-05-15
> **Use for:** The active repo-based queue of spec candidates that may still need new or refreshed specs
> **Do not use for:** Proof that a candidate is already specced, implemented, or prioritized above the roadmap without repo verification
> **Scoped maintenance:** 2026-05-15 Spec 307 Decision Register proof-link implementation update; 2026-05-15 Spec 306 Decision Register reconciliation update; 2026-05-15 Spec 304 Tenant Panel dead-code retirement guardrail update; 2026-05-12 admin workspace navigation and tenant-owned surface repair candidate intake after the repo-verified navigation/panel audit; 2026-05-06 cross-domain progress and indicator semantics candidate intake; 2026-05-04 OperationRun progress maturity plus Tenant Dashboard active-operations summary candidate intake; 2026-05-03 OperationRun activity feedback candidate intake plus the 2026-05-02 repo-based queue re-audit and enterprise-SaaS deep-research alignment against current `specs/` truth, including Specs 263 and current-branch 264.
>
@ -872,10 +872,10 @@ #### 287 — Cutover Quality Gates & No-Legacy Enforcement
### Admin Workspace Navigation & Tenant-owned Surface Repair candidate group
- **Priority posture**: immediate manual promotion for the Inventory repair slice, then bounded audit prep, then product-sensitive follow-up cutovers, and only then legacy retirement cleanup
- **Repo truth**: the current runtime is already `admin` plus `system`, workspace-first environment routing is repo-real, and several tenant-owned admin surfaces already resolve context through the workspace shell. At the same time, Inventory and Entra Groups still carry admin-hidden navigation contracts that conflict with their repo-real admin runtime access, while the workspace-home clean-sidebar rule remains a separate intentional contract.
- **Why promotable now**: this is the clearest current repo-verified navigation and panel drift seam. Inventory is an active product break, and the adjacent route-audit, groups, contract, and dead-code follow-through should be tracked explicitly instead of living only in audit prose.
- **Why manual promotion only**: only the Inventory slice is immediate implementation-ready. The rest depend on either a repo-wide audit pass, an explicit information-architecture decision, or post-migration cleanup, so they should not be bundled into one repair umbrella or auto-prepped out of order.
- **Priority posture**: historical promotion sequence now runs through Specs 301-304; only the conditional `navigation-contract-split` follow-up remains if post-cutover drift still shows one shared test/registration contract fighting both workspace-home cleanliness and environment-bound admin visibility.
- **Repo truth**: the current runtime is already `admin` plus `system`, workspace-first environment routing is repo-real, and Specs 301, 302, 303, and 304 now cover the Inventory cutover, tenant-owned surface audit, Entra Groups cutover, and tenant-panel dead-code guardrail cleanup. The remaining question is whether any residual shared navigation-contract drift still justifies a dedicated split.
- **Why promotable now**: this group remains useful as historical sequencing context and as the home for one bounded residual follow-through candidate. The immediate Inventory break is no longer the active next slice.
- **Why manual promotion only**: any further work should happen only if fresh repo evidence proves the remaining drift. It should stay narrower than reopening the earlier migration sequence or bundling unrelated admin IA decisions.
- **Anchors**:
- `docs/product/implementation-ledger.md`
- `apps/platform/app/Filament/Clusters/Inventory/InventoryCluster.php`
@ -888,14 +888,16 @@ ### Admin Workspace Navigation & Tenant-owned Surface Repair candidate group
- `apps/platform/tests/Feature/Filament/InventoryCoverageAdminTenantParityTest.php`
- `apps/platform/tests/Feature/Filament/EntraGroupAdminScopeTest.php`
- **Recommended promotion order**:
1. `admin-inventory-navigation-cutover`
2. `tenant-owned-surface-route-audit`
3. `admin-directory-groups-cutover`
4. `navigation-contract-split`, only if drift remains after the first three candidates
5. `tenant-panel-dead-code-retirement` -> Spec 304
1. `admin-inventory-navigation-cutover` -> Spec 301
2. `tenant-owned-surface-route-audit` -> Spec 302
3. `admin-directory-groups-cutover` -> Spec 303
4. `tenant-panel-dead-code-retirement` -> Spec 304
5. `navigation-contract-split`, only if drift remains after Specs 301-304
#### `admin-inventory-navigation-cutover`
- **Status**: promoted to Spec 301 on 2026-05-14. Keep this entry as historical sequencing context unless the Inventory visibility contract regresses.
- **Goal**: restore Inventory as a workspace-environment-scoped admin surface without reopening the workspace-home sidebar or broadening the repair into other tenant-owned domains.
- **Scope**:
- remove the blanket admin-hidden navigation behavior for Inventory only where a real admin workspace environment context exists
@ -915,6 +917,8 @@ #### `admin-inventory-navigation-cutover`
#### `tenant-owned-surface-route-audit`
- **Status**: promoted to Spec 302 on 2026-05-14. Keep this entry as historical sequencing context unless the audit findings need a refreshed follow-up after new tenant-owned surfaces land.
- **Goal**: produce a repo-verified audit and repair-prep inventory of admin-reachable tenant-owned surfaces that are fully migrated, partially migrated, stale-nav hidden, product-decision blocked, or still legacy dependent.
- **Scope**:
- audit routes, `shouldRegisterNavigation()`, context resolution, global search, and high-signal runtime tests across tenant-owned admin surfaces
@ -932,6 +936,8 @@ #### `tenant-owned-surface-route-audit`
#### `admin-directory-groups-cutover`
- **Status**: promoted to Spec 303 on 2026-05-14. Keep this entry as historical sequencing context unless the chosen Directory/Groups contract regresses.
- **Goal**: decide and implement the correct admin workspace role for Directory / Entra Groups after an explicit information-architecture decision.
- **Scope**:
- decide whether groups belong in primary navigation, a secondary Identity/Directory lane, or only contextual entry points from diagnostics, permissions, providers, or policy detail
@ -949,6 +955,8 @@ #### `admin-directory-groups-cutover`
#### `navigation-contract-split`
- **Status**: conditional follow-up only after Specs 301-304. Promote this only if fresh repo evidence still shows workspace-home cleanliness and environment-bound admin visibility fighting through one shared contract.
- **Goal**: separate workspace-home clean-sidebar rules from environment-bound tenant-owned navigation rules so future repairs do not keep fighting one shared test contract.
- **Scope**:
- split tests and guards for workspace-home navigation, environment-shell navigation, and surface-specific registration behavior
@ -982,7 +990,7 @@ #### `tenant-panel-dead-code-retirement`
### Decision Register Evidence / OperationRun Link Polish
- **Priority**: 1
- **Historical priority**: 1
- **Status**: promoted to Spec 307 and implemented as a narrow productization follow-up on 2026-05-15; keep this entry as historical sequencing context unless proof/run link polish regresses.
- **Repo truth**: Spec 265, Spec 307, `DecisionRegister`, `GovernanceDecisionRegisterBuilder`, FindingException detail handoff, evidence/report proof links, source/evidence `OperationRun` links, and focused tests are repo-verified. The broad Decision Register & Approval Workflow v1 candidate is no longer an open Greenfield candidate.
- **Why promoted**: Spec 306 classified the current register as partial productization and identified the narrowest next step as proof-link polish, not a rebuild.
@ -995,6 +1003,22 @@ ### Decision Register Evidence / OperationRun Link Polish
- `specs/257-governance-decision-convergence/spec.md`
- `docs/product/roadmap.md`
### Decision Register Customer-Safe Summary / Review-Pack Inclusion
- **Priority**: 1
- **Status**: open manual-promotion follow-up after Spec 307. Do not reopen Spec 265 or repackage the proof-link polish as a broader Decision Register rebuild.
- **Repo truth**: Spec 265 proves the bounded operator Decision Register, Spec 306 reconciles the current runtime, and Spec 307 closes direct evidence/report plus source/evidence `OperationRun` proof-link polish. `CustomerReviewWorkspace`, review-pack foundations, and governance-package delivery already summarize accepted-risk decisions, but there is still no explicit customer-safe Decision Register surface or review-pack inclusion contract.
- **Why promotable now**: the proof/run-link follow-up is complete, so the next remaining Decision Register productization question is whether accepted-risk decisions need a bounded customer-safe summary and/or explicit review-pack inclusion.
- **Why manual promotion only**: this is product-sensitive and must stay narrower than a new Decision Register v1. Reuse existing decision truth, customer-review foundations, and review-pack packaging instead of introducing a new workflow engine, duplicate decision summary, or customer portal.
- **Anchors**:
- `specs/265-decision-register-approval/spec.md`
- `specs/306-decision-register-reconciliation/decision-register-reconciliation.md`
- `specs/307-decision-register-evidence-operationrun-link-polish/spec.md`
- `specs/258-customer-review-productization/spec.md`
- `specs/260-governance-service-packaging/spec.md`
- `apps/platform/app/Filament/Pages/Governance/DecisionRegister.php`
- `apps/platform/app/Support/GovernanceDecisions/GovernanceDecisionRegisterBuilder.php`
### Governance Artifact Lifecycle & Retention v1
- **Priority**: 2

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.