resolve(spec430ResolverResourceType($canonicalType)); expect($decision->outcome)->toBe(CaptureOutcome::BlockedMissingContract) ->and($decision->reasonCode)->toBe(CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE) ->and($decision->sourceContractState)->toBe(CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE) ->and($decision->contractKey)->toBe('exchange_powershell.'.$canonicalType) ->and($decision->sourceEndpoint)->toBeNull() ->and($decision->capturable())->toBeFalse() ->and($decision->sourceMetadata['source_contract_state'])->toBe(CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE) ->and($decision->sourceMetadata['provider_adapter_state'])->toBe('adapter_contract_available') ->and($decision->sourceMetadata['capture_eligibility_state'])->toBe('pending_capture') ->and($decision->sourceMetadata['source_surface'])->toBe('exchange_online_powershell_rest') ->and($decision->sourceMetadata['adapter_pattern'])->toBe('new_exchange_powershell_adapter') ->and($decision->sourceMetadata['provider_calls_allowed'])->toBeFalse() ->and($decision->sourceMetadata['execution_enabled'])->toBeFalse() ->and($decision->sourceMetadata['evidence_promotion_allowed'])->toBeFalse() ->and($decision->sourceMetadata['command_contract']['command_name'])->toBe($commandName) ->and(config("graph_contracts.types.{$canonicalType}", []))->toBe([]); })->with([ 'transportRule' => ['transportRule', 'Get-TransportRule'], 'remoteDomain' => ['remoteDomain', 'Get-RemoteDomain'], 'inboundConnector' => ['inboundConnector', 'Get-InboundConnector'], ]); it('Spec430 preserves blocked or deferred states for excluded Exchange and Teams types', function (string $canonicalType, Workload $workload): void { $decision = (new CoverageSourceContractResolver(new GraphContractRegistry)) ->resolve(spec430AdHocResolverResourceType($canonicalType, $workload)); expect($decision->sourceContractState)->not->toBe(CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE) ->and($decision->sourceMetadata['provider_adapter_state'] ?? null)->not->toBe('adapter_contract_available') ->and($decision->capturable())->toBeFalse() ->and(config("graph_contracts.types.{$canonicalType}", []))->toBe([]); })->with([ 'acceptedDomain' => ['acceptedDomain', Workload::Exchange], 'organizationConfig' => ['organizationConfig', Workload::Exchange], 'mailboxPlan' => ['mailboxPlan', Workload::Exchange], 'outboundConnector' => ['outboundConnector', Workload::Exchange], 'sharingPolicy' => ['sharingPolicy', Workload::Exchange], 'appPermissionPolicy' => ['appPermissionPolicy', Workload::Teams], 'appSetupPolicy' => ['appSetupPolicy', Workload::Teams], 'meetingPolicy' => ['meetingPolicy', Workload::Teams], 'messagingPolicy' => ['messagingPolicy', Workload::Teams], 'teamsUpdateManagementPolicy' => ['teamsUpdateManagementPolicy', Workload::Teams], 'teamsChannelsPolicy' => ['teamsChannelsPolicy', Workload::Teams], 'externalAccessPolicy' => ['externalAccessPolicy', Workload::Teams], ]); it('Spec430 adds inboundConnector only to the inactive Exchange planning registry denominator', function (): void { $definitions = collect(ResourceTypeRegistry::defaultDefinitions()); $inbound = $definitions->firstWhere('canonical_type', 'inboundConnector'); expect($inbound)->not->toBeNull() ->and($inbound['workload'])->toBe(Workload::Exchange->value) ->and($inbound['source_class'])->toBe(SourceClass::Tcm->value) ->and($inbound['support_state'])->toBe(SupportState::OutOfScope->value) ->and($inbound['default_coverage_level'])->toBe(CoverageLevel::Detected->value) ->and($inbound['default_evidence_state'])->toBe(EvidenceState::NotCaptured->value) ->and($inbound['default_claim_state'])->toBe(ClaimState::InternalOnly->value) ->and($inbound['restore_tier'])->toBe(RestoreTier::NotRestorable->value) ->and($definitions->firstWhere('canonical_type', 'outboundConnector'))->toBeNull() ->and($definitions->firstWhere('canonical_type', 'sharingPolicy'))->toBeNull() ->and($definitions->firstWhere('canonical_type', 'externalAccessPolicy'))->toBeNull(); }); function spec430ResolverResourceType(string $canonicalType): TenantConfigurationResourceType { $definition = collect(ResourceTypeRegistry::defaultDefinitions()) ->firstWhere('canonical_type', $canonicalType); expect($definition)->not->toBeNull("Missing default resource type definition for {$canonicalType}."); return new TenantConfigurationResourceType($definition); } function spec430AdHocResolverResourceType(string $canonicalType, Workload $workload): TenantConfigurationResourceType { $definition = collect(ResourceTypeRegistry::defaultDefinitions()) ->firstWhere('canonical_type', $canonicalType); if (is_array($definition)) { return new TenantConfigurationResourceType($definition); } return new TenantConfigurationResourceType([ 'canonical_type' => $canonicalType, 'display_name' => $canonicalType, 'source_class' => SourceClass::Tcm->value, 'workload' => $workload->value, 'resource_class' => ResourceClass::Configuration->value, 'support_state' => SupportState::OutOfScope->value, 'default_coverage_level' => CoverageLevel::Detected->value, 'default_evidence_state' => EvidenceState::NotCaptured->value, 'default_identity_state' => IdentityState::Derived->value, 'default_claim_state' => ClaimState::InternalOnly->value, 'restore_tier' => RestoreTier::NotRestorable->value, 'is_active' => true, 'metadata' => ['kernel' => 'coverage_v2'], ]); }