create(); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'manager', ]); WorkspaceSetting::query()->create([ 'workspace_id' => (int) $workspace->getKey(), 'domain' => 'ai', 'key' => 'policy_mode', 'value' => $policyMode, 'updated_by_user_id' => (int) $user->getKey(), ]); return [$workspace, $user]; } it('allows approved local-private support-diagnostics requests and writes bounded audit metadata', function (): void { [$workspace, $user] = aiPolicyWorkspace(); $tenant = Tenant::factory()->create(['workspace_id' => (int) $workspace->getKey()]); $decision = assertNoOutboundHttp(fn () => app(GovernedAiExecutionBoundary::class)->evaluate(new AiExecutionRequest( workspace: $workspace, tenant: $tenant, actor: $user, useCaseKey: 'support_diagnostics.summary_draft', requestedProviderClass: AiProviderClass::LocalPrivate->value, dataClassifications: [AiDataClassification::RedactedSupportSummary->value], sourceFamily: 'support_diagnostics', callerSurface: 'support_diagnostics', contextFingerprint: 'support_diagnostics:summary:v1', ))); expect($decision->isAllowed())->toBeTrue() ->and($decision->reasonCode)->toBe(AiDecisionReasonCode::Allowed) ->and($decision->workspaceAiPolicyMode)->toBe('private_only') ->and($decision->matchedOperationalControlScope)->toBeNull(); $audit = AuditLog::query()->latest('id')->first(); expect($audit)->not->toBeNull() ->and($audit?->action)->toBe(AuditActionId::AiExecutionDecisionEvaluated->value) ->and($audit?->workspace_id)->toBe((int) $workspace->getKey()) ->and($audit?->tenant_id)->toBe((int) $tenant->getKey()) ->and(data_get($audit?->metadata, 'decision_outcome'))->toBe('allowed') ->and(data_get($audit?->metadata, 'decision_reason'))->toBe(AiDecisionReasonCode::Allowed->value) ->and(data_get($audit?->metadata, 'use_case_key'))->toBe('support_diagnostics.summary_draft') ->and(data_get($audit?->metadata, 'requested_provider_class'))->toBe('local_private') ->and(data_get($audit?->metadata, 'data_classifications'))->toBe(['redacted_support_summary']) ->and(data_get($audit?->metadata, 'context_fingerprint'))->toBe('support_diagnostics:summary:v1') ->and(data_get($audit?->metadata, 'prompt_text'))->toBeNull() ->and(data_get($audit?->metadata, 'output_text'))->toBeNull(); }); it('blocks external-public provider classes before any provider resolution', function (): void { [$workspace, $user] = aiPolicyWorkspace(); $decision = assertNoOutboundHttp(fn () => app(GovernedAiExecutionBoundary::class)->evaluate(new AiExecutionRequest( workspace: $workspace, tenant: null, actor: $user, useCaseKey: 'product_knowledge.answer_draft', requestedProviderClass: AiProviderClass::ExternalPublic->value, dataClassifications: [ AiDataClassification::ProductKnowledge->value, AiDataClassification::OperationalMetadata->value, ], sourceFamily: 'product_knowledge', callerSurface: 'product_knowledge', contextFingerprint: 'product_knowledge:answer:v1', ))); expect($decision->isBlocked())->toBeTrue() ->and($decision->reasonCode)->toBe(AiDecisionReasonCode::ProviderClassBlocked) ->and($decision->matchedOperationalControlScope)->toBeNull(); }); it('blocks disallowed data classifications before any provider resolution', function (): void { [$workspace, $user] = aiPolicyWorkspace(); $tenant = Tenant::factory()->create(['workspace_id' => (int) $workspace->getKey()]); $decision = assertNoOutboundHttp(fn () => app(GovernedAiExecutionBoundary::class)->evaluate(new AiExecutionRequest( workspace: $workspace, tenant: $tenant, actor: $user, useCaseKey: 'support_diagnostics.summary_draft', requestedProviderClass: AiProviderClass::LocalPrivate->value, dataClassifications: [AiDataClassification::RawProviderPayload->value], sourceFamily: 'support_diagnostics', callerSurface: 'support_diagnostics', contextFingerprint: 'support_diagnostics:raw:v1', ))); expect($decision->isBlocked())->toBeTrue() ->and($decision->reasonCode)->toBe(AiDecisionReasonCode::DataClassificationBlocked); }); it('blocks unregistered use cases', function (): void { [$workspace, $user] = aiPolicyWorkspace(); $decision = assertNoOutboundHttp(fn () => app(GovernedAiExecutionBoundary::class)->evaluate(new AiExecutionRequest( workspace: $workspace, tenant: null, actor: $user, useCaseKey: 'customer_email.reply', requestedProviderClass: AiProviderClass::LocalPrivate->value, dataClassifications: [AiDataClassification::ProductKnowledge->value], sourceFamily: 'product_knowledge', callerSurface: 'product_knowledge', contextFingerprint: 'customer_email:reply:v1', ))); expect($decision->isBlocked())->toBeTrue() ->and($decision->reasonCode)->toBe(AiDecisionReasonCode::UnregisteredUseCase); }); it('lets the ai execution operational control override an otherwise valid request', function (): void { [$workspace, $user] = aiPolicyWorkspace(); OperationalControlActivation::factory()->forGlobalScope()->create([ 'control_key' => 'ai.execution', 'reason_text' => 'Paused for AI rollout review.', ]); $decision = assertNoOutboundHttp(fn () => app(GovernedAiExecutionBoundary::class)->evaluate(new AiExecutionRequest( workspace: $workspace, tenant: null, actor: $user, useCaseKey: 'product_knowledge.answer_draft', requestedProviderClass: AiProviderClass::LocalPrivate->value, dataClassifications: [ AiDataClassification::ProductKnowledge->value, AiDataClassification::OperationalMetadata->value, ], sourceFamily: 'product_knowledge', callerSurface: 'product_knowledge', contextFingerprint: 'product_knowledge:answer:v1', ))); expect($decision->isBlocked())->toBeTrue() ->and($decision->reasonCode)->toBe(AiDecisionReasonCode::OperationalControlPaused) ->and($decision->matchedOperationalControlScope)->toBe('global'); });