| /* |
| * 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}`); |
| } |