resolve(new CanonicalControlResolutionRequest( provider: 'microsoft', consumerContext: 'evidence', subjectFamilyKey: 'deviceConfiguration', workload: 'intune', signalKey: 'intune.device_configuration_drift', )); $complianceResult = $resolver->resolve(new CanonicalControlResolutionRequest( provider: 'microsoft', consumerContext: 'review', subjectFamilyKey: 'deviceCompliancePolicy', workload: 'intune', signalKey: 'intune.device_compliance_policy', )); expect($configurationResult->toArray()['control']['control_key'])->toBe('endpoint_hardening_compliance') ->and($complianceResult->toArray()['control']['control_key'])->toBe('endpoint_hardening_compliance') ->and($configurationResult->toArray()['control']['name'])->toBe($complianceResult->toArray()['control']['name']); }); it('uses supplied signal context instead of letting workload labels become primary identity', function (): void { $resolver = app(CanonicalControlResolver::class); $strongAuthentication = $resolver->resolve(new CanonicalControlResolutionRequest( provider: 'microsoft', consumerContext: 'evidence', subjectFamilyKey: 'conditional_access_policy', workload: 'entra', signalKey: 'conditional_access.require_mfa', )); $accessEnforcement = $resolver->resolve(new CanonicalControlResolutionRequest( provider: 'microsoft', consumerContext: 'evidence', subjectFamilyKey: 'conditional_access_policy', workload: 'entra', signalKey: 'conditional_access.policy_state', )); expect($strongAuthentication->toArray()['control']['control_key'])->toBe('strong_authentication') ->and($accessEnforcement->toArray()['control']['control_key'])->toBe('conditional_access_enforcement'); }); it('returns explicit unresolved reason codes instead of fallback labels', function (): void { $resolver = app(CanonicalControlResolver::class); expect($resolver->resolve(new CanonicalControlResolutionRequest( provider: 'unknown', consumerContext: 'evidence', subjectFamilyKey: 'deviceConfiguration', ))->toArray())->toMatchArray([ 'status' => 'unresolved', 'reason_code' => 'unsupported_provider', ]); expect($resolver->resolve(new CanonicalControlResolutionRequest( provider: 'microsoft', consumerContext: 'evidence', ))->toArray())->toMatchArray([ 'status' => 'unresolved', 'reason_code' => 'insufficient_context', ]); expect($resolver->resolve(new CanonicalControlResolutionRequest( provider: 'microsoft', consumerContext: 'evidence', subjectFamilyKey: 'not_bound', ))->toArray())->toMatchArray([ 'status' => 'unresolved', 'reason_code' => 'missing_binding', ]); }); it('fails deterministically when a binding context is ambiguous', function (): void { $resolver = new CanonicalControlResolver(new CanonicalControlCatalog([ spec236ControlDefinition('first_control', [ 'microsoft_bindings' => [ spec236Binding('shared_subject', primary: false), ], ]), spec236ControlDefinition('second_control', [ 'microsoft_bindings' => [ spec236Binding('shared_subject', primary: false), ], ]), ])); $result = $resolver->resolve(new CanonicalControlResolutionRequest( provider: 'microsoft', consumerContext: 'evidence', subjectFamilyKey: 'shared_subject', workload: 'entra', signalKey: 'shared.signal', ))->toArray(); expect($result['status'])->toBe('ambiguous') ->and($result['reason_code'])->toBe('ambiguous_binding') ->and($result['candidate_control_keys'])->toBe(['first_control', 'second_control']); }); it('keeps retired controls resolvable for historical references', function (): void { $resolver = new CanonicalControlResolver(new CanonicalControlCatalog([ spec236ControlDefinition('retired_control', [ 'historical_status' => 'retired', 'microsoft_bindings' => [ spec236Binding('retired_subject'), ], ]), ])); $result = $resolver->resolve(new CanonicalControlResolutionRequest( provider: 'microsoft', consumerContext: 'review', subjectFamilyKey: 'retired_subject', workload: 'entra', signalKey: 'shared.signal', ))->toArray(); expect($result['status'])->toBe('resolved') ->and($result['control']['control_key'])->toBe('retired_control') ->and($result['control']['historical_status'])->toBe('retired'); }); /** * @param array $overrides * @return array */ function spec236ControlDefinition(string $controlKey, array $overrides = []): array { return array_replace_recursive([ 'control_key' => $controlKey, 'name' => str_replace('_', ' ', ucfirst($controlKey)), 'domain_key' => 'identity_access', 'subdomain_key' => 'test_subjects', 'control_class' => 'preventive', 'summary' => 'Test summary.', 'operator_description' => 'Test operator description.', 'detectability_class' => 'direct_technical', 'evaluation_strategy' => 'state_evaluated', 'evidence_archetypes' => ['configuration_snapshot'], 'artifact_suitability' => [ 'baseline' => true, 'drift' => true, 'finding' => true, 'exception' => true, 'evidence' => true, 'review' => true, 'report' => true, ], 'historical_status' => 'active', 'microsoft_bindings' => [], ], $overrides); } /** * @return array */ function spec236Binding(string $subjectFamilyKey, bool $primary = true): array { return [ 'subject_family_key' => $subjectFamilyKey, 'workload' => 'entra', 'signal_keys' => ['shared.signal'], 'supported_contexts' => ['evidence', 'review'], 'primary' => $primary, ]; }