fix(pi): prove packaged workers end to end

This commit is contained in:
brother7 committed 2026-08-24 16:51:13 +08:00
1 parent dc166a18cb
commit 2ff2e4af79
8 files changed
+825 -493

No files matched your search

@@ -8,7 +8,7 @@
- Worktree: D:\Datas\OthersProjects\makelore-pi-release-proof-3e725ac7 - Worktree: D:\Datas\OthersProjects\makelore-pi-release-proof-3e725ac7
- Base commit: 977445ba450f4ad32b6e6db2caf048517513ab39 - Base commit: 977445ba450f4ad32b6e6db2caf048517513ab39
- Owner: codex-root - Owner: codex-root
- Status: Ready for Planner Re-review (platform release blockers remain) - Status: In Progress (second planner Spec review fixes required)
## Scope ## Scope
@@ -37,10 +37,9 @@
risks, never Pass; every substitute report retains risks, never Pass; every substitute report retains
`realTurnVerified=false`. `realTurnVerified=false`.
- The user explicitly skipped macOS validation. Do not manufacture or reuse - The user explicitly skipped macOS validation. Do not manufacture or reuse
static/cross-platform evidence for macOS x64 or arm64. Their missing final static/cross-platform evidence for macOS x64 or arm64. Per the planner's
artifact, closure/native/resource, loopback, and performance evidence remains second review this evidence is deferred to PI-160: it remains Not Pass and a
an explicit release blocker, so this task cannot claim cross-platform final cross-platform release blocker, but is not a PI-150 blocker.
release-ready or PI-150 Done.
- The canonical project memory at this base is stale and still describes - The canonical project memory at this base is stale and still describes
OpenCode. Treat the planner-owned PI Spec/ticket and current source as the OpenCode. Treat the planner-owned PI Spec/ticket and current source as the
task authority; do not edit canonical memory in this feature worktree. task authority; do not edit canonical memory in this feature worktree.
@@ -85,10 +84,12 @@
under the pinned upstream `@earendil-works/pi-ai/dist/providers/opencode*` under the pinned upstream `@earendil-works/pi-ai/dist/providers/opencode*`
closure remain classified separately because Pi imports them; they are not closure remain classified separately because Pi imports them; they are not
Makelore-owned runtime residue. Makelore-owned runtime residue.
- The five requested fixes are ready for planner re-review, but PI-150 remains - The second planner review accepted the ASAR enumeration and RPM fixes but
blocked from `Done`, PI-160 must not start, and the product is not found three remaining P1 proof defects: final subagent still used a fake
cross-platform release-ready: macOS x64/arm64 was explicitly skipped and no in-process child, managed milestone reports joined different probe samples
independent native non-WSL Linux desktop/compositor acceptance run exists. and omitted `worker.queue_wait`, and the 4+4 PIDs were placeholder Node
processes rather than real Pi workers. PI-150 remains blocked and PI-160 must
not start until those defects and native non-WSL Linux evidence are resolved.
QG-004/QG-005 remain `Explicitly Waived / Accepted Risk`, with QG-004/QG-005 remain `Explicitly Waived / Accepted Risk`, with
`realTurnVerified=false`. `realTurnVerified=false`.
@@ -147,16 +148,17 @@
## Follow-ups ## Follow-ups
- Request planner re-review of `c7e7772..df1151b`. The five prior findings now - Planner re-review of `c7e7772..dc166a1` returned `Standards Pass / Spec Needs
have direct final-product implementation and structured Windows/Linux Fix`. ASAR enumeration and RPM are closed; final packaged subagent dispatch,
evidence; no source-only unit-test result is used as their release proof. a single Main-owned cold/warm timeline including zero-valued queue wait, and
real persistent-parent/ephemeral-child 4+4 pressure remain P1.
- Do not start PI-160 or integrate `df1151b` while PI-150 remains blocked by the - Do not start PI-160 or integrate `df1151b` while PI-150 remains blocked by the
accepted platform evidence boundary. If the planner accepts the five fixes, accepted platform evidence boundary. If the planner accepts the five fixes,
keep PI-150 as the active frontier until the missing platform decision or keep PI-150 as the active frontier until the missing platform decision or
evidence is resolved. evidence is resolved.
- Keep missing macOS x64/arm64 artifact/runtime/resource/performance evidence as - Keep missing macOS x64/arm64 artifact/runtime/resource/performance evidence as
a release blocker; do not promote a cross-platform release-ready or PI-150 `Deferred to PI-160 / Not Pass`; do not promote a final cross-platform release
`Done` conclusion without independent macOS execution. without independent macOS execution, but do not use it to block PI-150.
- Keep the lack of an independent native non-WSL Linux - Keep the lack of an independent native non-WSL Linux
desktop/compositor/distribution run explicit. The WSL2/WSLg run qualifies the desktop/compositor/distribution run explicit. The WSL2/WSLg run qualifies the
generated Linux artifacts and final process seam but does not erase that generated Linux artifacts and final process seam but does not erase that
File diff suppressed because it is too large. Load diff
+24 -7
View File
@@ -207,6 +207,7 @@ interface WorkerRecord {
generationResources: Record<PiGenerationResourceKind, Map<string, () => void>>; generationResources: Record<PiGenerationResourceKind, Map<string, () => void>>;
rebuildFlight?: Promise<WorkerRecord>; rebuildFlight?: Promise<WorkerRecord>;
acceptedPromptCount: number; acceptedPromptCount: number;
coldStart: boolean;
processLease: PiProcessLease | null; processLease: PiProcessLease | null;
processStopFlight?: Promise<void>; processStopFlight?: Promise<void>;
reconfigureAfterSettled: boolean; reconfigureAfterSettled: boolean;
@@ -281,7 +282,15 @@ export class PiWorkerPool {
revision, revision,
}) })
.then(async ({ opened: { worker, session }, lease }) => { .then(async ({ opened: { worker, session }, lease }) => {
const record = this.createRecord(conversation, worker, session, generation, revision, lease); const record = this.createRecord(
conversation,
worker,
session,
generation,
revision,
lease,
true,
);
this.workers.set(conversation.conversationId, record); this.workers.set(conversation.conversationId, record);
this.notifyReclaimableWorker(); this.notifyReclaimableWorker();
const state = this.publicState(record); const state = this.publicState(record);
@@ -336,6 +345,7 @@ export class PiWorkerPool {
generation, generation,
revision, revision,
lease, lease,
true,
); );
this.workers.set(conversation.conversationId, record); this.workers.set(conversation.conversationId, record);
this.notifyReclaimableWorker(); this.notifyReclaimableWorker();
@@ -536,8 +546,8 @@ export class PiWorkerPool {
this.launchTopLevel(record, pending); this.launchTopLevel(record, pending);
return { accepted }; return { accepted };
} }
this.waitingRuns.push(pending);
pending.queuedAt = this.now(); pending.queuedAt = this.now();
this.waitingRuns.push(pending);
record.state = 'queued'; record.state = 'queued';
this.notifyReclaimableWorker(); this.notifyReclaimableWorker();
return { queuePosition: this.waitingRuns.length, accepted }; return { queuePosition: this.waitingRuns.length, accepted };
@@ -619,7 +629,7 @@ export class PiWorkerPool {
this.activeRuns.set(run.conversationId, { this.activeRuns.set(run.conversationId, {
runId: run.runId, runId: run.runId,
generation: record.generation, generation: record.generation,
cold: record.acceptedPromptCount === 0, cold: record.coldStart && record.acceptedPromptCount === 0,
}); });
void this.acceptTopLevel(record, run); void this.acceptTopLevel(record, run);
} }
@@ -632,12 +642,16 @@ export class PiWorkerPool {
const active = this.activeRuns.get(run.conversationId); const active = this.activeRuns.get(run.conversationId);
if (!active) throw new Error('Top-level run was cancelled before acceptance'); if (!active) throw new Error('Top-level run was cancelled before acceptance');
active.generation = current.generation; active.generation = current.generation;
active.cold = current.coldStart && current.acceptedPromptCount === 0;
current.state = 'running'; current.state = 'running';
this.revisions.beginRun(current.revisionWorkerId); this.revisions.beginRun(current.revisionWorkerId);
beganRun = true; beganRun = true;
if (run.queuedAt !== undefined) { this.recordMilestone(
this.recordMilestone(current, run, 'worker.queue_wait', this.now() - run.queuedAt); current,
} run,
'worker.queue_wait',
run.queuedAt === undefined ? 0 : this.now() - run.queuedAt,
);
const acceptedAt = this.now(); const acceptedAt = this.now();
const response = await current.worker.request(run.command); const response = await current.worker.request(run.command);
if (run.command.type === 'prompt') { if (run.command.type === 'prompt') {
@@ -792,6 +806,7 @@ export class PiWorkerPool {
generation, generation,
revision, revision,
lease, lease,
false,
); );
replacement.state = record.state === 'spawning' && this.activeRuns.has(conversationId) replacement.state = record.state === 'spawning' && this.activeRuns.has(conversationId)
? 'running' ? 'running'
@@ -822,6 +837,7 @@ export class PiWorkerPool {
generation: number, generation: number,
revision: PiManagedInputRevision, revision: PiManagedInputRevision,
processLease: PiProcessLease, processLease: PiProcessLease,
coldStart: boolean,
): WorkerRecord { ): WorkerRecord {
const revisionWorkerId = `${conversation.conversationId}:${generation}`; const revisionWorkerId = `${conversation.conversationId}:${generation}`;
this.revisions.registerWorker(revisionWorkerId, revision); this.revisions.registerWorker(revisionWorkerId, revision);
@@ -841,6 +857,7 @@ export class PiWorkerPool {
child: new Map(), child: new Map(),
}, },
acceptedPromptCount: 0, acceptedPromptCount: 0,
coldStart,
processLease, processLease,
reconfigureAfterSettled: false, reconfigureAfterSettled: false,
}; };
@@ -1058,7 +1075,7 @@ export class PiWorkerPool {
run: Pick<PendingTopLevelRun, 'runId'>, run: Pick<PendingTopLevelRun, 'runId'>,
milestone: 'worker.queue_wait' | 'prompt.accepted' | 'agent.settled', milestone: 'worker.queue_wait' | 'prompt.accepted' | 'agent.settled',
durationMs: number, durationMs: number,
cold = record.acceptedPromptCount === 0, cold = record.coldStart && record.acceptedPromptCount === 0,
): void { ): void {
if (!this.onTelemetry) return; if (!this.onTelemetry) return;
this.onTelemetry(createPiRuntimeTelemetryEvent({ this.onTelemetry(createPiRuntimeTelemetryEvent({
@@ -208,6 +208,16 @@ export class PiWorkerProcess {
return this.generationValue; return this.generationValue;
} }
get processId(): number | undefined {
return this.child?.pid;
}
get isRunning(): boolean {
return this.child !== null
&& this.child.exitCode === null
&& this.child.signalCode === null;
}
get pendingCommandCount(): number { get pendingCommandCount(): number {
return this.rpc?.pendingCount ?? 0; return this.rpc?.pendingCount ?? 0;
} }
+21 -19
View File
@@ -140,8 +140,10 @@ async function runProductProofSamples(artifact, projectRoot, samples) {
function summarizeManagedMilestone(reports, cold, milestone) { function summarizeManagedMilestone(reports, cold, milestone) {
return summarizeMeasurements(reports.flatMap(({ extension }) => ( return summarizeMeasurements(reports.flatMap(({ extension }) => (
extension.managedWorkerMilestones extension.managedTurns
.filter((event) => event.cold === cold && event.milestone === milestone) .filter((turn) => turn.cold === cold)
.flatMap((turn) => turn.milestones)
.filter((event) => event.milestone === milestone)
.map(({ durationMs }) => durationMs) .map(({ durationMs }) => durationMs)
))); )));
} }
@@ -180,32 +182,29 @@ export async function runPiReleasePerformance(options) {
options.projectRoot, options.projectRoot,
options.samples, options.samples,
); );
const providerMetric = (temperature, field) => summarizeMeasurements(
providerContracts.protocols.flatMap(
({ qualification }) => qualification[temperature][field],
),
);
const managedMilestones = { const managedMilestones = {
definition: 'Actual createPiManagedWorkerOpener execution from final app.asar Main against final pi-runtime; cold creates a session, warm reopens it.', definition: 'Each sample is one Main-owned correlated final-ASAR turn against the final pi-runtime; cold creates a session and invokes a real packaged subagent, warm reopens the same session. No milestone is joined from an external probe.',
cold: { cold: {
workerQueueWaitMs: summarizeManagedMilestone(productProofs, true, 'worker.queue_wait'),
resourcesReadyMs: summarizeManagedMilestone(productProofs, true, 'resources.ready'), resourcesReadyMs: summarizeManagedMilestone(productProofs, true, 'resources.ready'),
workerSpawnMs: summarizeManagedMilestone(productProofs, true, 'worker.spawn'), workerSpawnMs: summarizeManagedMilestone(productProofs, true, 'worker.spawn'),
rpcReadyMs: summarizeManagedMilestone(productProofs, true, 'rpc.ready'), rpcReadyMs: summarizeManagedMilestone(productProofs, true, 'rpc.ready'),
sessionOpenMs: summarizeManagedMilestone(productProofs, true, 'session.open'), sessionOpenMs: summarizeManagedMilestone(productProofs, true, 'session.open'),
promptAcceptedMs: providerMetric('cold', 'promptAcceptedSamplesMs'), promptAcceptedMs: summarizeManagedMilestone(productProofs, true, 'prompt.accepted'),
agentStartMs: providerMetric('cold', 'agentStartSamplesMs'), agentStartMs: summarizeManagedMilestone(productProofs, true, 'agent.start'),
providerFirstEventMs: providerMetric('cold', 'providerFirstEventSamplesMs'), providerFirstEventMs: summarizeManagedMilestone(productProofs, true, 'provider.first_event'),
agentSettledMs: providerMetric('cold', 'agentSettledSamplesMs'), agentSettledMs: summarizeManagedMilestone(productProofs, true, 'agent.settled'),
}, },
warm: { warm: {
workerQueueWaitMs: summarizeManagedMilestone(productProofs, false, 'worker.queue_wait'),
resourcesReadyMs: summarizeManagedMilestone(productProofs, false, 'resources.ready'), resourcesReadyMs: summarizeManagedMilestone(productProofs, false, 'resources.ready'),
workerSpawnMs: summarizeManagedMilestone(productProofs, false, 'worker.spawn'), workerSpawnMs: summarizeManagedMilestone(productProofs, false, 'worker.spawn'),
rpcReadyMs: summarizeManagedMilestone(productProofs, false, 'rpc.ready'), rpcReadyMs: summarizeManagedMilestone(productProofs, false, 'rpc.ready'),
sessionOpenMs: summarizeManagedMilestone(productProofs, false, 'session.open'), sessionOpenMs: summarizeManagedMilestone(productProofs, false, 'session.open'),
promptAcceptedMs: providerMetric('warm', 'promptAcceptedSamplesMs'), promptAcceptedMs: summarizeManagedMilestone(productProofs, false, 'prompt.accepted'),
agentStartMs: providerMetric('warm', 'agentStartSamplesMs'), agentStartMs: summarizeManagedMilestone(productProofs, false, 'agent.start'),
providerFirstEventMs: providerMetric('warm', 'providerFirstEventSamplesMs'), providerFirstEventMs: summarizeManagedMilestone(productProofs, false, 'provider.first_event'),
agentSettledMs: providerMetric('warm', 'agentSettledSamplesMs'), agentSettledMs: summarizeManagedMilestone(productProofs, false, 'agent.settled'),
}, },
}; };
const pressureUiInteractiveMs = summarizeMeasurements( const pressureUiInteractiveMs = summarizeMeasurements(
@@ -230,7 +229,7 @@ export async function runPiReleasePerformance(options) {
const git = await gitEvidence(options.projectRoot); const git = await gitEvidence(options.projectRoot);
const scenarios = [ const scenarios = [
{ id: 1, name: 'fresh userData metadata and Composer', evidence: ['metadata fragments', 'Electron Composer samples'], result: 'pass' }, { id: 1, name: 'fresh userData metadata and Composer', evidence: ['metadata fragments', 'Electron Composer samples'], result: 'pass' },
{ id: 2, name: 'first prompt milestone split', evidence: ['final-ASAR resources.ready/worker.spawn/rpc.ready/session.open', 'separate cold prompt.accepted/agent.start/provider.first_event/agent.settled', 'Renderer commit'], result: 'pass' }, { id: 2, name: 'first prompt milestone split', evidence: ['single correlated final-ASAR worker.queue_wait/resources.ready/worker.spawn/rpc.ready/session.open/prompt.accepted/agent.start/provider.first_event/agent.settled timeline per turn', 'Renderer commit'], result: 'pass' },
{ id: 3, name: 'warm Conversation restore', evidence: ['packaged session stop/reopen/get_entries', 'warm rpc.ready samples'], result: 'pass' }, { id: 3, name: 'warm Conversation restore', evidence: ['packaged session stop/reopen/get_entries', 'warm rpc.ready samples'], result: 'pass' },
{ id: 4, name: 'two projects provider-shaped overlap and abort isolation', evidence: providerContracts.protocols.map(({ protocol }) => protocol), result: 'pass' }, { id: 4, name: 'two projects provider-shaped overlap and abort isolation', evidence: providerContracts.protocols.map(({ protocol }) => protocol), result: 'pass' },
{ id: 5, name: 'same-project read-only concurrency', evidence: ['pi-worker-pool.test.ts', 'pi-subagent.test.ts'], result: 'pass' }, { id: 5, name: 'same-project read-only concurrency', evidence: ['pi-worker-pool.test.ts', 'pi-subagent.test.ts'], result: 'pass' },
@@ -302,8 +301,11 @@ export async function runPiReleasePerformance(options) {
budgets, budgets,
result: 'pass', result: 'pass',
crossPlatformReleaseReady: false, crossPlatformReleaseReady: false,
releaseBlockers: [ releaseBlockers: platform() === 'linux' && !release().toLowerCase().includes('microsoft')
'macOS x64 and macOS arm64 PI-150 performance/final-artifact validation was skipped by explicit user direction', ? []
: ['Native non-WSL Linux desktop/compositor PI-150 evidence is not established by this report'],
deferredToPi160: [
'macOS x64 and macOS arm64 final release validation was skipped by explicit user direction and remains Not Pass',
], ],
}; };
if (options.reportPath) { if (options.reportPath) {
+21 -3
View File
@@ -56,7 +56,11 @@ function assertActivePressure(pressure) {
const expected = { const expected = {
parentWorkers: 4, parentWorkers: 4,
childWorkers: 4, childWorkers: 4,
parentProcesses: 4,
childProcesses: 4,
liveProcesses: 8, liveProcesses: 8,
parentProviderRequests: 4,
childProviderRequests: 4,
processBudget: 8, processBudget: 8,
childPermits: 4, childPermits: 4,
dispatches: 4, dispatches: 4,
@@ -65,7 +69,11 @@ function assertActivePressure(pressure) {
const actual = { const actual = {
parentWorkers: pressure?.parentWorkers, parentWorkers: pressure?.parentWorkers,
childWorkers: pressure?.childWorkers, childWorkers: pressure?.childWorkers,
parentProcesses: pressure?.parentProcessIds?.length,
childProcesses: pressure?.childProcessIds?.length,
liveProcesses: pressure?.liveProcessIds?.length, liveProcesses: pressure?.liveProcessIds?.length,
parentProviderRequests: pressure?.providerRequests?.parent,
childProviderRequests: pressure?.providerRequests?.child,
processBudget: pressure?.processBudget?.active, processBudget: pressure?.processBudget?.active,
childPermits: pressure?.childPermits?.active, childPermits: pressure?.childPermits?.active,
dispatches: pressure?.dispatches?.active, dispatches: pressure?.dispatches?.active,
@@ -80,7 +88,11 @@ function assertReleasedPressure(pressure) {
const counts = [ const counts = [
pressure?.parentWorkers, pressure?.parentWorkers,
pressure?.childWorkers, pressure?.childWorkers,
pressure?.parentProcessIds?.length,
pressure?.childProcessIds?.length,
pressure?.liveProcessIds?.length, pressure?.liveProcessIds?.length,
pressure?.providerRequests?.parent,
pressure?.providerRequests?.child,
pressure?.processBudget?.active, pressure?.processBudget?.active,
pressure?.processBudget?.waiting, pressure?.processBudget?.waiting,
pressure?.childPermits?.active, pressure?.childPermits?.active,
@@ -147,10 +159,16 @@ export async function runPackagedProductProof(options) {
const extension = await evaluateProof(electronApplication, 'extension'); const extension = await evaluateProof(electronApplication, 'extension');
assertPackagedMain(extension); assertPackagedMain(extension);
if (extension.extension?.subagentStatus !== 'complete' if (extension.extension?.subagentStatus !== 'complete'
|| extension.extension?.childToolNames?.length !== 0 || extension.extension?.childToolNames?.join(',') !== 'find,grep,ls,read'
|| !extension.extension?.parentToolNames?.includes('subagent') || !extension.extension?.parentToolNames?.includes('subagent')
|| extension.extension?.managedWorkerMilestones?.filter(({ cold }) => cold).length !== 4 || extension.extension?.parentProcessIds?.length < 2
|| extension.extension?.managedWorkerMilestones?.filter(({ cold }) => !cold).length !== 4) { || extension.extension?.childProcessIds?.length !== 1
|| extension.extension?.providerRequests?.child !== 1
|| extension.extension?.managedTurns?.length !== 2
|| extension.extension.managedTurns.some(({ milestones }) => (
milestones.map(({ milestone }) => milestone).join(',')
!== 'worker.queue_wait,resources.ready,worker.spawn,rpc.ready,session.open,prompt.accepted,agent.start,provider.first_event,agent.settled'
))) {
throw new Error(`Final ASAR extension/subagent proof failed: ${JSON.stringify(extension.extension)}`); throw new Error(`Final ASAR extension/subagent proof failed: ${JSON.stringify(extension.extension)}`);
} }
+27 -4
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
import { execFile } from 'node:child_process';
import { mkdir, writeFile } from 'node:fs/promises'; import { mkdir, writeFile } from 'node:fs/promises';
import { arch, platform } from 'node:os'; import { arch, platform, release } from 'node:os';
import { dirname, resolve } from 'node:path'; import { dirname, resolve } from 'node:path';
import { pathToFileURL } from 'node:url'; import { pathToFileURL } from 'node:url';
@@ -11,6 +12,23 @@ import { parsePiArtifactVerifierArgs } from './verify-pi-product-artifact.mjs';
import { runProbe } from './probe-pi-runtime.mjs'; import { runProbe } from './probe-pi-runtime.mjs';
import { runPackagedProductProof } from './run-pi-subagent-packaged-smoke.mjs'; import { runPackagedProductProof } from './run-pi-subagent-packaged-smoke.mjs';
function runGit(projectRoot, args) {
return new Promise((resolvePromise, reject) => {
execFile('git', args, { cwd: projectRoot, windowsHide: true }, (error, stdout) => {
if (error) reject(error);
else resolvePromise(stdout.trim());
});
});
}
async function gitEvidence(projectRoot) {
const [commit, status] = await Promise.all([
runGit(projectRoot, ['rev-parse', 'HEAD']),
runGit(projectRoot, ['status', '--porcelain']),
]);
return { commit, dirty: Boolean(status) };
}
async function runExtensionSmoke(projectRoot, artifact) { async function runExtensionSmoke(projectRoot, artifact) {
return await runPackagedProductProof({ return await runPackagedProductProof({
projectRoot, projectRoot,
@@ -21,6 +39,7 @@ async function runExtensionSmoke(projectRoot, artifact) {
} }
export async function runRealPiSmoke(options) { export async function runRealPiSmoke(options) {
const git = await gitEvidence(options.projectRoot);
const artifact = await verifyPiProductArtifact(options); const artifact = await verifyPiProductArtifact(options);
const common = { const common = {
samples: options.samples, samples: options.samples,
@@ -41,7 +60,8 @@ export async function runRealPiSmoke(options) {
const report = { const report = {
schemaVersion: 1, schemaVersion: 1,
generatedAt: new Date().toISOString(), generatedAt: new Date().toISOString(),
commit: process.env.MAKELORE_BUILD_COMMIT ?? null, commit: git.commit,
git,
platform: { platform: platform(), arch: arch() }, platform: { platform: platform(), arch: arch() },
scope: { scope: {
finalProductArtifact: true, finalProductArtifact: true,
@@ -76,8 +96,11 @@ export async function runRealPiSmoke(options) {
}, },
result: 'pass', result: 'pass',
crossPlatformReleaseReady: false, crossPlatformReleaseReady: false,
releaseBlockers: [ releaseBlockers: platform() === 'linux' && !release().toLowerCase().includes('microsoft')
'macOS x64 and macOS arm64 PI-150 final-artifact/runtime/resource/performance validation was skipped by explicit user direction', ? []
: ['Native non-WSL Linux desktop/compositor PI-150 evidence is not established by this report'],
deferredToPi160: [
'macOS x64 and macOS arm64 final release validation was skipped by explicit user direction and remains Not Pass',
], ],
}; };
if (Object.values(report.coverage).some((value) => value !== true)) { if (Object.values(report.coverage).some((value) => value !== true)) {
+12 -3
View File
@@ -467,8 +467,10 @@ describe('Pi worker pool', () => {
it('rebuilds stale idle workers before prompt and lets running workers settle first', async () => { it('rebuilds stale idle workers before prompt and lets running workers settle first', async () => {
const workers = new Map<string, FakeWorker[]>(); const workers = new Map<string, FakeWorker[]>();
const revisions: Array<{ conversationId: string; provider: number; resources: number }> = []; const revisions: Array<{ conversationId: string; provider: number; resources: number }> = [];
const telemetry: PiRuntimeTelemetryEvent[] = [];
const pool = new PiWorkerPool({ const pool = new PiWorkerPool({
maxIdle: 4, maxIdle: 4,
onTelemetry: (event) => telemetry.push(event),
openWorker: async ({ conversation: input, revision }) => { openWorker: async ({ conversation: input, revision }) => {
const worker = new FakeWorker(`worker-${input.conversationId}-${(workers.get(input.conversationId)?.length ?? 0) + 1}`); const worker = new FakeWorker(`worker-${input.conversationId}-${(workers.get(input.conversationId)?.length ?? 0) + 1}`);
workers.set(input.conversationId, [...(workers.get(input.conversationId) ?? []), worker]); workers.set(input.conversationId, [...(workers.get(input.conversationId) ?? []), worker]);
@@ -494,7 +496,7 @@ describe('Pi worker pool', () => {
await expect.poll(() => workers.get('conversation-running')![0]!.requests.length).toBe(1); await expect.poll(() => workers.get('conversation-running')![0]!.requests.length).toBe(1);
pool.markProviderStale(); pool.markProviderStale();
pool.startTopLevel({ const idleTicket = pool.startTopLevel({
conversationId: 'conversation-idle', conversationId: 'conversation-idle',
runId: 'run-idle', runId: 'run-idle',
command: { type: 'prompt', message: 'idle' }, command: { type: 'prompt', message: 'idle' },
@@ -503,6 +505,11 @@ describe('Pi worker pool', () => {
await expect.poll(() => workers.get('conversation-idle')?.length).toBe(2); await expect.poll(() => workers.get('conversation-idle')?.length).toBe(2);
expect(workers.get('conversation-idle')![0]!.stopped).toBe(true); expect(workers.get('conversation-idle')![0]!.stopped).toBe(true);
await expect.poll(() => workers.get('conversation-idle')![1]!.requests.length).toBe(1); await expect.poll(() => workers.get('conversation-idle')![1]!.requests.length).toBe(1);
await idleTicket.accepted;
expect(telemetry.filter(({ runRef }) => runRef === 'runidle')).toEqual([
expect.objectContaining({ milestone: 'worker.queue_wait', workerGeneration: 2, cold: false }),
expect.objectContaining({ milestone: 'prompt.accepted', workerGeneration: 2, cold: false }),
]);
expect(workers.get('conversation-running')).toHaveLength(1); expect(workers.get('conversation-running')).toHaveLength(1);
expect(workers.get('conversation-running')![0]!.stopped).toBe(false); expect(workers.get('conversation-running')![0]!.stopped).toBe(false);
@@ -756,15 +763,17 @@ describe('Pi worker pool', () => {
await second.accepted; await second.accepted;
expect(telemetry.map(({ milestone }) => milestone)).toEqual([ expect(telemetry.map(({ milestone }) => milestone)).toEqual([
'worker.queue_wait',
'prompt.accepted', 'prompt.accepted',
'agent.settled', 'agent.settled',
'worker.queue_wait', 'worker.queue_wait',
'prompt.accepted', 'prompt.accepted',
]); ]);
expect(telemetry[1]).toMatchObject({ expect(telemetry[0]).toMatchObject({ durationMs: 0, workerGeneration: 1, cold: true });
expect(telemetry[2]).toMatchObject({
milestone: 'agent.settled', durationMs: 20, workerGeneration: 1, cold: true, milestone: 'agent.settled', durationMs: 20, workerGeneration: 1, cold: true,
}); });
expect(telemetry[2]).toMatchObject({ durationMs: 20, workerGeneration: 1, cold: true }); expect(telemetry[3]).toMatchObject({ durationMs: 20, workerGeneration: 1, cold: true });
const serialized = JSON.stringify(telemetry); const serialized = JSON.stringify(telemetry);
expect(serialized).not.toContain('private first prompt'); expect(serialized).not.toContain('private first prompt');
expect(serialized).not.toContain('private second prompt'); expect(serialized).not.toContain('private second prompt');