forceFill([ 'name' => 'Spec369 Ready Baseline', 'capture_mode' => BaselineCaptureMode::Opportunistic->value, ])->save(); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); return [$user, $tenant, $profile->fresh(), $snapshot->fresh()]; } it('renders a ready baseline profile with a decision-first summary before technical scope', function (): void { [$user, , $profile, $snapshot] = spec369ReadyProfile(); $this->actingAs($user) ->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin')) ->assertOk() ->assertSee('Decision') ->assertSee('Ready to compare') ->assertSee('Current snapshot is consumable and at least one assigned environment is available for compare.') ->assertSee('Operators can compare assigned environments against the current baseline snapshot.') ->assertSee('Snapshot #'.$snapshot->getKey().' (Complete)') ->assertSee('Matches current snapshot') ->assertSee('Assigned to 1 environment with compare access.') ->assertSee('Dominant next action') ->assertSee('Compare now') ->assertSeeInOrder([ 'Decision', 'Ready to compare', 'Baseline truth', 'Related context', 'Profile', 'Scope', 'Normalization lineage', 'Metadata', ]); }); it('shows capture-oriented decision guidance when no consumable snapshot exists', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $profile = BaselineProfile::factory()->active()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'name' => 'Spec369 Capture Needed Baseline', ]); BaselineTenantAssignment::factory()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'managed_environment_id' => (int) $tenant->getKey(), 'baseline_profile_id' => (int) $profile->getKey(), ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); $this->actingAs($user) ->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin')) ->assertOk() ->assertSee('Decision') ->assertSee('Capture needed before compare') ->assertSee('Assignments can be prepared, but compare waits for a complete current snapshot.') ->assertSee('No complete snapshot') ->assertSee('No capture attempts yet') ->assertSee('Assigned to 1 environment with compare access.') ->assertSee('Capture baseline') ->assertSeeInOrder(['Decision', 'Capture needed before compare', 'Baseline truth', 'Scope']); }); it('shows scope repair guidance for invalid governed-subject scope without exposing raw scope json', function (): void { [$user, , $profile] = spec369ReadyProfile(); DB::table('baseline_profiles') ->where('id', (int) $profile->getKey()) ->update([ 'scope_jsonb' => json_encode([ 'version' => 2, 'entries' => [], ], JSON_THROW_ON_ERROR), 'updated_at' => now(), ]); $profile = $profile->fresh(); $this->actingAs($user) ->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin')) ->assertOk() ->assertSee('Scope review required') ->assertSee('Stored governed-subject scope is invalid. Review the profile definition before compare.') ->assertSee('Capture and compare are blocked until governed-subject scope is reviewed.') ->assertSee('Edit profile definition') ->assertDontSee('subject_type_keys') ->assertDontSee('canonical_scope') ->assertSeeInOrder(['Decision', 'Scope review required', 'Scope', 'Stored scope is invalid']); }); it('keeps readonly members on non-mutating decision copy and disabled high-impact actions', function (): void { [$owner, $tenant, $profile] = spec369ReadyProfile(); [$readonly] = createUserWithTenant(tenant: $tenant, user: null, role: 'readonly', workspaceRole: 'readonly'); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); Livewire::actingAs($readonly) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) ->assertSee('Assignment or access required') ->assertSee('No assigned environment is available to this actor for compare.') ->assertSee('Open compare matrix') ->assertActionVisible('capture') ->assertActionDisabled('capture') ->assertActionVisible('compareNow') ->assertActionDisabled('compareNow'); $this->actingAs($owner); }); it('keeps related snapshot and compare matrix context available below the decision', function (): void { [$user, , $profile] = spec369ReadyProfile(); $this->actingAs($user); $entryKeys = collect(BaselineProfileResource::detailRelatedContextEntries($profile)) ->pluck('key') ->all(); expect($entryKeys)->toContain('baseline_snapshot', 'compare_matrix'); $this->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin')) ->assertOk() ->assertSee('Related context') ->assertSee('View snapshot') ->assertSee('Open compare matrix') ->assertSeeInOrder(['Decision', 'Related context', 'View snapshot', 'Compare matrix', 'Open compare matrix']); });