resolve($type, [ 'id' => 'assignment-filter-1', 'displayName' => 'Corporate devices', ], [ 'source_contract_key' => 'assignmentFilter', ]); expect($result->identityState)->toBe(IdentityState::Stable) ->and($result->keyKind)->toBe(CanonicalKeyKind::TcmResourceIdentifier) ->and($result->sourceResourceId)->toBe('assignment-filter-1') ->and($result->canonicalResourceKey)->toContain('deviceAndAppManagementAssignmentFilter:tcm_resource_identifier:') ->and($result->secondaryKeys['displayName'])->toBe('Corporate devices'); }); it('Spec417 resolves documented composite identity as derived instead of stable', function (): void { $type = spec417UnitResourceType('deviceAndAppManagementAssignmentFilter', SourceClass::Tcm); $result = app(CanonicalIdentityResolver::class)->resolve($type, [ 'displayName' => 'Corporate devices', 'platform' => 'windows10AndLater', 'assignmentFilterManagementType' => 'devices', 'rule' => '(device.osVersion -startsWith "10.")', ], [ 'source_contract_key' => 'assignmentFilter', ]); expect($result->identityState)->toBe(IdentityState::Derived) ->and($result->keyKind)->toBe(CanonicalKeyKind::SourceComposite) ->and($result->derivedClaimsAllowed)->toBeFalse() ->and($result->diagnostics['reason_code'])->toBe('source_composite_identity_resolved'); }); it('Spec417 rejects display-name-only payloads as stable identity', function (): void { $type = spec417UnitResourceType('deviceAndAppManagementAssignmentFilter', SourceClass::Tcm); $result = app(CanonicalIdentityResolver::class)->resolve($type, [ 'displayName' => 'Corporate devices', ], [ 'source_contract_key' => 'assignmentFilter', ]); expect($result->identityState)->toBe(IdentityState::MissingExternalId) ->and($result->keyKind)->toBe(CanonicalKeyKind::Unsupported) ->and($result->diagnostics['reason_code'])->toBe('missing_external_id') ->and($result->canonicalResourceKey)->not->toContain('displayName'); }); it('Spec417 keeps beta Graph identity derived and experimental', function (): void { $type = spec417UnitResourceType('roleScopeTag', SourceClass::GraphBetaExperimental); $result = app(CanonicalIdentityResolver::class)->resolve($type, [ 'id' => 'scope-tag-1', 'displayName' => 'Pilot', ], [ 'source_contract_key' => 'roleScopeTag', 'source_version' => 'beta', ]); expect($result->identityState)->toBe(IdentityState::Derived) ->and($result->keyKind)->toBe(CanonicalKeyKind::ExperimentalSourceKey) ->and($result->derivedClaimsAllowed)->toBeFalse() ->and($result->diagnostics['reason_code'])->toBe('experimental_identity_resolved'); }); it('Spec417 marks unknown resource types as unsupported identity', function (): void { $type = spec417UnitResourceType('unknownCanonicalType', SourceClass::Tcm); $result = app(CanonicalIdentityResolver::class)->resolve($type, [ 'id' => 'unknown-1', ]); expect($result->identityState)->toBe(IdentityState::UnsupportedIdentity) ->and($result->keyKind)->toBe(CanonicalKeyKind::Unsupported) ->and($result->diagnostics['reason_code'])->toBe('unsupported_identity_strategy'); }); function spec417UnitResourceType(string $canonicalType, SourceClass $sourceClass): TenantConfigurationResourceType { return new TenantConfigurationResourceType([ 'canonical_type' => $canonicalType, 'source_class' => $sourceClass->value, ]); }