maka-agent 0.2.0-dev.3.20260830 → 0.2.0-dev.5.20260830
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-core.js +1 -0
- package/dist/eval-bundle-path.js +1 -1
- package/dist/pi-tui-runner.js +3 -1
- package/dist/runtime-host-cli.js +37 -3
- package/dist/runtime-host-onboarding.js +10 -1
- package/dist/runtime-host-peer-mesh-management-command.js +17 -0
- package/dist/runtime-host-setup-command.js +33 -15
- package/dist/runtime-host-systemd-service.js +9 -3
- package/dist/runtime-host-tui-context.js +2 -6
- package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
- package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
- package/node_modules/@maka/eval/dist/external-subject.js +1 -1
- package/node_modules/@maka/eval/dist/harbor-external-subject.js +2 -2
- package/node_modules/@maka/runtime/dist/sandbox/macos-seatbelt.js +68 -9
- package/node_modules/@maka/runtime-host/dist/client/peer-client.js +22 -3
- package/node_modules/@maka/runtime-host/dist/operator/peer-mesh-management-frame.js +14 -0
- package/{packages/eval/dist/index.js → node_modules/@maka/runtime-host/dist/peer-mesh/display-name.js} +12 -10
- package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +32 -21
- package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +207 -47
- package/node_modules/@maka/runtime-host/dist/peer-mesh/owner.js +2 -0
- package/node_modules/@maka/runtime-host/dist/peer-mesh/store.js +18 -5
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
- package/node_modules/@maka/runtime-host/dist/protocol/peer-mesh.js +59 -2
- package/node_modules/@maka/runtime-host/dist/server/access-authority.js +11 -7
- package/node_modules/@maka/runtime-host/dist/server/connection-session.js +3 -0
- package/node_modules/@maka/runtime-host/dist/server/execution-service.js +1 -0
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/peer-mesh-authority.js +19 -0
- package/package.json +1 -2
- package/packages/eval/dist/attempt-store.js +0 -138
- package/packages/eval/dist/cli.js +0 -119
- package/packages/eval/dist/experiment-directory.js +0 -59
- package/packages/eval/dist/experiment.js +0 -49
- package/packages/eval/dist/external-subject.js +0 -488
- package/packages/eval/dist/harbor-external-subject.js +0 -929
- package/packages/eval/dist/harbor-maka-subject.js +0 -83
- package/packages/eval/dist/harness-environment.js +0 -80
- package/packages/eval/dist/harness-executor.js +0 -1013
- package/packages/eval/dist/install-preflight.js +0 -201
- package/packages/eval/dist/maka-artifacts.js +0 -126
- package/packages/eval/dist/maka-runtime-policy.js +0 -42
- package/packages/eval/dist/maka-subject.js +0 -268
- package/packages/eval/dist/metering-checkpoint.js +0 -80
- package/packages/eval/dist/provider-admission.js +0 -39
- package/packages/eval/dist/provider-metering.js +0 -67
- package/packages/eval/dist/provider-web-tool-surface.js +0 -74
- package/packages/eval/dist/relay-result-frame.js +0 -38
- package/packages/eval/dist/result.js +0 -94
- package/packages/eval/dist/runner.js +0 -290
- package/packages/eval/dist/spec.js +0 -135
- package/packages/eval/dist/toolchain-verification.js +0 -130
- package/packages/eval/harbor/deepseek-codex-models.json +0 -69
- package/packages/eval/harbor/deepseek-harness-profile/cordis.patch.yml +0 -140
- package/packages/eval/harbor/deepseek-harness-profile/cordis.yml +0 -18
- package/packages/eval/harbor/deepseek-harness-profile/package.json +0 -10
- package/packages/eval/harbor/docker-compose-egress-proxy.yaml +0 -57
- package/packages/eval/harbor/egress-proxy/Dockerfile +0 -25
- package/packages/eval/harbor/egress-proxy/entrypoint.sh +0 -62
- package/packages/eval/harbor/egress-proxy/network-policy +0 -148
- package/packages/eval/harbor/egress_filter.py +0 -435
- package/packages/eval/harbor/eval_framework.py +0 -40
- package/packages/eval/harbor/relay_agent.py +0 -686
- package/packages/eval/harbor/run_trial.py +0 -223
- package/packages/eval/package.json +0 -20
|
@@ -1,1013 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
-
* or more contributor license agreements. See the NOTICE file
|
|
4
|
-
* distributed with this work for additional information
|
|
5
|
-
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
-
* to you under the Apache License, Version 2.0 (the
|
|
7
|
-
* "License"); you may not use this file except in compliance
|
|
8
|
-
* with the License. You may obtain a copy of the License at
|
|
9
|
-
*
|
|
10
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
-
*
|
|
12
|
-
* Unless required by applicable law or agreed to in writing,
|
|
13
|
-
* software distributed under the License is distributed on an
|
|
14
|
-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
-
* KIND, either express or implied. See the License for the
|
|
16
|
-
* specific language governing permissions and limitations
|
|
17
|
-
* under the License.
|
|
18
|
-
*/
|
|
19
|
-
import { spawn } from 'node:child_process';
|
|
20
|
-
import { createHash, randomBytes } from 'node:crypto';
|
|
21
|
-
import { once } from 'node:events';
|
|
22
|
-
import { createReadStream } from 'node:fs';
|
|
23
|
-
import { chmod, lstat, mkdir, readFile, readdir, unlink, writeFile } from 'node:fs/promises';
|
|
24
|
-
import { createServer } from 'node:net';
|
|
25
|
-
import { basename, dirname, join, posix, relative, resolve, sep } from 'node:path';
|
|
26
|
-
import { createInterface } from 'node:readline';
|
|
27
|
-
import { decodeJsonObject } from './experiment.js';
|
|
28
|
-
import { BUNDLED_HARNESS_RELAY_ROOT, createHarnessPreparationEnvironment, resolveRealPathWithinRoot, } from './harness-environment.js';
|
|
29
|
-
import { preflightHarnessInstallation, } from './install-preflight.js';
|
|
30
|
-
import { MAKA_RUNTIME_ARTIFACT_PATH, MAKA_SUBJECT_STDERR_PATH, MAKA_SUBJECT_STDOUT_PATH, } from './maka-artifacts.js';
|
|
31
|
-
const PIER_FRAMEWORK_LOG_MOUNTS = Object.freeze([
|
|
32
|
-
{ directory: 'agent', target: '/logs/agent' },
|
|
33
|
-
{ directory: 'verifier', target: '/logs/verifier' },
|
|
34
|
-
{ directory: 'artifacts', target: '/logs/artifacts' },
|
|
35
|
-
]);
|
|
36
|
-
export function createHarborExecutor(config, specPath) {
|
|
37
|
-
return createHarnessExecutor('harbor', config, specPath);
|
|
38
|
-
}
|
|
39
|
-
export function createPierExecutor(config, specPath) {
|
|
40
|
-
return createHarnessExecutor('pier', config, specPath);
|
|
41
|
-
}
|
|
42
|
-
function createHarnessExecutor(framework, config, specPath) {
|
|
43
|
-
const options = decodeHarnessOptions(config, framework);
|
|
44
|
-
const executor = {
|
|
45
|
-
kind: framework,
|
|
46
|
-
preflight: (input, dependencies) => preflightHarnessInstallation({
|
|
47
|
-
framework,
|
|
48
|
-
options,
|
|
49
|
-
specPath,
|
|
50
|
-
subjectCredentialNames: input.subjectCredentialNames,
|
|
51
|
-
...(input.signal ? { signal: input.signal } : {}),
|
|
52
|
-
}, dependencies),
|
|
53
|
-
validate: (cell) => {
|
|
54
|
-
decodeTask(framework, options, cell);
|
|
55
|
-
},
|
|
56
|
-
runAttempt: (input, operation) => runHarnessAttempt(framework, options, specPath, input, operation),
|
|
57
|
-
};
|
|
58
|
-
return executor;
|
|
59
|
-
}
|
|
60
|
-
async function runHarnessAttempt(framework, options, specPath, { cell, subjectCredentialNames, signal, }, operation) {
|
|
61
|
-
if (signal?.aborted)
|
|
62
|
-
return notStarted('cancelled');
|
|
63
|
-
let prepared;
|
|
64
|
-
try {
|
|
65
|
-
prepared = await startTrial(framework, options, specPath, cell, subjectCredentialNames, signal);
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return notStarted('preparation-failed');
|
|
69
|
-
}
|
|
70
|
-
if (prepared.kind === 'not_started')
|
|
71
|
-
return prepared;
|
|
72
|
-
const state = prepared.state;
|
|
73
|
-
let decision = false;
|
|
74
|
-
let value;
|
|
75
|
-
let hasValue = false;
|
|
76
|
-
let hostCancellationObserved = false;
|
|
77
|
-
let verificationConfirmedBeforeCancellation = false;
|
|
78
|
-
let finalizationEvidence;
|
|
79
|
-
let cleanupAction;
|
|
80
|
-
let cleanupEvidence;
|
|
81
|
-
let cleanup;
|
|
82
|
-
const terminate = () => {
|
|
83
|
-
cleanupAction ??= state.used ? 'abort' : 'terminate-unused';
|
|
84
|
-
cleanup ??= (async () => {
|
|
85
|
-
const evidence = await waitForTrial(state.child, {
|
|
86
|
-
phase: state.used ? 'abort' : 'unused',
|
|
87
|
-
deadlineMs: state.used ? RELAY_SETTLEMENT_DEADLINE_MS : TERM_SETTLEMENT_DEADLINE_MS,
|
|
88
|
-
});
|
|
89
|
-
if (evidence.outcome === 'unsettled')
|
|
90
|
-
await state.closeRelay();
|
|
91
|
-
return evidence;
|
|
92
|
-
})();
|
|
93
|
-
return cleanup;
|
|
94
|
-
};
|
|
95
|
-
const onAbort = () => {
|
|
96
|
-
hostCancellationObserved = true;
|
|
97
|
-
void terminate();
|
|
98
|
-
};
|
|
99
|
-
signal?.addEventListener('abort', onAbort, { once: true });
|
|
100
|
-
if (signal?.aborted)
|
|
101
|
-
onAbort();
|
|
102
|
-
const decide = () => {
|
|
103
|
-
if (decision)
|
|
104
|
-
return true;
|
|
105
|
-
if (!sendRelayMessage(state.transport, 'decision', { token: state.token, kind: 'verify' }, true)) {
|
|
106
|
-
void terminate();
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
decision = true;
|
|
110
|
-
return true;
|
|
111
|
-
};
|
|
112
|
-
try {
|
|
113
|
-
value = await operation({
|
|
114
|
-
context: relayContext(state, signal),
|
|
115
|
-
verify: async () => {
|
|
116
|
-
if (!decide()) {
|
|
117
|
-
cleanupEvidence = await terminate();
|
|
118
|
-
finalizationEvidence = cleanupEvidence;
|
|
119
|
-
throw new Error('relay verify decision was not delivered');
|
|
120
|
-
}
|
|
121
|
-
const completed = cleanup
|
|
122
|
-
? await cleanup
|
|
123
|
-
: await waitForTrial(state.child, { phase: 'completion' });
|
|
124
|
-
finalizationEvidence = completed;
|
|
125
|
-
if (!finalizationConfirmed(completed))
|
|
126
|
-
throw new Error('Trial did not finalize cleanly');
|
|
127
|
-
const verification = await readVerification(state, cell, framework, Boolean(options.egressProxy));
|
|
128
|
-
verificationConfirmedBeforeCancellation = !hostCancellationObserved;
|
|
129
|
-
return verification;
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
hasValue = true;
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
signal?.removeEventListener('abort', onAbort);
|
|
136
|
-
if (!decision || cleanup) {
|
|
137
|
-
cleanupEvidence = await terminate();
|
|
138
|
-
finalizationEvidence = cleanupEvidence;
|
|
139
|
-
}
|
|
140
|
-
await state.closeRelay();
|
|
141
|
-
}
|
|
142
|
-
if (hasValue &&
|
|
143
|
-
(state.transport.failure || cleanupAction || state.diagnostic?.category !== 'none')) {
|
|
144
|
-
value = {
|
|
145
|
-
...value,
|
|
146
|
-
artifacts: [
|
|
147
|
-
...value.artifacts,
|
|
148
|
-
...(state.transport.failure
|
|
149
|
-
? [{ kind: 'executor-relay', ...state.transport.failure }]
|
|
150
|
-
: []),
|
|
151
|
-
...(cleanupAction
|
|
152
|
-
? [
|
|
153
|
-
{
|
|
154
|
-
kind: 'executor-cleanup',
|
|
155
|
-
action: cleanupAction,
|
|
156
|
-
phase: cleanupEvidence.phase,
|
|
157
|
-
deadlineMs: cleanupEvidence.deadlineMs,
|
|
158
|
-
escalation: cleanupEvidence.escalation,
|
|
159
|
-
outcome: cleanupEvidence.outcome,
|
|
160
|
-
},
|
|
161
|
-
]
|
|
162
|
-
: []),
|
|
163
|
-
...(state.diagnostic && state.diagnostic.category !== 'none'
|
|
164
|
-
? [{ kind: 'executor-relay-result', ...state.diagnostic }]
|
|
165
|
-
: []),
|
|
166
|
-
],
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
if (hostCancellationObserved && !verificationConfirmedBeforeCancellation) {
|
|
170
|
-
return hasValue
|
|
171
|
-
? { kind: 'indeterminate', cause: 'host-cancelled', value }
|
|
172
|
-
: { kind: 'indeterminate', cause: 'host-cancelled' };
|
|
173
|
-
}
|
|
174
|
-
if (!finalizationEvidence || !finalizationConfirmed(finalizationEvidence)) {
|
|
175
|
-
return hasValue
|
|
176
|
-
? { kind: 'indeterminate', cause: 'cleanup-unconfirmed', value }
|
|
177
|
-
: { kind: 'indeterminate', cause: 'cleanup-unconfirmed' };
|
|
178
|
-
}
|
|
179
|
-
if (!hasValue)
|
|
180
|
-
throw new Error('executor operation did not settle');
|
|
181
|
-
return { kind: 'settled', value };
|
|
182
|
-
}
|
|
183
|
-
function relayContext(state, signal) {
|
|
184
|
-
const resultToken = randomBytes(16).toString('hex');
|
|
185
|
-
const metadata = {
|
|
186
|
-
trialName: state.trialName,
|
|
187
|
-
trialPath: state.trialPath,
|
|
188
|
-
meteringSecret: resultToken,
|
|
189
|
-
};
|
|
190
|
-
return {
|
|
191
|
-
cwd: state.cwd,
|
|
192
|
-
taskInput: state.taskInput,
|
|
193
|
-
metadata,
|
|
194
|
-
...(signal ? { signal } : {}),
|
|
195
|
-
execute: async (input) => {
|
|
196
|
-
signal?.throwIfAborted();
|
|
197
|
-
if (state.used)
|
|
198
|
-
throw new Error('Trial already executed its subject');
|
|
199
|
-
state.used = true;
|
|
200
|
-
const credentials = Object.fromEntries(Object.entries(input.credentialEnvironment).map(([target, source]) => {
|
|
201
|
-
const value = state.credentials[source];
|
|
202
|
-
if (value === undefined)
|
|
203
|
-
throw new Error(`credential ${source} was not admitted`);
|
|
204
|
-
return [target, value];
|
|
205
|
-
}));
|
|
206
|
-
if (!sendRelayMessage(state.transport, 'execute', {
|
|
207
|
-
token: state.token,
|
|
208
|
-
kind: 'execute',
|
|
209
|
-
command: input.command,
|
|
210
|
-
args: input.args,
|
|
211
|
-
environment: mergeExecutionEnvironment(state.executionEnvironment, input.environment ?? {}),
|
|
212
|
-
credentials,
|
|
213
|
-
resultToken,
|
|
214
|
-
captureStdout: input.captureStdout ?? true,
|
|
215
|
-
})) {
|
|
216
|
-
throw new Error('relay transport is unavailable');
|
|
217
|
-
}
|
|
218
|
-
state.transport.stage = 'receive';
|
|
219
|
-
let executed;
|
|
220
|
-
try {
|
|
221
|
-
executed = await readLine(state.lines);
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
state.transport.failure ??= {
|
|
225
|
-
stage: 'receive',
|
|
226
|
-
category: 'protocol-error',
|
|
227
|
-
delivery: 'unknown',
|
|
228
|
-
};
|
|
229
|
-
throw new Error('relay execution result was unavailable');
|
|
230
|
-
}
|
|
231
|
-
if (executed.token !== state.token ||
|
|
232
|
-
executed.kind !== 'executed' ||
|
|
233
|
-
(executed.termination !== 'exited' && executed.termination !== 'framework_timeout') ||
|
|
234
|
-
typeof executed.exitCode !== 'number' ||
|
|
235
|
-
typeof executed.stdout !== 'string' ||
|
|
236
|
-
!validProcessDiagnostic(executed.diagnostic)) {
|
|
237
|
-
state.transport.failure ??= {
|
|
238
|
-
stage: 'receive',
|
|
239
|
-
category: 'protocol-error',
|
|
240
|
-
delivery: 'unknown',
|
|
241
|
-
};
|
|
242
|
-
throw new Error('relay returned an invalid execution result');
|
|
243
|
-
}
|
|
244
|
-
state.diagnostic = executed.diagnostic;
|
|
245
|
-
return {
|
|
246
|
-
termination: executed.termination,
|
|
247
|
-
exitCode: executed.exitCode,
|
|
248
|
-
stdout: executed.stdout,
|
|
249
|
-
diagnostic: executed.diagnostic,
|
|
250
|
-
};
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
function validProcessDiagnostic(value) {
|
|
255
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
256
|
-
return false;
|
|
257
|
-
const diagnostic = value;
|
|
258
|
-
const fields = Object.keys(diagnostic);
|
|
259
|
-
return ((diagnostic.category === 'none' && fields.length === 1) ||
|
|
260
|
-
([
|
|
261
|
-
'unstructured-output',
|
|
262
|
-
'result-frame-missing',
|
|
263
|
-
'result-frame-invalid',
|
|
264
|
-
'result-frame-ambiguous',
|
|
265
|
-
'result-frame-oversize',
|
|
266
|
-
'execution-scope-unavailable',
|
|
267
|
-
].includes(String(diagnostic.category)) &&
|
|
268
|
-
fields.length === 3 &&
|
|
269
|
-
typeof diagnostic.bytes === 'number' &&
|
|
270
|
-
Number.isSafeInteger(diagnostic.bytes) &&
|
|
271
|
-
diagnostic.bytes >= 0 &&
|
|
272
|
-
typeof diagnostic.sha256 === 'string' &&
|
|
273
|
-
/^[0-9a-f]{64}$/u.test(diagnostic.sha256)));
|
|
274
|
-
}
|
|
275
|
-
async function startTrial(framework, options, specPath, cell, subjectCredentialNames, signal) {
|
|
276
|
-
const credentials = requireCredentials(cell.subject.credentials);
|
|
277
|
-
const token = randomBytes(24).toString('hex');
|
|
278
|
-
const trialsRoot = resolve(process.env[options.trialsRootEnv]);
|
|
279
|
-
await mkdir(trialsRoot, { recursive: true, mode: 0o700 });
|
|
280
|
-
await chmod(trialsRoot, 0o700);
|
|
281
|
-
const trialName = `${safeName(cell.id)}-${randomBytes(6).toString('hex')}`;
|
|
282
|
-
const configPath = join(trialsRoot, `${trialName}.json`);
|
|
283
|
-
const trialPath = join(trialsRoot, trialName);
|
|
284
|
-
const task = decodeTask(framework, options, cell);
|
|
285
|
-
const timeoutMultiplier = positive(cell.budget.timeoutMultiplier, 'budget.timeoutMultiplier');
|
|
286
|
-
const egressPaths = await resolveEgressPaths(options);
|
|
287
|
-
const environmentConfig = resolveEnvironmentConfig(options, egressPaths, framework, trialPath);
|
|
288
|
-
const networkPolicyPath = egressPaths?.networkPolicyPath;
|
|
289
|
-
const executionEnvironment = {
|
|
290
|
-
...UNATTENDED_EXECUTION_ENVIRONMENT,
|
|
291
|
-
...egressExecutionEnvironment(options.egressProxy),
|
|
292
|
-
};
|
|
293
|
-
const environment = createHarnessPreparationEnvironment({
|
|
294
|
-
subjectCredentialNames: [...subjectCredentialNames, ...cell.subject.credentials],
|
|
295
|
-
declared: options.preparationEnvironment,
|
|
296
|
-
...(options.egressProxy && networkPolicyPath
|
|
297
|
-
? {
|
|
298
|
-
egress: {
|
|
299
|
-
allowedHost: options.egressProxy.allowedHost,
|
|
300
|
-
networkPolicyPath,
|
|
301
|
-
},
|
|
302
|
-
}
|
|
303
|
-
: {}),
|
|
304
|
-
});
|
|
305
|
-
const server = createServer();
|
|
306
|
-
const connections = new Set();
|
|
307
|
-
server.on('connection', (socket) => {
|
|
308
|
-
connections.add(socket);
|
|
309
|
-
socket.on('error', () => undefined);
|
|
310
|
-
socket.once('close', () => connections.delete(socket));
|
|
311
|
-
});
|
|
312
|
-
let child;
|
|
313
|
-
let serverClosed;
|
|
314
|
-
const stopServer = (destroyConnections = false) => {
|
|
315
|
-
serverClosed ??= closeServer(server);
|
|
316
|
-
if (destroyConnections) {
|
|
317
|
-
for (const socket of connections)
|
|
318
|
-
socket.destroy();
|
|
319
|
-
}
|
|
320
|
-
return serverClosed;
|
|
321
|
-
};
|
|
322
|
-
let stage = 'spawn';
|
|
323
|
-
try {
|
|
324
|
-
server.listen(0, '127.0.0.1');
|
|
325
|
-
await once(server, 'listening');
|
|
326
|
-
const address = server.address();
|
|
327
|
-
if (!address || typeof address === 'string')
|
|
328
|
-
throw new Error('relay did not bind TCP');
|
|
329
|
-
await writeFile(configPath, `${JSON.stringify({
|
|
330
|
-
task,
|
|
331
|
-
trial_name: trialName,
|
|
332
|
-
trials_dir: trialsRoot,
|
|
333
|
-
timeout_multiplier: timeoutMultiplier,
|
|
334
|
-
agent: {
|
|
335
|
-
import_path: 'relay_agent:RelayAgent',
|
|
336
|
-
kwargs: {
|
|
337
|
-
relay_host: '127.0.0.1',
|
|
338
|
-
relay_port: address.port,
|
|
339
|
-
relay_token: token,
|
|
340
|
-
teardown_timeout_ms: PYTHON_TEARDOWN_DEADLINE_MS,
|
|
341
|
-
},
|
|
342
|
-
},
|
|
343
|
-
environment: environmentConfig,
|
|
344
|
-
...(options.egressProxy
|
|
345
|
-
? {
|
|
346
|
-
artifacts: [
|
|
347
|
-
{
|
|
348
|
-
source: '/opt/maka-egress-state/hits.jsonl',
|
|
349
|
-
destination: EGRESS_AUDIT_DESTINATION,
|
|
350
|
-
service: 'maka-eval-mitmproxy',
|
|
351
|
-
},
|
|
352
|
-
],
|
|
353
|
-
}
|
|
354
|
-
: {}),
|
|
355
|
-
})}\n`, { flag: 'wx', mode: 0o600 });
|
|
356
|
-
child = spawn(process.env[options.pythonPathEnv], [
|
|
357
|
-
join(BUNDLED_HARNESS_RELAY_ROOT, 'run_trial.py'),
|
|
358
|
-
framework,
|
|
359
|
-
options.frameworkVersion,
|
|
360
|
-
configPath,
|
|
361
|
-
], { cwd: dirname(specPath), env: environment, stdio: 'ignore' });
|
|
362
|
-
await once(child, 'spawn', signal ? { signal } : undefined);
|
|
363
|
-
stage = 'exit-before-ready';
|
|
364
|
-
const exitedBeforeReady = once(child, 'exit').then(([code]) => {
|
|
365
|
-
throw new Error(`Trial exited before Agent.run (${code})`);
|
|
366
|
-
});
|
|
367
|
-
const connectionWait = new AbortController();
|
|
368
|
-
const connectionSignal = signal
|
|
369
|
-
? AbortSignal.any([signal, connectionWait.signal])
|
|
370
|
-
: connectionWait.signal;
|
|
371
|
-
let socket;
|
|
372
|
-
try {
|
|
373
|
-
socket = await Promise.race([
|
|
374
|
-
once(server, 'connection', { signal: connectionSignal }).then(([connected]) => connected),
|
|
375
|
-
exitedBeforeReady,
|
|
376
|
-
]);
|
|
377
|
-
}
|
|
378
|
-
finally {
|
|
379
|
-
connectionWait.abort();
|
|
380
|
-
}
|
|
381
|
-
const relayClosed = stopServer();
|
|
382
|
-
stage = 'ready-decode';
|
|
383
|
-
const transport = createRelayTransport(socket);
|
|
384
|
-
transport.stage = 'receive';
|
|
385
|
-
const lines = createInterface({ input: socket, crlfDelay: Number.POSITIVE_INFINITY })[Symbol.asyncIterator]();
|
|
386
|
-
const ready = await abortable(Promise.race([readLine(lines), exitedBeforeReady]), signal);
|
|
387
|
-
if (ready.token !== token ||
|
|
388
|
-
ready.kind !== 'ready' ||
|
|
389
|
-
typeof ready.instruction !== 'string' ||
|
|
390
|
-
typeof ready.cwd !== 'string' ||
|
|
391
|
-
!ready.cwd.startsWith('/')) {
|
|
392
|
-
throw new Error('relay returned an invalid ready message');
|
|
393
|
-
}
|
|
394
|
-
return {
|
|
395
|
-
kind: 'ready',
|
|
396
|
-
state: {
|
|
397
|
-
child,
|
|
398
|
-
transport,
|
|
399
|
-
closeRelay: async () => {
|
|
400
|
-
for (const connection of connections)
|
|
401
|
-
connection.destroy();
|
|
402
|
-
await relayClosed;
|
|
403
|
-
},
|
|
404
|
-
lines,
|
|
405
|
-
token,
|
|
406
|
-
trialName,
|
|
407
|
-
trialPath,
|
|
408
|
-
taskInput: ready.instruction,
|
|
409
|
-
credentials,
|
|
410
|
-
cwd: ready.cwd,
|
|
411
|
-
executionEnvironment,
|
|
412
|
-
used: false,
|
|
413
|
-
},
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
catch (error) {
|
|
417
|
-
const relayClosed = stopServer(true);
|
|
418
|
-
if (child?.pid !== undefined) {
|
|
419
|
-
await waitForTrial(child, {
|
|
420
|
-
phase: 'unused',
|
|
421
|
-
deadlineMs: TERM_SETTLEMENT_DEADLINE_MS,
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
await relayClosed;
|
|
425
|
-
await unlink(configPath).catch(() => undefined);
|
|
426
|
-
await mkdir(trialPath, { recursive: true, mode: 0o700 });
|
|
427
|
-
const diagnosticPath = 'preparation-error.json';
|
|
428
|
-
const code = preparationCode(stage, child?.exitCode ?? null, signal);
|
|
429
|
-
await writeFile(join(trialPath, diagnosticPath), `${JSON.stringify({
|
|
430
|
-
stage,
|
|
431
|
-
framework,
|
|
432
|
-
code,
|
|
433
|
-
errorCode: safeErrorCode(error),
|
|
434
|
-
exitCode: child?.exitCode ?? null,
|
|
435
|
-
signal: child?.signalCode ?? null,
|
|
436
|
-
})}\n`, { flag: 'wx', mode: 0o600 });
|
|
437
|
-
return notStarted(code, [
|
|
438
|
-
{ kind: 'executor-preparation', framework, trialName, path: diagnosticPath },
|
|
439
|
-
]);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
function createRelayTransport(socket) {
|
|
443
|
-
const transport = { socket, stage: 'ready' };
|
|
444
|
-
socket.on('error', (error) => {
|
|
445
|
-
transport.failure ??= {
|
|
446
|
-
stage: transport.stage,
|
|
447
|
-
category: relayTransportCategory(error.code),
|
|
448
|
-
delivery: 'unknown',
|
|
449
|
-
};
|
|
450
|
-
});
|
|
451
|
-
return transport;
|
|
452
|
-
}
|
|
453
|
-
function sendRelayMessage(transport, stage, value, end = false) {
|
|
454
|
-
transport.stage = stage;
|
|
455
|
-
const socket = transport.socket;
|
|
456
|
-
if (socket.destroyed || !socket.writable || socket.writableEnded || transport.failure) {
|
|
457
|
-
transport.failure = {
|
|
458
|
-
stage,
|
|
459
|
-
category: transport.failure?.category ?? 'peer-closed',
|
|
460
|
-
delivery: 'not-delivered',
|
|
461
|
-
};
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
try {
|
|
465
|
-
const payload = `${JSON.stringify(value)}\n`;
|
|
466
|
-
if (end)
|
|
467
|
-
socket.end(payload);
|
|
468
|
-
else {
|
|
469
|
-
socket.write(payload, (error) => {
|
|
470
|
-
if (!error)
|
|
471
|
-
return;
|
|
472
|
-
transport.failure = {
|
|
473
|
-
stage,
|
|
474
|
-
category: relayTransportCategory(error.code),
|
|
475
|
-
delivery: 'unknown',
|
|
476
|
-
};
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
return true;
|
|
480
|
-
}
|
|
481
|
-
catch (error) {
|
|
482
|
-
transport.failure = {
|
|
483
|
-
stage,
|
|
484
|
-
category: relayTransportCategory(error.code),
|
|
485
|
-
delivery: 'not-delivered',
|
|
486
|
-
};
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
function relayTransportCategory(code) {
|
|
491
|
-
if (code === 'EPIPE')
|
|
492
|
-
return 'broken-pipe';
|
|
493
|
-
if (code === 'ECONNRESET')
|
|
494
|
-
return 'connection-reset';
|
|
495
|
-
if (code === 'ERR_STREAM_WRITE_AFTER_END')
|
|
496
|
-
return 'peer-closed';
|
|
497
|
-
return 'transport-error';
|
|
498
|
-
}
|
|
499
|
-
function notStarted(code, artifacts = []) {
|
|
500
|
-
return { kind: 'not_started', code, artifacts };
|
|
501
|
-
}
|
|
502
|
-
function preparationCode(stage, exitCode, signal) {
|
|
503
|
-
if (signal?.aborted)
|
|
504
|
-
return 'cancelled';
|
|
505
|
-
if (stage === 'exit-before-ready' && exitCode === 78)
|
|
506
|
-
return 'framework-version-mismatch';
|
|
507
|
-
if (stage === 'spawn')
|
|
508
|
-
return 'spawn-failed';
|
|
509
|
-
if (stage === 'ready-decode')
|
|
510
|
-
return 'invalid-ready';
|
|
511
|
-
return 'exit-before-ready';
|
|
512
|
-
}
|
|
513
|
-
function mergeExecutionEnvironment(required, subject) {
|
|
514
|
-
const overlap = Object.keys(required).filter((name) => Object.hasOwn(subject, name));
|
|
515
|
-
if (overlap.length > 0) {
|
|
516
|
-
throw new Error(`subject environment overrides Eval execution environment: ${overlap.join(', ')}`);
|
|
517
|
-
}
|
|
518
|
-
return { ...subject, ...required };
|
|
519
|
-
}
|
|
520
|
-
// Every subject runs unattended in a fresh container, where a package manager
|
|
521
|
-
// that stops to ask a question is indistinguishable from one that hung. That is
|
|
522
|
-
// a property of the environment, not of any one arm: tasks install packages, and
|
|
523
|
-
// subjects only decide when.
|
|
524
|
-
const UNATTENDED_EXECUTION_ENVIRONMENT = {
|
|
525
|
-
DEBIAN_FRONTEND: 'noninteractive',
|
|
526
|
-
TZ: 'Etc/UTC',
|
|
527
|
-
};
|
|
528
|
-
function egressExecutionEnvironment(options) {
|
|
529
|
-
if (!options)
|
|
530
|
-
return {};
|
|
531
|
-
const noProxy = '127.0.0.1,localhost';
|
|
532
|
-
return {
|
|
533
|
-
HTTP_PROXY: options.proxyUrl,
|
|
534
|
-
HTTPS_PROXY: options.proxyUrl,
|
|
535
|
-
http_proxy: options.proxyUrl,
|
|
536
|
-
https_proxy: options.proxyUrl,
|
|
537
|
-
NO_PROXY: noProxy,
|
|
538
|
-
no_proxy: noProxy,
|
|
539
|
-
SSL_CERT_FILE: options.containerCaPath,
|
|
540
|
-
REQUESTS_CA_BUNDLE: options.containerCaPath,
|
|
541
|
-
CURL_CA_BUNDLE: options.containerCaPath,
|
|
542
|
-
GIT_SSL_CAINFO: options.containerCaPath,
|
|
543
|
-
NODE_EXTRA_CA_CERTS: options.containerCaPath,
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
function safeErrorCode(error) {
|
|
547
|
-
const code = error instanceof Error ? error.code : undefined;
|
|
548
|
-
return code && ['ENOENT', 'EACCES', 'EPERM'].includes(code) ? code : null;
|
|
549
|
-
}
|
|
550
|
-
async function closeServer(server) {
|
|
551
|
-
if (!server.listening)
|
|
552
|
-
return;
|
|
553
|
-
await new Promise((resolveClose, rejectClose) => {
|
|
554
|
-
server.close((error) => (error ? rejectClose(error) : resolveClose()));
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
export const EGRESS_AUDIT_DESTINATION = 'egress-hits.jsonl';
|
|
558
|
-
export const EGRESS_AUDIT_ARTIFACT_PATH = `artifacts/${EGRESS_AUDIT_DESTINATION}`;
|
|
559
|
-
export function collectEgressAuditArtifact(audit, expected) {
|
|
560
|
-
if (!expected)
|
|
561
|
-
return { missing: false, failureReason: null, artifacts: [] };
|
|
562
|
-
if (audit === undefined) {
|
|
563
|
-
return {
|
|
564
|
-
missing: true,
|
|
565
|
-
failureReason: 'egress audit log missing',
|
|
566
|
-
artifacts: [{ kind: 'egress-audit-missing', path: EGRESS_AUDIT_ARTIFACT_PATH }],
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
const forensics = inspectEgressAudit(audit);
|
|
570
|
-
return {
|
|
571
|
-
missing: false,
|
|
572
|
-
failureReason: null,
|
|
573
|
-
artifacts: [
|
|
574
|
-
{
|
|
575
|
-
kind: 'egress-audit',
|
|
576
|
-
path: EGRESS_AUDIT_ARTIFACT_PATH,
|
|
577
|
-
bytes: audit.byteLength,
|
|
578
|
-
sha256: `sha256:${createHash('sha256').update(audit).digest('hex')}`,
|
|
579
|
-
truncated: forensics.truncated,
|
|
580
|
-
policyErrorCount: forensics.policyErrorCount,
|
|
581
|
-
malformedLineCount: forensics.malformedLineCount,
|
|
582
|
-
},
|
|
583
|
-
],
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
function inspectEgressAudit(audit) {
|
|
587
|
-
let truncated = false;
|
|
588
|
-
let policyErrorCount = 0;
|
|
589
|
-
let malformedLineCount = 0;
|
|
590
|
-
for (const line of audit.toString('utf8').split(/\r?\n/)) {
|
|
591
|
-
const trimmed = line.trim();
|
|
592
|
-
if (!trimmed)
|
|
593
|
-
continue;
|
|
594
|
-
let record;
|
|
595
|
-
try {
|
|
596
|
-
record = JSON.parse(trimmed);
|
|
597
|
-
}
|
|
598
|
-
catch {
|
|
599
|
-
malformedLineCount += 1;
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
603
|
-
malformedLineCount += 1;
|
|
604
|
-
continue;
|
|
605
|
-
}
|
|
606
|
-
const ruleId = record.ruleId;
|
|
607
|
-
if (ruleId === 'audit_truncated')
|
|
608
|
-
truncated = true;
|
|
609
|
-
if (ruleId === 'policy_error')
|
|
610
|
-
policyErrorCount += 1;
|
|
611
|
-
}
|
|
612
|
-
return { truncated, policyErrorCount, malformedLineCount };
|
|
613
|
-
}
|
|
614
|
-
async function readVerification(state, cell, framework, expectEgressAudit) {
|
|
615
|
-
const result = JSON.parse(await readFile(join(state.trialPath, 'result.json'), 'utf8'));
|
|
616
|
-
const score = result.verifier_result?.rewards?.[rewardKey(cell)] ?? null;
|
|
617
|
-
const subjectException = ['AgentTimeoutError', 'NonZeroAgentExitCodeError'].includes(String(result.exception_info?.exception_type));
|
|
618
|
-
if (result.exception_info && !subjectException) {
|
|
619
|
-
throw new Error('Trial failed outside subject execution');
|
|
620
|
-
}
|
|
621
|
-
const egressAuditPath = join(state.trialPath, EGRESS_AUDIT_ARTIFACT_PATH);
|
|
622
|
-
let egressAudit;
|
|
623
|
-
try {
|
|
624
|
-
egressAudit = await readFile(egressAuditPath);
|
|
625
|
-
}
|
|
626
|
-
catch (error) {
|
|
627
|
-
const code = error.code;
|
|
628
|
-
if (expectEgressAudit && code !== 'ENOENT') {
|
|
629
|
-
return {
|
|
630
|
-
status: 'infra_failed',
|
|
631
|
-
score,
|
|
632
|
-
failureReason: `failed to read egress audit log ${egressAuditPath}${code ? ` (${code})` : ''}`,
|
|
633
|
-
artifacts: [
|
|
634
|
-
{ kind: 'trial', framework: cell.executor.kind, trialName: state.trialName },
|
|
635
|
-
...(await collectedArtifactInventory(state.trialPath, framework)),
|
|
636
|
-
{ kind: 'egress-audit-unreadable', path: EGRESS_AUDIT_ARTIFACT_PATH },
|
|
637
|
-
],
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
const audit = collectEgressAuditArtifact(egressAudit, expectEgressAudit);
|
|
642
|
-
return {
|
|
643
|
-
status: audit.failureReason
|
|
644
|
-
? 'infra_failed'
|
|
645
|
-
: score === null
|
|
646
|
-
? 'infra_failed'
|
|
647
|
-
: subjectException
|
|
648
|
-
? 'subject_failed'
|
|
649
|
-
: 'completed',
|
|
650
|
-
score,
|
|
651
|
-
failureReason: audit.failureReason ?? (score === null ? 'verifier produced no reward' : null),
|
|
652
|
-
artifacts: [
|
|
653
|
-
{ kind: 'trial', framework: cell.executor.kind, trialName: state.trialName },
|
|
654
|
-
...(await collectedArtifactInventory(state.trialPath, framework)),
|
|
655
|
-
...audit.artifacts,
|
|
656
|
-
],
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
async function collectedArtifactInventory(trialPath, framework) {
|
|
660
|
-
const root = framework === 'pier'
|
|
661
|
-
? join(trialPath, 'artifacts')
|
|
662
|
-
: join(trialPath, 'artifacts', 'logs', 'artifacts');
|
|
663
|
-
const files = [];
|
|
664
|
-
const targets = [
|
|
665
|
-
join(root, basename(MAKA_RUNTIME_ARTIFACT_PATH)),
|
|
666
|
-
join(root, basename(MAKA_SUBJECT_STDOUT_PATH)),
|
|
667
|
-
join(root, basename(MAKA_SUBJECT_STDERR_PATH)),
|
|
668
|
-
];
|
|
669
|
-
for (const target of targets) {
|
|
670
|
-
await walkCollectedArtifacts(trialPath, target, files).catch((error) => {
|
|
671
|
-
if (error.code !== 'ENOENT')
|
|
672
|
-
throw error;
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
return files.sort((left, right) => String(left.path).localeCompare(String(right.path)));
|
|
676
|
-
}
|
|
677
|
-
async function walkCollectedArtifacts(trialPath, current, files) {
|
|
678
|
-
const metadata = await lstat(current);
|
|
679
|
-
if (metadata.isSymbolicLink())
|
|
680
|
-
return;
|
|
681
|
-
if (metadata.isFile()) {
|
|
682
|
-
const hash = createHash('sha256');
|
|
683
|
-
for await (const chunk of createReadStream(current))
|
|
684
|
-
hash.update(chunk);
|
|
685
|
-
files.push({
|
|
686
|
-
kind: 'collected-artifact',
|
|
687
|
-
path: relative(trialPath, current).split(sep).join('/'),
|
|
688
|
-
bytes: metadata.size,
|
|
689
|
-
sha256: `sha256:${hash.digest('hex')}`,
|
|
690
|
-
});
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
if (!metadata.isDirectory())
|
|
694
|
-
return;
|
|
695
|
-
for (const entry of await readdir(current, { withFileTypes: true })) {
|
|
696
|
-
const path = join(current, entry.name);
|
|
697
|
-
if (entry.isSymbolicLink())
|
|
698
|
-
continue;
|
|
699
|
-
await walkCollectedArtifacts(trialPath, path, files);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
function decodeHarnessOptions(value, framework) {
|
|
703
|
-
if (!Object.hasOwn(value, 'preparationEnvironment')) {
|
|
704
|
-
throw new Error('executor.config.preparationEnvironment is required');
|
|
705
|
-
}
|
|
706
|
-
const fields = [
|
|
707
|
-
'frameworkVersion',
|
|
708
|
-
'pythonPathEnv',
|
|
709
|
-
'trialsRootEnv',
|
|
710
|
-
'environment',
|
|
711
|
-
'preparationEnvironment',
|
|
712
|
-
'mounts',
|
|
713
|
-
];
|
|
714
|
-
// Only the Harbor branch of run_trial.py applies the namespace policy, so a
|
|
715
|
-
// pier spec declaring egressProxy would set the proxy up and inject its
|
|
716
|
-
// environment while enforcement silently did not exist.
|
|
717
|
-
if (framework === 'pier' && Object.hasOwn(value, 'egressProxy')) {
|
|
718
|
-
throw new Error('executor.config.egressProxy is Harbor-only: pier does not apply the subject namespace policy');
|
|
719
|
-
}
|
|
720
|
-
if (Object.hasOwn(value, 'egressProxy'))
|
|
721
|
-
fields.push('egressProxy');
|
|
722
|
-
if (framework === 'pier')
|
|
723
|
-
fields.push('tasksRootEnv');
|
|
724
|
-
const options = exact(value, fields, 'executor.config');
|
|
725
|
-
const preparationEnvironment = array(options.preparationEnvironment, 'preparationEnvironment').map((name, index) => machinePathEnv(name, `preparationEnvironment[${index}]`));
|
|
726
|
-
if (new Set(preparationEnvironment).size !== preparationEnvironment.length) {
|
|
727
|
-
throw new Error('preparationEnvironment must contain unique names');
|
|
728
|
-
}
|
|
729
|
-
const decoded = {
|
|
730
|
-
frameworkVersion: text(options.frameworkVersion, 'frameworkVersion'),
|
|
731
|
-
pythonPathEnv: machinePathEnv(options.pythonPathEnv, 'pythonPathEnv'),
|
|
732
|
-
trialsRootEnv: machinePathEnv(options.trialsRootEnv, 'trialsRootEnv'),
|
|
733
|
-
environment: decodeJsonObject(options.environment, 'environment'),
|
|
734
|
-
preparationEnvironment,
|
|
735
|
-
...(Object.hasOwn(options, 'egressProxy')
|
|
736
|
-
? { egressProxy: decodeEgressProxy(options.egressProxy) }
|
|
737
|
-
: {}),
|
|
738
|
-
mounts: array(options.mounts, 'mounts').map((mount, index) => decodeMount(mount, index)),
|
|
739
|
-
...(framework === 'pier'
|
|
740
|
-
? { tasksRootEnv: machinePathEnv(options.tasksRootEnv, 'tasksRootEnv') }
|
|
741
|
-
: {}),
|
|
742
|
-
};
|
|
743
|
-
if (framework === 'pier') {
|
|
744
|
-
const reservedTargets = PIER_FRAMEWORK_LOG_MOUNTS.map((mount) => mount.target);
|
|
745
|
-
const collision = decoded.mounts.find((mount) => {
|
|
746
|
-
const target = posix.normalize(mount.target);
|
|
747
|
-
return reservedTargets.some((reserved) => target === reserved || target.startsWith(`${reserved}/`));
|
|
748
|
-
});
|
|
749
|
-
if (collision) {
|
|
750
|
-
throw new Error(`Pier mount target ${collision.target} is reserved for framework logs`);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
for (const name of [
|
|
754
|
-
decoded.pythonPathEnv,
|
|
755
|
-
decoded.trialsRootEnv,
|
|
756
|
-
decoded.tasksRootEnv,
|
|
757
|
-
decoded.egressProxy?.composeSourceEnv,
|
|
758
|
-
]) {
|
|
759
|
-
if (name && !process.env[name])
|
|
760
|
-
throw new Error(`machine path ${name} is unavailable`);
|
|
761
|
-
}
|
|
762
|
-
return decoded;
|
|
763
|
-
}
|
|
764
|
-
function decodeEgressProxy(value) {
|
|
765
|
-
const proxy = exact(value, [
|
|
766
|
-
'composeSourceEnv',
|
|
767
|
-
'composeRelativePath',
|
|
768
|
-
'networkPolicyRelativePath',
|
|
769
|
-
'proxyUrl',
|
|
770
|
-
'allowedHost',
|
|
771
|
-
'containerCaPath',
|
|
772
|
-
], 'egressProxy');
|
|
773
|
-
const proxyUrl = text(proxy.proxyUrl, 'egressProxy.proxyUrl');
|
|
774
|
-
if (!URL.canParse(proxyUrl) || new URL(proxyUrl).protocol !== 'http:') {
|
|
775
|
-
throw new Error('egressProxy.proxyUrl must be an HTTP proxy URL');
|
|
776
|
-
}
|
|
777
|
-
return {
|
|
778
|
-
composeSourceEnv: machinePathEnv(proxy.composeSourceEnv, 'egressProxy.composeSourceEnv'),
|
|
779
|
-
composeRelativePath: relativePath(proxy.composeRelativePath, 'egressProxy.composeRelativePath'),
|
|
780
|
-
networkPolicyRelativePath: relativePath(proxy.networkPolicyRelativePath, 'egressProxy.networkPolicyRelativePath'),
|
|
781
|
-
proxyUrl,
|
|
782
|
-
allowedHost: hostName(proxy.allowedHost, 'egressProxy.allowedHost'),
|
|
783
|
-
containerCaPath: absolute(proxy.containerCaPath, 'egressProxy.containerCaPath'),
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
function decodeMount(value, index) {
|
|
787
|
-
const mount = exact(value, ['sourceEnv', 'target', 'readOnly'], `mounts[${index}]`);
|
|
788
|
-
const sourceEnv = machinePathEnv(mount.sourceEnv, `mounts[${index}].sourceEnv`);
|
|
789
|
-
if (!process.env[sourceEnv])
|
|
790
|
-
throw new Error(`machine path ${sourceEnv} is unavailable`);
|
|
791
|
-
if (mount.readOnly !== true)
|
|
792
|
-
throw new Error(`mounts[${index}] must be read-only`);
|
|
793
|
-
return {
|
|
794
|
-
sourceEnv,
|
|
795
|
-
target: absolute(mount.target, `mounts[${index}].target`),
|
|
796
|
-
readOnly: true,
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
function resolveMounts(mounts) {
|
|
800
|
-
return mounts.map((mount) => ({
|
|
801
|
-
type: 'bind',
|
|
802
|
-
source: resolve(process.env[mount.sourceEnv]),
|
|
803
|
-
target: mount.target,
|
|
804
|
-
read_only: true,
|
|
805
|
-
}));
|
|
806
|
-
}
|
|
807
|
-
function resolveEnvironmentConfig(options, egressPaths, framework, trialPath) {
|
|
808
|
-
const configuredMounts = resolveMounts(options.mounts);
|
|
809
|
-
const mounts = framework === 'pier'
|
|
810
|
-
? [
|
|
811
|
-
...configuredMounts,
|
|
812
|
-
...PIER_FRAMEWORK_LOG_MOUNTS.map(({ directory, target }) => ({
|
|
813
|
-
type: 'bind',
|
|
814
|
-
source: join(trialPath, directory),
|
|
815
|
-
target,
|
|
816
|
-
})),
|
|
817
|
-
]
|
|
818
|
-
: configuredMounts;
|
|
819
|
-
const base = { ...options.environment, mounts };
|
|
820
|
-
if (!options.egressProxy)
|
|
821
|
-
return base;
|
|
822
|
-
if (!egressPaths)
|
|
823
|
-
throw new Error('egress proxy paths are unavailable');
|
|
824
|
-
return { ...base, extra_docker_compose: [egressPaths.composePath] };
|
|
825
|
-
}
|
|
826
|
-
async function resolveEgressPaths(options) {
|
|
827
|
-
if (!options.egressProxy)
|
|
828
|
-
return undefined;
|
|
829
|
-
const source = resolve(process.env[options.egressProxy.composeSourceEnv]);
|
|
830
|
-
const [composePath, networkPolicyPath] = await Promise.all([
|
|
831
|
-
resolveRealPathWithinRoot(source, options.egressProxy.composeRelativePath, 'egress proxy compose path'),
|
|
832
|
-
resolveRealPathWithinRoot(source, options.egressProxy.networkPolicyRelativePath, 'egress network policy path'),
|
|
833
|
-
]);
|
|
834
|
-
return { composePath, networkPolicyPath };
|
|
835
|
-
}
|
|
836
|
-
function decodeTask(framework, options, cell) {
|
|
837
|
-
if (framework === 'harbor') {
|
|
838
|
-
const benchmark = exact(cell.benchmark.config, ['repository'], 'benchmark.config');
|
|
839
|
-
const task = exact(cell.task.config, ['harbor'], 'task.config');
|
|
840
|
-
const harbor = exact(task.harbor, ['path'], 'task.config.harbor');
|
|
841
|
-
const revision = text(cell.benchmark.version, 'benchmark.version');
|
|
842
|
-
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(revision)) {
|
|
843
|
-
throw new Error('Harbor benchmark.version must be a complete Git commit');
|
|
844
|
-
}
|
|
845
|
-
return {
|
|
846
|
-
path: text(harbor.path, 'task.config.harbor.path'),
|
|
847
|
-
git_url: text(benchmark.repository, 'benchmark.config.repository'),
|
|
848
|
-
git_commit_id: revision,
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
const task = exact(cell.task.config, ['pier'], 'task.config');
|
|
852
|
-
const pier = exact(task.pier, ['path'], 'task.config.pier');
|
|
853
|
-
const root = resolve(process.env[options.tasksRootEnv]);
|
|
854
|
-
const path = resolve(root, text(pier.path, 'task.config.pier.path'));
|
|
855
|
-
const fromRoot = relative(root, path);
|
|
856
|
-
if (fromRoot === '..' || fromRoot.startsWith(`..${sep}`))
|
|
857
|
-
throw new Error('Pier task escapes tasksRoot');
|
|
858
|
-
return { path };
|
|
859
|
-
}
|
|
860
|
-
function rewardKey(cell) {
|
|
861
|
-
return text(exact(cell.verifier, ['reward'], 'verifier').reward, 'verifier.reward');
|
|
862
|
-
}
|
|
863
|
-
function requireCredentials(names) {
|
|
864
|
-
return Object.freeze(Object.fromEntries(names.map((name) => {
|
|
865
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name))
|
|
866
|
-
throw new Error(`invalid credential name ${name}`);
|
|
867
|
-
const value = process.env[name];
|
|
868
|
-
if (!value)
|
|
869
|
-
throw new Error(`subject credential ${name} is required`);
|
|
870
|
-
return [name, value];
|
|
871
|
-
})));
|
|
872
|
-
}
|
|
873
|
-
async function readLine(lines) {
|
|
874
|
-
const line = await lines.next();
|
|
875
|
-
if (line.done)
|
|
876
|
-
throw new Error('relay closed before settlement');
|
|
877
|
-
return JSON.parse(line.value);
|
|
878
|
-
}
|
|
879
|
-
async function abortable(operation, signal) {
|
|
880
|
-
if (!signal)
|
|
881
|
-
return operation;
|
|
882
|
-
signal.throwIfAborted();
|
|
883
|
-
return new Promise((resolveOperation, rejectOperation) => {
|
|
884
|
-
let settled = false;
|
|
885
|
-
const settle = (callback) => {
|
|
886
|
-
if (settled)
|
|
887
|
-
return;
|
|
888
|
-
settled = true;
|
|
889
|
-
signal.removeEventListener('abort', onAbort);
|
|
890
|
-
callback();
|
|
891
|
-
};
|
|
892
|
-
const onAbort = () => settle(() => rejectOperation(signal.reason));
|
|
893
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
894
|
-
void operation.then((value) => settle(() => resolveOperation(value)), (error) => settle(() => rejectOperation(error)));
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
const RELAY_SETTLEMENT_DEADLINE_MS = 120_000;
|
|
898
|
-
const PYTHON_TEARDOWN_DEADLINE_MS = 110_000;
|
|
899
|
-
const TERM_SETTLEMENT_DEADLINE_MS = 20_000;
|
|
900
|
-
const KILL_SETTLEMENT_DEADLINE_MS = 5_000;
|
|
901
|
-
async function waitForTrial(child, wait) {
|
|
902
|
-
const exit = child.exitCode !== null || child.signalCode !== null
|
|
903
|
-
? Promise.resolve({ code: child.exitCode, signal: child.signalCode })
|
|
904
|
-
: once(child, 'exit').then(([code, childSignal]) => ({ code, signal: childSignal }));
|
|
905
|
-
if (wait.phase === 'completion') {
|
|
906
|
-
const completed = await exit;
|
|
907
|
-
return {
|
|
908
|
-
phase: wait.phase,
|
|
909
|
-
deadlineMs: null,
|
|
910
|
-
escalation: 'none',
|
|
911
|
-
outcome: completed.code === 0 && completed.signal === null ? 'completed' : 'failed',
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
child.kill('SIGTERM');
|
|
915
|
-
const terminated = await within(exit, wait.deadlineMs);
|
|
916
|
-
if (terminated) {
|
|
917
|
-
return {
|
|
918
|
-
phase: wait.phase,
|
|
919
|
-
deadlineMs: wait.deadlineMs,
|
|
920
|
-
escalation: 'term',
|
|
921
|
-
outcome: terminated.code === 0 && terminated.signal === null ? 'confirmed' : 'terminated',
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
child.kill('SIGKILL');
|
|
925
|
-
const killed = await within(exit, KILL_SETTLEMENT_DEADLINE_MS);
|
|
926
|
-
if (killed) {
|
|
927
|
-
return {
|
|
928
|
-
phase: wait.phase,
|
|
929
|
-
deadlineMs: wait.deadlineMs,
|
|
930
|
-
escalation: 'kill',
|
|
931
|
-
outcome: 'killed',
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
child.unref();
|
|
935
|
-
return {
|
|
936
|
-
phase: wait.phase,
|
|
937
|
-
deadlineMs: wait.deadlineMs,
|
|
938
|
-
escalation: 'detached',
|
|
939
|
-
outcome: 'unsettled',
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
function finalizationConfirmed(evidence) {
|
|
943
|
-
return evidence.outcome === 'completed' || evidence.outcome === 'confirmed';
|
|
944
|
-
}
|
|
945
|
-
async function within(operation, timeoutMs) {
|
|
946
|
-
let timer;
|
|
947
|
-
try {
|
|
948
|
-
return await Promise.race([
|
|
949
|
-
operation,
|
|
950
|
-
new Promise((resolve) => {
|
|
951
|
-
timer = setTimeout(resolve, timeoutMs);
|
|
952
|
-
}),
|
|
953
|
-
]);
|
|
954
|
-
}
|
|
955
|
-
finally {
|
|
956
|
-
if (timer)
|
|
957
|
-
clearTimeout(timer);
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
function exact(value, fields, where) {
|
|
961
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
962
|
-
throw new Error(`${where} must be an object`);
|
|
963
|
-
const record = value;
|
|
964
|
-
if (fields.some((field) => !Object.hasOwn(record, field)) ||
|
|
965
|
-
Object.keys(record).some((field) => !fields.includes(field))) {
|
|
966
|
-
throw new Error(`${where} fields are invalid`);
|
|
967
|
-
}
|
|
968
|
-
return record;
|
|
969
|
-
}
|
|
970
|
-
function array(value, where) {
|
|
971
|
-
if (!Array.isArray(value))
|
|
972
|
-
throw new Error(`${where} must be an array`);
|
|
973
|
-
return value;
|
|
974
|
-
}
|
|
975
|
-
function text(value, where) {
|
|
976
|
-
if (typeof value !== 'string' || value.length === 0)
|
|
977
|
-
throw new Error(`${where} is required`);
|
|
978
|
-
return value;
|
|
979
|
-
}
|
|
980
|
-
function machinePathEnv(value, where) {
|
|
981
|
-
const name = text(value, where);
|
|
982
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name))
|
|
983
|
-
throw new Error(`${where} is invalid`);
|
|
984
|
-
return name;
|
|
985
|
-
}
|
|
986
|
-
function relativePath(value, where) {
|
|
987
|
-
const path = text(value, where);
|
|
988
|
-
if (path.startsWith('/') || path === '..' || path.startsWith(`..${sep}`)) {
|
|
989
|
-
throw new Error(`${where} must stay within its source root`);
|
|
990
|
-
}
|
|
991
|
-
return path;
|
|
992
|
-
}
|
|
993
|
-
function hostName(value, where) {
|
|
994
|
-
const host = text(value, where).toLowerCase();
|
|
995
|
-
if (!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/u.test(host)) {
|
|
996
|
-
throw new Error(`${where} must be a hostname`);
|
|
997
|
-
}
|
|
998
|
-
return host;
|
|
999
|
-
}
|
|
1000
|
-
function absolute(value, where) {
|
|
1001
|
-
const path = text(value, where);
|
|
1002
|
-
if (!path.startsWith('/'))
|
|
1003
|
-
throw new Error(`${where} must be absolute`);
|
|
1004
|
-
return path;
|
|
1005
|
-
}
|
|
1006
|
-
function positive(value, where) {
|
|
1007
|
-
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
1008
|
-
throw new Error(`${where} must be positive`);
|
|
1009
|
-
return value;
|
|
1010
|
-
}
|
|
1011
|
-
function safeName(value) {
|
|
1012
|
-
return value.replaceAll(/[^A-Za-z0-9._-]/gu, '-').slice(0, 80);
|
|
1013
|
-
}
|