fix(coding): remediate ML-07 plugin authority
This commit is contained in:
@@ -1,11 +1,20 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
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 { 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 } from '../../shared/coding-plugins';
|
||||
import { productToolDetailsOfResult } from '../../shared/coding-conversation-product-tool-protocol';
|
||||
import type { DataServiceErrorContext, DataServiceHostResult } from '../../shared/data-service';
|
||||
|
||||
const policy: PluginPolicyClientState = {
|
||||
status: 'current',
|
||||
@@ -80,6 +89,7 @@ function registry(overrides: Partial<ConstructorParameters<typeof CodingCapabili
|
||||
policyClient: { getState: () => policy },
|
||||
getEnabledPluginIds: async () => ['makelore.data-service'],
|
||||
adapters: [adapter()],
|
||||
definitions: [DATA_SERVICE_PLUGIN_DEFINITION],
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
@@ -150,4 +160,124 @@ describe('CodingCapabilityRegistry', () => {
|
||||
});
|
||||
expect(JSON.stringify(invalidIdentity)).not.toMatch(/[0-9a-f]{8}-[0-9a-f]{4}/i);
|
||||
});
|
||||
|
||||
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<never> => ({
|
||||
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<Record<string, unknown>> = {
|
||||
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<typeof fetch>()
|
||||
.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 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user