@mettlelabs/mettle 0.0.0-beta.1 → 0.0.0-beta.1-linux-x64
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/THIRD_PARTY_NOTICES.md +11479 -0
- package/bin/mettle +4 -0
- package/harness/audit-investigation.mjs +203 -0
- package/harness/chunk-13tgmdey.mjs +33761 -0
- package/harness/chunk-1sygmp11.mjs +18142 -0
- package/harness/chunk-1ykxpmaz.mjs +7887 -0
- package/harness/chunk-2gt4cy7x.mjs +2290 -0
- package/harness/chunk-2pm2sayw.mjs +248 -0
- package/harness/chunk-32y07kkp.mjs +3747 -0
- package/harness/chunk-3m7jn5ac.mjs +16242 -0
- package/harness/chunk-3wvjs93w.mjs +538 -0
- package/harness/chunk-4bepyxmq.mjs +113 -0
- package/harness/chunk-4jtnt9k5.mjs +95 -0
- package/harness/chunk-50sbrf09.mjs +18972 -0
- package/harness/chunk-56csr8sw.mjs +322 -0
- package/harness/chunk-5cvb1jnb.mjs +41 -0
- package/harness/chunk-5wgmv4np.mjs +585 -0
- package/harness/chunk-65eyq32y.mjs +225 -0
- package/harness/chunk-6fnh6qhk.mjs +195 -0
- package/harness/chunk-6kgqw9ny.mjs +32303 -0
- package/harness/chunk-7b242j0n.mjs +14 -0
- package/harness/chunk-7bg2gnv2.mjs +7540 -0
- package/harness/chunk-ageqxj9v.mjs +63008 -0
- package/harness/chunk-b0qbt5jd.mjs +14 -0
- package/harness/chunk-b5g1r3fz.mjs +4785 -0
- package/harness/chunk-b71gpkxg.mjs +1514 -0
- package/harness/chunk-bdv1rx4a.mjs +20364 -0
- package/harness/chunk-c1631s2x.mjs +12241 -0
- package/harness/chunk-d5xgxsnp.mjs +74 -0
- package/harness/chunk-erg1fbh0.mjs +0 -0
- package/harness/chunk-fh78v9vn.mjs +19366 -0
- package/harness/chunk-fjx29qvp.mjs +50 -0
- package/harness/chunk-gryka6kg.mjs +39 -0
- package/harness/chunk-jb0r19et.mjs +180 -0
- package/harness/chunk-mfn574ss.mjs +179 -0
- package/harness/chunk-msg872ea.mjs +334 -0
- package/harness/chunk-nz5sv9as.mjs +1642 -0
- package/harness/chunk-p9d3cgvq.mjs +452 -0
- package/harness/chunk-pq6wvwxr.mjs +12 -0
- package/harness/chunk-qcm9arf8.mjs +7736 -0
- package/harness/chunk-qr8zfqjd.mjs +24 -0
- package/harness/chunk-rz2tz6jp.mjs +26 -0
- package/harness/chunk-sbkgc3kn.mjs +8 -0
- package/harness/chunk-v66qvpnb.mjs +169 -0
- package/harness/chunk-vjaxzab3.mjs +136 -0
- package/harness/chunk-vr1n5nza.mjs +1045 -0
- package/harness/chunk-vsf2zajv.mjs +3867 -0
- package/harness/chunk-wq253fj4.mjs +19041 -0
- package/harness/chunk-wyv7ceq4.mjs +0 -0
- package/harness/chunk-xnqknk2r.mjs +198 -0
- package/harness/chunk-xqyc4ca2.mjs +1890 -0
- package/harness/chunk-z6n9thea.mjs +45146 -0
- package/harness/chunk-zty0d3bw.mjs +0 -0
- package/harness/chunk-zvkxtj82.mjs +42 -0
- package/harness/issue-api-token.mjs +35 -0
- package/harness/launcher.mjs +159 -0
- package/harness/sandbox.mjs +132 -0
- package/harness/server.mjs +66322 -0
- package/package.json +13 -15
- package/bin/mettle.js +0 -51
package/bin/mettle
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HarnessError,
|
|
3
|
+
publicError,
|
|
4
|
+
sha256,
|
|
5
|
+
captureEvidence
|
|
6
|
+
} from "./chunk-2gt4cy7x.mjs";
|
|
7
|
+
import {
|
|
8
|
+
privateDirectory,
|
|
9
|
+
createJournalPool
|
|
10
|
+
} from "./chunk-7bg2gnv2.mjs";
|
|
11
|
+
import"./chunk-zvkxtj82.mjs";
|
|
12
|
+
import {
|
|
13
|
+
databaseConfig
|
|
14
|
+
} from "./chunk-5cvb1jnb.mjs";
|
|
15
|
+
import {
|
|
16
|
+
uuid
|
|
17
|
+
} from "./chunk-1sygmp11.mjs";
|
|
18
|
+
|
|
19
|
+
// apps/harness/src/audit.ts
|
|
20
|
+
import { randomUUID } from "node:crypto";
|
|
21
|
+
import { constants } from "node:fs";
|
|
22
|
+
import { mkdir, open, rename, rm, writeFile } from "node:fs/promises";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
26
|
+
async function readRunAudit(pool, selector) {
|
|
27
|
+
if (selector !== "latest")
|
|
28
|
+
uuid().parse(selector);
|
|
29
|
+
const client = await pool.connect();
|
|
30
|
+
try {
|
|
31
|
+
await client.query("BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY");
|
|
32
|
+
const found = await client.query("SELECT r.id,r.target,r.config->>'model' AS model,r.started_at,r.finished_at,r.seq,r.spent_tokens,r.spent_nanousd::text,r.active_ms::text,r.terminal FROM harness_mastra.runs r WHERE NOT r.chat AND ($1::uuid IS NULL OR r.id=$1) ORDER BY r.started_at DESC,r.id DESC LIMIT 1", [selector === "latest" ? null : selector]);
|
|
33
|
+
const run = found.rows[0];
|
|
34
|
+
if (!run)
|
|
35
|
+
throw new HarnessError("run_not_found", "No matching investigation exists in this database.");
|
|
36
|
+
const events = await client.query("SELECT payload FROM harness_mastra.events WHERE run_id=$1 ORDER BY seq", [run.id]);
|
|
37
|
+
const actions = await client.query("SELECT a.call_id,a.tool,a.state,a.args_digest,a.child_id,a.artifact_ref,e.sha256,e.manifest,e.envelope,e.bytes FROM harness_mastra.actions a LEFT JOIN harness_mastra.artifacts e ON e.run_id=a.run_id AND e.ref=a.artifact_ref WHERE a.run_id=$1 ORDER BY a.call_id", [run.id]);
|
|
38
|
+
const models = await client.query("SELECT request_id,child_id,provider,requested_model,state,failure_code,provider_observation,final_usage_known,reserved_tokens,reserved_nanousd::text,observed_input_tokens,observed_output_tokens,observed_nanousd::text,charged_nanousd::text,response->>'content' AS commentary,response->'toolCalls' AS proposed_tool_calls FROM harness_mastra.model_dispatches WHERE run_id=$1 ORDER BY request_id", [run.id]);
|
|
39
|
+
const network = await client.query("SELECT id,call_id,state,bytes,request_digest,response_digest,dispatched_at FROM harness_mastra.web_requests WHERE run_id=$1 ORDER BY dispatched_at,id", [run.id]);
|
|
40
|
+
const sandboxes = await client.query("SELECT call_id,worker_id,container_id,image,state,created_at,removed_at FROM harness_mastra.sandboxes WHERE run_id=$1 ORDER BY created_at,call_id", [run.id]);
|
|
41
|
+
const stages = await client.query("SELECT stage_key,input_digest FROM harness_mastra.investigation_stages WHERE run_id=$1 ORDER BY stage_key", [run.id]);
|
|
42
|
+
const findings = await client.query("SELECT id,status,result,finding FROM harness_mastra.findings WHERE run_id=$1 ORDER BY id", [run.id]);
|
|
43
|
+
await client.query("COMMIT");
|
|
44
|
+
const clean = (value) => captureEvidence(value).value;
|
|
45
|
+
return {
|
|
46
|
+
capturedAt: new Date().toISOString(),
|
|
47
|
+
source: "PostgreSQL committed records; read-only snapshot",
|
|
48
|
+
run: {
|
|
49
|
+
...run,
|
|
50
|
+
started_at: run.started_at.toISOString(),
|
|
51
|
+
finished_at: run.finished_at?.toISOString() ?? null,
|
|
52
|
+
target: clean(run.target)
|
|
53
|
+
},
|
|
54
|
+
usage: {
|
|
55
|
+
tokens: run.spent_tokens,
|
|
56
|
+
usd: Number(run.spent_nanousd) / 1e9,
|
|
57
|
+
wallSec: Number(run.active_ms) / 1000
|
|
58
|
+
},
|
|
59
|
+
stages: stages.rows,
|
|
60
|
+
events: events.rows.map(({ payload }) => ({
|
|
61
|
+
seq: payload.seq,
|
|
62
|
+
type: payload.type,
|
|
63
|
+
record: clean(payload)
|
|
64
|
+
})),
|
|
65
|
+
actions: actions.rows.map(({ bytes, ...action }) => ({
|
|
66
|
+
...action,
|
|
67
|
+
evidenceHashMatches: typeof bytes === "string" ? sha256(bytes) === action.sha256 : null,
|
|
68
|
+
outputExcerpt: typeof bytes === "string" ? clean(bytes.slice(0, 8192)) : null,
|
|
69
|
+
outputTruncated: typeof bytes === "string" && bytes.length > 8192
|
|
70
|
+
})),
|
|
71
|
+
models: models.rows.map(clean),
|
|
72
|
+
network: network.rows.map((row) => ({
|
|
73
|
+
...row,
|
|
74
|
+
dispatched_at: row.dispatched_at.toISOString()
|
|
75
|
+
})),
|
|
76
|
+
sandboxes: sandboxes.rows.map((row) => ({
|
|
77
|
+
...row,
|
|
78
|
+
created_at: row.created_at.toISOString(),
|
|
79
|
+
removed_at: row.removed_at?.toISOString() ?? null
|
|
80
|
+
})),
|
|
81
|
+
findings: findings.rows.map(clean),
|
|
82
|
+
limitsOfThisExport: [
|
|
83
|
+
"Model commentary and proposed tool calls are not execution proof; match them to committed actions and evidence.",
|
|
84
|
+
"Reserved model cost is not a charge. Dispatched network bytes may still be held reservations.",
|
|
85
|
+
"Output excerpts are redacted and bounded. Full artifacts remain in the evidence store; external objects are not downloaded here.",
|
|
86
|
+
"Inline artifact hashes are compared; this export does not independently verify evidence signatures or vulnerability claims.",
|
|
87
|
+
"An unchanged event sequence or heartbeat alone does not prove forward progress."
|
|
88
|
+
]
|
|
89
|
+
};
|
|
90
|
+
} catch (error) {
|
|
91
|
+
await client.query("ROLLBACK");
|
|
92
|
+
throw error;
|
|
93
|
+
} finally {
|
|
94
|
+
client.release();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function atomic(path, value) {
|
|
98
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
99
|
+
try {
|
|
100
|
+
await writeFile(temporary, value, { flag: "wx", mode: 384 });
|
|
101
|
+
await rename(temporary, path);
|
|
102
|
+
} finally {
|
|
103
|
+
await rm(temporary, { force: true });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function saveRunAudit(root, audit, after = -1, writeStatus = true) {
|
|
107
|
+
uuid().parse(audit.run.id);
|
|
108
|
+
await mkdir(root, { recursive: true, mode: 448 });
|
|
109
|
+
const base = await privateDirectory(root);
|
|
110
|
+
const directory = join(base, audit.run.id);
|
|
111
|
+
await mkdir(directory, { mode: 448 }).catch((error) => {
|
|
112
|
+
if (error.code !== "EEXIST")
|
|
113
|
+
throw error;
|
|
114
|
+
});
|
|
115
|
+
await privateDirectory(directory);
|
|
116
|
+
const path = join(directory, "progress.log");
|
|
117
|
+
const file = await open(path, constants.O_CREAT | constants.O_WRONLY | constants.O_APPEND | constants.O_NOFOLLOW | constants.O_NONBLOCK, 384);
|
|
118
|
+
try {
|
|
119
|
+
const info = await file.stat();
|
|
120
|
+
if (!info.isFile() || info.uid !== process.getuid?.() || info.mode & 63)
|
|
121
|
+
throw new HarnessError("private_file", "The audit log must be a private operator-owned regular file.");
|
|
122
|
+
const fresh = audit.events.filter((event) => event.seq > after);
|
|
123
|
+
const lines = fresh.map((event) => `[observed ${audit.capturedAt}] ${event.type === "step" ? "MODEL/STAGE NOTE" : "JOURNAL"} ${JSON.stringify(event.record)}`);
|
|
124
|
+
const latest = audit.events.at(-1);
|
|
125
|
+
const lastErrorEvent = [...audit.events].reverse().find((event) => event.type === "error");
|
|
126
|
+
const lastError = lastErrorEvent && typeof lastErrorEvent.record.message === "string" ? lastErrorEvent.record.message.slice(0, 200) : null;
|
|
127
|
+
if (writeStatus)
|
|
128
|
+
lines.push(`[observed ${audit.capturedAt}] STATUS ${JSON.stringify({ runId: audit.run.id, seq: audit.run.seq, usage: audit.usage, terminal: audit.run.terminal, completedStages: audit.stages.map((stage) => stage.stage_key), modelCalls: audit.models.length, toolActions: audit.actions.length, httpRequests: audit.network.length, containersPendingRemoval: audit.sandboxes.filter((container) => container.state !== "removed").length, lastEvent: latest?.type ?? null, lastError, stalled: fresh.length === 0 && !audit.run.terminal })}`);
|
|
129
|
+
if (lines.length)
|
|
130
|
+
await file.appendFile(`${lines.join(`
|
|
131
|
+
`)}
|
|
132
|
+
`, "utf8");
|
|
133
|
+
} finally {
|
|
134
|
+
await file.close();
|
|
135
|
+
}
|
|
136
|
+
await atomic(join(directory, "audit.json"), `${JSON.stringify(audit, null, 2)}
|
|
137
|
+
`);
|
|
138
|
+
if (after < 0)
|
|
139
|
+
await atomic(join(base, "latest.txt"), `${directory}
|
|
140
|
+
`);
|
|
141
|
+
return directory;
|
|
142
|
+
}
|
|
143
|
+
async function auditInvestigation(args) {
|
|
144
|
+
const [selector, flag] = args;
|
|
145
|
+
if (!selector || args.length > 2 || flag !== undefined && flag !== "--watch")
|
|
146
|
+
throw new HarnessError("audit_usage", "Use investigation:audit <RUN_ID|latest> [--watch]. Latest selects the newest investigation once.");
|
|
147
|
+
if (selector !== "latest")
|
|
148
|
+
uuid().parse(selector);
|
|
149
|
+
const pool = createJournalPool(databaseConfig(), "investigation-audit-read-only");
|
|
150
|
+
const leaving = new AbortController;
|
|
151
|
+
const stop = () => leaving.abort();
|
|
152
|
+
process.once("SIGINT", stop);
|
|
153
|
+
process.once("SIGTERM", stop);
|
|
154
|
+
let runId = selector;
|
|
155
|
+
let after = -1;
|
|
156
|
+
let lastStatusSeq = -2;
|
|
157
|
+
let polls = 0;
|
|
158
|
+
try {
|
|
159
|
+
while (!leaving.signal.aborted) {
|
|
160
|
+
const audit = await readRunAudit(pool, runId);
|
|
161
|
+
runId = audit.run.id;
|
|
162
|
+
polls += 1;
|
|
163
|
+
const writeStatus = audit.run.seq !== lastStatusSeq || polls % 15 === 0;
|
|
164
|
+
if (writeStatus)
|
|
165
|
+
lastStatusSeq = audit.run.seq;
|
|
166
|
+
const directory = await saveRunAudit(join(homedir(), ".mettle", "audits"), audit, after, writeStatus);
|
|
167
|
+
console.log(JSON.stringify({
|
|
168
|
+
directory,
|
|
169
|
+
runId,
|
|
170
|
+
seq: audit.run.seq,
|
|
171
|
+
usage: audit.usage,
|
|
172
|
+
terminal: audit.run.terminal,
|
|
173
|
+
providerCallsByAudit: 0
|
|
174
|
+
}));
|
|
175
|
+
after = audit.run.seq;
|
|
176
|
+
if (flag !== "--watch" || audit.run.terminal)
|
|
177
|
+
break;
|
|
178
|
+
await sleep(2000, undefined, { signal: leaving.signal }).catch((error) => {
|
|
179
|
+
if (!leaving.signal.aborted)
|
|
180
|
+
throw error;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
} finally {
|
|
184
|
+
process.off("SIGINT", stop);
|
|
185
|
+
process.off("SIGTERM", stop);
|
|
186
|
+
try {
|
|
187
|
+
if (leaving.signal.aborted && runId !== "latest") {
|
|
188
|
+
const final = await readRunAudit(pool, runId);
|
|
189
|
+
await saveRunAudit(join(homedir(), ".mettle", "audits"), final, after);
|
|
190
|
+
}
|
|
191
|
+
} finally {
|
|
192
|
+
await pool.end();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// apps/harness/scripts/audit-investigation.ts
|
|
198
|
+
try {
|
|
199
|
+
await auditInvestigation(process.argv.slice(2));
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error(publicError(error));
|
|
202
|
+
process.exitCode = 1;
|
|
203
|
+
}
|