resolve(spec427PermissionResourceType($canonicalType)); $permissions = $decision->sourceMetadata['permission_model']; expect($decision->sourceContractState)->toBe(CoverageSourceContractDecision::CONTRACT_BLOCKED_REPO_ADAPTER_MISSING) ->and($permissions['status'])->toBe('not_productized') ->and($permissions['required_application_permissions'])->toBe([]) ->and($permissions['delegated_permissions'])->toBe([]) ->and($permissions['admin_consent_required'])->toBeTrue() ->and($permissions['permission_failure_mode'])->toBe('block_without_provider_call') ->and($permissions['redacted_permission_context'])->toBeTrue() ->and(config("graph_contracts.types.{$canonicalType}", []))->toBe([]); })->with([ 'transportRule', 'acceptedDomain', 'appPermissionPolicy', 'meetingPolicy', ]); it('Spec427 does not add target-specific Graph contract permissions for the Exchange and Teams blocker slice', function (): void { $registered = array_keys((array) config('graph_contracts.types', [])); expect($registered) ->not->toContain('transportRule') ->not->toContain('acceptedDomain') ->not->toContain('appPermissionPolicy') ->not->toContain('meetingPolicy'); }); function spec427PermissionResourceType(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); }