@openpond/evals 0.11.20 → 0.12.0
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/README.md +12 -0
- package/dist/connected-evidence/codex-command-observations.js +81 -0
- package/dist/connected-evidence/collector-coverage-contracts.js +160 -0
- package/dist/connected-evidence/contracts.js +13 -0
- package/dist/connected-evidence/git-file-contracts.js +16 -0
- package/dist/connected-evidence/git-file-proof.js +86 -0
- package/dist/connected-evidence/git-starting-state.js +81 -0
- package/dist/connected-evidence/git.js +2 -0
- package/dist/connected-evidence/imports.js +2 -1
- package/dist/connected-evidence/index.js +2 -0
- package/dist/connected-evidence/normalize.js +1 -0
- package/dist/connected-evidence/sources/codex-metadata.js +33 -0
- package/dist/connected-evidence/sources/codex.js +16 -2
- package/dist/execution-contracts.js +1 -1
- package/dist/graders.js +10 -4
- package/dist/javascript-isolate-node.js +2 -1
- package/dist/javascript-isolate-process-source.js +1 -1
- package/dist/javascript-isolate-process.js +2 -1
- package/dist/javascript-isolate.js +2 -1
- package/dist/javascript-verifier-worker-source.js +1 -1
- package/dist/javascript-verifier.js +1 -0
- package/dist/native-conversations/codex-window-inventory.js +96 -0
- package/dist/native-conversations/collector-acquisition.js +149 -0
- package/dist/native-conversations/collector-branches.js +1 -0
- package/dist/native-conversations/collector-calendar-gate.js +19 -0
- package/dist/native-conversations/collector-contracts.js +4 -3
- package/dist/native-conversations/collector-coverage.js +100 -0
- package/dist/native-conversations/collector-cron.js +43 -0
- package/dist/native-conversations/collector-schedule.js +61 -0
- package/dist/native-conversations/collector-service-files.js +51 -0
- package/dist/native-conversations/collector-snapshots.js +70 -0
- package/dist/native-conversations/collector-source-cache.js +41 -0
- package/dist/native-conversations/collector-store.js +43 -8
- package/dist/native-conversations/collector.js +189 -261
- package/dist/native-conversations/git-assets.js +61 -0
- package/dist/native-conversations/history.js +9 -0
- package/dist/native-conversations/index.js +2 -0
- package/dist/native-conversations/location.js +4 -2
- package/dist/native-conversations/supervisor.js +131 -81
- package/dist/prepared-process-isolation.js +131 -0
- package/dist/rewards.js +17 -5
- package/dist/sql-execution-node.js +2 -1
- package/dist/tasksets.js +7 -1
- package/dist/types/builtin-benchmarks/harness-refiner.d.ts +4 -0
- package/dist/types/builtin-benchmarks/harness-refiner.d.ts.map +1 -1
- package/dist/types/conformance.d.ts +8 -0
- package/dist/types/conformance.d.ts.map +1 -1
- package/dist/types/connected-evidence/codex-command-observations.d.ts +15 -0
- package/dist/types/connected-evidence/codex-command-observations.d.ts.map +1 -0
- package/dist/types/connected-evidence/collector-coverage-contracts.d.ts +98 -0
- package/dist/types/connected-evidence/collector-coverage-contracts.d.ts.map +1 -0
- package/dist/types/connected-evidence/contracts.d.ts +22 -0
- package/dist/types/connected-evidence/contracts.d.ts.map +1 -1
- package/dist/types/connected-evidence/git-file-contracts.d.ts +18 -0
- package/dist/types/connected-evidence/git-file-contracts.d.ts.map +1 -0
- package/dist/types/connected-evidence/git-file-proof.d.ts +13 -0
- package/dist/types/connected-evidence/git-file-proof.d.ts.map +1 -0
- package/dist/types/connected-evidence/git-starting-state.d.ts +28 -0
- package/dist/types/connected-evidence/git-starting-state.d.ts.map +1 -0
- package/dist/types/connected-evidence/git.d.ts +3 -0
- package/dist/types/connected-evidence/git.d.ts.map +1 -0
- package/dist/types/connected-evidence/imports.d.ts.map +1 -1
- package/dist/types/connected-evidence/index.d.ts +2 -0
- package/dist/types/connected-evidence/index.d.ts.map +1 -1
- package/dist/types/connected-evidence/normalize.d.ts +1 -0
- package/dist/types/connected-evidence/normalize.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/claude-code.d.ts +10 -0
- package/dist/types/connected-evidence/sources/claude-code.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/codex-metadata.d.ts +10 -0
- package/dist/types/connected-evidence/sources/codex-metadata.d.ts.map +1 -0
- package/dist/types/connected-evidence/sources/codex.d.ts +10 -0
- package/dist/types/connected-evidence/sources/codex.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/grok-build.d.ts +10 -0
- package/dist/types/connected-evidence/sources/grok-build.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/history.d.ts +20 -0
- package/dist/types/connected-evidence/sources/history.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/opencode.d.ts +10 -0
- package/dist/types/connected-evidence/sources/opencode.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/pi.d.ts +10 -0
- package/dist/types/connected-evidence/sources/pi.d.ts.map +1 -1
- package/dist/types/execution-contracts.d.ts +8 -0
- package/dist/types/execution-contracts.d.ts.map +1 -1
- package/dist/types/graders.d.ts +3 -0
- package/dist/types/graders.d.ts.map +1 -1
- package/dist/types/javascript-isolate-node.d.ts.map +1 -1
- package/dist/types/javascript-isolate-process.d.ts.map +1 -1
- package/dist/types/javascript-isolate.d.ts.map +1 -1
- package/dist/types/javascript-verifier.d.ts +1 -0
- package/dist/types/javascript-verifier.d.ts.map +1 -1
- package/dist/types/learning/admission.d.ts +1 -0
- package/dist/types/learning/admission.d.ts.map +1 -1
- package/dist/types/learning/authoring-service.d.ts +2 -0
- package/dist/types/learning/authoring-service.d.ts.map +1 -1
- package/dist/types/learning/authoring.d.ts +8 -0
- package/dist/types/learning/authoring.d.ts.map +1 -1
- package/dist/types/learning/batch-service.d.ts +4 -0
- package/dist/types/learning/batch-service.d.ts.map +1 -1
- package/dist/types/learning/contracts.d.ts +17 -0
- package/dist/types/learning/contracts.d.ts.map +1 -1
- package/dist/types/learning/operations.d.ts +8 -0
- package/dist/types/learning/operations.d.ts.map +1 -1
- package/dist/types/learning/repository.d.ts +14 -0
- package/dist/types/learning/repository.d.ts.map +1 -1
- package/dist/types/learning/reward-calibration-closure.d.ts +9 -1
- package/dist/types/learning/reward-calibration-closure.d.ts.map +1 -1
- package/dist/types/learning/reward-checks.d.ts +2 -0
- package/dist/types/learning/reward-checks.d.ts.map +1 -1
- package/dist/types/learning/transport.d.ts +18 -0
- package/dist/types/learning/transport.d.ts.map +1 -1
- package/dist/types/native-conversations/codex-window-inventory.d.ts +12 -0
- package/dist/types/native-conversations/codex-window-inventory.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-acquisition.d.ts +17 -0
- package/dist/types/native-conversations/collector-acquisition.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-branches.d.ts.map +1 -1
- package/dist/types/native-conversations/collector-calendar-gate.d.ts +9 -0
- package/dist/types/native-conversations/collector-calendar-gate.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-contracts.d.ts +41 -5
- package/dist/types/native-conversations/collector-contracts.d.ts.map +1 -1
- package/dist/types/native-conversations/collector-coverage.d.ts +62 -0
- package/dist/types/native-conversations/collector-coverage.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-cron.d.ts +20 -0
- package/dist/types/native-conversations/collector-cron.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-schedule.d.ts +13 -0
- package/dist/types/native-conversations/collector-schedule.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-service-files.d.ts +20 -0
- package/dist/types/native-conversations/collector-service-files.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-snapshots.d.ts +25 -0
- package/dist/types/native-conversations/collector-snapshots.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-source-cache.d.ts +31 -0
- package/dist/types/native-conversations/collector-source-cache.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-store.d.ts +11 -2
- package/dist/types/native-conversations/collector-store.d.ts.map +1 -1
- package/dist/types/native-conversations/collector.d.ts +5 -1
- package/dist/types/native-conversations/collector.d.ts.map +1 -1
- package/dist/types/native-conversations/git-assets.d.ts +32 -0
- package/dist/types/native-conversations/git-assets.d.ts.map +1 -0
- package/dist/types/native-conversations/history.d.ts +7 -0
- package/dist/types/native-conversations/history.d.ts.map +1 -1
- package/dist/types/native-conversations/index.d.ts +2 -0
- package/dist/types/native-conversations/index.d.ts.map +1 -1
- package/dist/types/native-conversations/location.d.ts.map +1 -1
- package/dist/types/native-conversations/supervisor.d.ts +4 -3
- package/dist/types/native-conversations/supervisor.d.ts.map +1 -1
- package/dist/types/prepared-process-isolation.d.ts +5 -0
- package/dist/types/prepared-process-isolation.d.ts.map +1 -0
- package/dist/types/review-conformance.d.ts +5 -5
- package/dist/types/rewards.d.ts +9 -0
- package/dist/types/rewards.d.ts.map +1 -1
- package/dist/types/sql-execution-node.d.ts.map +1 -1
- package/dist/types/tasksets.d.ts +16 -0
- package/dist/types/tasksets.d.ts.map +1 -1
- package/package.json +8 -2
- package/schemas/learning/v1/binding.schema.json +8 -0
- package/schemas/learning/v1/command-request.schema.json +8 -0
- package/schemas/learning/v1/decision.schema.json +5 -1
- package/schemas/learning/v1/draft.schema.json +8 -0
- package/schemas/learning/v1/grade.schema.json +5 -1
- package/schemas/learning/v1/package.schema.json +113 -94
- package/schemas/learning/v1/reward_check.schema.json +2 -1
|
@@ -7,6 +7,8 @@ import { homedir } from "node:os";
|
|
|
7
7
|
import { isAbsolute, join } from "node:path";
|
|
8
8
|
import { contentHash } from "@openpond/harness";
|
|
9
9
|
import { CollectorStore } from "./collector-store.js";
|
|
10
|
+
import { validateCollectorSchedule } from "./collector-schedule.js";
|
|
11
|
+
import { collectorServiceFiles } from "./collector-service-files.js";
|
|
10
12
|
const execute = promisify(execFile);
|
|
11
13
|
const command = async (file, args) => {
|
|
12
14
|
await execute(file, args, {
|
|
@@ -15,12 +17,6 @@ const command = async (file, args) => {
|
|
|
15
17
|
windowsHide: true,
|
|
16
18
|
});
|
|
17
19
|
};
|
|
18
|
-
const xml = (value) => value
|
|
19
|
-
.replace(/&/gu, "&")
|
|
20
|
-
.replace(/</gu, "<")
|
|
21
|
-
.replace(/>/gu, ">")
|
|
22
|
-
.replace(/"/gu, """);
|
|
23
|
-
const systemd = (value) => `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"').replace(/%/gu, "%%").replace(/\$/gu, "$$")}"`;
|
|
24
20
|
function identity(directory) {
|
|
25
21
|
return `openpond-import-${contentHash(directory).slice(0, 16)}`;
|
|
26
22
|
}
|
|
@@ -70,86 +66,154 @@ export async function controlCollector(directory, action, connectionId) {
|
|
|
70
66
|
store.close();
|
|
71
67
|
}
|
|
72
68
|
}
|
|
73
|
-
/**
|
|
69
|
+
/** Install calendar triggers only. Registration never launches an import. */
|
|
74
70
|
export async function installCollectorService(input) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
throw new Error("
|
|
71
|
+
// Reloading a launchd calendar also terminates its active process. Preserve
|
|
72
|
+
// the current import instead of silently cancelling it during a settings edit.
|
|
73
|
+
if (process.platform === "darwin" && (await collectorStatus(input.directory)).running)
|
|
74
|
+
throw new Error("Wait for the current import to finish, or cancel it, before changing its macOS schedule.");
|
|
75
|
+
if (!isAbsolute(input.directory) || !isAbsolute(input.executable) ||
|
|
76
|
+
[input.directory, input.executable, ...input.args].some(value => /[\r\n\0]/u.test(value)))
|
|
77
|
+
throw new Error("Collector jobs require absolute paths and single-line arguments.");
|
|
79
78
|
const retainedRuntime = await retainAppImageRuntime(input);
|
|
80
79
|
if (retainedRuntime)
|
|
81
80
|
input = { ...input, ...retainedRuntime, environment: { ...input.environment, ELECTRON_RUN_AS_NODE: "1", APPIMAGE_EXTRACT_AND_RUN: "1" } };
|
|
82
81
|
const environment = Object.entries(input.environment ?? {});
|
|
83
|
-
if (environment.some(([key, value]) => !["OPENPOND_HOME", "ELECTRON_RUN_AS_NODE", "APPIMAGE_EXTRACT_AND_RUN"].includes(key) ||
|
|
84
|
-
typeof value !== "string" ||
|
|
85
|
-
/[\r\n\0]/u.test(value)))
|
|
82
|
+
if (environment.some(([key, value]) => !["OPENPOND_HOME", "ELECTRON_RUN_AS_NODE", "APPIMAGE_EXTRACT_AND_RUN"].includes(key) || typeof value !== "string" || /[\r\n\0]/u.test(value)))
|
|
86
83
|
throw new Error("Unsupported service environment setting.");
|
|
87
84
|
const home = input.home ?? homedir(), name = identity(input.directory);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
85
|
+
const store = await CollectorStore.open(input.directory);
|
|
86
|
+
let schedules;
|
|
87
|
+
try {
|
|
88
|
+
schedules = store.connections().map(connection => store.schedule(connection.id)).filter((schedule) => schedule !== null);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
store.close();
|
|
92
|
+
}
|
|
93
|
+
const gatePath = join(input.directory, "calendar-gate.cjs");
|
|
94
|
+
const files = collectorServiceFiles({ name, gatePath, executable: input.executable,
|
|
95
|
+
args: [...input.args, "import", "service", "run", "--scheduled", "--collector-dir", input.directory], environment, schedules });
|
|
96
|
+
await atomicServiceFile(gatePath, files.gate, { mode: 0o600 });
|
|
96
97
|
if (process.platform === "linux") {
|
|
97
98
|
const directory = join(home, ".config/systemd/user");
|
|
98
99
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
99
|
-
|
|
100
|
-
await
|
|
101
|
-
await
|
|
100
|
+
// Remove the old login enablement without stopping an already owned job.
|
|
101
|
+
await optionalCommand("systemctl", ["--user", "disable", `${name}.service`]);
|
|
102
|
+
await atomicServiceFile(join(directory, `${name}.service`), files.service, { mode: 0o600 });
|
|
103
|
+
if (schedules.length) {
|
|
104
|
+
await atomicServiceFile(join(directory, `${name}.timer`), files.timer, { mode: 0o600 });
|
|
105
|
+
await command("systemctl", ["--user", "daemon-reload"]);
|
|
106
|
+
await command("systemctl", ["--user", "enable", "--now", `${name}.timer`]);
|
|
107
|
+
await command("systemctl", ["--user", "restart", `${name}.timer`]);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
await optionalCommand("systemctl", ["--user", "disable", "--now", `${name}.timer`]);
|
|
111
|
+
await rm(join(directory, `${name}.timer`), { force: true });
|
|
112
|
+
await command("systemctl", ["--user", "daemon-reload"]);
|
|
113
|
+
}
|
|
102
114
|
}
|
|
103
115
|
else if (process.platform === "darwin") {
|
|
104
|
-
const directory = join(home, "Library/LaunchAgents");
|
|
116
|
+
const directory = join(home, "Library/LaunchAgents"), file = join(directory, `${name}.plist`);
|
|
105
117
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
await optionalCommand("launchctl", ["bootout", `gui/${process.getuid()}/${name}`]);
|
|
119
|
+
if (schedules.length) {
|
|
120
|
+
await atomicServiceFile(file, files.plist, { mode: 0o600 });
|
|
121
|
+
await command("launchctl", ["bootstrap", `gui/${process.getuid()}`, file]);
|
|
122
|
+
}
|
|
123
|
+
else
|
|
124
|
+
await rm(file, { force: true });
|
|
125
|
+
}
|
|
126
|
+
else if (process.platform === "win32") {
|
|
127
|
+
if (!schedules.length)
|
|
128
|
+
await optionalCommand("schtasks", ["/Delete", "/TN", name, "/F"]);
|
|
129
|
+
else {
|
|
130
|
+
const user = (await execute("whoami", [], { timeout: 5000, maxBuffer: 1024 })).stdout.trim();
|
|
131
|
+
const file = join(input.directory, "scheduled-task.xml"), script = join(input.directory, "start-collector.ps1");
|
|
132
|
+
await mkdir(input.directory, { recursive: true, mode: 0o700 });
|
|
133
|
+
await atomicServiceFile(script, files.powershell, { mode: 0o600 });
|
|
134
|
+
await atomicServiceFile(file, files.windowsTask(user, script), { mode: 0o600 });
|
|
135
|
+
await command("schtasks", ["/Create", "/TN", name, "/XML", file, "/F"]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else
|
|
139
|
+
throw new Error("Scheduled imports need a supported OS scheduler. Use import sync manually on this OS.");
|
|
140
|
+
return { name, platform: process.platform, status: await collectorStatus(input.directory) };
|
|
141
|
+
}
|
|
142
|
+
// Absence is allowed only when removing an optional trigger. Installation and
|
|
143
|
+
// activation failures remain errors and never claim that a schedule is enabled.
|
|
144
|
+
async function optionalCommand(file, args) {
|
|
145
|
+
try {
|
|
146
|
+
await command(file, args);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
const result = error;
|
|
150
|
+
if (result.code === "ENOENT" || !/not (?:loaded|found|exist)|does not exist|No such|could not be found|cannot find|not been loaded/iu.test(result.stderr ?? result.message))
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Serialize OS/config updates; a failed installation restores retained intent. */
|
|
155
|
+
export async function setCollectorSchedule(input, connectionId, schedule) {
|
|
156
|
+
if (schedule !== null)
|
|
157
|
+
schedule = validateCollectorSchedule(schedule);
|
|
158
|
+
if (process.platform === "darwin" && (await collectorStatus(input.directory)).running)
|
|
159
|
+
throw new Error("Wait for the current import to finish, or cancel it, before changing its macOS schedule.");
|
|
160
|
+
const store = await CollectorStore.open(input.directory), token = randomUUID();
|
|
161
|
+
let prior;
|
|
162
|
+
store.database.exec("BEGIN IMMEDIATE");
|
|
163
|
+
try {
|
|
164
|
+
const connection = store.connections().find(item => item.id === connectionId);
|
|
165
|
+
if (!connection || (schedule && connection.state === "disconnected"))
|
|
166
|
+
throw new Error("Select a connected source before scheduling imports.");
|
|
167
|
+
const owner = store.setting("scheduleOwner");
|
|
168
|
+
if (owner) {
|
|
169
|
+
const { pid } = JSON.parse(owner);
|
|
170
|
+
let alive = true;
|
|
171
|
+
try {
|
|
172
|
+
process.kill(pid, 0);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
alive = error.code !== "ESRCH";
|
|
176
|
+
}
|
|
177
|
+
if (alive)
|
|
178
|
+
throw new Error("Another schedule update is in progress. Try again shortly.");
|
|
179
|
+
}
|
|
180
|
+
prior = store.setting(`schedule:${connectionId}`);
|
|
181
|
+
store.set("scheduleOwner", JSON.stringify({ pid: process.pid, token }));
|
|
182
|
+
store.set(`schedule:${connectionId}`, schedule ? JSON.stringify(schedule) : "");
|
|
183
|
+
store.database.exec("COMMIT");
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
store.database.exec("ROLLBACK");
|
|
187
|
+
store.close();
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
await installCollectorService(input);
|
|
192
|
+
return store.status();
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
store.set(`schedule:${connectionId}`, prior ?? "");
|
|
108
196
|
try {
|
|
109
|
-
await
|
|
110
|
-
loaded = true;
|
|
197
|
+
await installCollectorService(input);
|
|
111
198
|
}
|
|
112
199
|
catch {
|
|
113
|
-
|
|
200
|
+
throw new Error(`Schedule installation failed and OS rollback could not be verified. Review the OS scheduler. ${error instanceof Error ? error.message : ""}`);
|
|
114
201
|
}
|
|
115
|
-
|
|
116
|
-
await command("launchctl", [
|
|
117
|
-
"bootout",
|
|
118
|
-
`gui/${process.getuid()}/${name}`,
|
|
119
|
-
]);
|
|
120
|
-
await writeFile(file, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>${name}</string><key>ProgramArguments</key><array>${[input.executable, ...launch].map((value) => `<string>${xml(value)}</string>`).join("")}</array><key>EnvironmentVariables</key><dict>${environment.map(([key, value]) => `<key>${xml(key)}</key><string>${xml(value)}</string>`).join("")}</dict><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>ThrottleInterval</key><integer>5</integer></dict></plist>`, { mode: 0o600 });
|
|
121
|
-
await command("launchctl", ["bootstrap", `gui/${process.getuid()}`, file]);
|
|
202
|
+
throw error;
|
|
122
203
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
await mkdir(input.directory, { recursive: true });
|
|
127
|
-
const quoted = (value) => `"${value.replace(/(\\*)"/gu, '$1$1\\"').replace(/(\\+)$/u, "$1$1")}"`;
|
|
128
|
-
const script = join(input.directory, "start-collector.ps1");
|
|
129
|
-
const ps = (value) => `'${value.replace(/'/gu, "''")}'`;
|
|
130
|
-
await writeFile(script, `${environment.map(([key, value]) => `$env:${key} = ${ps(value)}`).join("\n")}\n& ${[input.executable, ...launch].map(ps).join(" ")}\nexit $LASTEXITCODE\n`, { mode: 0o600 });
|
|
131
|
-
await writeFile(file, `<?xml version="1.0"?><Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"><Triggers><LogonTrigger><Enabled>true</Enabled><UserId>${xml(user)}</UserId></LogonTrigger></Triggers><Principals><Principal id="Owner"><UserId>${xml(user)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals><Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure></Settings><Actions Context="Owner"><Exec><Command>powershell.exe</Command><Arguments>${xml(["-NoProfile", "-NonInteractive", "-File", script].map(quoted).join(" "))}</Arguments></Exec></Actions></Task>`, { mode: 0o600 });
|
|
132
|
-
await command("schtasks", ["/Create", "/TN", name, "/XML", file, "/F"]);
|
|
204
|
+
finally {
|
|
205
|
+
store.set("scheduleOwner", "");
|
|
206
|
+
store.close();
|
|
133
207
|
}
|
|
134
|
-
else
|
|
135
|
-
throw new Error("This OS has no qualified per-user supervisor. Run the collector in the foreground.");
|
|
136
|
-
return {
|
|
137
|
-
name,
|
|
138
|
-
platform: process.platform,
|
|
139
|
-
status: await collectorStatus(input.directory),
|
|
140
|
-
};
|
|
141
208
|
}
|
|
142
209
|
/** Removing supervision preserves receipts, pending uploads and retained datasets. */
|
|
143
210
|
export async function uninstallCollectorService(directory, home = homedir()) {
|
|
144
211
|
await controlCollector(directory, "stop");
|
|
145
212
|
const name = identity(directory);
|
|
146
213
|
if (process.platform === "linux") {
|
|
147
|
-
await
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"--now",
|
|
151
|
-
`${name}.service`,
|
|
152
|
-
]);
|
|
214
|
+
await optionalCommand("systemctl", ["--user", "disable", "--now", `${name}.timer`]);
|
|
215
|
+
await rm(join(home, ".config/systemd/user", `${name}.timer`), { force: true });
|
|
216
|
+
await command("systemctl", ["--user", "disable", "--now", `${name}.service`]);
|
|
153
217
|
await rm(join(home, ".config/systemd/user", `${name}.service`), {
|
|
154
218
|
force: true,
|
|
155
219
|
});
|
|
@@ -169,30 +233,16 @@ export async function uninstallCollectorService(directory, home = homedir()) {
|
|
|
169
233
|
}
|
|
170
234
|
else
|
|
171
235
|
throw new Error("No per-user supervisor is available.");
|
|
236
|
+
await rm(join(directory, "calendar-gate.cjs"), { force: true });
|
|
172
237
|
if (process.platform === "linux")
|
|
173
238
|
await removeAppImageRuntimes(directory);
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
export async function startCollectorService(directory) {
|
|
177
|
-
await controlCollector(directory, "start");
|
|
178
|
-
const name = identity(directory);
|
|
239
|
+
const store = await CollectorStore.open(directory);
|
|
179
240
|
try {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
else if (process.platform === "darwin")
|
|
183
|
-
await command("launchctl", [
|
|
184
|
-
"kickstart",
|
|
185
|
-
"-k",
|
|
186
|
-
`gui/${process.getuid()}/${name}`,
|
|
187
|
-
]);
|
|
188
|
-
else if (process.platform === "win32")
|
|
189
|
-
await command("schtasks", ["/Run", "/TN", name]);
|
|
190
|
-
else
|
|
191
|
-
throw new Error("No per-user supervisor is available.");
|
|
241
|
+
for (const connection of store.connections())
|
|
242
|
+
store.set(`schedule:${connection.id}`, "");
|
|
192
243
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
throw error;
|
|
244
|
+
finally {
|
|
245
|
+
store.close();
|
|
196
246
|
}
|
|
197
247
|
return collectorStatus(directory);
|
|
198
248
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/** Staged by the private verifier owner. Untrusted policy programs run with no
|
|
2
|
+
* capabilities as a different UID, outside the sealed private verifier tree.
|
|
3
|
+
* The parent must assess returned behavior; an exit code is never correctness. */
|
|
4
|
+
export const PREPARED_PROCESS_ISOLATION_SOURCE = String.raw `
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { randomInt } from "node:crypto";
|
|
10
|
+
|
|
11
|
+
const MAX_BYTES = 15 * 1024 * 1024, MAX_OUTPUT = 1024 * 1024;
|
|
12
|
+
const usedUsers = new Set();
|
|
13
|
+
const safePath = value => typeof value === "string" && value.length > 0 && value.length <= 2000
|
|
14
|
+
&& !value.startsWith("/") && !/[\\\x00-\x1f\x7f]/.test(value)
|
|
15
|
+
&& value.split("/").every(part => part && part !== "." && part !== ".." && !part.startsWith(".env"));
|
|
16
|
+
|
|
17
|
+
/** files contain only public original/policy bytes and a public invocation
|
|
18
|
+
* adapter. Never stage independent assertions, expected answers or checkers. */
|
|
19
|
+
export async function runPolicyProgram(input) {
|
|
20
|
+
const privateRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
if (process.getuid?.() !== 0 || !path.basename(privateRoot).startsWith(".openpond-reward-verifier-"))
|
|
22
|
+
throw new Error("policy_process_isolation_runtime_unavailable");
|
|
23
|
+
if (!Array.isArray(input.files) || input.files.length < 1 || input.files.length > 100
|
|
24
|
+
|| !safePath(input.entrypoint) || !input.files.some(file => file.path === input.entrypoint)
|
|
25
|
+
|| !Number.isSafeInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 60000
|
|
26
|
+
|| !Array.isArray(input.args ?? []) || (input.args ?? []).length > 100
|
|
27
|
+
|| (input.args ?? []).some(arg => typeof arg !== "string" || arg.length > 2000 || arg.includes("\0")))
|
|
28
|
+
throw new Error("policy_process_isolation_input_invalid");
|
|
29
|
+
let total = 0;
|
|
30
|
+
const paths = new Set();
|
|
31
|
+
const files = input.files.map(file => {
|
|
32
|
+
if (!safePath(file.path) || paths.has(file.path) || typeof file.base64 !== "string")
|
|
33
|
+
throw new Error("policy_process_isolation_file_invalid");
|
|
34
|
+
paths.add(file.path);
|
|
35
|
+
const bytes = Buffer.from(file.base64, "base64");
|
|
36
|
+
if (bytes.toString("base64") !== file.base64) throw new Error("policy_process_isolation_file_invalid");
|
|
37
|
+
total += bytes.length;
|
|
38
|
+
if (total > MAX_BYTES) throw new Error("policy_process_isolation_file_limit");
|
|
39
|
+
return { path: file.path, bytes };
|
|
40
|
+
});
|
|
41
|
+
// Directory access seals request JSON, checker modules, helper source and
|
|
42
|
+
// private artifacts before any untrusted program can be spawned.
|
|
43
|
+
await fs.chmod(privateRoot, 0o700);
|
|
44
|
+
const stat = await fs.stat(privateRoot);
|
|
45
|
+
if (stat.uid !== 0 || (stat.mode & 0o777) !== 0o700) throw new Error("policy_process_private_tree_not_sealed");
|
|
46
|
+
for (const executable of ["/usr/bin/setsid", "/usr/bin/setpriv", "/usr/local/bin/bun"])
|
|
47
|
+
await fs.access(executable, 1);
|
|
48
|
+
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "openpond-policy-"));
|
|
49
|
+
let user;
|
|
50
|
+
do { user = randomInt(1000000, 2000000); } while (usedUsers.has(user));
|
|
51
|
+
usedUsers.add(user);
|
|
52
|
+
let child, timer, timedOut = false;
|
|
53
|
+
const kill = () => {
|
|
54
|
+
if (!child) return;
|
|
55
|
+
try { process.kill(-child.pid, "SIGKILL"); } catch {}
|
|
56
|
+
try { child.kill("SIGKILL"); } catch {}
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
await fs.chmod(directory, 0o700);
|
|
60
|
+
await fs.chown(directory, user, user);
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
const target = path.join(directory, file.path);
|
|
63
|
+
const parent = path.dirname(target);
|
|
64
|
+
await fs.mkdir(parent, { recursive: true, mode: 0o700 });
|
|
65
|
+
let current = parent;
|
|
66
|
+
while (current !== directory) {
|
|
67
|
+
await fs.chmod(current, 0o700); await fs.chown(current, user, user);
|
|
68
|
+
current = path.dirname(current);
|
|
69
|
+
}
|
|
70
|
+
await fs.writeFile(target, file.bytes, { mode: 0o600, flag: "wx" });
|
|
71
|
+
await fs.chown(target, user, user);
|
|
72
|
+
}
|
|
73
|
+
child = Bun.spawn(["/usr/bin/setsid", "/usr/bin/setpriv", "--reuid=" + user, "--regid=" + user, "--clear-groups",
|
|
74
|
+
"--no-new-privs", "--bounding-set=-all", "--inh-caps=-all", "--ambient-caps=-all",
|
|
75
|
+
"/usr/local/bin/bun", path.join(directory, input.entrypoint), ...(input.args ?? [])], {
|
|
76
|
+
cwd: directory, env: { PATH: "/usr/local/bin:/usr/bin:/bin", TMPDIR: directory }, stdout: "pipe", stderr: "pipe", stdin: "ignore",
|
|
77
|
+
});
|
|
78
|
+
timer = setTimeout(() => { timedOut = true; kill(); }, input.timeoutMs);
|
|
79
|
+
const read = async stream => {
|
|
80
|
+
const reader = stream.getReader(), chunks = [];
|
|
81
|
+
let bytes = 0;
|
|
82
|
+
try {
|
|
83
|
+
while (true) {
|
|
84
|
+
const item = await reader.read(); if (item.done) break;
|
|
85
|
+
bytes += item.value.byteLength;
|
|
86
|
+
if (bytes > MAX_OUTPUT) { kill(); throw new Error("policy_process_output_limit"); }
|
|
87
|
+
chunks.push(item.value);
|
|
88
|
+
}
|
|
89
|
+
return new TextDecoder("utf8", { fatal: true }).decode(Buffer.concat(chunks));
|
|
90
|
+
} finally { await reader.cancel(); reader.releaseLock(); }
|
|
91
|
+
};
|
|
92
|
+
const [stdout, stderr, exitCode] = await Promise.all([read(child.stdout), read(child.stderr), child.exited]);
|
|
93
|
+
return { stdout, stderr, exitCode, timedOut, isolation: { uid: user, gid: user, privateTreeSealed: true } };
|
|
94
|
+
} finally {
|
|
95
|
+
if (timer) clearTimeout(timer);
|
|
96
|
+
kill();
|
|
97
|
+
// A policy child can call setsid and escape its initial process group.
|
|
98
|
+
// Drain every process with this invocation's unique real UID before the
|
|
99
|
+
// next call; namespaces cannot grant a different outside UID or privilege.
|
|
100
|
+
try { await drainPolicyProcesses(user); }
|
|
101
|
+
finally { await fs.rm(directory, { recursive: true, force: true }); }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function drainPolicyProcesses(user) {
|
|
106
|
+
const deadline = Date.now() + 2000;
|
|
107
|
+
let empty = 0;
|
|
108
|
+
while (Date.now() < deadline) {
|
|
109
|
+
const entries = (await fs.readdir("/proc")).filter(value => /^\d+$/.test(value));
|
|
110
|
+
if (entries.length > 10000) throw new Error("policy_process_inventory_limit");
|
|
111
|
+
let owned = 0;
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
let status;
|
|
114
|
+
try { status = await fs.readFile("/proc/" + entry + "/status", "utf8"); }
|
|
115
|
+
catch (error) { if (["ENOENT", "ESRCH"].includes(error.code)) continue; throw error; }
|
|
116
|
+
const ids = /^Uid:\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/m.exec(status);
|
|
117
|
+
if (ids && ids.slice(1).some(value => Number(value) === user)) {
|
|
118
|
+
// Zombies cannot fork, inspect a subsequent invocation or hold files.
|
|
119
|
+
if (/^State:\s+Z/m.test(status)) continue;
|
|
120
|
+
owned++;
|
|
121
|
+
try { process.kill(Number(entry), "SIGKILL"); }
|
|
122
|
+
catch (error) { if (error.code !== "ESRCH") throw error; }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
empty = owned === 0 ? empty + 1 : 0;
|
|
126
|
+
if (empty >= 2) return;
|
|
127
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
128
|
+
}
|
|
129
|
+
throw new Error("policy_process_cleanup_unconfirmed");
|
|
130
|
+
}
|
|
131
|
+
`;
|
package/dist/rewards.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ImmutableAssetRefSchema, ImmutableReleaseRefSchema, ReleaseHashSchema,
|
|
|
4
4
|
import { gradeEvidence, GraderEvidenceSchema } from "./graders.js";
|
|
5
5
|
import { CustomVerifierGraderSpecSchema, DeterministicGraderSpecSchema, GraderSpecSchema, HumanGraderSpecSchema, ModelJudgeGraderSpecSchema } from "./tasksets.js";
|
|
6
6
|
import { assertBoundedTaskJson, validateTaskSchema } from "./task-schema.js";
|
|
7
|
-
const bindingFields = { id: true, version: true, weight: true, hardGate: true, rewardEligible: true, privileged: true };
|
|
7
|
+
const bindingFields = { taskIds: true, id: true, version: true, weight: true, hardGate: true, rewardEligible: true, privileged: true };
|
|
8
8
|
export const FeedbackKeySchema = z.string().regex(/^[a-z][a-z0-9_]{0,79}$/);
|
|
9
9
|
export function feedbackKeyForReward(reward) {
|
|
10
10
|
return reward.feedbackKey ?? `reward_${contentHash(reward.id).slice(0, 24)}`;
|
|
@@ -49,6 +49,7 @@ export const RewardNormalizationSchema = z.discriminatedUnion("kind", [
|
|
|
49
49
|
]);
|
|
50
50
|
export const RewardBindingSourceSchema = z.object({
|
|
51
51
|
graderId: ReleaseIdSchema,
|
|
52
|
+
taskIds: z.array(ReleaseIdSchema).min(1).max(10_000).refine(ids => new Set(ids).size === ids.length, "Task scope must be unique.").optional(),
|
|
52
53
|
reward: RewardReleaseRefSchema,
|
|
53
54
|
role: z.enum(["training", "evaluation"]),
|
|
54
55
|
normalization: RewardNormalizationSchema,
|
|
@@ -87,7 +88,7 @@ export const BoundRewardResultSchema = z.object({
|
|
|
87
88
|
graderId: ReleaseIdSchema,
|
|
88
89
|
reward: RewardReleaseRefSchema,
|
|
89
90
|
role: z.enum(["training", "evaluation"]),
|
|
90
|
-
status: z.enum(["scored", "pending", "unavailable", "failed"]),
|
|
91
|
+
status: z.enum(["scored", "pending", "unavailable", "failed", "not_applicable"]),
|
|
91
92
|
rawScore: z.number().finite().nullable(),
|
|
92
93
|
normalizedScore: z.number().min(0).max(1).nullable(),
|
|
93
94
|
passed: z.boolean().nullable(),
|
|
@@ -99,6 +100,7 @@ export const RewardCompositionContentSchema = z.object({
|
|
|
99
100
|
schemaVersion: z.literal("openpond.rewardComposition.v1"),
|
|
100
101
|
binding: RewardReleaseRefSchema,
|
|
101
102
|
taskHash: ReleaseHashSchema,
|
|
103
|
+
taskId: ReleaseIdSchema.optional(),
|
|
102
104
|
outputHash: ReleaseHashSchema,
|
|
103
105
|
results: z.array(BoundRewardResultSchema).max(100),
|
|
104
106
|
training: z.object({ status: z.enum(["scored", "pending", "unscorable", "not_configured"]), score: z.number().min(0).max(1).nullable(), passed: z.boolean().nullable() }).strict(),
|
|
@@ -149,6 +151,10 @@ export async function executeRewardBinding(input) {
|
|
|
149
151
|
for (const { source, reward } of resolved) {
|
|
150
152
|
input.signal?.throwIfAborted();
|
|
151
153
|
const result = { graderId: source.graderId, reward: source.reward, role: source.role, status: "unavailable", rawScore: null, normalizedScore: null, passed: null, evidenceHashes: [], message: null };
|
|
154
|
+
if (source.taskIds && !source.taskIds.includes(input.task.id)) {
|
|
155
|
+
results.push({ ...result, status: "not_applicable" });
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
152
158
|
try {
|
|
153
159
|
if (reward.implementation.kind === "human") {
|
|
154
160
|
result.status = "pending";
|
|
@@ -183,11 +189,13 @@ export async function executeRewardBinding(input) {
|
|
|
183
189
|
}
|
|
184
190
|
results.push(BoundRewardResultSchema.parse(result));
|
|
185
191
|
}
|
|
186
|
-
return composeBoundRewards({ binding: input.binding, taskHash: contentHash(input.task), outputHash: contentHash(input.evidence), results });
|
|
192
|
+
return composeBoundRewards({ binding: input.binding, ...(input.binding.sources.some(source => source.taskIds) ? { taskId: input.task.id } : {}), taskHash: contentHash(input.task), outputHash: contentHash(input.evidence), results });
|
|
187
193
|
}
|
|
188
194
|
export function composeBoundRewards(input) {
|
|
189
195
|
const binding = RewardBindingSchema.parse(input.binding);
|
|
190
196
|
assertRewardHash(binding);
|
|
197
|
+
if (binding.sources.some(source => source.taskIds) && !input.taskId)
|
|
198
|
+
throw new LearningDomainError("reward_task_scope_missing");
|
|
191
199
|
const results = input.results.map((result) => BoundRewardResultSchema.parse(result));
|
|
192
200
|
if (results.length !== binding.sources.length || new Set(results.map((result) => result.graderId)).size !== results.length)
|
|
193
201
|
throw new LearningDomainError("reward_result_set_incomplete");
|
|
@@ -195,6 +203,9 @@ export function composeBoundRewards(input) {
|
|
|
195
203
|
const result = results.find((candidate) => candidate.graderId === source.graderId);
|
|
196
204
|
if (!result || contentHash(result.reward) !== contentHash(source.reward) || result.role !== source.role)
|
|
197
205
|
throw new LearningDomainError("reward_result_binding_mismatch");
|
|
206
|
+
const applicable = !source.taskIds || source.taskIds.includes(input.taskId);
|
|
207
|
+
if (applicable === (result.status === "not_applicable") || !applicable && (result.graderEvidence || result.evidenceHashes.length || result.message !== null))
|
|
208
|
+
throw new LearningDomainError("reward_result_task_scope_mismatch");
|
|
198
209
|
if (result.graderEvidence) {
|
|
199
210
|
const { contentHash: evidenceHash, ...body } = result.graderEvidence;
|
|
200
211
|
if (contentHash(body) !== evidenceHash || !result.evidenceHashes.includes(evidenceHash)
|
|
@@ -219,6 +230,7 @@ export function composeBoundRewards(input) {
|
|
|
219
230
|
schemaVersion: "openpond.rewardComposition.v1",
|
|
220
231
|
binding: { id: binding.id, revision: binding.revision, contentHash: binding.contentHash },
|
|
221
232
|
taskHash: input.taskHash,
|
|
233
|
+
...(input.taskId ? { taskId: input.taskId } : {}),
|
|
222
234
|
outputHash: input.outputHash,
|
|
223
235
|
results,
|
|
224
236
|
training: aggregate("training"),
|
|
@@ -226,7 +238,7 @@ export function composeBoundRewards(input) {
|
|
|
226
238
|
});
|
|
227
239
|
return RewardCompositionSchema.parse({ ...content, contentHash: contentHash(content) });
|
|
228
240
|
function aggregate(role) {
|
|
229
|
-
const selected = binding.sources.filter((source) => source.role === role).map((source) => ({ source, result: results.find((candidate) => candidate.graderId === source.graderId) }));
|
|
241
|
+
const selected = binding.sources.filter((source) => source.role === role && (!source.taskIds || source.taskIds.includes(input.taskId))).map((source) => ({ source, result: results.find((candidate) => candidate.graderId === source.graderId) }));
|
|
230
242
|
if (!selected.length)
|
|
231
243
|
return { status: "not_configured", score: null, passed: null };
|
|
232
244
|
if (selected.some(({ source, result }) => source.required && result.status !== "scored" && result.status !== "pending"))
|
|
@@ -250,5 +262,5 @@ function withoutRewardHash(value) { const { contentHash: _hash, ...content } = v
|
|
|
250
262
|
function compileSource(source, reward) {
|
|
251
263
|
if (reward.implementation.kind === "learned_model")
|
|
252
264
|
throw new LearningDomainError("learned_reward_requires_model_execution_adapter");
|
|
253
|
-
return GraderSpecSchema.parse({ ...reward.implementation, id: source.graderId, version: String(reward.revision), weight: source.weight, hardGate: source.hardGate, rewardEligible: source.role === "training", privileged: source.privileged });
|
|
265
|
+
return GraderSpecSchema.parse({ ...reward.implementation, ...(source.taskIds ? { taskIds: source.taskIds } : {}), id: source.graderId, version: String(reward.revision), weight: source.weight, hardGate: source.hardGate, rewardEligible: source.role === "training", privileged: source.privileged });
|
|
254
266
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { sqlExecutionProcessSource } from "./sql-execution-process-source.js";
|
|
3
2
|
import { assertBoundedTaskJson } from "./task-schema.js";
|
|
4
3
|
import { assertSqlExecutionRequest, SqlExecutionResultSchema } from "./sql-execution-contract.js";
|
|
5
4
|
export * from "./sql-execution-contract.js";
|
|
@@ -9,6 +8,8 @@ export async function executeSqlInProcess(input) {
|
|
|
9
8
|
if (!Number.isSafeInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 30_000)
|
|
10
9
|
throw new Error("sql_timeout_invalid");
|
|
11
10
|
const request = assertSqlExecutionRequest(input.request);
|
|
11
|
+
const { sqlExecutionProcessSource } = await import("./sql-execution-process-source.js");
|
|
12
|
+
input.signal?.throwIfAborted();
|
|
12
13
|
const { signal } = input;
|
|
13
14
|
const program = `globalThis.__openpondSqlInput = JSON.parse(${JSON.stringify(JSON.stringify(request))});\n${sqlExecutionProcessSource}`;
|
|
14
15
|
return new Promise((resolve, reject) => {
|
package/dist/tasksets.js
CHANGED
|
@@ -28,6 +28,7 @@ export const EnvironmentContractSchema = z.object({
|
|
|
28
28
|
}).strict();
|
|
29
29
|
const GraderBaseSchema = z.object({
|
|
30
30
|
id: ReleaseIdSchema,
|
|
31
|
+
taskIds: z.array(ReleaseIdSchema).min(1).max(10_000).refine(ids => new Set(ids).size === ids.length, "Grader task IDs must be unique.").optional(),
|
|
31
32
|
version: z.string().trim().min(1).max(100),
|
|
32
33
|
weight: z.number().nonnegative().max(1_000).default(1),
|
|
33
34
|
hardGate: z.boolean().default(false),
|
|
@@ -89,7 +90,7 @@ export const TasksetReleaseContentSchema = z.object({
|
|
|
89
90
|
tools: z.array(ToolDeclarationSchema).max(200),
|
|
90
91
|
capabilities: z.array(CapabilityRequirementSchema).max(200),
|
|
91
92
|
tasks: z.array(TaskRecordSchema).min(1).max(1_000_000),
|
|
92
|
-
graders: z.array(GraderSpecSchema).
|
|
93
|
+
graders: z.array(GraderSpecSchema).max(1_000),
|
|
93
94
|
verifierSetRelease: z.object({ id: ReleaseIdSchema, contentHash: ReleaseHashSchema }).strict().optional(),
|
|
94
95
|
metrics: TasksetMetricPolicySchema.optional(),
|
|
95
96
|
metadata: MetadataSchema,
|
|
@@ -120,6 +121,11 @@ export function validateTasksetRelease(input) {
|
|
|
120
121
|
path: "environmentRelease",
|
|
121
122
|
});
|
|
122
123
|
}
|
|
124
|
+
const taskIds = new Set(taskset.tasks.map(task => task.id));
|
|
125
|
+
for (const grader of taskset.graders)
|
|
126
|
+
if (grader.taskIds?.some(id => !taskIds.has(id))) {
|
|
127
|
+
issues.push({ code: "grader_task_missing", severity: "error", message: `Grader ${grader.id} refers to a task outside this release.`, path: "graders" });
|
|
128
|
+
}
|
|
123
129
|
const clusterSplits = new Map();
|
|
124
130
|
for (const task of taskset.tasks) {
|
|
125
131
|
if (taskset.metrics?.aggregation === "weighted_mean" && !Object.hasOwn(taskset.metrics.taskWeights, task.id)) {
|
|
@@ -73,6 +73,7 @@ export declare const harnessRefinerBenchmarkRelease: {
|
|
|
73
73
|
privileged: boolean;
|
|
74
74
|
kind: "content" | "schema" | "artifact" | "runtime_event" | "state";
|
|
75
75
|
config: Record<string, unknown>;
|
|
76
|
+
taskIds?: string[] | undefined;
|
|
76
77
|
} | {
|
|
77
78
|
id: string;
|
|
78
79
|
version: string;
|
|
@@ -90,6 +91,7 @@ export declare const harnessRefinerBenchmarkRelease: {
|
|
|
90
91
|
visibility: "policy" | "verifier" | "host_private";
|
|
91
92
|
};
|
|
92
93
|
calibrationStatus: "pending" | "passed" | "failed";
|
|
94
|
+
taskIds?: string[] | undefined;
|
|
93
95
|
model?: {
|
|
94
96
|
providerId: string;
|
|
95
97
|
modelId: string;
|
|
@@ -114,6 +116,7 @@ export declare const harnessRefinerBenchmarkRelease: {
|
|
|
114
116
|
};
|
|
115
117
|
timeoutMs: number;
|
|
116
118
|
networkPolicy: "none";
|
|
119
|
+
taskIds?: string[] | undefined;
|
|
117
120
|
runtime?: "isolated_javascript" | "sandbox_process" | undefined;
|
|
118
121
|
exportName?: string | undefined;
|
|
119
122
|
} | {
|
|
@@ -133,6 +136,7 @@ export declare const harnessRefinerBenchmarkRelease: {
|
|
|
133
136
|
visibility: "policy" | "verifier" | "host_private";
|
|
134
137
|
};
|
|
135
138
|
reviewerRole: string;
|
|
139
|
+
taskIds?: string[] | undefined;
|
|
136
140
|
form?: {
|
|
137
141
|
schemaVersion: "openpond.humanForm.v1";
|
|
138
142
|
mode: "individual" | "pairwise";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness-refiner.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-benchmarks/harness-refiner.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,8BAA8B
|
|
1
|
+
{"version":3,"file":"harness-refiner.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-benchmarks/harness-refiner.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqqD1C,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASzE,CAAC"}
|
|
@@ -183,6 +183,7 @@ export declare const genericToolConformance: {
|
|
|
183
183
|
privileged: boolean;
|
|
184
184
|
kind: "content" | "schema" | "artifact" | "runtime_event" | "state";
|
|
185
185
|
config: Record<string, unknown>;
|
|
186
|
+
taskIds?: string[] | undefined;
|
|
186
187
|
} | {
|
|
187
188
|
id: string;
|
|
188
189
|
version: string;
|
|
@@ -200,6 +201,7 @@ export declare const genericToolConformance: {
|
|
|
200
201
|
visibility: "policy" | "verifier" | "host_private";
|
|
201
202
|
};
|
|
202
203
|
calibrationStatus: "pending" | "passed" | "failed";
|
|
204
|
+
taskIds?: string[] | undefined;
|
|
203
205
|
model?: {
|
|
204
206
|
providerId: string;
|
|
205
207
|
modelId: string;
|
|
@@ -224,6 +226,7 @@ export declare const genericToolConformance: {
|
|
|
224
226
|
};
|
|
225
227
|
timeoutMs: number;
|
|
226
228
|
networkPolicy: "none";
|
|
229
|
+
taskIds?: string[] | undefined;
|
|
227
230
|
runtime?: "isolated_javascript" | "sandbox_process" | undefined;
|
|
228
231
|
exportName?: string | undefined;
|
|
229
232
|
} | {
|
|
@@ -243,6 +246,7 @@ export declare const genericToolConformance: {
|
|
|
243
246
|
visibility: "policy" | "verifier" | "host_private";
|
|
244
247
|
};
|
|
245
248
|
reviewerRole: string;
|
|
249
|
+
taskIds?: string[] | undefined;
|
|
246
250
|
form?: {
|
|
247
251
|
schemaVersion: "openpond.humanForm.v1";
|
|
248
252
|
mode: "individual" | "pairwise";
|
|
@@ -568,6 +572,7 @@ export declare const marketingPortfolioConformance: {
|
|
|
568
572
|
privileged: boolean;
|
|
569
573
|
kind: "content" | "schema" | "artifact" | "runtime_event" | "state";
|
|
570
574
|
config: Record<string, unknown>;
|
|
575
|
+
taskIds?: string[] | undefined;
|
|
571
576
|
} | {
|
|
572
577
|
id: string;
|
|
573
578
|
version: string;
|
|
@@ -585,6 +590,7 @@ export declare const marketingPortfolioConformance: {
|
|
|
585
590
|
visibility: "policy" | "verifier" | "host_private";
|
|
586
591
|
};
|
|
587
592
|
calibrationStatus: "pending" | "passed" | "failed";
|
|
593
|
+
taskIds?: string[] | undefined;
|
|
588
594
|
model?: {
|
|
589
595
|
providerId: string;
|
|
590
596
|
modelId: string;
|
|
@@ -609,6 +615,7 @@ export declare const marketingPortfolioConformance: {
|
|
|
609
615
|
};
|
|
610
616
|
timeoutMs: number;
|
|
611
617
|
networkPolicy: "none";
|
|
618
|
+
taskIds?: string[] | undefined;
|
|
612
619
|
runtime?: "isolated_javascript" | "sandbox_process" | undefined;
|
|
613
620
|
exportName?: string | undefined;
|
|
614
621
|
} | {
|
|
@@ -628,6 +635,7 @@ export declare const marketingPortfolioConformance: {
|
|
|
628
635
|
visibility: "policy" | "verifier" | "host_private";
|
|
629
636
|
};
|
|
630
637
|
reviewerRole: string;
|
|
638
|
+
taskIds?: string[] | undefined;
|
|
631
639
|
form?: {
|
|
632
640
|
schemaVersion: "openpond.humanForm.v1";
|
|
633
641
|
mode: "individual" | "pairwise";
|