syncDefaults(); config([ ExchangePowerShellInvocationGate::FEATURE_CONFIG_PATH => true, ExchangePowerShellInvocationGate::PRODUCTION_RUNNER_CONFIG_PATH => true, 'tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => [], 'tenantpilot.exchange_powershell.permission_evidence.currentness_days' => 30, 'tenantpilot.exchange_powershell.runtime.allowed_environments' => [app()->environment()], 'tenantpilot.exchange_powershell.runtime.module_check_enabled' => true, ]); }); it('Spec433 uses existing provider credential and managed environment permission metadata without schema changes', function (): void { [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment); spec433Credential($connection, ProviderCredentialKind::Certificate->value); spec433SeedExchangePowerShellPermission($environment, $connection); config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]); $result = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection); expect($result->allowed)->toBeTrue() ->and($result->context['readiness_state'])->toBe('ready_for_live_invocation') ->and($result->context['credential_state'])->toBe('certificate_supported') ->and($result->context['permission_evidence_state'])->toBe('verified') ->and(Schema::hasColumn('provider_credentials', 'provider_connection_id'))->toBeTrue() ->and(Schema::hasColumn('provider_credentials', 'credential_kind'))->toBeTrue() ->and(Schema::hasColumn('provider_credentials', 'source'))->toBeTrue() ->and(Schema::hasColumn('provider_credentials', 'last_rotated_at'))->toBeTrue() ->and(Schema::hasColumn('provider_credentials', 'expires_at'))->toBeTrue() ->and(Schema::hasColumn('managed_environment_permissions', 'workspace_id'))->toBeTrue() ->and(Schema::hasColumn('managed_environment_permissions', 'details'))->toBeTrue() ->and(glob(database_path('migrations/*spec433*')) ?: [])->toBe([]) ->and(glob(database_path('migrations/*exchange*credential*')) ?: [])->toBe([]); }); it('Spec433 credential readiness blocks unsafe or unobservable states without leaking material', function ( ?string $credentialKind, array $overrides, string $expectedFailureCode, string $expectedState, ): void { [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false); if ($credentialKind !== null) { spec433Credential($connection, $credentialKind, [ 'payload' => [ 'private_key' => 'spec433-private-key', 'certificate_password' => 'spec433-certificate-password', 'client_secret' => 'spec433-client-secret', 'access_token' => 'spec433-access-token', ], ...$overrides, ]); } $result = app(ExchangePowerShellCredentialReferenceResolver::class)->resolve($connection); $payload = json_encode($result, JSON_THROW_ON_ERROR); expect($result->allowed)->toBeFalse() ->and($result->failureCode)->toBe($expectedFailureCode) ->and($result->context['credential_state'])->toBe($expectedState) ->and($payload) ->not->toContain('spec433-private-key') ->not->toContain('spec433-certificate-password') ->not->toContain('spec433-client-secret') ->not->toContain('spec433-access-token') ->not->toContain('client_secret'); })->with([ 'missing credential' => [null, [], 'credential_blocked_missing_reference', 'missing'], 'client secret' => [ProviderCredentialKind::ClientSecret->value, [], 'credential_blocked_secret_based_app', 'secret_based_app_unsupported'], 'certificate missing expiry metadata' => [ProviderCredentialKind::Certificate->value, ['expires_at' => null], 'credential_blocked_certificate_missing', 'certificate_missing'], 'certificate expired' => [ProviderCredentialKind::Certificate->value, ['expires_at' => now()->subDay()], 'credential_blocked_certificate_expired', 'certificate_expired'], 'certificate unsupported' => [ProviderCredentialKind::Certificate->value, [], 'credential_blocked_certificate_unsupported', 'certificate_unsupported'], 'federated unsupported' => [ProviderCredentialKind::Federated->value, [], 'credential_blocked_federated_unsupported', 'federated_unsupported'], 'managed identity unsupported' => ['managed_identity', [], 'credential_blocked_managed_identity_unsupported', 'managed_identity_unsupported'], 'unknown kind' => ['unknown_kind', [], 'credential_blocked_unknown_kind', 'unknown_kind'], ]); it('Spec433 permission evidence enforces source currentness and provider connection scope', function ( array $detailsOverrides, array $rowOverrides, string $expectedFailureCode, string $expectedState, ): void { [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false); spec433SeedExchangePowerShellPermission($environment, $connection, [ 'details' => [ 'source' => 'provider_verification', 'observed_at' => now()->toJSON(), 'verified_at' => now()->toJSON(), 'evaluator' => 'exchange_powershell_permission_evidence', 'evaluator_version' => 'v1', ...$detailsOverrides, ], ...$rowOverrides, ]); $result = app(ExchangePowerShellPermissionEvidenceEvaluator::class)->evaluate($environment, $connection); expect($result->allowed)->toBeFalse() ->and($result->failureCode)->toBe($expectedFailureCode) ->and($result->context['permission_evidence_state'])->toBe($expectedState); })->with([ 'absent' => [[], ['status' => 'missing'], 'permission_evidence_blocked_absent', 'absent'], 'unknown' => [[], ['status' => 'error'], 'permission_evidence_blocked_unknown', 'unknown'], 'source missing' => [['source' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'source untrusted' => [['source' => 'manual_note'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'source malformed' => [['source' => 'provider verification raw payload'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'observed timestamp missing' => [['observed_at' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'verified timestamp missing' => [['verified_at' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'observed timestamp relative string' => [['observed_at' => 'now'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'verified timestamp relative string' => [['verified_at' => 'now'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'observed timestamp invalid calendar date' => [['observed_at' => '2026-02-30T10:00:00Z'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'verified timestamp invalid offset' => [['verified_at' => '2026-07-07T22:41:10+24:00'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'verified before observed' => [['observed_at' => now()->toJSON(), 'verified_at' => now()->subMinute()->toJSON()], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'evaluator missing' => [['evaluator' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'evaluator version missing' => [['evaluator_version' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'], 'observed timestamp stale' => [['observed_at' => now()->subDays(31)->toJSON(), 'verified_at' => now()->toJSON()], [], 'permission_evidence_blocked_stale', 'stale'], 'verified timestamp stale' => [['observed_at' => now()->subDays(31)->subMinute()->toJSON(), 'verified_at' => now()->subDays(31)->toJSON()], [], 'permission_evidence_blocked_stale', 'stale'], 'stale by configurable threshold' => [[], ['last_checked_at' => now()->subDays(31)], 'permission_evidence_blocked_stale', 'stale'], 'wrong workspace' => [['workspace_id' => 999999], [], 'permission_evidence_blocked_wrong_workspace', 'wrong_workspace'], 'wrong environment' => [['managed_environment_id' => 999999], [], 'permission_evidence_blocked_wrong_environment', 'wrong_environment'], 'unsupported provider' => [['provider' => 'other'], [], 'permission_evidence_blocked_unsupported_provider', 'unsupported_provider'], 'wrong provider connection' => [['provider_connection_id' => 999999], [], 'permission_evidence_blocked_wrong_provider_connection', 'wrong_provider_connection'], ]); it('Spec433 rejects malformed permission evidence scope identifiers before scope matching', function ( string $scopeKey, string $malformedSuffix, ): void { [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false); $expectedValue = match ($scopeKey) { 'workspace_id' => (int) $connection->workspace_id, 'managed_environment_id' => (int) $connection->managed_environment_id, 'provider_connection_id' => (int) $connection->getKey(), default => throw new InvalidArgumentException('Unsupported scope key for Spec433 test.'), }; spec433SeedExchangePowerShellPermission($environment, $connection, [ 'details' => [ $scopeKey => ((string) $expectedValue).$malformedSuffix, ], ]); $result = app(ExchangePowerShellPermissionEvidenceEvaluator::class)->evaluate($environment, $connection); expect($result->allowed)->toBeFalse() ->and($result->failureCode)->toBe('permission_evidence_blocked_unvalidated') ->and($result->context['permission_evidence_state'])->toBe('unvalidated'); })->with([ 'workspace id numeric prefix' => fn (): array => [ 'workspace_id', 'abc', ], 'managed environment id decimal' => fn (): array => [ 'managed_environment_id', '.0', ], 'provider connection id scientific notation' => fn (): array => [ 'provider_connection_id', 'e0', ], 'provider connection id whitespace padded' => fn (): array => [ 'provider_connection_id', ' ', ], ]); it('Spec433 honors configured permission evidence currentness without fallback-to-latest behavior', function (): void { config(['tenantpilot.exchange_powershell.permission_evidence.currentness_days' => 10]); [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false); spec433SeedExchangePowerShellPermission($environment, $connection, [ 'last_checked_at' => now()->subDays(11), ]); $result = app(ExchangePowerShellPermissionEvidenceEvaluator::class)->evaluate($environment, $connection); expect($result->allowed)->toBeFalse() ->and($result->failureCode)->toBe('permission_evidence_blocked_stale') ->and($result->context['permission_currentness_days'])->toBe(10) ->and(ManagedEnvironmentPermission::query() ->where('managed_environment_id', (int) $environment->getKey()) ->where('permission_key', 'Exchange.ManageAsApp') ->count())->toBe(1); }); it('Spec433 combined readiness requires both credential and permission evidence and does not promote evidence', function (): void { [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false); spec433Credential($connection, ProviderCredentialKind::ClientSecret->value); spec433SeedExchangePowerShellPermission($environment, $connection); $secretCredentialResult = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection); $connection->credential()->delete(); config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]); spec433Credential($connection, ProviderCredentialKind::Certificate->value); spec433SeedExchangePowerShellPermission($environment, $connection, [ 'last_checked_at' => now()->subDays(31), ]); $stalePermissionResult = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection); spec433SeedExchangePowerShellPermission($environment, $connection); $readyResult = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection); expect($secretCredentialResult->allowed)->toBeFalse() ->and($secretCredentialResult->context['readiness_blocker'])->toBe('credential') ->and($stalePermissionResult->allowed)->toBeFalse() ->and($stalePermissionResult->context['readiness_blocker'])->toBe('permission') ->and($readyResult->allowed)->toBeTrue() ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); }); it('Spec433 provider capability is supported only when combined readiness passes', function ( string $case, array $connectionOverrides, ?string $credentialKind, array $permissionOverrides, ?ProviderCapabilityStatus $expectedStatus, ): void { config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]); [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment, $connectionOverrides, seedExchangePowerShellPermission: false); if ($credentialKind !== null) { spec433Credential($connection, $credentialKind); } if ($permissionOverrides !== ['missing' => true]) { spec433SeedExchangePowerShellPermission($environment, $connection, $permissionOverrides); } $result = app(ProviderCapabilityEvaluator::class)->evaluate($environment, $connection, 'exchange_powershell_invoke'); expect($result->status)->toBe($expectedStatus) ->and($result->status)->not->toBe( $case === 'ready' ? ProviderCapabilityStatus::Missing : ProviderCapabilityStatus::Supported, ); })->with([ 'admin consent missing' => ['admin_consent_missing', ['consent_status' => ProviderConsentStatus::Required->value, 'consent_granted_at' => null], ProviderCredentialKind::Certificate->value, [], ProviderCapabilityStatus::Missing], 'client secret' => ['client_secret', [], ProviderCredentialKind::ClientSecret->value, [], ProviderCapabilityStatus::Blocked], 'missing credential' => ['missing_credential', [], null, [], ProviderCapabilityStatus::Missing], 'missing permission' => ['missing_permission', [], ProviderCredentialKind::Certificate->value, ['missing' => true], ProviderCapabilityStatus::Missing], 'stale permission' => ['stale_permission', [], ProviderCredentialKind::Certificate->value, ['last_checked_at' => now()->subDays(31)], ProviderCapabilityStatus::Unknown], 'wrong scope permission' => ['wrong_scope_permission', [], ProviderCredentialKind::Certificate->value, ['details' => ['provider_connection_id' => 999999]], ProviderCapabilityStatus::Blocked], 'unsupported provider' => ['unsupported_provider', ['provider' => 'other'], ProviderCredentialKind::Certificate->value, ['details' => ['provider' => 'other']], ProviderCapabilityStatus::NotApplicable], 'ready' => ['ready', [], ProviderCredentialKind::Certificate->value, [], ProviderCapabilityStatus::Supported], ]); it('Spec433 provider capability uses configured Exchange evidence currentness as the shared readiness truth', function (): void { config([ 'tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate'], 'tenantpilot.exchange_powershell.permission_evidence.currentness_days' => 45, ]); [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false); spec433Credential($connection, ProviderCredentialKind::Certificate->value); spec433SeedExchangePowerShellPermission($environment, $connection, [ 'details' => [ 'observed_at' => now()->subDays(31)->toJSON(), 'verified_at' => now()->subDays(31)->toJSON(), ], 'last_checked_at' => now()->subDays(31), ]); $capability = app(ProviderCapabilityEvaluator::class)->evaluate($environment, $connection, 'exchange_powershell_invoke'); $readiness = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection); expect($readiness->allowed)->toBeTrue() ->and($readiness->context['permission_currentness_days'])->toBe(45) ->and($capability->status)->toBe(ProviderCapabilityStatus::Supported); }); it('Spec433 production runner consumes combined readiness before runtime or process execution', function (): void { config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]); [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false); spec433Credential($connection, ProviderCredentialKind::Certificate->value); spec433SeedExchangePowerShellPermission($environment, $connection, [ 'last_checked_at' => now()->subDays(31), ]); $executor = spec433FakeExecutor(); $result = app(ExchangePowerShellProductionRunner::class)->run( spec433VerifiedContract('transportRule'), new ExchangePowerShellInvocationContext( operationRunId: 433, workspaceId: (int) $environment->workspace_id, managedEnvironmentId: (int) $environment->getKey(), providerConnectionId: (int) $connection->getKey(), canonicalType: 'transportRule', commandName: 'Get-TransportRule', runnerMode: ExchangePowerShellInvocationGate::RUNNER_MODE_PRODUCTION, redactionPolicy: ExchangePowerShellInvocationGate::REDACTION_POLICY, sourceContractState: 'contract_verified_pending_capture', ), ); expect($executor->calls)->toBe([]) ->and($result->blocked)->toBeTrue() ->and($result->failureCode)->toBe('permission_evidence_blocked_stale') ->and($result->context['readiness_state'])->toBe('blocked') ->and($result->context['readiness_blocker'])->toBe('permission'); }); it('Spec433 invocation context persists only safe readiness states and no evidence rows after readiness pass', function (): void { config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]); [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec433ProviderConnection($environment); spec433Credential($connection, ProviderCredentialKind::Certificate->value, [ 'payload' => [ 'private_key' => 'spec433-private-key', 'certificate_password' => 'spec433-certificate-password', ], ]); spec433FakeExecutor([ ExchangePowerShellProcessResult::completed(0, "7.4.0\n"), ExchangePowerShellProcessResult::completed(0, "ExchangeOnlineManagement\n"), ExchangePowerShellProcessResult::completed(0, '[{"id":"rule-1","secret":"raw-output-secret"}]', durationMs: 14), ]); $run = app(ExchangePowerShellInvocationGate::class)->invoke( tenant: $environment, providerConnection: $connection, actor: $user, canonicalType: 'transportRule', runnerMode: ExchangePowerShellInvocationGate::RUNNER_MODE_PRODUCTION, ); $persisted = json_encode([ 'context' => $run->context, 'failure_summary' => $run->failure_summary, 'summary_counts' => $run->summary_counts, ], JSON_THROW_ON_ERROR); expect($run->status)->toBe(OperationRunStatus::Completed->value) ->and($run->outcome)->toBe(OperationRunOutcome::Succeeded->value) ->and(data_get($run->context, 'runner_result.readiness_state'))->toBe('ready_for_live_invocation') ->and(data_get($run->context, 'runner_result.credential_state'))->toBe('certificate_supported') ->and(data_get($run->context, 'runner_result.permission_evidence_state'))->toBe('verified') ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0) ->and($persisted) ->not->toContain('raw-output-secret') ->not->toContain('spec433-private-key') ->not->toContain('spec433-certificate-password') ->not->toContain('raw_shell_stdout') ->not->toContain('raw_shell_stderr') ->not->toContain('client_secret') ->not->toContain('access_token'); }); it('Spec433 adds no UI route trigger migration or tenant-id ownership surface', function (): void { $runtimeFiles = [ app_path('Services/TenantConfiguration/ExchangePowerShellCredentialReferenceResolver.php'), app_path('Services/TenantConfiguration/ExchangePowerShellPermissionEvidenceEvaluator.php'), app_path('Services/TenantConfiguration/ExchangePowerShellInvocationReadinessEvaluator.php'), app_path('Services/TenantConfiguration/ExchangePowerShellProductionRunner.php'), app_path('Support/Providers/Capabilities/ProviderCapabilityEvaluator.php'), ]; $runtimeSource = collect($runtimeFiles) ->map(fn (string $file): string => file_get_contents($file) ?: '') ->implode("\n"); expect(preg_match('/\btenant_id\b/', $runtimeSource))->toBe(0); expect($runtimeSource) ->not->toContain('Start-') ->not->toContain('Set-') ->not->toContain('New-') ->not->toContain('Remove-') ->and(glob(app_path('Filament/**/*ExchangePowerShell*')) ?: [])->toBe([]) ->and(glob(base_path('routes/*exchange*')) ?: [])->toBe([]) ->and(glob(resource_path('views/**/*ExchangePowerShell*')) ?: [])->toBe([]) ->and(glob(database_path('migrations/*exchange*')) ?: [])->toBe([]) ->and(File::exists(app_path('Jobs/TenantConfiguration/InvokeExchangePowerShell.php')))->toBeFalse(); }); /** * @param array $overrides */ function spec433ProviderConnection( ManagedEnvironment $environment, array $overrides = [], bool $seedExchangePowerShellPermission = true, ): ProviderConnection { $connection = ProviderConnection::factory() ->dedicated() ->consentGranted() ->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'entra_tenant_id' => $environment->providerTenantContext(), 'is_default' => true, 'verification_status' => ProviderVerificationStatus::Healthy->value, 'last_health_check_at' => now(), ...$overrides, ]); if ($seedExchangePowerShellPermission) { spec433SeedExchangePowerShellPermission($environment, $connection); } return $connection; } /** * @param array $overrides */ function spec433SeedExchangePowerShellPermission(ManagedEnvironment $environment, ProviderConnection $connection, array $overrides = []): ManagedEnvironmentPermission { $details = [ 'source' => 'provider_verification', 'observed_at' => now()->toJSON(), 'verified_at' => now()->toJSON(), 'evaluator' => 'exchange_powershell_permission_evidence', 'evaluator_version' => 'v1', 'workspace_id' => (int) $connection->workspace_id, 'managed_environment_id' => (int) $connection->managed_environment_id, 'provider' => (string) $connection->provider, 'provider_connection_id' => (int) $connection->getKey(), ]; if (is_array($overrides['details'] ?? null)) { $details = array_replace($details, $overrides['details']); } unset($overrides['details']); return ManagedEnvironmentPermission::query()->updateOrCreate( [ 'managed_environment_id' => (int) $environment->getKey(), 'permission_key' => 'Exchange.ManageAsApp', 'workspace_id' => (int) $environment->workspace_id, ], [ 'status' => 'granted', 'details' => $details, 'last_checked_at' => now(), ...$overrides, ], ); } /** * @param array $overrides */ function spec433Credential(ProviderConnection $connection, string $kind, array $overrides = []): ProviderCredential { $storedKind = in_array($kind, ProviderCredentialKind::values(), true) ? $kind : ProviderCredentialKind::ClientSecret->value; $credential = ProviderCredential::factory()->create([ 'provider_connection_id' => (int) $connection->getKey(), 'type' => $storedKind, 'credential_kind' => $storedKind, 'source' => ProviderCredentialSource::DedicatedManual->value, 'last_rotated_at' => now(), 'expires_at' => now()->addYear(), ...$overrides, ]); if ($storedKind !== $kind) { DB::table('provider_credentials') ->where('id', (int) $credential->getKey()) ->update([ 'type' => $kind, 'credential_kind' => $kind, ]); } return $credential; } function spec433FakeExecutor(array $results = []): FakeExchangePowerShellProcessExecutor { $executor = new FakeExchangePowerShellProcessExecutor(results: $results); app()->instance(ExchangePowerShellProcessExecutor::class, $executor); return $executor; } function spec433VerifiedContract(string $canonicalType): ExchangePowerShellCommandContract { $contracts = new ExchangePowerShellCommandContracts; $contract = $contracts->contractForCanonicalType($canonicalType); if (! is_array($contract)) { throw new RuntimeException('Missing Spec433 command contract.'); } return ExchangePowerShellCommandContract::fromVerifiedArray($contract, $contracts, []); }