fix(pi): harden release proof lifecycle
This commit is contained in:
@@ -133,6 +133,7 @@ async function runProductProofSamples(artifact, projectRoot, samples) {
|
||||
runtimeRoot: artifact.artifact.runtimeRoot,
|
||||
electronExecutable: artifact.artifact.executable,
|
||||
reportPath: undefined,
|
||||
verifyCleanupFailure: false,
|
||||
}));
|
||||
}
|
||||
return reports;
|
||||
|
||||
@@ -16,6 +16,7 @@ function parseArgs(argv, projectRoot = process.cwd()) {
|
||||
runtimeRoot: undefined,
|
||||
electronExecutable: undefined,
|
||||
reportPath: undefined,
|
||||
verifyCleanupFailure: true,
|
||||
};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const argument = argv[index];
|
||||
@@ -107,6 +108,27 @@ function assertReleasedPressure(pressure) {
|
||||
}
|
||||
}
|
||||
|
||||
function assertManagedTurns(extension) {
|
||||
const expectedMilestones = 'worker.queue_wait,resources.ready,worker.spawn,rpc.ready,session.open,prompt.accepted,agent.start,provider.first_event,agent.settled';
|
||||
const expectedDelayMs = extension?.providerFirstEventDelayMs;
|
||||
if (!Number.isFinite(expectedDelayMs) || expectedDelayMs < 1) {
|
||||
throw new Error(`PI proof did not expose a controlled Provider first-event delay: ${expectedDelayMs}`);
|
||||
}
|
||||
for (const turn of extension?.managedTurns ?? []) {
|
||||
if (turn.milestones.map(({ milestone }) => milestone).join(',') !== expectedMilestones) {
|
||||
throw new Error(`PI managed turn timeline is incomplete: ${JSON.stringify(turn)}`);
|
||||
}
|
||||
const agentStart = turn.milestones.find(({ milestone }) => milestone === 'agent.start');
|
||||
const providerFirstEvent = turn.milestones.find(({ milestone }) => milestone === 'provider.first_event');
|
||||
if (agentStart?.source !== 'pi.agent_start'
|
||||
|| providerFirstEvent?.source !== 'pi.assistant_message_start'
|
||||
|| providerFirstEvent.at <= agentStart.at
|
||||
|| providerFirstEvent.durationMs < Math.floor(expectedDelayMs * 0.75)) {
|
||||
throw new Error(`PI Provider first-event milestone is not Provider-response-backed: ${JSON.stringify(turn)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function evaluateProof(electronApplication, action) {
|
||||
return await electronApplication.evaluate(async (_electron, requestedAction) => {
|
||||
const proof = globalThis.__niancodeRunPiReleaseProofE2E;
|
||||
@@ -158,17 +180,14 @@ export async function runPackagedProductProof(options) {
|
||||
|
||||
const extension = await evaluateProof(electronApplication, 'extension');
|
||||
assertPackagedMain(extension);
|
||||
assertManagedTurns(extension.extension);
|
||||
if (extension.extension?.subagentStatus !== 'complete'
|
||||
|| extension.extension?.childToolNames?.join(',') !== 'find,grep,ls,read'
|
||||
|| !extension.extension?.parentToolNames?.includes('subagent')
|
||||
|| extension.extension?.parentProcessIds?.length < 2
|
||||
|| 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'
|
||||
))) {
|
||||
|| extension.extension?.managedTurns?.length !== 2) {
|
||||
throw new Error(`Final ASAR extension/subagent proof failed: ${JSON.stringify(extension.extension)}`);
|
||||
}
|
||||
|
||||
@@ -187,6 +206,34 @@ export async function runPackagedProductProof(options) {
|
||||
assertPackagedMain(pressureFinish);
|
||||
assertReleasedPressure(pressureFinish.pressure);
|
||||
|
||||
let failureCleanup;
|
||||
if (options.verifyCleanupFailure === true) {
|
||||
const failureStart = await evaluateProof(electronApplication, 'pressure.start');
|
||||
pressureActive = true;
|
||||
assertPackagedMain(failureStart);
|
||||
assertActivePressure(failureStart.pressure);
|
||||
let injectedFailure;
|
||||
try {
|
||||
await evaluateProof(electronApplication, 'pressure.finish.inject-failure');
|
||||
} catch (error) {
|
||||
injectedFailure = error;
|
||||
}
|
||||
if (!injectedFailure
|
||||
|| !String(injectedFailure).includes('PI release pressure cleanup failed: parents.settle')) {
|
||||
throw new Error(`PI pressure cleanup failure injection did not fail as expected: ${String(injectedFailure)}`);
|
||||
}
|
||||
const retryFinish = await evaluateProof(electronApplication, 'pressure.finish');
|
||||
pressureActive = false;
|
||||
assertPackagedMain(retryFinish);
|
||||
assertReleasedPressure(retryFinish.pressure);
|
||||
failureCleanup = {
|
||||
injectedAt: 'parents.settle',
|
||||
firstFinish: 'failed-as-injected',
|
||||
retry: 'pass',
|
||||
released: retryFinish.pressure,
|
||||
};
|
||||
}
|
||||
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
@@ -203,6 +250,7 @@ export async function runPackagedProductProof(options) {
|
||||
durationMs: uiInteractiveMs,
|
||||
},
|
||||
released: pressureFinish.pressure,
|
||||
...(failureCleanup ? { failureCleanup } : {}),
|
||||
},
|
||||
result: 'pass',
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ async function runExtensionSmoke(projectRoot, artifact) {
|
||||
runtimeRoot: artifact.artifact.runtimeRoot,
|
||||
electronExecutable: artifact.artifact.executable,
|
||||
reportPath: undefined,
|
||||
verifyCleanupFailure: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user