maka-agent 0.2.0-dev.53.20260927 → 0.2.0-dev.62.20260928
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/LICENSE +6 -5
- package/dist/workspace-root.js +2 -2
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/acp-executor-plugin/dist/index.js +333 -28
- package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +283 -27
- package/node_modules/@maka/core/dist/executor-catalog.js +10 -1
- package/node_modules/@maka/core/dist/message-queue-order.js +47 -0
- package/node_modules/@maka/core/dist/model-metadata.js +2 -1
- package/node_modules/@maka/core/dist/redaction.js +42 -3
- package/node_modules/@maka/core/dist/relative-time-policy.js +71 -0
- package/node_modules/@maka/core/dist/relative-time.js +3 -54
- package/node_modules/@maka/core/package.json +1 -0
- package/node_modules/@maka/eval/dist/egress-audit.js +127 -0
- package/node_modules/@maka/eval/dist/harness-executor.js +16 -96
- package/node_modules/@maka/eval/harbor/egress-proxy/entrypoint.sh +4 -4
- package/node_modules/@maka/eval/harbor/egress_filter.py +153 -104
- package/node_modules/@maka/eval/harbor/eval_framework.py +37 -14
- package/node_modules/@maka/eval/harbor/relay_agent.py +4 -6
- package/node_modules/@maka/eval/harbor/run_trial.py +11 -4
- package/node_modules/@maka/runtime/dist/agent-graph-supervisor-wake.js +33 -6
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +1 -1
- package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +26 -5
- package/node_modules/@maka/runtime/dist/plugin-executor-service.js +36 -0
- package/node_modules/@maka/runtime/dist/provider-error-classification.js +8 -73
- package/node_modules/@maka/runtime/dist/provider-error-signals.js +31 -0
- package/node_modules/@maka/runtime/dist/provider-retry-policy.js +79 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +6 -4
- package/node_modules/@maka/runtime/dist/session-projection-helpers.js +2 -0
- package/node_modules/@maka/runtime/dist/shell-run-manager.js +8 -8
- package/node_modules/@maka/runtime/dist/terminal-run-commit.js +7 -1
- package/node_modules/@maka/runtime/dist/tool-runtime.js +5 -4
- package/node_modules/@maka/runtime-host/dist/adapter/provider-retry-projector.js +63 -0
- package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +7 -51
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -3
- package/node_modules/@maka/runtime-host/dist/protocol/message-queue-limits.js +21 -0
- package/node_modules/@maka/runtime-host/dist/protocol/message.js +6 -104
- package/node_modules/@maka/runtime-host/dist/protocol/queue-mutation.js +136 -0
- package/node_modules/@maka/runtime-host/dist/protocol/turn-provider-retry.js +69 -0
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +2 -43
- package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +42 -5
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +105 -233
- package/node_modules/@maka/runtime-host/dist/server/message-queue-state.js +69 -0
- package/node_modules/@maka/runtime-host/dist/server/provider-retry-projection.js +71 -0
- package/node_modules/@maka/runtime-host/dist/server/queued-mutation-executor.js +94 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +9 -58
- package/node_modules/@maka/storage/dist/agent-run-store-contract.js +0 -3
- package/node_modules/@maka/storage/dist/agent-run-store.js +15 -6
- package/node_modules/@maka/storage/dist/execution-persistence-provider.js +1 -0
- package/node_modules/@maka/storage/dist/execution-stores.js +1 -0
- package/node_modules/@maka/storage/dist/project-catalog.js +41 -42
- package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +102 -31
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +52 -1
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +34 -2
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
export const RELATIVE_HORIZON_MS = 7 * 24 * 60 * 60 * 1000;
|
|
20
|
+
export const JUST_NOW_MS = 60_000;
|
|
21
|
+
export const MINUTE_MS = 60_000;
|
|
22
|
+
export const HOUR_MS = 60 * MINUTE_MS;
|
|
23
|
+
export const DAY_MS = 24 * HOUR_MS;
|
|
24
|
+
export const MONTH_MS = 30 * DAY_MS;
|
|
25
|
+
export const YEAR_MS = 12 * MONTH_MS;
|
|
26
|
+
const MAX_SIDEBAR_REFRESH_MS = 24 * DAY_MS;
|
|
27
|
+
export const JUST_NOW = {
|
|
28
|
+
'zh-CN': '刚刚',
|
|
29
|
+
'zh-TW': '剛剛',
|
|
30
|
+
en: 'just now',
|
|
31
|
+
};
|
|
32
|
+
const SIDEBAR_TIME_BUCKETS = [
|
|
33
|
+
{ unitMs: MINUTE_MS, maxValue: 60, suffix: 'min' },
|
|
34
|
+
{ unitMs: HOUR_MS, maxValue: 24, suffix: 'h' },
|
|
35
|
+
{ unitMs: DAY_MS, maxValue: 30, suffix: 'd' },
|
|
36
|
+
{ unitMs: MONTH_MS, maxValue: 12, suffix: 'mo' },
|
|
37
|
+
{ unitMs: YEAR_MS, maxValue: Number.POSITIVE_INFINITY, suffix: 'y' },
|
|
38
|
+
];
|
|
39
|
+
export function relativeAgeMs(timestamp, now) {
|
|
40
|
+
return Math.max(0, now - timestamp);
|
|
41
|
+
}
|
|
42
|
+
export function sidebarTimeBucket(ageMs) {
|
|
43
|
+
for (const bucket of SIDEBAR_TIME_BUCKETS) {
|
|
44
|
+
const value = Math.round(ageMs / bucket.unitMs);
|
|
45
|
+
if (value < bucket.maxValue)
|
|
46
|
+
return { value, unitMs: bucket.unitMs, suffix: bucket.suffix };
|
|
47
|
+
}
|
|
48
|
+
throw new Error('Sidebar time buckets must end with an unbounded bucket');
|
|
49
|
+
}
|
|
50
|
+
function nextRoundedBoundaryDelay(ageMs, unitMs, value) {
|
|
51
|
+
return Math.max(1, Math.ceil((value + 0.5) * unitMs - ageMs));
|
|
52
|
+
}
|
|
53
|
+
export function nextRelativeRefreshDelay(timestamp, now) {
|
|
54
|
+
const ageMs = relativeAgeMs(timestamp, now);
|
|
55
|
+
if (ageMs > RELATIVE_HORIZON_MS)
|
|
56
|
+
return null;
|
|
57
|
+
if (ageMs < JUST_NOW_MS)
|
|
58
|
+
return JUST_NOW_MS - ageMs;
|
|
59
|
+
if (ageMs < HOUR_MS)
|
|
60
|
+
return MINUTE_MS;
|
|
61
|
+
return 10 * MINUTE_MS;
|
|
62
|
+
}
|
|
63
|
+
export function nextSidebarRefreshDelay(timestamp, now) {
|
|
64
|
+
const ageMs = relativeAgeMs(timestamp, now);
|
|
65
|
+
if (!Number.isFinite(ageMs))
|
|
66
|
+
return null;
|
|
67
|
+
if (ageMs < JUST_NOW_MS)
|
|
68
|
+
return JUST_NOW_MS - ageMs;
|
|
69
|
+
const bucket = sidebarTimeBucket(ageMs);
|
|
70
|
+
return Math.min(nextRoundedBoundaryDelay(ageMs, bucket.unitMs, bucket.value), MAX_SIDEBAR_REFRESH_MS);
|
|
71
|
+
}
|
|
@@ -24,33 +24,7 @@
|
|
|
24
24
|
* useful than a relative label like "300 天前".
|
|
25
25
|
*/
|
|
26
26
|
import { uiLocaleToIntlLocale } from './ui-locale.js';
|
|
27
|
-
|
|
28
|
-
const RELATIVE_HORIZON_MS = 7 * 24 * 60 * 60 * 1000;
|
|
29
|
-
/** Age below this stays on one just-now label instead of counting seconds. */
|
|
30
|
-
const JUST_NOW_MS = 60_000;
|
|
31
|
-
const MINUTE_MS = 60_000;
|
|
32
|
-
const HOUR_MS = 60 * MINUTE_MS;
|
|
33
|
-
const DAY_MS = 24 * HOUR_MS;
|
|
34
|
-
const MONTH_MS = 30 * DAY_MS;
|
|
35
|
-
const YEAR_MS = 12 * MONTH_MS;
|
|
36
|
-
/** Stays below the browser timer ceiling while avoiding needless daily wakes. */
|
|
37
|
-
const MAX_SIDEBAR_REFRESH_MS = 24 * DAY_MS;
|
|
38
|
-
const SIDEBAR_TIME_BUCKETS = [
|
|
39
|
-
{ unitMs: MINUTE_MS, maxValue: 60, suffix: 'min' },
|
|
40
|
-
{ unitMs: HOUR_MS, maxValue: 24, suffix: 'h' },
|
|
41
|
-
{ unitMs: DAY_MS, maxValue: 30, suffix: 'd' },
|
|
42
|
-
{ unitMs: MONTH_MS, maxValue: 12, suffix: 'mo' },
|
|
43
|
-
{ unitMs: YEAR_MS, maxValue: Number.POSITIVE_INFINITY, suffix: 'y' },
|
|
44
|
-
];
|
|
45
|
-
const JUST_NOW = {
|
|
46
|
-
'zh-CN': '刚刚',
|
|
47
|
-
'zh-TW': '剛剛',
|
|
48
|
-
en: 'just now',
|
|
49
|
-
};
|
|
50
|
-
/** Future timestamps are treated as age zero and therefore display as just now. */
|
|
51
|
-
function relativeAgeMs(ts, now) {
|
|
52
|
-
return Math.max(0, now - ts);
|
|
53
|
-
}
|
|
27
|
+
import { JUST_NOW, JUST_NOW_MS, RELATIVE_HORIZON_MS, relativeAgeMs, nextRelativeRefreshDelay as nextRelativeRefreshDelayPolicy, nextSidebarRefreshDelay as nextSidebarRefreshDelayPolicy, sidebarTimeBucket, } from './relative-time-policy.js';
|
|
54
28
|
// One cache per formatter. They used to share `cachedLocale` and clear each
|
|
55
29
|
// other on a miss, so alternating relative and absolute reads — which is what
|
|
56
30
|
// the sidebar does, once per row — rebuilt an `Intl` formatter every call.
|
|
@@ -175,33 +149,8 @@ export function resetRelativeTimeFormatters() {
|
|
|
175
149
|
* past the horizon (never re-render).
|
|
176
150
|
*/
|
|
177
151
|
export function nextRelativeRefreshDelay(ts, now = Date.now()) {
|
|
178
|
-
|
|
179
|
-
if (ageMs > RELATIVE_HORIZON_MS)
|
|
180
|
-
return null;
|
|
181
|
-
if (ageMs < JUST_NOW_MS)
|
|
182
|
-
return JUST_NOW_MS - ageMs;
|
|
183
|
-
if (ageMs < 60 * 60_000)
|
|
184
|
-
return 60_000;
|
|
185
|
-
return 10 * 60_000;
|
|
186
|
-
}
|
|
187
|
-
function nextRoundedBoundaryDelay(ageMs, unitMs, value) {
|
|
188
|
-
return Math.max(1, Math.ceil((value + 0.5) * unitMs - ageMs));
|
|
189
|
-
}
|
|
190
|
-
function sidebarTimeBucket(ageMs) {
|
|
191
|
-
for (const bucket of SIDEBAR_TIME_BUCKETS) {
|
|
192
|
-
const value = Math.round(ageMs / bucket.unitMs);
|
|
193
|
-
if (value < bucket.maxValue)
|
|
194
|
-
return { value, unitMs: bucket.unitMs, suffix: bucket.suffix };
|
|
195
|
-
}
|
|
196
|
-
throw new Error('Sidebar time buckets must end with an unbounded bucket');
|
|
152
|
+
return nextRelativeRefreshDelayPolicy(ts, now);
|
|
197
153
|
}
|
|
198
|
-
/** Refreshes at the next visible sidebar bucket, capped below the timer ceiling. */
|
|
199
154
|
export function nextSidebarRefreshDelay(ts, now = Date.now()) {
|
|
200
|
-
|
|
201
|
-
if (!Number.isFinite(ageMs))
|
|
202
|
-
return null;
|
|
203
|
-
if (ageMs < JUST_NOW_MS)
|
|
204
|
-
return JUST_NOW_MS - ageMs;
|
|
205
|
-
const bucket = sidebarTimeBucket(ageMs);
|
|
206
|
-
return Math.min(nextRoundedBoundaryDelay(ageMs, bucket.unitMs, bucket.value), MAX_SIDEBAR_REFRESH_MS);
|
|
155
|
+
return nextSidebarRefreshDelayPolicy(ts, now);
|
|
207
156
|
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"./execution-inspect": "./dist/execution-inspect.js",
|
|
29
29
|
"./events": "./dist/events.js",
|
|
30
30
|
"./provider-retry-countdown": "./dist/provider-retry-countdown.js",
|
|
31
|
+
"./message-queue-order": "./dist/message-queue-order.js",
|
|
31
32
|
"./interaction": "./dist/interaction.js",
|
|
32
33
|
"./session": "./dist/session.js",
|
|
33
34
|
"./turn-origin": "./dist/turn-origin.js",
|
|
@@ -0,0 +1,127 @@
|
|
|
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 { createHash } from 'node:crypto';
|
|
20
|
+
import { readFile } from 'node:fs/promises';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
export const EGRESS_AUDIT_DESTINATION = 'egress-hits.jsonl';
|
|
23
|
+
export const EGRESS_AUDIT_ARTIFACT_PATH = `artifacts/${EGRESS_AUDIT_DESTINATION}`;
|
|
24
|
+
export function egressAuditCollection() {
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
source: '/opt/maka-egress-state/hits.jsonl',
|
|
28
|
+
destination: EGRESS_AUDIT_DESTINATION,
|
|
29
|
+
service: 'maka-eval-mitmproxy',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
export function describeEgressAudit(bytes, required) {
|
|
34
|
+
if (!required)
|
|
35
|
+
return cleanEvidence();
|
|
36
|
+
if (bytes === undefined)
|
|
37
|
+
return missingEvidence();
|
|
38
|
+
return {
|
|
39
|
+
failureReason: null,
|
|
40
|
+
artifacts: [
|
|
41
|
+
{
|
|
42
|
+
kind: 'egress-audit',
|
|
43
|
+
path: EGRESS_AUDIT_ARTIFACT_PATH,
|
|
44
|
+
bytes: bytes.byteLength,
|
|
45
|
+
sha256: digest(bytes),
|
|
46
|
+
...summarize(bytes),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function readEgressAuditEvidence(trialPath, required) {
|
|
52
|
+
if (!required)
|
|
53
|
+
return cleanEvidence();
|
|
54
|
+
const auditPath = join(trialPath, EGRESS_AUDIT_ARTIFACT_PATH);
|
|
55
|
+
try {
|
|
56
|
+
return describeEgressAudit(await readFile(auditPath), true);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const code = errorCode(error);
|
|
60
|
+
if (code === 'ENOENT')
|
|
61
|
+
return missingEvidence();
|
|
62
|
+
return {
|
|
63
|
+
failureReason: `failed to read egress audit log ${auditPath}${code ? ` (${code})` : ''}`,
|
|
64
|
+
artifacts: [{ kind: 'egress-audit-unreadable', path: EGRESS_AUDIT_ARTIFACT_PATH }],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function summarize(bytes) {
|
|
69
|
+
let truncated = false;
|
|
70
|
+
let policyErrorCount = 0;
|
|
71
|
+
let malformedLineCount = 0;
|
|
72
|
+
for (const line of jsonLines(bytes)) {
|
|
73
|
+
const record = decodeRecord(line);
|
|
74
|
+
if (record === undefined)
|
|
75
|
+
continue;
|
|
76
|
+
if (record === null) {
|
|
77
|
+
malformedLineCount += 1;
|
|
78
|
+
}
|
|
79
|
+
else if (record.ruleId === 'audit_truncated') {
|
|
80
|
+
truncated = true;
|
|
81
|
+
}
|
|
82
|
+
else if (record.ruleId === 'policy_error') {
|
|
83
|
+
policyErrorCount += 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { truncated, policyErrorCount, malformedLineCount };
|
|
87
|
+
}
|
|
88
|
+
function* jsonLines(bytes) {
|
|
89
|
+
let start = 0;
|
|
90
|
+
while (start < bytes.length) {
|
|
91
|
+
const newline = bytes.indexOf(0x0a, start);
|
|
92
|
+
const end = newline === -1 ? bytes.length : newline;
|
|
93
|
+
yield bytes.subarray(start, end);
|
|
94
|
+
start = newline === -1 ? bytes.length : newline + 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function decodeRecord(line) {
|
|
98
|
+
if (line.length === 0)
|
|
99
|
+
return undefined;
|
|
100
|
+
try {
|
|
101
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(line).trim();
|
|
102
|
+
if (!text)
|
|
103
|
+
return undefined;
|
|
104
|
+
const decoded = JSON.parse(text);
|
|
105
|
+
if (!decoded || typeof decoded !== 'object' || Array.isArray(decoded))
|
|
106
|
+
return null;
|
|
107
|
+
return decoded;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function cleanEvidence() {
|
|
114
|
+
return { failureReason: null, artifacts: [] };
|
|
115
|
+
}
|
|
116
|
+
function missingEvidence() {
|
|
117
|
+
return {
|
|
118
|
+
failureReason: 'egress audit log missing',
|
|
119
|
+
artifacts: [{ kind: 'egress-audit-missing', path: EGRESS_AUDIT_ARTIFACT_PATH }],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function digest(bytes) {
|
|
123
|
+
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
124
|
+
}
|
|
125
|
+
function errorCode(error) {
|
|
126
|
+
return error instanceof Error ? error.code : undefined;
|
|
127
|
+
}
|
|
@@ -24,6 +24,7 @@ import { chmod, lstat, mkdir, readFile, readdir, unlink, writeFile } from 'node:
|
|
|
24
24
|
import { createServer } from 'node:net';
|
|
25
25
|
import { basename, dirname, join, posix, relative, resolve, sep } from 'node:path';
|
|
26
26
|
import { createInterface } from 'node:readline';
|
|
27
|
+
import { egressAuditCollection, readEgressAuditEvidence } from './egress-audit.js';
|
|
27
28
|
import { decodeJsonObject } from './experiment.js';
|
|
28
29
|
import { BUNDLED_HARNESS_RELAY_ROOT, createHarnessPreparationEnvironment, resolveRealPathWithinRoot, } from './harness-environment.js';
|
|
29
30
|
import { preflightHarnessInstallation, } from './install-preflight.js';
|
|
@@ -344,13 +345,7 @@ async function startTrial(framework, options, specPath, cell, subjectCredentialN
|
|
|
344
345
|
environment: environmentConfig,
|
|
345
346
|
...(options.egressProxy
|
|
346
347
|
? {
|
|
347
|
-
artifacts:
|
|
348
|
-
{
|
|
349
|
-
source: '/opt/maka-egress-state/hits.jsonl',
|
|
350
|
-
destination: EGRESS_AUDIT_DESTINATION,
|
|
351
|
-
service: 'maka-eval-mitmproxy',
|
|
352
|
-
},
|
|
353
|
-
],
|
|
348
|
+
artifacts: egressAuditCollection(),
|
|
354
349
|
}
|
|
355
350
|
: {}),
|
|
356
351
|
})}\n`, { flag: 'wx', mode: 0o600 });
|
|
@@ -555,108 +550,33 @@ async function closeServer(server) {
|
|
|
555
550
|
server.close((error) => (error ? rejectClose(error) : resolveClose()));
|
|
556
551
|
});
|
|
557
552
|
}
|
|
558
|
-
|
|
559
|
-
export const EGRESS_AUDIT_ARTIFACT_PATH = `artifacts/${EGRESS_AUDIT_DESTINATION}`;
|
|
560
|
-
export function collectEgressAuditArtifact(audit, expected) {
|
|
561
|
-
if (!expected)
|
|
562
|
-
return { missing: false, failureReason: null, artifacts: [] };
|
|
563
|
-
if (audit === undefined) {
|
|
564
|
-
return {
|
|
565
|
-
missing: true,
|
|
566
|
-
failureReason: 'egress audit log missing',
|
|
567
|
-
artifacts: [{ kind: 'egress-audit-missing', path: EGRESS_AUDIT_ARTIFACT_PATH }],
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
const forensics = inspectEgressAudit(audit);
|
|
571
|
-
return {
|
|
572
|
-
missing: false,
|
|
573
|
-
failureReason: null,
|
|
574
|
-
artifacts: [
|
|
575
|
-
{
|
|
576
|
-
kind: 'egress-audit',
|
|
577
|
-
path: EGRESS_AUDIT_ARTIFACT_PATH,
|
|
578
|
-
bytes: audit.byteLength,
|
|
579
|
-
sha256: `sha256:${createHash('sha256').update(audit).digest('hex')}`,
|
|
580
|
-
truncated: forensics.truncated,
|
|
581
|
-
policyErrorCount: forensics.policyErrorCount,
|
|
582
|
-
malformedLineCount: forensics.malformedLineCount,
|
|
583
|
-
},
|
|
584
|
-
],
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
function inspectEgressAudit(audit) {
|
|
588
|
-
let truncated = false;
|
|
589
|
-
let policyErrorCount = 0;
|
|
590
|
-
let malformedLineCount = 0;
|
|
591
|
-
for (const line of audit.toString('utf8').split(/\r?\n/)) {
|
|
592
|
-
const trimmed = line.trim();
|
|
593
|
-
if (!trimmed)
|
|
594
|
-
continue;
|
|
595
|
-
let record;
|
|
596
|
-
try {
|
|
597
|
-
record = JSON.parse(trimmed);
|
|
598
|
-
}
|
|
599
|
-
catch {
|
|
600
|
-
malformedLineCount += 1;
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
604
|
-
malformedLineCount += 1;
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
const ruleId = record.ruleId;
|
|
608
|
-
if (ruleId === 'audit_truncated')
|
|
609
|
-
truncated = true;
|
|
610
|
-
if (ruleId === 'policy_error')
|
|
611
|
-
policyErrorCount += 1;
|
|
612
|
-
}
|
|
613
|
-
return { truncated, policyErrorCount, malformedLineCount };
|
|
614
|
-
}
|
|
615
|
-
async function readVerification(state, cell, framework, expectEgressAudit) {
|
|
553
|
+
async function readVerification(state, cell, framework, auditRequired) {
|
|
616
554
|
const result = JSON.parse(await readFile(join(state.trialPath, 'result.json'), 'utf8'));
|
|
617
555
|
const score = result.verifier_result?.rewards?.[rewardKey(cell)] ?? null;
|
|
618
556
|
const subjectException = ['AgentTimeoutError', 'NonZeroAgentExitCodeError'].includes(String(result.exception_info?.exception_type));
|
|
619
557
|
if (result.exception_info && !subjectException) {
|
|
620
558
|
throw new Error('Trial failed outside subject execution');
|
|
621
559
|
}
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
catch (error) {
|
|
628
|
-
const code = error.code;
|
|
629
|
-
if (expectEgressAudit && code !== 'ENOENT') {
|
|
630
|
-
return {
|
|
631
|
-
status: 'infra_failed',
|
|
632
|
-
score,
|
|
633
|
-
failureReason: `failed to read egress audit log ${egressAuditPath}${code ? ` (${code})` : ''}`,
|
|
634
|
-
artifacts: [
|
|
635
|
-
{ kind: 'trial', framework: cell.executor.kind, trialName: state.trialName },
|
|
636
|
-
...(await collectedArtifactInventory(state.trialPath, framework)),
|
|
637
|
-
{ kind: 'egress-audit-unreadable', path: EGRESS_AUDIT_ARTIFACT_PATH },
|
|
638
|
-
],
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
const audit = collectEgressAuditArtifact(egressAudit, expectEgressAudit);
|
|
560
|
+
const audit = await readEgressAuditEvidence(state.trialPath, auditRequired);
|
|
561
|
+
const baseFailure = score === null ? 'verifier produced no reward' : null;
|
|
562
|
+
const status = verificationStatus(audit.failureReason, score, subjectException);
|
|
563
|
+
const collectedArtifacts = await collectedArtifactInventory(state.trialPath, framework);
|
|
643
564
|
return {
|
|
644
|
-
status
|
|
645
|
-
? 'infra_failed'
|
|
646
|
-
: score === null
|
|
647
|
-
? 'infra_failed'
|
|
648
|
-
: subjectException
|
|
649
|
-
? 'subject_failed'
|
|
650
|
-
: 'completed',
|
|
565
|
+
status,
|
|
651
566
|
score,
|
|
652
|
-
failureReason: audit.failureReason ??
|
|
567
|
+
failureReason: audit.failureReason ?? baseFailure,
|
|
653
568
|
artifacts: [
|
|
654
569
|
{ kind: 'trial', framework: cell.executor.kind, trialName: state.trialName },
|
|
655
|
-
...
|
|
656
|
-
...audit.artifacts,
|
|
570
|
+
...collectedArtifacts,
|
|
571
|
+
...Array.from(audit.artifacts),
|
|
657
572
|
],
|
|
658
573
|
};
|
|
659
574
|
}
|
|
575
|
+
function verificationStatus(auditFailure, score, subjectException) {
|
|
576
|
+
if (auditFailure !== null || score === null)
|
|
577
|
+
return 'infra_failed';
|
|
578
|
+
return subjectException ? 'subject_failed' : 'completed';
|
|
579
|
+
}
|
|
660
580
|
async function collectedArtifactInventory(trialPath, framework) {
|
|
661
581
|
const root = framework === 'pier'
|
|
662
582
|
? join(trialPath, 'artifacts')
|
|
@@ -22,10 +22,10 @@ STATE_DIR=/opt/maka-egress-state
|
|
|
22
22
|
CERT_DIR=/opt/maka-egress
|
|
23
23
|
|
|
24
24
|
mkdir -p "$STATE_DIR" "$CERT_DIR"
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
touch "$
|
|
25
|
+
AUDIT_LOG="$STATE_DIR/hits.jsonl"
|
|
26
|
+
# Proxy restarts share one append-only evidence file for the whole trial.
|
|
27
|
+
# Only create it when absent so an earlier process cannot lose recorded hits.
|
|
28
|
+
test -e "$AUDIT_LOG" || touch "$AUDIT_LOG"
|
|
29
29
|
|
|
30
30
|
# confdir holds the CA private key and this cell's audit log, so it stays
|
|
31
31
|
# container-private and only the certificate reaches the volume the subject
|