// @vitest-environment node import { describe, expect, it, vi } from 'vitest'; import type { PluginPolicyClientState } from '../../electron/services/plugin-policy-client'; import { CodingCapabilityRegistryImpl, } from '../../electron/coding-plugins/registry'; import type { AdapterInvocationResult, CodingPluginAdapter, } from '../../electron/coding-plugins/registry'; import { createDataServicePluginAdapter } from '../../electron/coding-plugins/adapters/data-service'; import { createDataServiceOperations, DataServiceCloudClient, type DataServiceOperations, } from '../../electron/services/data-service-client'; import { DATA_SERVICE_PLUGIN_DEFINITION, type CodingPluginDefinition, } from '../../shared/coding-plugins'; import { productToolDetailsOfResult } from '../../shared/coding-conversation-product-tool-protocol'; import type { DataServiceErrorContext, DataServiceHostResult } from '../../shared/data-service'; import type { EffectivePluginResolver, EffectivePluginSnapshot, } from '../../electron/coding-plugins/effective-resolver'; const policy: PluginPolicyClientState = { status: 'current', revision: 7, lastVerifiedAt: 1, catalog: { schema_version: 1, catalog_version: 'catalog-1', pricing_version: null, plugins: [{ plugin_id: 'makelore.data-service', supported_contract_versions: [1], status: 'active', capabilities: [ { capability_id: 'data-service.control', operations: ['configure', 'inspect', 'list_projects', 'remove_collection', 'reset', 'remove_project'] .map((operation) => ({ operation, billing: { mode: 'included' as const, entitlement_scope: null, notice: 'Included' }, })), }, { capability_id: 'data-service.documents', operations: ['get_document', 'list_documents', 'put_document', 'delete_document'] .map((operation) => ({ operation, billing: { mode: 'included' as const, entitlement_scope: null, notice: 'Included' }, })), }, { capability_id: 'data-service.preview', operations: ['get', 'list', 'put', 'delete'].map((operation) => ({ operation, billing: { mode: 'included' as const, entitlement_scope: null, notice: 'Included' }, })), }, ], }], }, }; const context = { conversationId: 'conversation-a', runId: 'run-a', resourceId: 'resource-a', projectId: 'local-project-a', projectPath: 'C:\\projects\\demo', skillIds: ['data-service'], }; function adapter(): CodingPluginAdapter { return { pluginId: 'makelore.data-service', async inspect() { return { status: 'ready' }; }, async invoke() { return { success: true, status: 200, code: null, error: null, retryable: false, payload_schema: 'data-service.v1', data: { id: 'one', data: {}, revision: 1 }, }; }, }; } function registry(overrides: Partial[0]> = {}) { return new CodingCapabilityRegistryImpl({ policyClient: { getState: () => policy, refresh: vi.fn().mockResolvedValue(undefined) }, getEnabledPluginIds: async () => ['makelore.data-service'], adapters: [adapter()], definitions: [DATA_SERVICE_PLUGIN_DEFINITION], ...overrides, }); } describe('CodingCapabilityRegistry', () => { it('joins package, selection, grant, role, and server policy for parent resources', async () => { const resources = await registry().resolveWorkerResources({ projectPath: context.projectPath, assignedSkillIds: context.skillIds, role: 'parent', }); expect(resources.catalogRevision).toBe(7); expect(resources.pluginIds).toEqual(['makelore.data-service']); expect(resources.effectiveSkillIds).toEqual(['data-service']); expect(resources.tools.map(({ name }) => name)).toHaveLength(10); }); it('never exposes plugin resources to child workers or before the first catalog', async () => { const child = await registry().resolveWorkerResources({ projectPath: context.projectPath, assignedSkillIds: context.skillIds, role: 'child', }); expect(child.pluginIds).toEqual([]); expect(child.tools).toEqual([]); const unavailable = await registry({ policyClient: { getState: () => ({ ...policy, catalog: null, status: 'unavailable', revision: 0 }), refresh: vi.fn().mockResolvedValue(undefined), }, }).resolveWorkerResources({ projectPath: context.projectPath, assignedSkillIds: context.skillIds, role: 'parent', }); expect(unavailable.tools).toEqual([]); }); it('does not wait for policy refresh when child or legacy standalone assignments cannot consume plugins', async () => { const refresh = vi.fn(() => new Promise(() => {})); const policyClient = { getState: () => policy, refresh }; const child = await registry({ policyClient }).resolveWorkerResources({ projectPath: context.projectPath, assignedSkillIds: context.skillIds, role: 'child', }); const coreOnly = await registry({ policyClient }).resolveWorkerResources({ projectPath: context.projectPath, assignedSkillIds: ['grilling'], role: 'parent', }); expect(child.tools).toEqual([]); expect(coreOnly.effectiveSkillIds).toEqual([]); expect(refresh).not.toHaveBeenCalled(); }); it('refreshes policy before resolving an assigned server-backed parent plugin', async () => { const refresh = vi.fn().mockResolvedValue(undefined); const getEnabledPluginIds = vi.fn().mockResolvedValue(['makelore.data-service']); await registry({ policyClient: { getState: () => policy, refresh }, getEnabledPluginIds, }).resolveWorkerResources({ projectPath: context.projectPath, assignedSkillIds: context.skillIds, role: 'parent', }); expect(refresh).toHaveBeenCalledOnce(); expect(refresh.mock.invocationCallOrder[0]).toBeLessThan(getEnabledPluginIds.mock.invocationCallOrder[0]); }); it('revalidates invocation, derives the stable Pi request id, and preserves domain faults', async () => { const result = await registry().invoke({ toolName: 'data_service_get_document', context, workerRole: 'parent', effectiveSkillIds: context.skillIds, value: { collection: 'todos', document_id: 'one' }, }); expect(result.details).toMatchObject({ schema: 'makelore-capability.v1', plugin_id: 'makelore.data-service', capability_id: 'data-service.documents', operation: 'get_document', request_id: 'pi:run-a:resource-a', billing: { mode: 'included', status: 'included' }, payload_schema: 'data-service.v1', }); const invalid = await registry().invoke({ toolName: 'data_service_get_document', context, workerRole: 'parent', effectiveSkillIds: context.skillIds, value: { collection: 'todos', projectPath: 'forbidden' }, }); expect(invalid.details).toMatchObject({ success: false, code: 'plugin_input_invalid' }); const disabled = await registry({ getEnabledPluginIds: async () => [] }).invoke({ toolName: 'data_service_get_document', context, workerRole: 'parent', effectiveSkillIds: context.skillIds, value: { collection: 'todos', document_id: 'one' }, }); expect(disabled.details).toMatchObject({ success: false, code: 'plugin_not_enabled' }); const invalidIdentity = await registry().invoke({ toolName: 'data_service_get_document', context: { ...context, runId: '' }, workerRole: 'parent', effectiveSkillIds: context.skillIds, value: { collection: 'todos', document_id: 'one' }, }); expect(invalidIdentity.details).toMatchObject({ success: false, code: 'plugin_input_invalid', request_id: 'invalid-request-id', }); expect(JSON.stringify(invalidIdentity)).not.toMatch(/[0-9a-f]{8}-[0-9a-f]{4}/i); }); it('dispatches a Marketplace-hosted tool from the frozen effective snapshot without a static tool list', async () => { const hostedDefinition: CodingPluginDefinition = { id: 'makelore.game-resource', version: '1.0.0', contractVersion: 1, displayName: 'Game Resource', description: 'Hosted game resources', runtimeKind: 'platform_hosted', acquisitionMode: 'user_acquired', releaseId: 'release-game-1', provenance: { source: 'marketplace', packageRoot: 'C:/packages/game-resource' }, scope: 'project', adapterId: '', requiresBackend: true, skills: [{ id: 'game-resource', entryPath: 'skills/game-resource/SKILL.md', grants: ['game-resource.generate'], }], tools: [{ name: 'game_resource_generate', label: 'Generate', description: 'Generate an asset', capabilityId: 'game-resource.generate', operation: 'generate', roles: ['parent'], mutation: 'write', projectWriteLease: false, permissions: ['hosted.game-resource.generate'], executionMode: 'job', inputSchema: { type: 'object', additionalProperties: false, required: ['kind'], properties: { kind: { type: 'string' } }, }, }], operations: [{ capabilityId: 'game-resource.generate', operation: 'generate', toolName: 'game_resource_generate', }], surfaces: {}, }; const hostedPolicy: PluginPolicyClientState = { status: 'current', revision: 9, lastVerifiedAt: 1, catalog: { schema_version: 1, catalog_version: 'hosted-1', pricing_version: 'pricing-1', plugins: [{ plugin_id: hostedDefinition.id, supported_contract_versions: [1], status: 'active', capabilities: [{ capability_id: 'game-resource.generate', operations: [{ operation: 'generate', billing: { mode: 'platform_metered', entitlement_scope: 'plugin_usage', notice: 'Metered', unit_name: 'generation', unit_size: 1, rate_points: '1.00', minimum_charge_points: '1.00', rounding_mode: 'ceil', }, }], }], }], }, }; const frozenSnapshot: EffectivePluginSnapshot = { accountSessionId: 'account-a\u00001', projectId: context.projectId, pluginReleaseIds: ['release-game-1'], effectiveSkillIds: ['game-resource'], skillEntries: [{ id: 'game-resource', entryPath: 'skills/game-resource/SKILL.md', packageRoot: 'C:/packages/game-resource', }], toolDefinitions: hostedDefinition.tools, runtimePolicies: [{ pluginId: hostedDefinition.id, pluginVersion: hostedDefinition.version, releaseId: hostedDefinition.releaseId, contractVersion: 1, capabilityId: 'game-resource.generate', operation: 'generate', billing: hostedPolicy.catalog!.plugins[0]!.capabilities[0]!.operations[0]!.billing, }], unavailableReasons: [], }; const resolve = vi.fn(async () => frozenSnapshot); const effectiveResolver = { resolve, resolveForInvocation: resolve, getSkillSources: vi.fn(async () => []), getPolicyState: vi.fn(() => hostedPolicy), getInstalledDefinition: vi.fn(async () => hostedDefinition), } as unknown as EffectivePluginResolver; const dispatchedBilling = { mode: 'platform_metered' as const, status: 'dispatched' as const, reserved_points: '2.00', usage_amount: 1, unit: 'generation', }; const invoke = vi.fn(async ( _context: unknown, _tool: unknown, _value: unknown, onProgress?: (result: AdapterInvocationResult) => void, ) => { onProgress?.({ success: true, status: 202, code: null, error: null, retryable: false, payload_schema: 'game-resource.v1', data: { phase: 'generating', executionId: 'execution-a' }, billing: dispatchedBilling, }); return { success: true as const, status: 202, code: null, error: null, retryable: false as const, payload_schema: 'game-resource.v1', data: { executionId: 'execution-a', status: 'accepted' }, billing: dispatchedBilling, }; }); const capabilityRegistry = registry({ definitions: [], effectiveResolver, adapters: [{ pluginId: hostedDefinition.id, inspect: async () => ({ status: 'ready' }), invoke }], policyClient: { getState: () => hostedPolicy, refresh: vi.fn() }, getEnabledPluginIds: async () => [hostedDefinition.id], }); const onUpdate = vi.fn(); const result = await capabilityRegistry.invoke({ toolName: 'game_resource_generate', context: { ...context, skillIds: ['game-resource'], effectiveSnapshot: frozenSnapshot }, workerRole: 'parent', effectiveSkillIds: ['game-resource'], value: { kind: 'pixel' }, onUpdate, }); expect(effectiveResolver.getInstalledDefinition).toHaveBeenCalledWith( hostedDefinition.id, hostedDefinition.releaseId, ); expect(invoke).toHaveBeenCalledWith(expect.objectContaining({ requestId: 'pi:run-a:resource-a', workerRole: 'parent', effectiveSkillIds: ['game-resource'], pluginReleaseId: hostedDefinition.releaseId, }), hostedDefinition.tools[0], { kind: 'pixel' }, expect.any(Function)); expect(onUpdate).toHaveBeenCalledWith(expect.objectContaining({ details: expect.objectContaining({ schema: 'makelore-capability.v1', plugin_id: hostedDefinition.id, operation: 'generate', status: 202, data: { phase: 'generating', executionId: 'execution-a' }, billing: dispatchedBilling, }), })); expect(result.details).toMatchObject({ schema: 'makelore-capability.v1', plugin_id: hostedDefinition.id, capability_id: 'game-resource.generate', operation: 'generate', request_id: 'pi:run-a:resource-a', success: true, status: 202, billing: { mode: 'platform_metered', status: 'dispatched', reserved_points: '2.00' }, payload_schema: 'game-resource.v1', }); resolve.mockResolvedValue({ ...frozenSnapshot, pluginReleaseIds: ['release-game-2'], runtimePolicies: [{ ...frozenSnapshot.runtimePolicies[0]!, pluginVersion: '2.0.0', releaseId: 'release-game-2', }], }); const stale = await capabilityRegistry.invoke({ toolName: 'game_resource_generate', context: { ...context, skillIds: ['game-resource'], effectiveSnapshot: frozenSnapshot }, workerRole: 'parent', effectiveSkillIds: ['game-resource'], value: { kind: 'pixel' }, }); expect(stale.details).toMatchObject({ success: false, code: 'plugin_runtime_stale' }); expect(invoke).toHaveBeenCalledTimes(1); }); it('refuses a new plugin action from an old worker after lifecycle invalidation', async () => { const frozenSnapshot: EffectivePluginSnapshot = { accountSessionId: 'account-a\u00001', projectId: context.projectId, pluginReleaseIds: [], effectiveSkillIds: ['data-service'], skillEntries: [{ id: 'data-service', entryPath: 'skills/data-service/SKILL.md' }], toolDefinitions: DATA_SERVICE_PLUGIN_DEFINITION.tools, runtimePolicies: [], unavailableReasons: [], }; const currentSnapshot: EffectivePluginSnapshot = { ...frozenSnapshot, effectiveSkillIds: [], skillEntries: [], toolDefinitions: [], unavailableReasons: [{ pluginId: DATA_SERVICE_PLUGIN_DEFINITION.id, code: 'project_disabled', message: 'Plugin is not enabled for this project', }], }; const effectiveResolver = { resolve: vi.fn(async () => currentSnapshot), resolveForInvocation: vi.fn(async () => currentSnapshot), getSkillSources: vi.fn(async () => []), getPolicyState: vi.fn(() => policy), } as unknown as EffectivePluginResolver; const result = await registry({ effectiveResolver }).invoke({ toolName: 'data_service_get_document', context: { ...context, effectiveSnapshot: frozenSnapshot }, workerRole: 'parent', effectiveSkillIds: frozenSnapshot.effectiveSkillIds, value: { collection: 'todos', document_id: 'one' }, }); expect(result.details).toMatchObject({ success: false, code: 'plugin_not_enabled' }); expect(effectiveResolver.resolveForInvocation).toHaveBeenCalledWith(expect.objectContaining({ projectId: context.projectId, projectPath: context.projectPath, role: 'parent', })); }); it('keeps the replay identity and parses bounded Data Service faults for all ten tools', async () => { const fault = ( code: string, context?: DataServiceErrorContext, retryAfter?: number, ): DataServiceHostResult => ({ success: false, status: code === 'rate_limited' ? 429 : code === 'document_too_large' ? 413 : 409, code, error: `Representative ${code} fault`, retryable: code === 'rate_limited', ...(context ? { context } : {}), ...(retryAfter === undefined ? {} : { retry_after_seconds: retryAfter }), data: null, }); const operations: DataServiceOperations = { configure: vi.fn(async () => fault('quota_exceeded', { resource: 'collections', limit: 20, current: 20, attempted: 21, })), inspect: vi.fn(async () => fault('instance_not_found')), listProjects: vi.fn(async () => fault('quota_exceeded', { resource: 'instances', limit: 20, current: 20, attempted: 21, })), getDocument: vi.fn(async () => fault('document_not_found')), listDocuments: vi.fn(async () => fault('rate_limited', undefined, 17)), putDocument: vi.fn(async () => fault('document_too_large', { resource: 'bytes', actual: 98_305, limit: 98_304, })), deleteDocument: vi.fn(async () => fault('revision_conflict', { current_revision: 7 })), removeCollection: vi.fn(async () => fault('collection_not_found')), reset: vi.fn(async () => fault('quota_exceeded', { resource: 'documents', limit: 5_000, current: 5_000, attempted: 5_001, })), removeProject: vi.fn(async () => fault('instance_not_found')), }; const capabilityRegistry = registry({ adapters: [createDataServicePluginAdapter(operations)], }); const inputs: Readonly> = { data_service_configure: { collections: ['todos'] }, data_service_inspect: {}, data_service_list_projects: {}, data_service_get_document: { collection: 'todos', document_id: 'one' }, data_service_list_documents: { collection: 'todos' }, data_service_put_document: { collection: 'todos', document_id: 'one', data: {} }, data_service_delete_document: { collection: 'todos', document_id: 'one', confirmed: true }, data_service_remove_collection: { collection: 'todos', confirmed: true }, data_service_reset: { confirmed: true }, data_service_remove_project: { confirmed: true }, }; for (const tool of DATA_SERVICE_PLUGIN_DEFINITION.tools) { const invoke = () => capabilityRegistry.invoke({ toolName: tool.name, context, workerRole: 'parent' as const, effectiveSkillIds: context.skillIds, value: inputs[tool.name], }); const first = await invoke(); const replay = await invoke(); expect(first.details.request_id).toBe('pi:run-a:resource-a'); expect(replay.details.request_id).toBe(first.details.request_id); expect(productToolDetailsOfResult(first)).toEqual(first.details); } expect(productToolDetailsOfResult(await capabilityRegistry.invoke({ toolName: 'data_service_put_document', context, workerRole: 'parent', effectiveSkillIds: context.skillIds, value: inputs.data_service_put_document, }))).toMatchObject({ context: { resource: 'bytes', actual: 98_305, limit: 98_304 } }); expect(productToolDetailsOfResult(await capabilityRegistry.invoke({ toolName: 'data_service_delete_document', context, workerRole: 'parent', effectiveSkillIds: context.skillIds, value: inputs.data_service_delete_document, }))).toMatchObject({ context: { current_revision: 7 } }); expect(productToolDetailsOfResult(await capabilityRegistry.invoke({ toolName: 'data_service_list_documents', context, workerRole: 'parent', effectiveSkillIds: context.skillIds, value: inputs.data_service_list_documents, }))).toMatchObject({ retry_after_seconds: 17 }); }); it('keeps the Pi identity through the one authoritative 401 refresh', async () => { const fetchImpl = vi.fn() .mockResolvedValueOnce(new Response(null, { status: 401 })) .mockResolvedValueOnce(new Response(JSON.stringify({ id: 'one', data: { done: false }, revision: 7, created_at: '2026-08-27T00:00:00Z', updated_at: '2026-08-27T00:00:00Z', }), { status: 200, headers: { 'content-type': 'application/json' } })); const getAccessToken = vi.fn(async (options?: { forceRefresh?: boolean }) => ( options?.forceRefresh ? 'refreshed-token' : 'stale-token' )); const client = new DataServiceCloudClient({ fetchImpl, getAccessToken }); const operations = createDataServiceOperations({ client, projects: { requireActiveRealProjectWithIdentity: vi.fn(async () => ({ projectId: '11111111-1111-4111-8111-111111111111', })) as never, }, }); const result = await registry({ adapters: [createDataServicePluginAdapter(operations)], }).invoke({ toolName: 'data_service_get_document', context, workerRole: 'parent', effectiveSkillIds: context.skillIds, value: { collection: 'todos', document_id: 'one' }, }); expect(result.details).toMatchObject({ success: true, request_id: 'pi:run-a:resource-a', data: { id: 'one', revision: 7 }, }); expect(fetchImpl).toHaveBeenCalledTimes(2); expect(getAccessToken).toHaveBeenNthCalledWith(1, { fetchImpl }); expect(getAccessToken).toHaveBeenNthCalledWith(2, { fetchImpl, forceRefresh: true }); }); });