Files
makelore/tests/unit/coding-plugin-marketplace-client.test.ts

1537 lines
65 KiB
TypeScript

// @vitest-environment node
import { createHash, generateKeyPairSync, sign } from 'node:crypto';
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
import path from 'node:path';
import AdmZip from 'adm-zip';
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
AccountPluginCache,
type AccountBinding,
type MarketplaceLibrarySnapshot,
} from '../../electron/coding-plugins/account-plugin-cache';
import {
MarketplaceClientError,
createMarketplaceClient,
type MarketplaceClient,
type ResolveRequest,
} from '../../electron/coding-plugins/marketplace-client';
import {
PluginPackageStore,
type DownloadGrant,
type ResolveSnapshot,
} from '../../electron/coding-plugins/package-store';
import {
buildPluginReleaseDescriptor,
serializePluginReleaseDescriptor,
} from '../../electron/coding-plugins/release-descriptor';
const ACCOUNT_A: AccountBinding = { accountKey: 'a'.repeat(64), epoch: 1 };
const ACCOUNT_B: AccountBinding = { accountKey: 'b'.repeat(64), epoch: 2 };
const PLUGIN_ID = 'makelore.example';
const RELEASE_ID = 'release-1';
const ADMISSION_ID = 'admission-1';
const SHA256 = 'a'.repeat(64);
function deferred<T>() {
let resolve!: (value: T) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((yes, no) => { resolve = yes; reject = no; });
return { promise, resolve, reject };
}
const catalogPage = {
items: [{
plugin_id: PLUGIN_ID,
title: 'Example',
summary: 'Example Skill',
category: 'tools',
tags: ['example'],
provider_display_name: 'MakeLore',
runtime_kind: 'skill_only',
runtime_status: 'enabled',
acquisition: 'free',
usage_billing: 'included',
included_operation_count: 0,
metered_operation_count: 0,
stable_version: '1.0.0',
beta_version: null,
}],
next_cursor: null,
total: 1,
catalog_generation: 7,
};
function response(body: unknown, init: ResponseInit = {}, headers: Record<string, string> = {}): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json', ...headers },
...init,
});
}
function requestBody(fetcher: ReturnType<typeof vi.fn>, index: number): Record<string, unknown> {
return JSON.parse(fetcher.mock.calls[index]?.[1]?.body as string) as Record<string, unknown>;
}
function makeResolveResult(input: ResolveRequest, itemOverrides: Record<string, unknown> = {}): ResolveSnapshot {
return {
resolveRequestId: input.resolveRequestId ?? 'generated',
resolveRequestDigest: input.resolveRequestDigest ?? SHA256,
items: [{
pluginId: PLUGIN_ID,
action: 'install',
releaseId: RELEASE_ID,
version: '1.0.0',
sha256: SHA256,
sizeBytes: 1,
releaseAdmissionId: ADMISSION_ID,
expiresAt: '2100-01-01T00:00:00Z',
channel: input.channel,
reason: null,
...itemOverrides,
}],
catalogGeneration: 7,
etag: '"plugins-7-tp-none"',
stale: false,
};
}
function buildSkillOnlyArchive(
overrides: Readonly<Record<string, Buffer | string>> = {},
): Buffer {
const zip = new AdmZip();
const files = new Map<string, Buffer | string>([
['plugin.json', JSON.stringify({
$schema: 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json',
name: PLUGIN_ID,
version: '1.0.0',
description: 'Example Skill',
author: { name: 'MakeLore' },
extensions: { 'com.makelore': { capabilityManifest: './com.makelore/capability.json' } },
})],
['com.makelore/capability.json', JSON.stringify({
schemaVersion: 2,
pluginId: PLUGIN_ID,
contractVersion: 1,
scope: 'project',
runtime: { kind: 'skill_only' },
skills: [{ id: 'example-skill', entry: '../skills/example-skill/SKILL.md', grants: [] }],
tools: [],
})],
['skills/example-skill/SKILL.md', '# Example\n'],
]);
for (const [filePath, content] of Object.entries(overrides)) files.set(filePath, content);
for (const [filePath, content] of files) {
zip.addFile(filePath, Buffer.isBuffer(content) ? content : Buffer.from(content));
}
return zip.toBuffer();
}
function buildHostedArchive(): Buffer {
return buildSkillOnlyArchive({
'com.makelore/capability.json': JSON.stringify({
schemaVersion: 2,
pluginId: PLUGIN_ID,
contractVersion: 1,
scope: 'project',
runtime: { kind: 'platform_hosted', protocol: 'makelore-hosted.v1' },
skills: [{ id: 'example-skill', entry: '../skills/example-skill/SKILL.md', grants: ['example.write'] }],
tools: [{
name: 'example_write', label: 'Write', description: 'Write an example output',
capabilityId: 'example.write', operation: 'write', roles: ['parent'],
mutation: 'write', projectWriteLease: true,
permissions: ['hosted.example.write'], executionMode: 'synchronous',
inputSchema: {
type: 'object', additionalProperties: false, required: ['value'],
properties: { value: { type: 'string', minLength: 1, maxLength: 100 } },
},
outputSchema: {
type: 'object', additionalProperties: false, required: ['saved'],
properties: { saved: { type: 'boolean' } },
},
}],
}),
});
}
function signedGrant(
archive: Buffer,
options: {
readonly releaseId?: string;
readonly signingKeyId?: string;
readonly minMakeloreVersion?: string;
readonly maxMakeloreVersion?: string | null;
} = {},
): { grant: DownloadGrant; publicKey: Buffer; signature: string } {
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
const sha256 = createHash('sha256').update(archive).digest('hex');
const releaseId = options.releaseId ?? RELEASE_ID;
const descriptor = buildPluginReleaseDescriptor({
pluginId: PLUGIN_ID,
version: '1.0.0',
packageSchemaVersion: 2,
contractVersion: 1,
minMakeloreVersion: options.minMakeloreVersion ?? '1.0.0',
maxMakeloreVersion: options.maxMakeloreVersion ?? null,
artifact: { sha256, sizeBytes: archive.byteLength },
});
const signature = sign(null, serializePluginReleaseDescriptor(descriptor), privateKey).toString('base64url');
return {
grant: {
releaseAdmissionId: ADMISSION_ID,
releaseId,
pluginId: PLUGIN_ID,
version: '1.0.0',
packageSchemaVersion: 2,
contractVersion: 1,
minMakeloreVersion: options.minMakeloreVersion ?? '1.0.0',
maxMakeloreVersion: options.maxMakeloreVersion ?? null,
sizeBytes: archive.byteLength,
sha256,
signingKeyId: options.signingKeyId ?? 'test-key',
descriptorSignature: signature,
expiresAt: '2100-01-01T00:00:00Z',
contentUrl: `/api/plugin-marketplace/v1/releases/${releaseId}/content?release_admission_id=${ADMISSION_ID}`,
},
publicKey: publicKey.export({ type: 'spki', format: 'der' }) as Buffer,
signature,
};
}
describe('Marketplace client and account cache', () => {
afterEach(() => vi.restoreAllMocks());
function rawLibraryEntry(pluginId: string, title: string) {
return {
plugin_id: pluginId,
title,
summary: title,
category: 'tools',
acquisition: 'free',
acquisition_mode: 'user_acquired',
catalog_status: 'active',
runtime_status: 'enabled',
acquired_at: '2026-08-28T00:00:00Z',
removed_at: null,
stable_version: '1.0.0',
beta_version: null,
};
}
function rawLibrary(entries: readonly Record<string, unknown>[]) {
return { items: entries, total: entries.length };
}
it('keeps Library and admission snapshots isolated by account and invalidates on logout', () => {
const cache = new AccountPluginCache();
const aLibrary: MarketplaceLibrarySnapshot = {
items: [{
pluginId: PLUGIN_ID,
title: 'A',
summary: 'A',
category: 'tools',
acquisition: 'free',
acquisitionMode: 'user_acquired',
catalogStatus: 'active',
runtimeStatus: 'enabled',
acquiredAt: null,
removedAt: null,
stableVersion: '1.0.0',
betaVersion: null,
}],
total: 1,
stale: false,
fetchedAt: 1,
};
cache.setLibrary(ACCOUNT_A, aLibrary);
cache.setResolve(ACCOUNT_A, 'resolve-a', {
resolveRequestId: 'resolve-a',
resolveRequestDigest: SHA256,
items: [{ pluginId: PLUGIN_ID, action: 'install', releaseId: RELEASE_ID }],
catalogGeneration: 1,
etag: null,
stale: false,
});
expect(cache.getLibrary(ACCOUNT_A)).toEqual(aLibrary);
expect(cache.getLibrary(ACCOUNT_B)).toBeNull();
expect(cache.getResolve(ACCOUNT_B, 'resolve-a')).toBeNull();
expect(cache.referencedReleaseIds()).toEqual(new Set([RELEASE_ID]));
cache.invalidateAll();
expect(cache.getLibrary(ACCOUNT_A)).toBeNull();
expect(cache.referencedReleaseIds()).toEqual(new Set());
});
it('does not let an older Library read overwrite a newer mutation/read intent', async () => {
const oldRead = deferred<Response>();
const mutation = deferred<Response>();
const newestRead = deferred<Response>();
const fetcher = vi.fn<typeof fetch>()
.mockImplementationOnce(() => oldRead.promise)
.mockImplementationOnce(() => mutation.promise)
.mockImplementationOnce(() => newestRead.promise);
const cache = new AccountPluginCache();
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => 'token',
getAccountBinding: () => ACCOUNT_A,
subscribeSession: () => () => undefined,
accountCache: cache,
});
const old = client.readLibrary();
const acquired = client.acquire(PLUGIN_ID);
mutation.resolve(response(rawLibraryEntry(PLUGIN_ID, 'acquired')));
await Promise.resolve();
newestRead.resolve(response(rawLibrary([rawLibraryEntry(PLUGIN_ID, 'newest')])));
oldRead.resolve(response(rawLibrary([rawLibraryEntry(PLUGIN_ID, 'old')])));
await Promise.all([old, acquired]);
expect(cache.getLibrary(ACCOUNT_A)?.items[0]?.title).toBe('newest');
});
it('keeps the latest same-account mutation intent when mutation responses complete out of order', async () => {
const firstMutation = deferred<Response>();
const secondMutation = deferred<Response>();
const firstRead = deferred<Response>();
const secondRead = deferred<Response>();
const fetcher = vi.fn<typeof fetch>()
.mockImplementationOnce(() => firstMutation.promise)
.mockImplementationOnce(() => secondMutation.promise)
.mockImplementationOnce(() => secondRead.promise)
.mockImplementationOnce(() => firstRead.promise);
const cache = new AccountPluginCache();
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => 'token',
getAccountBinding: () => ACCOUNT_A,
subscribeSession: () => () => undefined,
accountCache: cache,
});
const first = client.acquire('makelore.first');
const second = client.acquire('makelore.second');
secondMutation.resolve(response(rawLibraryEntry('makelore.second', 'second mutation')));
await Promise.resolve();
secondRead.resolve(response(rawLibrary([rawLibraryEntry('makelore.second', 'second newest')])));
firstMutation.resolve(response(rawLibraryEntry('makelore.first', 'first mutation')));
await Promise.resolve();
firstRead.resolve(response(rawLibrary([rawLibraryEntry('makelore.first', 'first stale')])));
await Promise.all([first, second]);
expect(cache.getLibrary(ACCOUNT_A)?.items[0]?.title).toBe('second newest');
});
it('parses bounded catalog metadata, refreshes exactly once after a 401, and marks stale data', async () => {
const fetcher = vi.fn<typeof fetch>();
fetcher
.mockResolvedValueOnce(new Response('', { status: 401 }))
.mockResolvedValueOnce(response(catalogPage, {}, {
ETag: '"plugins-7-tp-none"',
'X-Plugin-Catalog-Generation': '7',
'X-Token-Point-Pricing-Version': 'none',
}));
const refresh = vi.fn(async () => 'refreshed-token');
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async (options) => options?.forceRefresh ? refresh() : 'initial-token',
getAccountBinding: () => ACCOUNT_A,
subscribeSession: () => () => undefined,
});
await expect(client.readCatalog({ limit: 10 })).resolves.toMatchObject({
total: 1,
etag: '"plugins-7-tp-none"',
stale: false,
});
expect(fetcher).toHaveBeenCalledTimes(2);
expect(refresh).toHaveBeenCalledTimes(1);
expect(fetcher.mock.calls[1]?.[1]?.headers).toMatchObject({ Authorization: 'Bearer refreshed-token' });
fetcher.mockRejectedValueOnce(new Error('offline'));
await expect(client.readCatalog({ limit: 10 })).resolves.toMatchObject({ stale: true, total: 1 });
});
it('accepts a weak composite ETag from compressed Marketplace responses and reuses it verbatim', async () => {
const pricingVersionId = '00000000-0000-0000-0000-000000000420';
const etag = `W/"plugins-2-tp-${pricingVersionId}"`;
const fetcher = vi.fn<typeof fetch>()
.mockResolvedValueOnce(response({ ...catalogPage, catalog_generation: 2 }, {}, {
ETag: etag,
'X-Plugin-Catalog-Generation': '2',
'X-Token-Point-Pricing-Version': pricingVersionId,
}))
.mockResolvedValueOnce(new Response(null, { status: 304 }));
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => null,
subscribeSession: () => () => undefined,
});
await expect(client.readCatalog({ limit: 10 })).resolves.toMatchObject({
total: 1,
etag,
generation: 2,
pricingVersionId,
stale: false,
});
await expect(client.readCatalog({ limit: 10 })).resolves.toMatchObject({ etag, stale: false });
expect(fetcher.mock.calls[1]?.[1]?.headers).toMatchObject({ 'If-None-Match': etag });
});
it('refreshes download authentication at most once before accepting the artifact', async () => {
const archive = Buffer.from('signed-artifact');
const { grant } = signedGrant(archive);
const fetcher = vi.fn<typeof fetch>()
.mockResolvedValueOnce(new Response(null, { status: 401 }))
.mockResolvedValueOnce(new Response(archive, {
status: 200,
headers: { 'content-length': String(archive.byteLength) },
}));
const refresh = vi.fn(async () => 'refreshed-token');
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async (options) => options?.forceRefresh ? refresh() : 'initial-token',
getAccountBinding: () => ACCOUNT_A,
subscribeSession: () => () => undefined,
});
const downloaded = await client.downloadContent(grant);
expect(Buffer.from(downloaded)).toEqual(archive);
expect(fetcher).toHaveBeenCalledTimes(2);
expect(refresh).toHaveBeenCalledTimes(1);
expect(fetcher.mock.calls[1]?.[1]?.headers).toMatchObject({ Authorization: 'Bearer refreshed-token' });
});
it('parses the detail DTO with its nested release projection', async () => {
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(response({
...catalogPage.items[0],
description_markdown: 'Example details',
permissions: ['plugin.example.read'],
operations: [],
stable_release: null,
beta_release: null,
}, {}, {
ETag: '"plugins-7-tp-none"',
'X-Plugin-Catalog-Generation': '7',
'X-Token-Point-Pricing-Version': 'none',
}));
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => null,
subscribeSession: () => () => undefined,
});
await expect(client.readDetail(PLUGIN_ID)).resolves.toMatchObject({
pluginId: PLUGIN_ID,
descriptionMarkdown: 'Example details',
permissions: ['plugin.example.read'],
etag: '"plugins-7-tp-none"',
});
});
it('creates a new resolve identity per logical sync and preserves an explicit replay identity', async () => {
const fetcher = vi.fn<typeof fetch>().mockImplementation(async (_input, init) => {
const request = JSON.parse(init?.body as string) as Record<string, unknown>;
return response({
resolve_request_id: request.resolve_request_id,
resolve_request_digest: request.resolve_request_digest,
items: [],
catalog_generation: 7,
}, {}, { ETag: '"plugins-7-tp-none"' });
});
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => 'token',
getAccountBinding: () => ACCOUNT_A,
subscribeSession: () => () => undefined,
});
const base: ResolveRequest = {
makeloreVersion: '1.0.0',
channel: 'stable',
installed: [],
};
await client.resolve(base);
await client.resolve(base);
const firstId = requestBody(fetcher, 0).resolve_request_id;
const secondId = requestBody(fetcher, 1).resolve_request_id;
expect(secondId).not.toBe(firstId);
expect(requestBody(fetcher, 1).resolve_request_digest).toBe(requestBody(fetcher, 0).resolve_request_digest);
const replay = { ...base, resolveRequestId: 'makelore-resolve-logical-sync-a' };
await client.resolve(replay);
await client.resolve(replay);
expect(requestBody(fetcher, 2).resolve_request_id).toBe('makelore-resolve-logical-sync-a');
expect(requestBody(fetcher, 3).resolve_request_id).toBe('makelore-resolve-logical-sync-a');
});
it('keeps one generated resolve identity across the authenticated retry of a logical sync', async () => {
const fetcher = vi.fn<typeof fetch>()
.mockResolvedValueOnce(new Response('', { status: 401 }))
.mockImplementationOnce(async (_input, init) => {
const request = JSON.parse(init?.body as string) as Record<string, unknown>;
return response({
resolve_request_id: request.resolve_request_id,
resolve_request_digest: request.resolve_request_digest,
items: [],
catalog_generation: 7,
}, {}, { ETag: '"plugins-7-tp-none"' });
});
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async ({ forceRefresh } = {}) => forceRefresh ? 'refreshed-token' : 'initial-token',
getAccountBinding: () => ACCOUNT_A,
subscribeSession: () => () => undefined,
});
await client.resolve({ makeloreVersion: '1.0.0', channel: 'stable', installed: [] });
expect(requestBody(fetcher, 1).resolve_request_id).toBe(requestBody(fetcher, 0).resolve_request_id);
expect(requestBody(fetcher, 1).resolve_request_digest).toBe(requestBody(fetcher, 0).resolve_request_digest);
});
it('rejects a response body above the bounded DTO limit', async () => {
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(new Response('x'.repeat(2_100_000), { status: 200 }));
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => null,
subscribeSession: () => () => undefined,
});
await expect(client.readCatalog({ limit: 10 })).rejects.toMatchObject({ code: 'marketplace_response_too_large' });
});
it('applies one deadline to response headers and a body that never completes', async () => {
const neverBody = new ReadableStream<Uint8Array>({ start() { /* intentionally never closes */ } });
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(new Response(neverBody, { status: 200 }));
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
requestTimeoutMs: 20,
getAccessToken: async () => null,
subscribeSession: () => () => undefined,
});
await expect(client.readCatalog({ limit: 10 })).rejects.toMatchObject({ code: 'marketplace_request_failed' });
});
it('preserves a bounded server release status from the response body', async () => {
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(response({
success: false,
code: 'plugin_release_yanked',
error: 'Release is no longer available',
}, { status: 409 }));
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => null,
subscribeSession: () => () => undefined,
});
await expect(client.readCatalog({ limit: 10 })).rejects.toMatchObject({
code: 'plugin_release_yanked', status: 409,
});
});
it('rejects a malformed authenticated response with a stable client error', async () => {
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(response({ items: [] }));
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => 'token',
getAccountBinding: () => ACCOUNT_A,
subscribeSession: () => () => undefined,
});
await expect(client.readLibrary()).rejects.toBeInstanceOf(MarketplaceClientError);
});
});
describe('PluginPackageStore', () => {
let temporaryRoot: string | null = null;
afterEach(async () => {
if (temporaryRoot) await rm(temporaryRoot, { recursive: true, force: true });
temporaryRoot = null;
});
it('verifies and atomically installs a signed Skill-only package without account data in index', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
const resolve = vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
}));
const issueDownload = vi.fn(async () => grant);
const marketplace: MarketplaceClient = {
resolve,
issueDownload,
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
accountCache: new AccountPluginCache(),
getAccountBinding: () => ACCOUNT_A,
});
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.resolves.toMatchObject({ pluginId: PLUGIN_ID, releaseId: RELEASE_ID, status: 'installed' });
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({
pluginId: PLUGIN_ID,
releaseId: RELEASE_ID,
version: '1.0.0',
});
const index = JSON.parse(await readFile(path.join(temporaryRoot, 'index.json'), 'utf8')) as Record<string, unknown>;
expect(JSON.stringify(index)).not.toContain('account');
expect(JSON.stringify(index)).not.toContain('admission');
expect(JSON.stringify(index)).not.toContain('token');
expect(resolve).toHaveBeenCalledTimes(1);
expect(issueDownload).toHaveBeenCalledWith({ releaseId: RELEASE_ID, releaseAdmissionId: ADMISSION_ID });
});
it('persists a signed platform-hosted package with its write-lease tool contract', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildHostedArchive();
const { grant, publicKey } = signedGrant(archive);
const marketplace = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256, sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: vi.fn(async () => archive),
getCurrentAccountBinding: () => ACCOUNT_A,
} as unknown as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.resolves.toMatchObject({ status: 'installed', pluginId: PLUGIN_ID });
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({
runtimeKind: 'platform_hosted',
definition: {
runtimeKind: 'platform_hosted', requiresBackend: true,
tools: [{ name: 'example_write', projectWriteLease: true, executionMode: 'synchronous' }],
},
});
});
it('assigns a distinct logical resolve identity to each new Package Store sync', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
const resolve = vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
}));
const marketplace: MarketplaceClient = {
resolve,
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
const first = resolve.mock.calls[0]?.[0].resolveRequestId;
const second = resolve.mock.calls[1]?.[0].resolveRequestId;
expect(first).toMatch(/^makelore-resolve-/u);
expect(second).toMatch(/^makelore-resolve-/u);
expect(second).not.toBe(first);
});
it('removes only releases with no account or active-worker reference', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
const accountCache = new AccountPluginCache();
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
accountCache,
getAccountBinding: () => ACCOUNT_A,
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
accountCache.setResolve(ACCOUNT_A, 'protected', makeResolveResult({ makeloreVersion: '1.0.0' }, {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
}));
await expect(store.removeUnused(PLUGIN_ID)).resolves.toMatchObject({
status: 'kept', pluginId: PLUGIN_ID, releaseId: RELEASE_ID,
});
accountCache.invalidateAll();
store.registerActiveWorker(RELEASE_ID);
await expect(store.removeUnused(PLUGIN_ID)).resolves.toMatchObject({
status: 'kept', pluginId: PLUGIN_ID, releaseId: RELEASE_ID,
});
store.releaseActiveWorker(RELEASE_ID);
await expect(store.removeUnused(PLUGIN_ID)).resolves.toMatchObject({
status: 'removed', pluginId: PLUGIN_ID, releaseId: RELEASE_ID,
});
await expect(store.readInstalledIndex()).resolves.toEqual([]);
await expect(store.removeUnused(PLUGIN_ID)).resolves.toEqual({
status: 'removed', pluginId: PLUGIN_ID, reason: 'none',
});
});
it('preserves the old immutable release when index replacement fails', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
const replacement = signedGrant(archive, { releaseId: 'release-2' });
const replacementMarketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: replacement.grant.releaseId,
sha256: replacement.grant.sha256,
sizeBytes: replacement.grant.sizeBytes,
})),
issueDownload: vi.fn(async () => replacement.grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const failingStore = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace: replacementMarketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', replacement.publicKey]]),
getAccountBinding: () => ACCOUNT_A,
writeIndex: async () => { throw new Error('simulated index interruption'); },
});
await expect(failingStore.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.rejects.toMatchObject({ code: 'plugin_install_failed' });
await expect(failingStore.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: RELEASE_ID });
await expect(stat(path.join(temporaryRoot, 'packages', PLUGIN_ID, 'release-2')))
.resolves.toMatchObject({ isDirectory: expect.any(Function) });
const recoveredStore = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace: replacementMarketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', replacement.publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await expect(recoveredStore.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.resolves.toMatchObject({ status: 'installed', releaseId: 'release-2' });
await expect(recoveredStore.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: 'release-2' });
});
it('uninstalls a device package without removing the account Library snapshot', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
const accountCache = new AccountPluginCache();
const library: MarketplaceLibrarySnapshot = {
items: [{
pluginId: PLUGIN_ID, title: 'Example', summary: 'Example', category: 'tools',
acquisition: 'free', acquisitionMode: 'user_acquired', catalogStatus: 'active',
runtimeStatus: 'enabled', acquiredAt: '2026-08-28T00:00:00Z', removedAt: null,
stableVersion: '1.0.0', betaVersion: null,
}], total: 1, stale: false, fetchedAt: 1,
};
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256, sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
readLibrary: vi.fn(async () => library),
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot, marketplace, accountCache, getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([['test-key', publicKey]]), clientVersion: '1.0.0',
});
await store.syncLibrary();
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
await expect(store.uninstall(PLUGIN_ID)).resolves.toMatchObject({ status: 'removed', pluginId: PLUGIN_ID });
expect(accountCache.getLibrary(ACCOUNT_A)).toEqual(library);
expect(accountCache.referencedReleaseIds()).toEqual(new Set());
await expect(store.getInstalled(PLUGIN_ID)).resolves.toBeNull();
});
it('makes a cached rollback the Package Store current selection while retaining both immutable releases', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const first = signedGrant(archive, { releaseId: 'release-1' });
const second = signedGrant(archive, { releaseId: 'release-2', signingKeyId: 'test-key-2' });
let current = second;
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: current.grant.releaseId,
sha256: current.grant.sha256,
sizeBytes: current.grant.sizeBytes,
})),
issueDownload: vi.fn(async () => current.grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot, marketplace, getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([['test-key', first.publicKey], ['test-key-2', second.publicKey]]),
clientVersion: '1.0.0', now: (() => { let value = 1; return () => value++ * 1_000; })(),
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
current = first;
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
await expect(store.readInstalledIndex()).resolves.toHaveLength(2);
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: 'release-1' });
const index = JSON.parse(await readFile(path.join(temporaryRoot, 'index.json'), 'utf8')) as {
releases: Array<{ release_id: string }>;
};
expect(index.releases.map(({ release_id }) => release_id)).toEqual(['release-2', 'release-1']);
store.registerActiveWorker('release-1');
await expect(store.uninstall(PLUGIN_ID)).resolves.toEqual({
status: 'kept', pluginId: PLUGIN_ID, reason: 'active_worker_reference',
});
await expect(store.readInstalledIndex()).resolves.toHaveLength(1);
await expect(store.getInstalled(PLUGIN_ID)).resolves.toBeNull();
});
it('persists the installed channel and client range, and fails closed after a client upgrade', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const stable = signedGrant(archive, { releaseId: 'release-stable', minMakeloreVersion: '1.0.0', maxMakeloreVersion: '1.5.0' });
let current = stable;
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: current.grant.releaseId,
sha256: current.grant.sha256,
sizeBytes: current.grant.sizeBytes,
})),
issueDownload: vi.fn(async () => current.grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
getAccountBinding: () => ACCOUNT_A,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', stable.publicKey]]),
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0', channel: 'stable' });
const index = JSON.parse(await readFile(path.join(temporaryRoot, 'index.json'), 'utf8')) as {
releases: Array<Record<string, unknown>>;
};
expect(index.releases[0]).toMatchObject({
channel: 'stable',
min_makelore_version: '1.0.0',
max_makelore_version: '1.5.0',
});
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({
channel: 'stable', minMakeloreVersion: '1.0.0', maxMakeloreVersion: '1.5.0',
});
const upgradedStore = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
getAccountBinding: () => ACCOUNT_A,
clientVersion: '2.0.0',
keyStore: new Map([['test-key', stable.publicKey]]),
});
await expect(upgradedStore.getInstalled(PLUGIN_ID)).resolves.toMatchObject({
unavailableReason: 'plugin_incompatible_client',
});
await expect(upgradedStore.readInstalledIndex()).resolves.toHaveLength(1);
});
it('persists explicit Stable and Beta intent when resolve keeps the same Release', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
let action: 'install' | 'keep' = 'install';
const issueDownload = vi.fn(async () => grant);
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
action, sha256: grant.sha256, sizeBytes: grant.sizeBytes,
})),
issueDownload,
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
getAccountBinding: () => ACCOUNT_A,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0', channel: 'stable' });
action = 'keep';
await expect(store.resolveAndInstall({
pluginId: PLUGIN_ID, makeloreVersion: '1.0.0', channel: 'beta', explicitBeta: true,
})).resolves.toMatchObject({ status: 'kept', releaseId: RELEASE_ID, channel: 'beta' });
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: RELEASE_ID, channel: 'beta' });
await expect(store.resolveAndInstall({
pluginId: PLUGIN_ID, makeloreVersion: '1.0.0', channel: 'stable',
})).resolves.toMatchObject({ status: 'kept', releaseId: RELEASE_ID, channel: 'stable' });
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: RELEASE_ID, channel: 'stable' });
expect(issueDownload).toHaveBeenCalledOnce();
});
it('persists explicit channel intent when resolve reuses a cached immutable Release', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
let action: 'install' | 'update' = 'install';
const issueDownload = vi.fn(async () => grant);
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
action, sha256: grant.sha256, sizeBytes: grant.sizeBytes,
})),
issueDownload,
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
getAccountBinding: () => ACCOUNT_A,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0', channel: 'stable' });
action = 'update';
await expect(store.resolveAndInstall({
pluginId: PLUGIN_ID, makeloreVersion: '1.0.0', channel: 'beta', explicitBeta: true,
})).resolves.toMatchObject({ status: 'kept', releaseId: RELEASE_ID, channel: 'beta' });
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: RELEASE_ID, channel: 'beta' });
await expect(store.resolveAndInstall({
pluginId: PLUGIN_ID, makeloreVersion: '1.0.0', channel: 'stable',
})).resolves.toMatchObject({ status: 'kept', releaseId: RELEASE_ID, channel: 'stable' });
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: RELEASE_ID, channel: 'stable' });
expect(issueDownload).toHaveBeenCalledOnce();
});
it('removes only old releases and never guesses a new current selection from installedAt', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const first = signedGrant(archive, { releaseId: 'release-old' });
const second = signedGrant(archive, { releaseId: 'release-current', signingKeyId: 'test-key-2' });
let current = first;
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: current.grant.releaseId,
sha256: current.grant.sha256,
sizeBytes: current.grant.sizeBytes,
})),
issueDownload: vi.fn(async () => current.grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot, marketplace, getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([['test-key', first.publicKey], ['test-key-2', second.publicKey]]), clientVersion: '1.0.0',
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
current = second;
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
await expect(store.removeUnused(PLUGIN_ID)).resolves.toMatchObject({ status: 'kept', releaseId: 'release-current' });
await expect(store.readInstalledIndex()).resolves.toHaveLength(1);
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: 'release-current' });
await expect(readFile(path.join(temporaryRoot, 'current.json'), 'utf8')).resolves.toContain('release-current');
});
it('explicitly uninstalls every unprotected release without selecting by install time', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const first = signedGrant(archive, { releaseId: 'release-old' });
const second = signedGrant(archive, { releaseId: 'release-current', signingKeyId: 'test-key-2' });
let current = first;
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: current.grant.releaseId,
sha256: current.grant.sha256,
sizeBytes: current.grant.sizeBytes,
})),
issueDownload: vi.fn(async () => current.grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([['test-key', first.publicKey], ['test-key-2', second.publicKey]]),
clientVersion: '1.0.0',
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
current = second;
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
await expect(store.uninstall(PLUGIN_ID)).resolves.toMatchObject({
status: 'removed', pluginId: PLUGIN_ID, releaseId: 'release-current',
});
await expect(store.readInstalledIndex()).resolves.toEqual([]);
await expect(store.getInstalled(PLUGIN_ID)).resolves.toBeNull();
await expect(readFile(path.join(temporaryRoot, 'current.json'), 'utf8')).resolves.toContain('"current":{}');
});
it('removes an unprotected current Release while retaining only an active-worker Release', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const protectedRelease = signedGrant(archive, { releaseId: 'release-worker' });
const selectedRelease = signedGrant(archive, {
releaseId: 'release-current', signingKeyId: 'test-key-current',
});
let current = protectedRelease;
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: current.grant.releaseId,
sha256: current.grant.sha256,
sizeBytes: current.grant.sizeBytes,
})),
issueDownload: vi.fn(async () => current.grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot, marketplace, getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([
['test-key', protectedRelease.publicKey],
['test-key-current', selectedRelease.publicKey],
]),
clientVersion: '1.0.0',
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
current = selectedRelease;
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
store.registerActiveWorker('release-worker');
await expect(store.uninstall(PLUGIN_ID)).resolves.toEqual({
status: 'kept', pluginId: PLUGIN_ID, reason: 'active_worker_reference',
});
await expect(store.readInstalledIndex()).resolves.toEqual([
expect.objectContaining({ releaseId: 'release-worker' }),
]);
await expect(store.getInstalled(PLUGIN_ID)).resolves.toBeNull();
store.releaseActiveWorker('release-worker');
await expect(store.uninstall(PLUGIN_ID)).resolves.toEqual({
status: 'removed', pluginId: PLUGIN_ID, reason: 'none',
});
await expect(store.readInstalledIndex()).resolves.toEqual([]);
});
it('finishes an explicit uninstall when the last active worker releases its frozen Release', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const active = signedGrant(archive, { releaseId: 'release-active' });
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: active.grant.releaseId,
sha256: active.grant.sha256,
sizeBytes: active.grant.sizeBytes,
})),
issueDownload: vi.fn(async () => active.grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([['test-key', active.publicKey]]),
clientVersion: '1.0.0',
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
store.registerActiveWorker('release-active');
await expect(store.uninstall(PLUGIN_ID)).resolves.toEqual({
status: 'kept', pluginId: PLUGIN_ID, reason: 'active_worker_reference',
});
await expect(store.getInstalled(PLUGIN_ID)).resolves.toBeNull();
await expect(store.readInstalledIndex()).resolves.toHaveLength(1);
await store.releaseActiveWorker('release-active');
await expect(store.readInstalledIndex()).resolves.toEqual([]);
await expect(stat(path.join(temporaryRoot, 'packages', PLUGIN_ID, 'release-active'))).rejects.toThrow();
});
it('keeps deferred explicit cleanup blocked by another local account snapshot', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive, { releaseId: 'release-active' });
const accountCache = new AccountPluginCache();
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: grant.releaseId, sha256: grant.sha256, sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
accountCache,
getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([['test-key', publicKey]]),
clientVersion: '1.0.0',
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
accountCache.setResolve(ACCOUNT_B, 'account-b-reference', makeResolveResult({
makeloreVersion: '1.0.0', resolveRequestId: 'account-b-reference',
}, { releaseId: grant.releaseId, sha256: grant.sha256, sizeBytes: grant.sizeBytes }));
store.registerActiveWorker('release-active');
await expect(store.uninstall(PLUGIN_ID)).resolves.toEqual({
status: 'kept', pluginId: PLUGIN_ID, reason: 'active_worker_reference',
});
await store.releaseActiveWorker('release-active');
await expect(store.getInstalled(PLUGIN_ID)).resolves.toBeNull();
await expect(store.readInstalledIndex()).resolves.toEqual([
expect.objectContaining({ pluginId: PLUGIN_ID, releaseId: 'release-active' }),
]);
await expect(stat(path.join(temporaryRoot, 'packages', PLUGIN_ID, 'release-active')))
.resolves.toMatchObject({ isDirectory: expect.any(Function) });
});
it('cancels deferred cleanup when the user explicitly installs the same Release again', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive, { releaseId: 'release-active' });
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: grant.releaseId, sha256: grant.sha256, sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
getAccountBinding: () => ACCOUNT_A,
keyStore: new Map([['test-key', publicKey]]),
clientVersion: '1.0.0',
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
store.registerActiveWorker('release-active');
await expect(store.uninstall(PLUGIN_ID)).resolves.toMatchObject({
status: 'kept', reason: 'active_worker_reference',
});
await expect(store.resolveAndInstall({
pluginId: PLUGIN_ID, makeloreVersion: '1.0.0', channel: 'stable',
})).resolves.toMatchObject({ status: 'kept', releaseId: 'release-active' });
await store.releaseActiveWorker('release-active');
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: 'release-active' });
await expect(store.readInstalledIndex()).resolves.toHaveLength(1);
});
it('preserves the old release across download, signature, and extraction failures', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const oldArchive = buildSkillOnlyArchive();
const old = signedGrant(oldArchive, { releaseId: RELEASE_ID });
const oldMarketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: old.grant.sha256,
sizeBytes: old.grant.sizeBytes,
})),
issueDownload: vi.fn(async () => old.grant),
downloadContent: async () => oldArchive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const oldStore = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace: oldMarketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', old.publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await oldStore.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
const invalidArchive = Buffer.from('not a ZIP archive');
const cases = [
{
releaseId: 'release-download-failure',
expectedCode: 'plugin_install_failed',
artifact: oldArchive,
},
{
releaseId: 'release-signature-failure',
expectedCode: 'plugin_signature_invalid',
artifact: oldArchive,
},
{
releaseId: 'release-extraction-failure',
expectedCode: 'plugin_artifact_invalid',
artifact: invalidArchive,
},
] as const;
for (const [index, scenario] of cases.entries()) {
const signed = signedGrant(scenario.artifact, { releaseId: scenario.releaseId });
const grant = scenario.expectedCode === 'plugin_signature_invalid'
? { ...signed.grant, descriptorSignature: signed.grant.descriptorSignature[0] === 'A'
? `B${signed.grant.descriptorSignature.slice(1)}`
: `A${signed.grant.descriptorSignature.slice(1)}` }
: signed.grant;
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: grant.releaseId,
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => {
if (index === 0) throw new Error('simulated download interruption');
return scenario.artifact;
},
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', signed.publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.rejects.toMatchObject({ code: scenario.expectedCode });
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({
releaseId: RELEASE_ID,
version: '1.0.0',
});
await expect(store.readInstalledIndex()).resolves.toHaveLength(1);
}
});
it('rejects a Release outside the MakeLore client range before installation', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive, { maxMakeloreVersion: '1.5.0' });
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '2.0.0',
keyStore: new Map([['test-key', publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '2.0.0' }))
.rejects.toMatchObject({ code: 'plugin_incompatible_client' });
await expect(store.readInstalledIndex()).resolves.toEqual([]);
});
it('aborts an in-flight install when the Main account changes', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
let binding: AccountBinding | null = ACCOUNT_A;
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: vi.fn(async () => {
binding = ACCOUNT_B;
return archive;
}),
getCurrentAccountBinding: () => binding,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
keyStore: new Map([['test-key', publicKey]]),
getAccountBinding: () => binding,
});
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.rejects.toMatchObject({ code: 'plugin_account_changed' });
await expect(store.readInstalledIndex()).resolves.toEqual([]);
});
it('captures the account before queued package mutations execute', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
let binding: AccountBinding | null = ACCOUNT_A;
const firstResolve = deferred<ResolveSnapshot>();
const resolve = vi.fn(async (input: ResolveRequest) => (
resolve.mock.calls.length === 1
? firstResolve.promise
: makeResolveResult(input, { sha256: grant.sha256, sizeBytes: grant.sizeBytes })
));
const marketplace: MarketplaceClient = {
resolve,
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => binding,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
getAccountBinding: () => binding,
});
const inFlight = store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
await vi.waitFor(() => expect(resolve).toHaveBeenCalledTimes(1));
const queued = store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
binding = ACCOUNT_B;
firstResolve.resolve(makeResolveResult({ makeloreVersion: '1.0.0', channel: 'stable' }));
await expect(inFlight).rejects.toMatchObject({ code: 'plugin_account_changed' });
await expect(queued).rejects.toMatchObject({ code: 'plugin_account_changed' });
expect(resolve).toHaveBeenCalledTimes(1);
await expect(store.readInstalledIndex()).resolves.toEqual([]);
});
it('does not let a queued uninstall switch accounts while waiting', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const archive = buildSkillOnlyArchive();
const { grant, publicKey } = signedGrant(archive);
let binding: AccountBinding | null = ACCOUNT_A;
const resolve = vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
}));
const marketplace: MarketplaceClient = {
resolve,
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => binding,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
getAccountBinding: () => binding,
});
await store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
const firstResolve = deferred<ResolveSnapshot>();
resolve.mockImplementationOnce(async () => firstResolve.promise);
const inFlight = store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' });
await vi.waitFor(() => expect(resolve).toHaveBeenCalledTimes(2));
const queued = store.uninstall(PLUGIN_ID);
binding = ACCOUNT_B;
firstResolve.resolve(makeResolveResult({ makeloreVersion: '1.0.0', channel: 'stable' }, {
action: 'keep',
}));
await expect(inFlight).rejects.toMatchObject({ code: 'plugin_account_changed' });
await expect(queued).rejects.toMatchObject({ code: 'plugin_account_changed' });
await expect(store.getInstalled(PLUGIN_ID)).resolves.toMatchObject({ releaseId: RELEASE_ID });
});
it('rejects archive traversal before materializing a package', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const zip = new AdmZip();
zip.addFile('C:/outside.txt', Buffer.from('outside'));
const archive = zip.toBuffer();
const { grant, publicKey } = signedGrant(archive);
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
keyStore: new Map([['test-key', publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.rejects.toMatchObject({ code: 'plugin_artifact_invalid' });
await expect(store.readInstalledIndex()).resolves.toEqual([]);
});
it('enforces the canonical archive contract before installing a package', async () => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const oversizedManifest = JSON.stringify({
$schema: 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json',
name: PLUGIN_ID,
version: '1.0.0',
description: 'x'.repeat(256 * 1024),
author: { name: 'MakeLore' },
extensions: { 'com.makelore': { capabilityManifest: './com.makelore/capability.json' } },
});
const cases = [
{
name: 'executable Skill script in a downloadable artifact',
archive: buildSkillOnlyArchive({ 'skills/example-skill/scripts/run.mjs': Buffer.from('export {};') }),
},
{
name: 'non-UTF-8 Skill text',
archive: buildSkillOnlyArchive({ 'skills/example-skill/SKILL.md': Buffer.from([0xff, 0xfe, 0xfd]) }),
},
{
name: 'non-UTF-8 capability manifest',
archive: buildSkillOnlyArchive({ 'com.makelore/capability.json': Buffer.from([0xff, 0xfe, 0xfd]) }),
},
{
name: 'asset outside a declared Skill',
archive: buildSkillOnlyArchive({ 'skills/other-skill/notes.md': '# Hidden\n' }),
},
{
name: 'oversized manifest',
archive: buildSkillOnlyArchive({ 'plugin.json': oversizedManifest }),
},
{
name: 'oversized Skill',
archive: buildSkillOnlyArchive({ 'skills/example-skill/SKILL.md': Buffer.alloc(256 * 1024 + 1, 0x61) }),
},
];
for (const [index, scenario] of cases.entries()) {
const { grant, publicKey } = signedGrant(scenario.archive, { releaseId: `release-invalid-${index}` });
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
releaseId: grant.releaseId,
sha256: grant.sha256,
sizeBytes: grant.sizeBytes,
})),
issueDownload: vi.fn(async () => grant),
downloadContent: async () => scenario.archive,
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
clientVersion: '1.0.0',
keyStore: new Map([['test-key', publicKey]]),
getAccountBinding: () => ACCOUNT_A,
});
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }), scenario.name)
.rejects.toMatchObject({ code: 'plugin_artifact_invalid' });
await expect(store.readInstalledIndex()).resolves.toEqual([]);
}
});
it('requires explicit beta selection and rejects content paths outside the server route', async () => {
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(response({
release_admission_id: ADMISSION_ID,
release_id: RELEASE_ID,
plugin_id: PLUGIN_ID,
version: '1.0.0',
package_schema_version: 2,
contract_version: 1,
min_makelore_version: '1.0.0',
max_makelore_version: null,
size_bytes: 1,
sha256: SHA256,
signing_key_id: 'test-key',
descriptor_signature: 'a'.repeat(86),
expires_at: '2026-08-29T00:00:00Z',
content_url: 'https://evil.example/archive.zip',
}));
const client = createMarketplaceClient({
fetchImpl: fetcher,
apiBaseUrl: 'https://square.example',
getAccessToken: async () => 'token',
getAccountBinding: () => ACCOUNT_A,
subscribeSession: () => () => undefined,
});
await expect(client.issueDownload({ releaseId: RELEASE_ID, releaseAdmissionId: ADMISSION_ID }))
.rejects.toMatchObject({ code: 'marketplace_response_invalid' });
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input)),
issueDownload: vi.fn(),
} as MarketplaceClient;
const store = new PluginPackageStore({ rootDir: await mkdtemp(path.join(process.cwd(), '.marketplace-test-')), marketplace });
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, channel: 'beta', makeloreVersion: '1.0.0' }))
.rejects.toMatchObject({ code: 'plugin_beta_selection_required' });
});
it.each([
'plugin_release_yanked',
'plugin_incompatible_client',
'plugin_signature_invalid',
] as const)('preserves bounded resolve unavailable code %s for the UI projection', async (reason) => {
temporaryRoot = await mkdtemp(path.join(process.cwd(), '.marketplace-test-'));
const marketplace: MarketplaceClient = {
resolve: vi.fn(async (input: ResolveRequest) => makeResolveResult(input, {
action: 'unavailable',
releaseId: null,
version: null,
sha256: null,
sizeBytes: null,
releaseAdmissionId: null,
reason,
})),
issueDownload: vi.fn(),
downloadContent: vi.fn(),
getCurrentAccountBinding: () => ACCOUNT_A,
} as MarketplaceClient;
const store = new PluginPackageStore({
rootDir: temporaryRoot,
marketplace,
getAccountBinding: () => ACCOUNT_A,
clientVersion: '1.0.0',
});
await expect(store.resolveAndInstall({ pluginId: PLUGIN_ID, makeloreVersion: '1.0.0' }))
.rejects.toMatchObject({ code: reason });
});
});