toBe([ 'contract_verified_pending_capture', 'contract_blocked_missing_source', 'contract_blocked_permission_unclear', 'contract_blocked_beta_only', 'contract_blocked_response_shape_unsafe', 'contract_blocked_repo_adapter_missing', 'contract_blocked_identity_unsafe', 'contract_blocked_redaction_unsafe', ]); }); it('Spec427 resolves every target type to an exact non-capturable source-contract blocker', function (string $canonicalType): void { $decision = (new CoverageSourceContractResolver(new GraphContractRegistry)) ->resolve(spec427StateResourceType($canonicalType)); expect($decision->outcome)->toBe(CaptureOutcome::BlockedMissingContract) ->and($decision->reasonCode)->toBe(CoverageSourceContractDecision::CONTRACT_BLOCKED_REPO_ADAPTER_MISSING) ->and($decision->sourceContractState)->toBe(CoverageSourceContractDecision::CONTRACT_BLOCKED_REPO_ADAPTER_MISSING) ->and($decision->sourceMetadata['source_contract_state'])->toBe(CoverageSourceContractDecision::CONTRACT_BLOCKED_REPO_ADAPTER_MISSING) ->and($decision->sourceMetadata['capture_eligibility_state'])->toBe('blocked') ->and($decision->sourceMetadata['provider_adapter_state'])->toBe('missing') ->and($decision->capturable())->toBeFalse() ->and($decision->contractKey)->toBeNull() ->and($decision->sourceEndpoint)->toBeNull() ->and(config("graph_contracts.types.{$canonicalType}", []))->toBe([]); })->with([ 'transportRule', 'acceptedDomain', 'appPermissionPolicy', 'meetingPolicy', ]); it('Spec427 keeps non-target missing-contract resource types on the existing generic blocker', function (): void { $decision = (new CoverageSourceContractResolver(new GraphContractRegistry)) ->resolve(spec427StateResourceType('dlpCompliancePolicy')); expect($decision->outcome)->toBe(CaptureOutcome::BlockedMissingContract) ->and($decision->reasonCode)->toBe('missing_source_contract_mapping') ->and($decision->sourceContractState)->toBeNull() ->and($decision->sourceMetadata['reason_code'])->toBe('missing_source_contract_mapping'); }); it('Spec427 does not assign source-contract state to unsupported non-target resource types', function (): void { $decision = (new CoverageSourceContractResolver(new GraphContractRegistry)) ->resolve(spec427StateResourceType('application')); expect($decision->outcome)->toBe(CaptureOutcome::BlockedUnsupported) ->and($decision->reasonCode)->toBe('resource_type_unsupported') ->and($decision->sourceContractState)->toBeNull() ->and($decision->sourceMetadata['reason_code'])->toBe('resource_type_unsupported') ->and($decision->sourceMetadata)->not->toHaveKey('source_contract_state'); }); it('Spec427 does not relabel already enabled non-target source contracts', function (): void { $decision = (new CoverageSourceContractResolver(new GraphContractRegistry)) ->resolve(spec427StateResourceType('conditionalAccessPolicy')); expect($decision->outcome)->toBe(CaptureOutcome::Captured) ->and($decision->contractKey)->toBe('conditionalAccessPolicy') ->and($decision->sourceContractState)->toBeNull() ->and($decision->sourceMetadata)->not->toHaveKey('source_contract_state'); }); function spec427StateResourceType(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); }