test: add PI release proof harness
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { act, render } from '@testing-library/react';
|
||||
import { mkdtemp, rm } from 'node:fs/promises';
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
||||
import { createServer } from 'node:http';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
@@ -36,6 +36,16 @@ function percentile95(values: number[]): number {
|
||||
return ordered[Math.ceil(ordered.length * 0.95) - 1] ?? Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
function summarize(values: number[]): { samples: number; p50: number; p95: number; max: number } {
|
||||
const ordered = [...values].sort((left, right) => left - right);
|
||||
return {
|
||||
samples: ordered.length,
|
||||
p50: ordered[Math.ceil(ordered.length * 0.5) - 1] ?? Number.POSITIVE_INFINITY,
|
||||
p95: percentile95(ordered),
|
||||
max: ordered.at(-1) ?? Number.POSITIVE_INFINITY,
|
||||
};
|
||||
}
|
||||
|
||||
function pressurePatch(seq: number): ConversationPatch {
|
||||
if (seq === 1) {
|
||||
return {
|
||||
@@ -203,7 +213,7 @@ describe('REN-008 coding timeline pressure', () => {
|
||||
latencies.push(performance.now() - startedAt);
|
||||
}
|
||||
const measuredReactCommits = reactCommits - initialCommits;
|
||||
const p95Ms = percentile95(latencies);
|
||||
const mainToReactMs = summarize(latencies);
|
||||
const metrics = {
|
||||
runtimePatchItems,
|
||||
patchBatches,
|
||||
@@ -211,7 +221,8 @@ describe('REN-008 coding timeline pressure', () => {
|
||||
rendererTransactions,
|
||||
reactCommits: measuredReactCommits,
|
||||
wireBytes,
|
||||
mainToReactP95Ms: p95Ms,
|
||||
mainToReactMs,
|
||||
mainToReactP95Ms: mainToReactMs.p95,
|
||||
};
|
||||
console.info('REN-008 metrics', metrics);
|
||||
expect(metrics.runtimePatchItems).toBe(100);
|
||||
@@ -225,6 +236,8 @@ describe('REN-008 coding timeline pressure', () => {
|
||||
.entriesByConversationId[conversation.id]?.reducer.snapshot?.cursor.seq).toBe(100);
|
||||
view.unmount();
|
||||
unsubscribe();
|
||||
const reportPath = process.env.MAKELORE_PI_PERF_PRESSURE_FRAGMENT;
|
||||
if (reportPath) await writeFile(reportPath, `${JSON.stringify(metrics, null, 2)}\n`);
|
||||
} finally {
|
||||
controller.abort();
|
||||
await reader.cancel().catch(() => undefined);
|
||||
|
||||
100
tests/unit/pi-product-artifact.test.ts
Normal file
100
tests/unit/pi-product-artifact.test.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
assertNodeEngineCompatible,
|
||||
collectAbsoluteManifestValues,
|
||||
collectForbiddenResourcePaths,
|
||||
defaultProductExecutable,
|
||||
validatePiArtifactMetadata,
|
||||
} from '../../scripts/lib/pi-product-artifact.mjs';
|
||||
import { parsePiArtifactVerifierArgs } from '../../scripts/verify-pi-product-artifact.mjs';
|
||||
|
||||
const roots: string[] = [];
|
||||
const PI_PACKAGE = '@earendil-works/pi-coding-agent';
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
function matchingMetadata() {
|
||||
return {
|
||||
rootPackage: { dependencies: { [PI_PACKAGE]: '0.84.2' } },
|
||||
lockfile: {
|
||||
importers: {
|
||||
'.': { dependencies: { [PI_PACKAGE]: { specifier: '0.84.2', version: '0.84.2(ws@8.20.0)' } } },
|
||||
},
|
||||
},
|
||||
packagedPackage: { dependencies: { [PI_PACKAGE]: '0.84.2' } },
|
||||
runtimePackage: {
|
||||
name: PI_PACKAGE,
|
||||
version: '0.84.2',
|
||||
bin: { pi: 'dist/cli.js' },
|
||||
},
|
||||
manifest: {
|
||||
runtime: {
|
||||
packageName: PI_PACKAGE,
|
||||
version: '0.84.2',
|
||||
cliEntry: 'dist/cli.js',
|
||||
nodeEngine: '>=22.19.0',
|
||||
},
|
||||
target: { platform: 'win32', arch: 'x64' },
|
||||
},
|
||||
runtimePlatform: { platform: 'win32', arch: 'x64', node: '24.18.1' },
|
||||
};
|
||||
}
|
||||
|
||||
describe('final Pi product artifact verification', () => {
|
||||
it('requires the same pinned Pi version across package, lock, app, runtime, and manifest', () => {
|
||||
expect(validatePiArtifactMetadata(matchingMetadata())).toMatchObject({
|
||||
expected: '0.84.2',
|
||||
rootDependency: '0.84.2',
|
||||
lockedVersion: '0.84.2',
|
||||
manifest: '0.84.2',
|
||||
});
|
||||
const mismatch = matchingMetadata();
|
||||
mismatch.packagedPackage.dependencies[PI_PACKAGE] = '0.84.1';
|
||||
expect(() => validatePiArtifactMetadata(mismatch)).toThrow('versions do not match');
|
||||
});
|
||||
|
||||
it('checks the packaged Node version against the exact supported engine shape', () => {
|
||||
expect(() => assertNodeEngineCompatible('>=22.19.0', '24.18.1')).not.toThrow();
|
||||
expect(() => assertNodeEngineCompatible('>=22.19.0', '22.18.9')).toThrow('does not satisfy');
|
||||
expect(() => assertNodeEngineCompatible('^22.19.0', '24.18.1')).toThrow('Unsupported');
|
||||
});
|
||||
|
||||
it('rejects absolute manifest values and OpenCode-named artifact resources', async () => {
|
||||
expect(collectAbsoluteManifestValues({
|
||||
entry: 'dist/cli.js',
|
||||
asset: 'node_modules/example/file.wasm',
|
||||
leaked: 'D:\\work\\pi-runtime',
|
||||
})).toEqual([{ at: '$.leaked', value: 'D:\\work\\pi-runtime' }]);
|
||||
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-artifact-test-'));
|
||||
roots.push(root);
|
||||
await mkdir(path.join(root, 'pi-runtime', 'node_modules', 'opencode-ai'), { recursive: true });
|
||||
await writeFile(path.join(root, 'pi-runtime', 'node_modules', 'opencode-ai', 'package.json'), '{}');
|
||||
expect(await collectForbiddenResourcePaths(root)).toEqual([
|
||||
'pi-runtime/node_modules/opencode-ai',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses final unpacked-product paths and strictly parses verifier options', () => {
|
||||
expect(defaultProductExecutable('D:\\repo', 'win32'))
|
||||
.toBe(path.resolve('D:\\repo', 'release', 'win-unpacked', 'Makelore.exe'));
|
||||
expect(parsePiArtifactVerifierArgs([
|
||||
'--app-exe', 'release/custom/Makelore.exe',
|
||||
'--samples', '5',
|
||||
'--timeout-ms', '12000',
|
||||
'--report', 'release/evidence/pi.json',
|
||||
], 'D:\\repo')).toMatchObject({ samples: 5, timeoutMs: 12_000 });
|
||||
expect(() => parsePiArtifactVerifierArgs(['--samples', '0'], 'D:\\repo'))
|
||||
.toThrow('--samples must be a positive integer');
|
||||
expect(() => parsePiArtifactVerifierArgs(['--unknown'], 'D:\\repo'))
|
||||
.toThrow('Unknown argument');
|
||||
});
|
||||
});
|
||||
31
tests/unit/pi-release-docs.test.ts
Normal file
31
tests/unit/pi-release-docs.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const root = process.cwd();
|
||||
|
||||
describe('Pi release documentation', () => {
|
||||
it('keeps final-product commands and the external Provider waiver distinct', async () => {
|
||||
const [readme, runbook] = await Promise.all([
|
||||
readFile(`${root}/README.md`, 'utf8'),
|
||||
readFile(`${root}/docs/pi-runtime-release-runbook.md`, 'utf8'),
|
||||
]);
|
||||
|
||||
expect(readme).toContain('pnpm run verify:artifact:pi');
|
||||
expect(readme).toContain('pnpm run smoke:pi:real');
|
||||
expect(readme).toContain('pnpm run perf:pi:release');
|
||||
expect(runbook).toContain('realTurnVerified=false');
|
||||
expect(runbook).toContain('Explicitly Waived / Accepted Risk');
|
||||
expect(runbook).toContain('不得写成 Pass');
|
||||
});
|
||||
|
||||
it('requires all release targets and documents the incompatible rollback boundary', async () => {
|
||||
const runbook = await readFile(`${root}/docs/pi-runtime-release-runbook.md`, 'utf8');
|
||||
|
||||
for (const target of ['Windows x64', 'Linux x64', 'macOS x64', 'macOS arm64']) {
|
||||
expect(runbook).toContain(target);
|
||||
}
|
||||
expect(runbook).toContain('缺少任一目标的独立证据时,结论只能是 `Blocked`');
|
||||
expect(runbook).toContain('旧版本不能读取或续写它们');
|
||||
expect(runbook).toContain('只支持完整应用版本回滚');
|
||||
});
|
||||
});
|
||||
84
tests/unit/pi-release-performance-fragments.test.ts
Normal file
84
tests/unit/pi-release-performance-fragments.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { createCodingConversationStore } from '../../electron/coding-projects/conversation-store';
|
||||
import { createCodingProjectAgent } from '../../electron/coding-projects/project-config';
|
||||
import {
|
||||
createCodingProjectStore,
|
||||
createLocalCodingProject,
|
||||
createMemoryCodingProjectStorage,
|
||||
} from '../../electron/coding-projects/project-store';
|
||||
import { summarizeMeasurements } from '../../scripts/probe-pi-runtime.mjs';
|
||||
|
||||
const roots: string[] = [];
|
||||
const samples = Number.parseInt(process.env.MAKELORE_PI_PERF_SAMPLES ?? '10', 10);
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
describe('PI-150 local metadata performance fragments', () => {
|
||||
it('reports project, Agent, and Conversation metadata distributions', async () => {
|
||||
expect(samples).toBeGreaterThan(0);
|
||||
const projectMs: number[] = [];
|
||||
const agentMs: number[] = [];
|
||||
const conversationMs: number[] = [];
|
||||
for (let index = 0; index < samples; index += 1) {
|
||||
const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-pi-release-perf-'));
|
||||
roots.push(projectPath);
|
||||
const store = createCodingProjectStore(createMemoryCodingProjectStorage(), {
|
||||
createId: () => `project-${index}`,
|
||||
now: () => '2026-08-24T00:00:00.000Z',
|
||||
});
|
||||
|
||||
let startedAt = performance.now();
|
||||
await createLocalCodingProject({
|
||||
projectPath,
|
||||
now: '2026-08-24T00:00:00.000Z',
|
||||
}, store);
|
||||
projectMs.push(performance.now() - startedAt);
|
||||
|
||||
startedAt = performance.now();
|
||||
const agent = await createCodingProjectAgent(projectPath, {
|
||||
id: `builder-${index}`,
|
||||
avatarId: 'avatar-01',
|
||||
roleName: '实现者',
|
||||
name: `Builder ${index}`,
|
||||
model: { accountId: 'account-a', modelId: 'model-a', thinkingLevel: 'medium' },
|
||||
modelResolution: 'resolved',
|
||||
responsibility: {
|
||||
mission: 'Implement', owns: [], boundaries: [], collaborators: [], principles: [],
|
||||
},
|
||||
}, { now: '2026-08-24T00:00:00.000Z' });
|
||||
agentMs.push(performance.now() - startedAt);
|
||||
|
||||
const conversations = createCodingConversationStore(projectPath, {
|
||||
createId: () => `00000000-0000-4000-8000-${String(index).padStart(12, '0')}`,
|
||||
now: () => '2026-08-24T00:00:00.000Z',
|
||||
});
|
||||
startedAt = performance.now();
|
||||
await conversations.create({
|
||||
agentId: agent.id,
|
||||
title: `Conversation ${index}`,
|
||||
model: agent.model,
|
||||
modelResolution: agent.modelResolution,
|
||||
});
|
||||
conversationMs.push(performance.now() - startedAt);
|
||||
}
|
||||
|
||||
const report = {
|
||||
projectMetadataMs: summarizeMeasurements(projectMs),
|
||||
agentMetadataMs: summarizeMeasurements(agentMs),
|
||||
conversationMetadataMs: summarizeMeasurements(conversationMs),
|
||||
};
|
||||
expect(report.projectMetadataMs.p95).toBeLessThanOrEqual(1_000);
|
||||
expect(report.agentMetadataMs.p95).toBeLessThanOrEqual(500);
|
||||
expect(report.conversationMetadataMs.p95).toBeLessThanOrEqual(500);
|
||||
const reportPath = process.env.MAKELORE_PI_PERF_METADATA_FRAGMENT;
|
||||
if (reportPath) await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
||||
});
|
||||
});
|
||||
@@ -103,6 +103,12 @@ describe('Pi runtime production bundler', () => {
|
||||
expect(packageJson.scripts.package).toContain('bundle-pi-runtime.mjs --release-targets');
|
||||
expect(packageJson.scripts['package:stage:win-x64'])
|
||||
.toContain('bundle-pi-runtime.mjs --target win32-x64');
|
||||
expect(packageJson.scripts['verify:artifact:pi'])
|
||||
.toBe('node scripts/verify-pi-product-artifact.mjs');
|
||||
expect(packageJson.scripts['smoke:pi:real'])
|
||||
.toBe('node scripts/smoke-pi-real.mjs');
|
||||
expect(packageJson.scripts['perf:pi:release'])
|
||||
.toBe('node scripts/run-pi-release-performance.mjs');
|
||||
expect(builder.mac.extraResources).toContainEqual({
|
||||
from: 'build/pi-runtime/darwin-${arch}',
|
||||
to: 'pi-runtime',
|
||||
|
||||
@@ -16,6 +16,11 @@ import {
|
||||
const scratchRoots: string[] = [];
|
||||
const packagedRuntimeRoot = process.env.MAKELORE_PI_STAGED_RUNTIME_ROOT;
|
||||
|
||||
function electronExecutableFromEnvironment(): string {
|
||||
const requireFromProject = createRequire(resolve('package.json'));
|
||||
return process.env.MAKELORE_PI_ELECTRON_EXECUTABLE ?? requireFromProject('electron') as string;
|
||||
}
|
||||
|
||||
async function materializeActiveToolsProbe(root: string): Promise<{
|
||||
extensionPath: string;
|
||||
resultPath: string;
|
||||
@@ -56,8 +61,7 @@ afterEach(async () => {
|
||||
|
||||
describe('locked Pi worker process smoke', () => {
|
||||
it('loads the real Pi 0.84.2 entry through Electron Node and exits by closing stdin', async () => {
|
||||
const requireFromProject = createRequire(resolve('package.json'));
|
||||
const electronExecutable = requireFromProject('electron') as string;
|
||||
const electronExecutable = electronExecutableFromEnvironment();
|
||||
const packageRoot = realpathSync(resolve(
|
||||
'node_modules',
|
||||
'@earendil-works',
|
||||
@@ -124,8 +128,7 @@ describe('locked Pi worker process smoke', () => {
|
||||
}, 15_000);
|
||||
|
||||
it('starts a real ephemeral read-only child with the child extension role', async () => {
|
||||
const requireFromProject = createRequire(resolve('package.json'));
|
||||
const electronExecutable = requireFromProject('electron') as string;
|
||||
const electronExecutable = electronExecutableFromEnvironment();
|
||||
const packageRoot = realpathSync(resolve(
|
||||
'node_modules',
|
||||
'@earendil-works',
|
||||
@@ -181,8 +184,7 @@ describe('locked Pi worker process smoke', () => {
|
||||
it.skipIf(!packagedRuntimeRoot)(
|
||||
'starts the staged production-closure runtime as an ephemeral read-only child',
|
||||
async () => {
|
||||
const requireFromProject = createRequire(resolve('package.json'));
|
||||
const electronExecutable = requireFromProject('electron') as string;
|
||||
const electronExecutable = electronExecutableFromEnvironment();
|
||||
const packageRoot = realpathSync(packagedRuntimeRoot as string);
|
||||
const manifest = JSON.parse(await readFile(join(packageRoot, PI_RUNTIME_MANIFEST), 'utf8')) as {
|
||||
runtime?: { version?: string; cliEntry?: string };
|
||||
@@ -246,8 +248,7 @@ describe('locked Pi worker process smoke', () => {
|
||||
it.skipIf(!packagedRuntimeRoot)(
|
||||
'loads product tools in the staged production-closure parent runtime',
|
||||
async () => {
|
||||
const requireFromProject = createRequire(resolve('package.json'));
|
||||
const electronExecutable = requireFromProject('electron') as string;
|
||||
const electronExecutable = electronExecutableFromEnvironment();
|
||||
const packageRoot = realpathSync(packagedRuntimeRoot as string);
|
||||
const root = await mkdtemp(join(tmpdir(), 'makelore-pi-staged-product-tools-'));
|
||||
scratchRoots.push(root);
|
||||
|
||||
Reference in New Issue
Block a user