Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
238 lines
7.8 KiB
JavaScript
238 lines
7.8 KiB
JavaScript
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
import { createHash } from 'node:crypto';
|
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { basename, join, resolve } from 'node:path';
|
|
import {
|
|
createTaskFixture,
|
|
findEvalReleaseTarball,
|
|
findJsonDiagnostics,
|
|
isolatedEnvironment,
|
|
readFrameworkOutputs,
|
|
run,
|
|
runAllowingFailure,
|
|
} from './release-cli-eval-support.mjs';
|
|
|
|
const HARBOR_VERSION = '0.20.0';
|
|
const PIER_VERSION = '0.3.0';
|
|
const TASK_IMAGE =
|
|
'python:3.12-slim@sha256:dd29372629eeba2dd003fd9e9d35a5b8236c44727875a0364254b5127af88e65';
|
|
const releaseDirectory = resolve('packages/cli/release');
|
|
const tarballPath = findEvalReleaseTarball(releaseDirectory);
|
|
|
|
const root = mkdtempSync(join(tmpdir(), 'maka-cli-eval-validation-'));
|
|
let primaryError;
|
|
try {
|
|
validateChecksum();
|
|
const prefix = join(root, 'prefix');
|
|
const environment = isolatedEnvironment(join(root, 'home'));
|
|
logStep('installing the immutable tarball with an empty offline npm cache');
|
|
run(
|
|
'npm',
|
|
[
|
|
'install',
|
|
'--global',
|
|
'--ignore-scripts',
|
|
'--offline',
|
|
'--no-audit',
|
|
'--no-fund',
|
|
'--cache',
|
|
join(root, 'npm-cache'),
|
|
'--prefix',
|
|
prefix,
|
|
tarballPath,
|
|
],
|
|
environment,
|
|
root,
|
|
);
|
|
|
|
const maka = join(prefix, 'bin/maka');
|
|
const packageRoot = join(prefix, 'lib/node_modules/maka-agent');
|
|
if (
|
|
!existsSync(maka) ||
|
|
!existsSync(join(packageRoot, 'node_modules/@maka/eval/harbor/run_trial.py'))
|
|
) {
|
|
throw new Error('The installed candidate is missing its CLI or bundled Eval runtime');
|
|
}
|
|
|
|
const fixture = createTaskFixture(join(root, 'fixture'), environment, TASK_IMAGE);
|
|
for (const framework of ['harbor', 'pier']) {
|
|
logStep(`running a real ${framework} Docker cell from the installed candidate`);
|
|
validateFramework({
|
|
environment,
|
|
fixture,
|
|
framework,
|
|
maka,
|
|
root: join(root, framework),
|
|
});
|
|
}
|
|
logStep(`OK — installed ${basename(tarballPath)} completed real Harbor and Pier cells`);
|
|
} catch (error) {
|
|
primaryError = error;
|
|
} finally {
|
|
let cleanupError;
|
|
try {
|
|
rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
|
} catch (error) {
|
|
cleanupError = error;
|
|
}
|
|
if (primaryError && cleanupError) {
|
|
throw new AggregateError([primaryError, cleanupError], 'Eval validation and cleanup failed');
|
|
}
|
|
if (primaryError) throw primaryError;
|
|
if (cleanupError) throw cleanupError;
|
|
}
|
|
|
|
function validateFramework({ environment, fixture, framework, maka, root: frameworkRoot }) {
|
|
mkdirSync(frameworkRoot, { recursive: true, mode: 0o700 });
|
|
const outputRoot = join(frameworkRoot, 'output');
|
|
const trialsRoot = join(frameworkRoot, 'trials');
|
|
const pythonVariable =
|
|
framework === 'harbor' ? 'MAKA_RELEASE_HARBOR_PYTHON' : 'MAKA_RELEASE_PIER_PYTHON';
|
|
const pythonPath = process.env[pythonVariable];
|
|
if (!pythonPath) throw new Error(`${pythonVariable} is required`);
|
|
const pythonPathVariable = `MAKA_RELEASE_${framework.toUpperCase()}_PYTHON_PATH`;
|
|
const trialsVariable = `MAKA_RELEASE_${framework.toUpperCase()}_TRIALS`;
|
|
const tasksVariable = 'MAKA_RELEASE_PIER_TASKS';
|
|
const specPath = join(frameworkRoot, 'experiment.json');
|
|
const spec = experimentSpec({
|
|
fixture,
|
|
framework,
|
|
pythonPathVariable,
|
|
tasksVariable,
|
|
trialsVariable,
|
|
});
|
|
writeFileSync(specPath, `${JSON.stringify(spec)}\n`, { mode: 0o600 });
|
|
const childEnvironment = {
|
|
...environment,
|
|
[pythonPathVariable]: pythonPath,
|
|
[trialsVariable]: trialsRoot,
|
|
...(framework === 'pier' ? { [tasksVariable]: fixture.tasksRoot } : {}),
|
|
};
|
|
const invocation = runAllowingFailure(
|
|
maka,
|
|
['eval', 'run', specPath, '--out', outputRoot],
|
|
childEnvironment,
|
|
frameworkRoot,
|
|
);
|
|
const { summary, attempt } = readFrameworkOutputs({
|
|
framework,
|
|
invocation,
|
|
outputRoot,
|
|
trialsRoot,
|
|
});
|
|
const result = attempt.result;
|
|
if (
|
|
summary.experimentId !== `release-${framework}` ||
|
|
summary.cells !== 1 ||
|
|
summary.incomplete !== 0 ||
|
|
attempt.cellId !== 'task::1::subject' ||
|
|
attempt.sequence !== 1 ||
|
|
result?.status !== 'completed' ||
|
|
result?.score !== 1 ||
|
|
result?.usage !== null ||
|
|
result?.costUsd !== null ||
|
|
result?.failureReason !== null
|
|
) {
|
|
throw new Error(
|
|
`${framework} produced an invalid completed attempt: ${JSON.stringify({
|
|
invocation,
|
|
summary,
|
|
attempt,
|
|
frameworkDiagnostics: findJsonDiagnostics(trialsRoot),
|
|
})}`,
|
|
);
|
|
}
|
|
const trialArtifact = result.artifacts.find(
|
|
(artifact) => artifact.kind === 'trial' && artifact.framework === framework,
|
|
);
|
|
const processArtifact = result.artifacts.find(
|
|
(artifact) => artifact.kind === 'external_process' && artifact.exitCode === 0,
|
|
);
|
|
const collected = result.artifacts.filter((artifact) => artifact.kind === 'collected-artifact');
|
|
if (!trialArtifact || !processArtifact || collected.length < 2) {
|
|
throw new Error(`${framework} did not preserve the expected trial and subject artifacts`);
|
|
}
|
|
const containers = run(
|
|
'docker',
|
|
['ps', '--all', '--quiet', '--filter', `name=${trialArtifact.trialName}`],
|
|
childEnvironment,
|
|
frameworkRoot,
|
|
).trim();
|
|
if (containers) throw new Error(`${framework} left trial containers behind: ${containers}`);
|
|
}
|
|
|
|
function experimentSpec({ fixture, framework, pythonPathVariable, tasksVariable, trialsVariable }) {
|
|
return {
|
|
schemaVersion: 'maka.eval.v1',
|
|
id: `release-${framework}`,
|
|
benchmark: {
|
|
id: 'release-validation',
|
|
version: fixture.commit,
|
|
config: { repository: fixture.repository },
|
|
},
|
|
executor: {
|
|
kind: framework,
|
|
config: {
|
|
frameworkVersion: framework === 'harbor' ? HARBOR_VERSION : PIER_VERSION,
|
|
pythonPathEnv: pythonPathVariable,
|
|
trialsRootEnv: trialsVariable,
|
|
...(framework === 'pier' ? { tasksRootEnv: tasksVariable } : {}),
|
|
environment: { type: 'docker', delete: true },
|
|
preparationEnvironment: [],
|
|
mounts: [],
|
|
},
|
|
},
|
|
subjects: [
|
|
{
|
|
id: 'subject',
|
|
kind: 'external',
|
|
credentials: [],
|
|
config: { command: '/bin/true', args: [], result: 'exit-code' },
|
|
},
|
|
],
|
|
tasks: [
|
|
{
|
|
id: 'task',
|
|
input: 'Exit successfully without modifying the task.',
|
|
config: framework === 'harbor' ? { harbor: { path: 'task' } } : { pier: { path: 'task' } },
|
|
},
|
|
],
|
|
repetitions: 1,
|
|
budget: { timeoutMultiplier: 1 },
|
|
verifier: { reward: 'reward' },
|
|
};
|
|
}
|
|
|
|
function validateChecksum() {
|
|
const checksumPath = `${tarballPath}.sha256`;
|
|
const [expected, name, extra] = readFileSync(checksumPath, 'utf8').trim().split(/\s+/u);
|
|
if (!expected || name !== basename(tarballPath) || extra !== undefined) {
|
|
throw new Error(`Invalid checksum file ${checksumPath}`);
|
|
}
|
|
const actual = createHash('sha256').update(readFileSync(tarballPath)).digest('hex');
|
|
if (actual !== expected) throw new Error(`Checksum mismatch for ${tarballPath}`);
|
|
}
|
|
|
|
function logStep(message) {
|
|
console.log(`[release-cli-eval-validation] ${message}`);
|
|
}
|