resolve(spec426ContractResourceType($canonicalType)); expect($decision->outcome)->toBe(CaptureOutcome::BlockedMissingContract) ->and($decision->reasonCode)->toBe('missing_source_contract_mapping') ->and($decision->contractKey)->toBeNull() ->and($decision->sourceEndpoint)->toBeNull() ->and(config("graph_contracts.types.{$canonicalType}", []))->toBe([]); })->with([ 'transportRule', 'acceptedDomain', 'appPermissionPolicy', 'meetingPolicy', ]); it('Spec426 leaves non-selected M365 resource types fail-closed without alias-derived endpoints', function (): void { $decision = (new CoverageSourceContractResolver(new GraphContractRegistry)) ->resolve(spec426ContractResourceType('dlpCompliancePolicy')); expect($decision->outcome)->toBe(CaptureOutcome::BlockedMissingContract) ->and($decision->reasonCode)->toBe('missing_source_contract_mapping') ->and($decision->contractKey)->toBeNull() ->and($decision->sourceEndpoint)->toBeNull(); }); it('Spec426 does not register guessed Microsoft Graph endpoints for Exchange and Teams source contracts', function (): void { $registered = array_keys((array) config('graph_contracts.types', [])); expect($registered)->not->toContain('transportRule') ->and($registered)->not->toContain('acceptedDomain') ->and($registered)->not->toContain('appPermissionPolicy') ->and($registered)->not->toContain('meetingPolicy'); }); function spec426ContractResourceType(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); }