syncDefaults(); config([ ExchangePowerShellInvocationGate::FEATURE_CONFIG_PATH => true, ExchangePowerShellInvocationGate::PRODUCTION_RUNNER_CONFIG_PATH => true, 'tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate'], 'tenantpilot.exchange_powershell.permission_evidence.currentness_days' => 30, ]); }); it('Spec434 captures accepted Exchange PowerShell output as internal content-backed evidence only', function (): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); spec434Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $connection); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [spec434TransportRulePayload()], context: ['output_state' => 'structured_collection'], ), ); $run->refresh(); $resource = TenantConfigurationResource::query()->sole(); $evidence = TenantConfigurationResourceEvidence::query()->sole(); expect($result) ->toMatchArray([ 'canonical_type' => 'transportRule', 'outcome' => CaptureOutcome::Captured->value, 'adapter_outcome' => CaptureOutcome::Captured->value, 'item_count' => 1, 'reason_code' => null, 'source_contract_key' => 'exchange_powershell.transportRule', 'summary_counts' => [ 'total' => 1, 'processed' => 1, 'succeeded' => 1, 'failed' => 0, 'skipped' => 0, 'items' => 1, ], ]) ->and($result['evidence_ids'])->toBe([(int) $evidence->getKey()]) ->and($run->summary_counts)->toBe([ 'total' => 1, 'processed' => 1, 'succeeded' => 1, 'failed' => 0, 'skipped' => 0, 'items' => 1, ]) ->and($resource->workspace_id)->toBe((int) $environment->workspace_id) ->and($resource->managed_environment_id)->toBe((int) $environment->getKey()) ->and($resource->provider_connection_id)->toBe((int) $connection->getKey()) ->and($resource->latest_evidence_id)->toBe((int) $evidence->getKey()) ->and($resource->latest_identity_state)->toBe(IdentityState::Stable) ->and($resource->latest_claim_state)->toBe(ClaimState::InternalOnly) ->and($resource->latest_evidence_state)->toBe(EvidenceState::ContentBacked) ->and($evidence->coverage_level)->toBe(CoverageLevel::ContentBacked) ->and($evidence->evidence_state)->toBe(EvidenceState::ContentBacked) ->and($evidence->capture_outcome)->toBe(CaptureOutcome::Captured) ->and($evidence->source_contract_key)->toBe('exchange_powershell.transportRule') ->and($evidence->source_endpoint)->toBe(ExchangePowerShellCommandContracts::SOURCE_SURFACE.':Get-TransportRule') ->and($evidence->source_metadata['capture_adapter'])->toBe('exchange_powershell_evidence_capture_adapter') ->and($evidence->source_metadata['content_level_maximum'])->toBe('content_backed') ->and($evidence->source_metadata['customer_claims_allowed'])->toBeFalse() ->and($evidence->source_metadata['restore_allowed'])->toBeFalse() ->and($evidence->source_metadata['certification_allowed'])->toBeFalse() ->and($evidence->normalized_payload['source']['source_surface'])->toBe(ExchangePowerShellCommandContracts::SOURCE_SURFACE) ->and($evidence->permission_context['provider_connection_id'])->toBe((int) $connection->getKey()); $persistedRun = json_encode([ 'context' => $run->context, 'summary_counts' => $run->summary_counts, 'failure_summary' => $run->failure_summary, ], JSON_THROW_ON_ERROR); expect($persistedRun) ->not->toContain('raw_shell_stdout') ->not->toContain('raw_shell_stderr') ->not->toContain('provider_response') ->not->toContain('client_secret') ->not->toContain('access_token'); }); it('Spec434 accepts only the included Exchange PowerShell capture targets', function (string $canonicalType): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); spec434Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $connection, resourceTypes: [$canonicalType]); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: $canonicalType, runnerResult: ExchangePowerShellInvocationResult::succeeded( [spec434PayloadFor($canonicalType)], context: ['output_state' => 'structured_collection'], ), ); expect($result['outcome'])->toBe(CaptureOutcome::Captured->value) ->and($result['item_count'])->toBe(1) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(1); })->with([ 'transportRule', 'remoteDomain', 'inboundConnector', ]); it('Spec434 blocks unsupported Exchange and Teams targets before evidence append', function (string $canonicalType): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); spec434Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $connection, resourceTypes: [$canonicalType]); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: $canonicalType, runnerResult: ExchangePowerShellInvocationResult::succeeded( [['id' => 'unsupported-1', 'DisplayName' => 'Unsupported']], context: ['output_state' => 'structured_collection'], ), ); expect($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_PREREQUISITE_BLOCKED) ->and($result['reason_code'])->toBe('unsupported_exchange_capture_type') ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); })->with([ 'acceptedDomain', 'organizationConfig', 'meetingPolicy', ]); it('Spec434 blocks missing operation runs through the typed adapter boundary', function (): void { [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); spec434Credential($connection, ProviderCredentialKind::Certificate->value); expect(fn () => app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: null, canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [spec434TransportRulePayload()], context: ['output_state' => 'structured_collection'], ), ))->toThrow(TypeError::class); expect(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); }); it('Spec434 blocks wrong operation type and same-scope mismatches before evidence append', function (string $case): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); spec434Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $connection); if ($case === 'wrong_operation_type') { $run->forceFill(['type' => OperationRunType::TenantConfigurationExchangePowerShellInvocation->value])->save(); } if ($case === 'wrong_workspace_context') { $run->forceFill(['context' => array_replace_recursive($run->context, [ 'target_scope' => ['workspace_id' => (int) $environment->workspace_id + 999], ])])->save(); } if ($case === 'wrong_managed_environment_context') { $run->forceFill(['context' => array_replace_recursive($run->context, [ 'target_scope' => ['managed_environment_id' => (int) $environment->getKey() + 999], ])])->save(); } if ($case === 'wrong_provider_connection_context') { $run->forceFill(['context' => array_replace_recursive($run->context, [ 'target_scope' => ['provider_connection_id' => (int) $connection->getKey() + 999], ])])->save(); } $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run->refresh(), canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [spec434TransportRulePayload()], context: ['output_state' => 'structured_collection'], ), ); expect($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_PREREQUISITE_BLOCKED) ->and($result['reason_code'])->toBe($case === 'wrong_operation_type' ? 'operation_type_mismatch' : 'scope_mismatch') ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); })->with([ 'wrong_operation_type', 'wrong_workspace_context', 'wrong_managed_environment_context', 'wrong_provider_connection_context', ]); it('Spec434 blocks cross-scope provider connections before evidence append', function (): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); [, $otherEnvironment] = createMinimalUserWithTenant(role: 'owner'); $foreignConnection = spec434ReadyProviderConnection($otherEnvironment); spec434Credential($foreignConnection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $foreignConnection); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $foreignConnection, operationRun: $run, canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [spec434TransportRulePayload()], context: ['output_state' => 'structured_collection'], ), ); expect($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_PREREQUISITE_BLOCKED) ->and($result['reason_code'])->toBe('scope_mismatch') ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); }); it('Spec434 blocks missing readiness and non-supported provider capability before evidence append', function (string $case): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = match ($case) { 'missing_readiness' => spec434ReadyProviderConnection($environment, seedExchangePowerShellPermission: false), 'capability_not_supported' => spec434ReadyProviderConnection($environment, [ 'consent_status' => ProviderConsentStatus::Required->value, 'consent_granted_at' => null, ]), default => throw new InvalidArgumentException('Unsupported Spec434 readiness case.'), }; spec434Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $connection); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [spec434TransportRulePayload()], context: ['output_state' => 'structured_collection'], ), ); expect($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_PREREQUISITE_BLOCKED) ->and($result['reason_code'])->not->toBeNull() ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); })->with([ 'missing_readiness', 'capability_not_supported', ]); it('Spec434 blocks runner results that are not accepted structured or empty collections', function (ExchangePowerShellInvocationResult $runnerResult): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); spec434Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $connection); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'transportRule', runnerResult: $runnerResult, ); expect($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_PREREQUISITE_BLOCKED) ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); })->with([ 'missing output state' => fn (): ExchangePowerShellInvocationResult => ExchangePowerShellInvocationResult::succeeded([spec434TransportRulePayload()]), 'associative payload' => fn (): ExchangePowerShellInvocationResult => ExchangePowerShellInvocationResult::succeededPayload( ['Guid' => 'not-a-list'], ['output_state' => 'structured_collection'], ), 'failed runner' => fn (): ExchangePowerShellInvocationResult => ExchangePowerShellInvocationResult::failed( reasonCode: 'provider_failed', failureCode: 'runner_failed', message: 'Rejected safely.', ), ]); it('Spec434 blocks unsafe identity before resource or evidence writes', function (string $canonicalType, array $payload, string $reasonCode): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); spec434Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $connection, resourceTypes: [$canonicalType]); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: $canonicalType, runnerResult: ExchangePowerShellInvocationResult::succeeded( [$payload], context: ['output_state' => 'structured_collection'], ), ); expect($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_IDENTITY_BLOCKED) ->and($result['reason_code'])->toBe($reasonCode) ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); })->with([ 'transport rule display-name only' => [ 'transportRule', ['DisplayName' => 'Display names are not stable identifiers'], 'missing_stable_external_id', ], 'remote domain derived-only domain name' => [ 'remoteDomain', ['DomainName' => 'contoso.example', 'DisplayName' => 'Contoso'], 'missing_stable_external_id', ], ]); it('Spec434 blocks duplicate stable identities before resource or evidence writes', function (): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); spec434Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $connection); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [ spec434TransportRulePayload(['DisplayName' => 'Duplicate A']), spec434TransportRulePayload(['DisplayName' => 'Duplicate B']), ], context: ['output_state' => 'structured_collection'], ), ); expect($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_IDENTITY_BLOCKED) ->and($result['reason_code'])->toBe('duplicate_stable_identity') ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); }); it('Spec434 identity gate detects existing same-scope identity conflict before writer use', function (): void { [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); $resourceType = app(ResourceTypeRegistry::class)->findActive('transportRule'); expect($resourceType)->toBeInstanceOf(TenantConfigurationResourceType::class); $sourceMetadata = [ 'source_contract_key' => 'exchange_powershell.transportRule', 'source_version' => ExchangePowerShellCommandContracts::COMMAND_CONTRACT_VERSION, ]; $payload = ['DisplayName' => 'Collision without stable id']; $identity = app(CanonicalIdentityResolver::class)->resolve($resourceType, $payload, $sourceMetadata); TenantConfigurationResource::factory()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'provider_connection_id' => (int) $connection->getKey(), 'resource_type_id' => (int) $resourceType->getKey(), 'canonical_type' => 'transportRule', 'canonical_resource_key' => $identity->canonicalResourceKey, 'source_identity' => $identity->sourceIdentity, 'secondary_identity_keys' => $identity->secondaryKeys, 'latest_identity_state' => IdentityState::MissingExternalId->value, 'latest_claim_state' => ClaimState::ClaimBlocked->value, ]); $result = app(ExchangePowerShellIdentityEvidenceGate::class)->evaluateCollection( tenant: $environment, providerConnection: $connection, resourceType: $resourceType, items: [$payload], sourceMetadata: $sourceMetadata, ); $resource = TenantConfigurationResource::query()->sole(); expect($result['allowed'])->toBeFalse() ->and($result['reason_code'])->toBe('identity_conflict') ->and($resource->refresh()->latest_identity_state)->toBe(IdentityState::IdentityConflict) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); }); it('Spec434 identity gate reports unsupported identity states without writer use', function (): void { [, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); $resourceType = TenantConfigurationResourceType::factory()->create([ 'canonical_type' => 'unsupportedExchangeIdentity', ]); $result = app(ExchangePowerShellIdentityEvidenceGate::class)->evaluateCollection( tenant: $environment, providerConnection: $connection, resourceType: $resourceType, items: [['id' => 'unsupported-identity']], sourceMetadata: [], ); expect($result['allowed'])->toBeFalse() ->and($result['reason_code'])->toBe('unsupported_identity') ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); }); it('Spec434 empty collections persist only zero summary counts and no durable collection proof', function (): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec434ReadyProviderConnection($environment); spec434Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec434CaptureRun($environment, $user, $connection); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [], context: ['output_state' => 'empty_collection'], ), ); expect($result) ->toMatchArray([ 'outcome' => CaptureOutcome::Captured->value, 'adapter_outcome' => ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_EMPTY_COLLECTION, 'item_count' => 0, 'reason_code' => ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_EMPTY_COLLECTION, 'summary_counts' => [ 'total' => 0, 'processed' => 0, 'succeeded' => 0, 'failed' => 0, 'skipped' => 0, 'items' => 0, ], ]) ->and($run->refresh()->summary_counts)->toBe([ 'total' => 0, 'processed' => 0, 'succeeded' => 0, 'failed' => 0, 'skipped' => 0, 'items' => 0, ]) ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); }); it('Spec434 implementation has no Graph fallback path, product surface, tenant-id scope, or mini-platform', function (): void { $runtimeFiles = [ app_path('Services/TenantConfiguration/ExchangePowerShellEvidenceCaptureAdapter.php'), app_path('Services/TenantConfiguration/ExchangePowerShellCaptureEligibilityGate.php'), app_path('Services/TenantConfiguration/ExchangePowerShellIdentityEvidenceGate.php'), app_path('Services/TenantConfiguration/ExchangePowerShellContentOnlyEvidenceGuard.php'), app_path('Services/TenantConfiguration/CoverageEvidenceWriter.php'), ]; $runtimeSource = collect($runtimeFiles) ->map(fn (string $file): string => file_get_contents($file) ?: '') ->implode("\n"); expect(preg_match('/\btenant_id\b/', $runtimeSource))->toBe(0) ->and($runtimeSource) ->not->toContain('ProviderGateway') ->not->toContain('GenericContentEvidenceCaptureService') ->not->toContain('listPolicies(') ->not->toContain('/deviceManagement') ->not->toContain('fake_empty_success') ->not->toContain('fake_captured') ->and(Schema::hasColumn('tenant_configuration_resources', 'tenant_id'))->toBeFalse() ->and(Schema::hasColumn('tenant_configuration_resource_evidence', 'tenant_id'))->toBeFalse() ->and(glob(app_path('Filament/**/*ExchangePowerShell*')) ?: [])->toBe([]) ->and(glob(app_path('Livewire/**/*ExchangePowerShell*')) ?: [])->toBe([]) ->and(glob(base_path('routes/*exchange*')) ?: [])->toBe([]) ->and(glob(resource_path('views/**/*ExchangePowerShell*')) ?: [])->toBe([]) ->and(glob(database_path('migrations/*exchange*evidence*')) ?: [])->toBe([]) ->and(glob(database_path('migrations/*tenant*configuration*exchange*')) ?: [])->toBe([]) ->and(File::exists(app_path('Jobs/TenantConfiguration/CaptureExchangePowerShellEvidenceJob.php')))->toBeFalse() ->and(File::exists(app_path('Console/Commands/CaptureExchangePowerShellEvidence.php')))->toBeFalse(); $captureOutcomeValues = array_map( static fn (CaptureOutcome $outcome): string => $outcome->value, CaptureOutcome::cases(), ); expect($captureOutcomeValues) ->toContain(CaptureOutcome::Captured->value) ->not->toContain('fake_captured') ->not->toContain('fake_empty_success'); }); /** * @param array $overrides */ function spec434ReadyProviderConnection( 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) { spec434SeedExchangePowerShellPermission($environment, $connection); } return $connection; } /** * @param array $overrides */ function spec434SeedExchangePowerShellPermission( 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 spec434Credential(ProviderConnection $connection, string $kind, array $overrides = []): ProviderCredential { return ProviderCredential::factory()->create([ 'provider_connection_id' => (int) $connection->getKey(), 'type' => $kind, 'credential_kind' => $kind, 'source' => ProviderCredentialSource::DedicatedManual->value, 'last_rotated_at' => now(), 'expires_at' => now()->addYear(), ...$overrides, ]); } /** * @param array $overrides * @param list $resourceTypes */ function spec434CaptureRun( ManagedEnvironment $environment, User $user, ProviderConnection $connection, array $overrides = [], array $resourceTypes = ['transportRule'], ): OperationRun { $context = [ 'target_scope' => [ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'provider_connection_id' => (int) $connection->getKey(), ], 'resource_types' => $resourceTypes, 'required_capability' => 'exchange_powershell_invoke', ]; if (is_array($overrides['context'] ?? null)) { $context = array_replace_recursive($context, $overrides['context']); } unset($overrides['context']); return OperationRun::factory()->withUser($user)->forTenant($environment)->create([ 'type' => OperationRunType::TenantConfigurationCapture->value, 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, 'context' => $context, ...$overrides, ]); } /** * @param array $overrides * @return array */ function spec434TransportRulePayload(array $overrides = []): array { return [ 'Guid' => '11111111-2222-3333-4444-555555555555', 'RuleId' => 'spec434-rule-1', 'DisplayName' => 'Spec 434 transport rule', 'Name' => 'Spec 434 transport rule', 'Priority' => 0, 'Mode' => 'Enforce', 'State' => 'Enabled', 'Enabled' => true, 'Conditions' => ['SenderDomainIs' => ['contoso.example']], 'Actions' => ['RedirectMessageTo' => ['security@contoso.example']], 'WhenChanged' => 'volatile', ...$overrides, ]; } /** * @return array */ function spec434RemoteDomainPayload(): array { return [ 'Guid' => '22222222-3333-4444-5555-666666666666', 'RemoteDomainId' => 'spec434-remote-domain-1', 'Identity' => 'spec434-remote-domain-1', 'DomainName' => 'contoso.example', 'DisplayName' => 'Contoso remote domain', 'IsDefault' => false, 'AllowedOOFType' => 'External', 'AutoReplyEnabled' => true, ]; } /** * @return array */ function spec434InboundConnectorPayload(): array { return [ 'Guid' => '33333333-4444-5555-6666-777777777777', 'ConnectorId' => 'spec434-inbound-connector-1', 'Identity' => 'spec434-inbound-connector-1', 'DisplayName' => 'Spec 434 inbound connector', 'Name' => 'Spec 434 inbound connector', 'ConnectorType' => 'Partner', 'Enabled' => true, 'RequireTls' => true, ]; } /** * @return array */ function spec434PayloadFor(string $canonicalType): array { return match ($canonicalType) { 'transportRule' => spec434TransportRulePayload(), 'remoteDomain' => spec434RemoteDomainPayload(), 'inboundConnector' => spec434InboundConnectorPayload(), default => throw new InvalidArgumentException('Unsupported Spec434 payload type.'), }; }