@vela-science/canopus 0.4.0-rc.1
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/CHANGELOG.md +249 -0
- package/LICENSE-APACHE +176 -0
- package/LICENSE-MIT +21 -0
- package/README.md +124 -0
- package/SECURITY.md +35 -0
- package/capsules/erdos1056-k15/README.md +35 -0
- package/capsules/erdos1056-k15/bin/linux-arm64/10428401-10428600/verifier +0 -0
- package/capsules/erdos1056-k15/bin/linux-x86_64/10428401-10428600/verifier +0 -0
- package/capsules/formal-erdos-505-test-dim-one/README.md +20 -0
- package/capsules/formal-erdos-505-test-dim-one/verifier +105 -0
- package/dist/src/activity/events.d.ts +15 -0
- package/dist/src/activity/events.js +24 -0
- package/dist/src/activity/store.d.ts +10 -0
- package/dist/src/activity/store.js +165 -0
- package/dist/src/artifact/freeze.d.ts +21 -0
- package/dist/src/artifact/freeze.js +177 -0
- package/dist/src/artifact/materialize.d.ts +6 -0
- package/dist/src/artifact/materialize.js +29 -0
- package/dist/src/budget/enforce.d.ts +30 -0
- package/dist/src/budget/enforce.js +98 -0
- package/dist/src/candidate/validate.d.ts +3 -0
- package/dist/src/candidate/validate.js +32 -0
- package/dist/src/capability/withdrawal.d.ts +47 -0
- package/dist/src/capability/withdrawal.js +487 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +503 -0
- package/dist/src/contracts/candidate.d.ts +50 -0
- package/dist/src/contracts/candidate.js +93 -0
- package/dist/src/contracts/mission.d.ts +139 -0
- package/dist/src/contracts/mission.js +401 -0
- package/dist/src/contracts/validation.d.ts +25 -0
- package/dist/src/contracts/validation.js +95 -0
- package/dist/src/engines/codex-events.d.ts +25 -0
- package/dist/src/engines/codex-events.js +219 -0
- package/dist/src/engines/codex-exec.d.ts +17 -0
- package/dist/src/engines/codex-exec.js +136 -0
- package/dist/src/engines/codex-home.d.ts +7 -0
- package/dist/src/engines/codex-home.js +50 -0
- package/dist/src/engines/codex-tool-free.d.ts +26 -0
- package/dist/src/engines/codex-tool-free.js +213 -0
- package/dist/src/engines/codex-tools-native.d.ts +30 -0
- package/dist/src/engines/codex-tools-native.js +405 -0
- package/dist/src/engines/engine.d.ts +55 -0
- package/dist/src/engines/engine.js +4 -0
- package/dist/src/engines/fake.d.ts +7 -0
- package/dist/src/engines/fake.js +37 -0
- package/dist/src/evidence/manifests.d.ts +9 -0
- package/dist/src/evidence/manifests.js +56 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.js +32 -0
- package/dist/src/mission/prepare.d.ts +33 -0
- package/dist/src/mission/prepare.js +375 -0
- package/dist/src/product/custody.d.ts +27 -0
- package/dist/src/product/custody.js +131 -0
- package/dist/src/product/doctor.d.ts +61 -0
- package/dist/src/product/doctor.js +278 -0
- package/dist/src/product/profile-bundle.d.ts +21 -0
- package/dist/src/product/profile-bundle.js +78 -0
- package/dist/src/product/profile.d.ts +67 -0
- package/dist/src/product/profile.js +251 -0
- package/dist/src/product/replay.d.ts +10 -0
- package/dist/src/product/replay.js +71 -0
- package/dist/src/product/run.d.ts +31 -0
- package/dist/src/product/run.js +264 -0
- package/dist/src/product/runtime.d.ts +18 -0
- package/dist/src/product/runtime.js +64 -0
- package/dist/src/product/version.d.ts +1 -0
- package/dist/src/product/version.js +6 -0
- package/dist/src/product/withdraw.d.ts +8 -0
- package/dist/src/product/withdraw.js +255 -0
- package/dist/src/projection/diagnostic.d.ts +12 -0
- package/dist/src/projection/diagnostic.js +61 -0
- package/dist/src/projection/failure.d.ts +26 -0
- package/dist/src/projection/failure.js +63 -0
- package/dist/src/projection/run.d.ts +89 -0
- package/dist/src/projection/run.js +159 -0
- package/dist/src/receipt/map.d.ts +25 -0
- package/dist/src/receipt/map.js +162 -0
- package/dist/src/repair/plan.d.ts +2 -0
- package/dist/src/repair/plan.js +22 -0
- package/dist/src/roles.d.ts +2 -0
- package/dist/src/roles.js +9 -0
- package/dist/src/run.d.ts +94 -0
- package/dist/src/run.js +654 -0
- package/dist/src/util/canonical.d.ts +5 -0
- package/dist/src/util/canonical.js +53 -0
- package/dist/src/util/command.d.ts +26 -0
- package/dist/src/util/command.js +161 -0
- package/dist/src/util/files.d.ts +3 -0
- package/dist/src/util/files.js +65 -0
- package/dist/src/vela/cli.d.ts +71 -0
- package/dist/src/vela/cli.js +718 -0
- package/dist/src/vela/types.d.ts +27 -0
- package/dist/src/vela/types.js +1 -0
- package/dist/src/verifier/run.d.ts +24 -0
- package/dist/src/verifier/run.js +315 -0
- package/dist/src/workspace/prepare.d.ts +30 -0
- package/dist/src/workspace/prepare.js +183 -0
- package/docs/MISSIONS.md +78 -0
- package/docs/RELEASES.md +1036 -0
- package/docs/RUN_RECORD.md +70 -0
- package/missions/erdos1056-k15-next/mission.draft.json +40 -0
- package/missions/formal-erdos-505-test-dim-one/mission.draft.json +40 -0
- package/package.json +89 -0
- package/profiles/erdos1056-k15-10428401-10428600.json +33 -0
- package/profiles/formal-erdos-505-test-dim-one.json +35 -0
- package/runtime/native-worker/config-linux.toml +20 -0
- package/runtime/native-worker/config.toml +17 -0
- package/schemas/candidate.v0.json +125 -0
- package/schemas/engine-manifest.v0.json +45 -0
- package/schemas/engine-output.v0.json +41 -0
- package/schemas/mission.v0.json +175 -0
- package/schemas/mission.v1.json +224 -0
- package/schemas/verifier-manifest.v0.json +48 -0
- package/schemas/verifier-manifest.v1.json +59 -0
- package/scripts/run-hostile-native-custody-fixture.mjs +382 -0
- package/scripts/run-hostile-verifier-fixture.mjs +63 -0
- package/tests/fixtures/hostile-verifier/probe.py +35 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { arrayAt, enumAt, exactKeys, gitObjectAt, integerAt, objectAt, relativePathAt, sha256At, stringAt, } from "../contracts/validation.js";
|
|
2
|
+
export const RUN_RECORD_SCHEMA = "canopus.run.v0";
|
|
3
|
+
export const RUN_PROJECTION_SCHEMA = "canopus.run-projection.v0";
|
|
4
|
+
function literal(value, expected, at) {
|
|
5
|
+
if (value !== expected)
|
|
6
|
+
throw new Error(`${at} must be ${String(expected)}`);
|
|
7
|
+
return expected;
|
|
8
|
+
}
|
|
9
|
+
function rootsAt(value, at) {
|
|
10
|
+
const object = objectAt(value, at);
|
|
11
|
+
exactKeys(object, ["git_commit", "git_tree", "vela_event_log", "vela_snapshot"], [], at);
|
|
12
|
+
return {
|
|
13
|
+
git_commit: gitObjectAt(object.git_commit, `${at}.git_commit`),
|
|
14
|
+
git_tree: gitObjectAt(object.git_tree, `${at}.git_tree`),
|
|
15
|
+
vela_event_log: sha256At(object.vela_event_log, `${at}.vela_event_log`),
|
|
16
|
+
vela_snapshot: sha256At(object.vela_snapshot, `${at}.vela_snapshot`),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function nullableRoute(value, at) {
|
|
20
|
+
return value === null ? null : enumAt(value, at, ["permit", "defer"]);
|
|
21
|
+
}
|
|
22
|
+
function nullableCount(value, at) {
|
|
23
|
+
return value === null ? null : integerAt(value, at, 0, Number.MAX_SAFE_INTEGER);
|
|
24
|
+
}
|
|
25
|
+
export function parseRunRecord(value) {
|
|
26
|
+
const record = objectAt(value, "run");
|
|
27
|
+
exactKeys(record, [
|
|
28
|
+
"schema", "run_id", "status", "authority", "external_gate_credit", "mission",
|
|
29
|
+
"candidate", "verifier", "landing", "final_roots", "reproduction", "budget",
|
|
30
|
+
], [], "run");
|
|
31
|
+
const mission = objectAt(record.mission, "run.mission");
|
|
32
|
+
exactKeys(mission, ["id", "target", "digest", "starting_roots"], [], "run.mission");
|
|
33
|
+
const candidate = objectAt(record.candidate, "run.candidate");
|
|
34
|
+
exactKeys(candidate, ["digest", "status", "claim", "artifacts", "caveats"], [], "run.candidate");
|
|
35
|
+
const verifier = objectAt(record.verifier, "run.verifier");
|
|
36
|
+
exactKeys(verifier, ["status", "sandbox", "record"], [], "run.verifier");
|
|
37
|
+
const testRecord = objectAt(verifier.record, "run.verifier.record");
|
|
38
|
+
exactKeys(testRecord, ["argv", "executable_digest", "exit_code", "stdout_digest", "stderr_digest", "duration_ms"], [], "run.verifier.record");
|
|
39
|
+
const landing = objectAt(record.landing, "run.landing");
|
|
40
|
+
exactKeys(landing, [
|
|
41
|
+
"operation_id", "receipt_root", "proposal_id", "route", "original_route",
|
|
42
|
+
"accepted_event_delta", "publication_state",
|
|
43
|
+
], [], "run.landing");
|
|
44
|
+
const reproduction = objectAt(record.reproduction, "run.reproduction");
|
|
45
|
+
exactKeys(reproduction, ["matched", "roots", "verifier_status", "stdout_digest", "stderr_digest"], [], "run.reproduction");
|
|
46
|
+
const budget = objectAt(record.budget, "run.budget");
|
|
47
|
+
exactKeys(budget, [
|
|
48
|
+
"research_elapsed_ms", "research_processes", "research_output_bytes", "prompt_bytes",
|
|
49
|
+
"artifact_bytes", "attempts", "observed_tokens",
|
|
50
|
+
], [], "run.budget");
|
|
51
|
+
return {
|
|
52
|
+
schema: literal(record.schema, RUN_RECORD_SCHEMA, "run.schema"),
|
|
53
|
+
run_id: stringAt(record.run_id, "run.run_id", { min: 5, max: 128 }),
|
|
54
|
+
status: literal(record.status, "completed", "run.status"),
|
|
55
|
+
authority: literal(record.authority, "non_authoritative", "run.authority"),
|
|
56
|
+
external_gate_credit: literal(record.external_gate_credit, false, "run.external_gate_credit"),
|
|
57
|
+
mission: {
|
|
58
|
+
id: stringAt(mission.id, "run.mission.id", { min: 1, max: 134 }),
|
|
59
|
+
target: stringAt(mission.target, "run.mission.target", { min: 1, max: 256 }),
|
|
60
|
+
digest: sha256At(mission.digest, "run.mission.digest"),
|
|
61
|
+
starting_roots: rootsAt(mission.starting_roots, "run.mission.starting_roots"),
|
|
62
|
+
},
|
|
63
|
+
candidate: {
|
|
64
|
+
digest: sha256At(candidate.digest, "run.candidate.digest"),
|
|
65
|
+
status: enumAt(candidate.status, "run.candidate.status", ["success", "null", "failed"]),
|
|
66
|
+
claim: stringAt(candidate.claim, "run.candidate.claim", { min: 1, max: 8192 }),
|
|
67
|
+
artifacts: arrayAt(candidate.artifacts, "run.candidate.artifacts", { max: 10 }, (item, at) => {
|
|
68
|
+
const artifact = objectAt(item, at);
|
|
69
|
+
exactKeys(artifact, ["path", "kind", "digest", "bytes"], [], at);
|
|
70
|
+
return {
|
|
71
|
+
path: relativePathAt(artifact.path, `${at}.path`),
|
|
72
|
+
kind: stringAt(artifact.kind, `${at}.kind`, { min: 1, max: 128 }),
|
|
73
|
+
digest: sha256At(artifact.digest, `${at}.digest`),
|
|
74
|
+
bytes: integerAt(artifact.bytes, `${at}.bytes`, 0, 1_073_741_824),
|
|
75
|
+
};
|
|
76
|
+
}),
|
|
77
|
+
caveats: arrayAt(candidate.caveats, "run.candidate.caveats", { max: 10 }, (item, at) => stringAt(item, at, { min: 1, max: 4096 })),
|
|
78
|
+
},
|
|
79
|
+
verifier: {
|
|
80
|
+
status: enumAt(verifier.status, "run.verifier.status", ["passed", "failed", "error"]),
|
|
81
|
+
sandbox: enumAt(verifier.sandbox, "run.verifier.sandbox", ["macos_sandbox", "container_network_denied"]),
|
|
82
|
+
record: {
|
|
83
|
+
argv: arrayAt(testRecord.argv, "run.verifier.record.argv", { min: 1, max: 64 }, (item, at) => stringAt(item, at, { max: 4096 })),
|
|
84
|
+
executable_digest: sha256At(testRecord.executable_digest, "run.verifier.record.executable_digest"),
|
|
85
|
+
exit_code: integerAt(testRecord.exit_code, "run.verifier.record.exit_code", -1, 255),
|
|
86
|
+
stdout_digest: sha256At(testRecord.stdout_digest, "run.verifier.record.stdout_digest"),
|
|
87
|
+
stderr_digest: sha256At(testRecord.stderr_digest, "run.verifier.record.stderr_digest"),
|
|
88
|
+
duration_ms: integerAt(testRecord.duration_ms, "run.verifier.record.duration_ms", 0, 3_600_000),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
landing: {
|
|
92
|
+
operation_id: stringAt(landing.operation_id, "run.landing.operation_id", { min: 1, max: 256 }),
|
|
93
|
+
receipt_root: sha256At(landing.receipt_root, "run.landing.receipt_root"),
|
|
94
|
+
proposal_id: stringAt(landing.proposal_id, "run.landing.proposal_id", { min: 1, max: 256 }),
|
|
95
|
+
route: enumAt(landing.route, "run.landing.route", ["permit", "defer", "exact_retry"]),
|
|
96
|
+
original_route: nullableRoute(landing.original_route, "run.landing.original_route"),
|
|
97
|
+
accepted_event_delta: nullableCount(landing.accepted_event_delta, "run.landing.accepted_event_delta"),
|
|
98
|
+
publication_state: stringAt(landing.publication_state, "run.landing.publication_state", { min: 1, max: 64 }),
|
|
99
|
+
},
|
|
100
|
+
final_roots: rootsAt(record.final_roots, "run.final_roots"),
|
|
101
|
+
reproduction: {
|
|
102
|
+
matched: literal(reproduction.matched, true, "run.reproduction.matched"),
|
|
103
|
+
roots: rootsAt(reproduction.roots, "run.reproduction.roots"),
|
|
104
|
+
verifier_status: enumAt(reproduction.verifier_status, "run.reproduction.verifier_status", ["passed", "failed", "error"]),
|
|
105
|
+
stdout_digest: sha256At(reproduction.stdout_digest, "run.reproduction.stdout_digest"),
|
|
106
|
+
stderr_digest: sha256At(reproduction.stderr_digest, "run.reproduction.stderr_digest"),
|
|
107
|
+
},
|
|
108
|
+
budget: {
|
|
109
|
+
research_elapsed_ms: integerAt(budget.research_elapsed_ms, "run.budget.research_elapsed_ms", 0, 3_600_000),
|
|
110
|
+
research_processes: integerAt(budget.research_processes, "run.budget.research_processes", 0, 64),
|
|
111
|
+
research_output_bytes: integerAt(budget.research_output_bytes, "run.budget.research_output_bytes", 0, 67_108_864),
|
|
112
|
+
prompt_bytes: integerAt(budget.prompt_bytes, "run.budget.prompt_bytes", 0, 8_388_608),
|
|
113
|
+
artifact_bytes: integerAt(budget.artifact_bytes, "run.budget.artifact_bytes", 0, 1_073_741_824),
|
|
114
|
+
attempts: integerAt(budget.attempts, "run.budget.attempts", 0, 8),
|
|
115
|
+
observed_tokens: integerAt(budget.observed_tokens, "run.budget.observed_tokens", 0, 1_000_000),
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function projectRun(record) {
|
|
120
|
+
if ((record.landing.route === "exact_retry") !== (record.landing.original_route !== null)) {
|
|
121
|
+
throw new Error("exact retry must name exactly one durable original route");
|
|
122
|
+
}
|
|
123
|
+
const effectiveRoute = record.landing.route === "exact_retry"
|
|
124
|
+
? record.landing.original_route
|
|
125
|
+
: record.landing.route;
|
|
126
|
+
if (effectiveRoute === null)
|
|
127
|
+
throw new Error("landing has no effective route");
|
|
128
|
+
const expectedDelta = effectiveRoute === "permit" ? 1 : 0;
|
|
129
|
+
if (record.landing.accepted_event_delta !== null &&
|
|
130
|
+
record.landing.accepted_event_delta !== expectedDelta) {
|
|
131
|
+
throw new Error(`${record.landing.route} landing has invalid accepted-event delta ${String(record.landing.accepted_event_delta)}`);
|
|
132
|
+
}
|
|
133
|
+
const acceptedStateEffect = record.landing.route === "permit"
|
|
134
|
+
? "policy_admitted"
|
|
135
|
+
: record.landing.route === "defer"
|
|
136
|
+
? "unchanged_pending"
|
|
137
|
+
: "exact_retry";
|
|
138
|
+
return {
|
|
139
|
+
schema: RUN_PROJECTION_SCHEMA,
|
|
140
|
+
authority: "read_only_projection",
|
|
141
|
+
run_id: record.run_id,
|
|
142
|
+
mission_id: record.mission.id,
|
|
143
|
+
target: record.mission.target,
|
|
144
|
+
starting_roots: record.mission.starting_roots,
|
|
145
|
+
final_roots: record.final_roots,
|
|
146
|
+
candidate_digest: record.candidate.digest,
|
|
147
|
+
candidate_status: record.candidate.status,
|
|
148
|
+
artifact_digests: record.candidate.artifacts.map((artifact) => artifact.digest),
|
|
149
|
+
verifier_status: record.verifier.status,
|
|
150
|
+
receipt_root: record.landing.receipt_root,
|
|
151
|
+
proposal_id: record.landing.proposal_id,
|
|
152
|
+
route: record.landing.route,
|
|
153
|
+
original_route: record.landing.original_route,
|
|
154
|
+
accepted_state_effect: acceptedStateEffect,
|
|
155
|
+
publication_state: record.landing.publication_state,
|
|
156
|
+
clean_clone_reproduced: record.reproduction.matched,
|
|
157
|
+
deletion_test: "Vela replay and accepted state do not depend on this projection.",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type FrozenArtifactLocation } from "../artifact/freeze.js";
|
|
2
|
+
import type { BudgetSnapshot } from "../budget/enforce.js";
|
|
3
|
+
import { type Candidate } from "../contracts/candidate.js";
|
|
4
|
+
import type { Mission } from "../contracts/mission.js";
|
|
5
|
+
import type { EngineResult } from "../engines/engine.js";
|
|
6
|
+
import type { VerifierOutcome } from "../verifier/run.js";
|
|
7
|
+
import type { AuthoredReceiptInput } from "../vela/cli.js";
|
|
8
|
+
export declare function finalizeCandidate(options: {
|
|
9
|
+
mission: Mission;
|
|
10
|
+
engine: EngineResult;
|
|
11
|
+
frozen: readonly FrozenArtifactLocation[];
|
|
12
|
+
verifier: VerifierOutcome;
|
|
13
|
+
budget: BudgetSnapshot;
|
|
14
|
+
supportingArtifacts?: ReadonlyArray<{
|
|
15
|
+
path: string;
|
|
16
|
+
kind: string;
|
|
17
|
+
}>;
|
|
18
|
+
}): Candidate;
|
|
19
|
+
export declare function mapCandidateToReceipt(mission: Mission, candidate: Candidate, verifier: VerifierOutcome, work?: string): AuthoredReceiptInput;
|
|
20
|
+
export declare function installFrozenArtifacts(options: {
|
|
21
|
+
landingRepo: string;
|
|
22
|
+
frontier: string;
|
|
23
|
+
frozen: readonly FrozenArtifactLocation[];
|
|
24
|
+
maxBytes: number;
|
|
25
|
+
}): Promise<string[]>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { chmod, copyFile, mkdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { hashRegularFile, } from "../artifact/freeze.js";
|
|
5
|
+
import { parseCandidate } from "../contracts/candidate.js";
|
|
6
|
+
function unique(values) {
|
|
7
|
+
return [...new Set(values)];
|
|
8
|
+
}
|
|
9
|
+
export function finalizeCandidate(options) {
|
|
10
|
+
const draftPaths = options.engine.draft.artifacts.map((artifact) => `${artifact.path}:${artifact.kind}`).sort();
|
|
11
|
+
const supporting = new Map((options.supportingArtifacts ?? []).map((entry) => [entry.path, entry.kind]));
|
|
12
|
+
const frozenPaths = options.frozen
|
|
13
|
+
.filter((entry) => !supporting.has(entry.artifact.path))
|
|
14
|
+
.map((entry) => `${entry.artifact.path}:${entry.artifact.kind}`)
|
|
15
|
+
.sort();
|
|
16
|
+
if (JSON.stringify(draftPaths) !== JSON.stringify(frozenPaths)) {
|
|
17
|
+
throw new Error("frozen artifacts do not exactly match the engine declaration");
|
|
18
|
+
}
|
|
19
|
+
for (const [path, kind] of supporting) {
|
|
20
|
+
const entry = options.frozen.find((candidate) => candidate.artifact.path === path);
|
|
21
|
+
if (entry === undefined || entry.artifact.kind !== kind) {
|
|
22
|
+
throw new Error(`supporting artifact ${path} is absent or misclassified`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const verifierFailed = options.engine.draft.status === "success" && options.verifier.status !== "passed";
|
|
26
|
+
const status = verifierFailed ? "failed" : options.engine.draft.status;
|
|
27
|
+
const exactPositiveClaim = options.mission.schema === "canopus.mission.v1" &&
|
|
28
|
+
options.mission.result_contract !== undefined &&
|
|
29
|
+
options.engine.draft.status === "success" &&
|
|
30
|
+
options.verifier.status === "passed"
|
|
31
|
+
? options.mission.result_contract.claim_exact
|
|
32
|
+
: undefined;
|
|
33
|
+
const claim = verifierFailed
|
|
34
|
+
? `The engine proposed a candidate, but the declared verifier did not pass: ${options.engine.draft.claim}`
|
|
35
|
+
: exactPositiveClaim ?? options.engine.draft.claim;
|
|
36
|
+
const caveats = unique([
|
|
37
|
+
...options.engine.draft.caveats,
|
|
38
|
+
`Declared verifier outcome: ${options.verifier.status}.`,
|
|
39
|
+
"Canopus produced this record; it is not a human acceptance decision.",
|
|
40
|
+
]);
|
|
41
|
+
const base = {
|
|
42
|
+
schema: "canopus.candidate.v0",
|
|
43
|
+
mission_id: options.mission.id,
|
|
44
|
+
status,
|
|
45
|
+
claim,
|
|
46
|
+
artifacts: options.frozen.map((entry) => entry.artifact),
|
|
47
|
+
observations: options.engine.draft.observations,
|
|
48
|
+
tests: [options.verifier.record],
|
|
49
|
+
costs: {
|
|
50
|
+
wall_time_ms: options.budget.research_elapsed_ms,
|
|
51
|
+
attempt: options.budget.attempts,
|
|
52
|
+
input_tokens: options.engine.usage.input_tokens,
|
|
53
|
+
output_tokens: options.engine.usage.output_tokens,
|
|
54
|
+
},
|
|
55
|
+
caveats,
|
|
56
|
+
engine: options.engine.engine,
|
|
57
|
+
};
|
|
58
|
+
return parseCandidate(options.mission.parent_candidate === undefined
|
|
59
|
+
? base
|
|
60
|
+
: {
|
|
61
|
+
...base,
|
|
62
|
+
repair: {
|
|
63
|
+
parent_candidate: options.mission.parent_candidate,
|
|
64
|
+
reason: options.mission.repair_reason ?? "Bounded repair of the named candidate.",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export function mapCandidateToReceipt(mission, candidate, verifier, work) {
|
|
69
|
+
if (candidate.mission_id !== mission.id) {
|
|
70
|
+
throw new Error("candidate belongs to a different mission");
|
|
71
|
+
}
|
|
72
|
+
if (candidate.artifacts.length === 0) {
|
|
73
|
+
throw new Error("Receipt v1 requires at least one frozen artifact");
|
|
74
|
+
}
|
|
75
|
+
if (candidate.status === "success" && verifier.status !== "passed") {
|
|
76
|
+
throw new Error("a success candidate requires a passing declared verifier");
|
|
77
|
+
}
|
|
78
|
+
if (mission.schema === "canopus.mission.v1" && mission.result_contract !== undefined) {
|
|
79
|
+
const contract = mission.result_contract;
|
|
80
|
+
if (candidate.status !== contract.candidate_status ||
|
|
81
|
+
candidate.claim !== contract.claim_exact ||
|
|
82
|
+
verifier.status !== contract.verifier_status ||
|
|
83
|
+
mission.target !== contract.target ||
|
|
84
|
+
mission.claim_type !== contract.claim_type ||
|
|
85
|
+
mission.replayability !== contract.replayability) {
|
|
86
|
+
throw new Error("candidate does not satisfy the exact positive result contract");
|
|
87
|
+
}
|
|
88
|
+
const artifactKinds = new Set(candidate.artifacts.map((artifact) => artifact.kind));
|
|
89
|
+
for (const kind of contract.required_artifact_kinds) {
|
|
90
|
+
if (!artifactKinds.has(kind)) {
|
|
91
|
+
throw new Error(`candidate is missing result-contract artifact kind ${kind}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const result = verifier.status === "passed"
|
|
96
|
+
? "The declared verifier exited zero on the frozen artifact set."
|
|
97
|
+
: verifier.status === "failed"
|
|
98
|
+
? `The declared verifier returned nonzero exit ${verifier.record.exit_code}.`
|
|
99
|
+
: `The declared verifier could not complete: ${verifier.error ?? "unspecified error"}.`;
|
|
100
|
+
const evidence = [
|
|
101
|
+
...candidate.artifacts.map((artifact) => `artifact:${artifact.digest}`),
|
|
102
|
+
`verifier_executable:${verifier.record.executable_digest}`,
|
|
103
|
+
`verifier_stdout:${verifier.record.stdout_digest}`,
|
|
104
|
+
`verifier_stderr:${verifier.record.stderr_digest}`,
|
|
105
|
+
];
|
|
106
|
+
const counterevidence = verifier.status === "passed"
|
|
107
|
+
? []
|
|
108
|
+
: [
|
|
109
|
+
`verifier_outcome:${verifier.status}`,
|
|
110
|
+
];
|
|
111
|
+
const prediction = "predicted_observable" in mission.scientific_chain
|
|
112
|
+
? { predictedObservable: mission.scientific_chain.predicted_observable }
|
|
113
|
+
: { notApplicable: true };
|
|
114
|
+
return {
|
|
115
|
+
claim: candidate.claim,
|
|
116
|
+
claimType: mission.claim_type,
|
|
117
|
+
replayability: mission.replayability,
|
|
118
|
+
artifacts: candidate.artifacts.map((artifact) => ({
|
|
119
|
+
path: artifact.path,
|
|
120
|
+
kind: artifact.kind,
|
|
121
|
+
})),
|
|
122
|
+
caveats: unique(candidate.caveats),
|
|
123
|
+
...prediction,
|
|
124
|
+
performedTest: mission.scientific_chain.performed_test,
|
|
125
|
+
result,
|
|
126
|
+
evidence,
|
|
127
|
+
counterevidence,
|
|
128
|
+
...(mission.schema === "canopus.mission.v1" && mission.execution_binding !== undefined
|
|
129
|
+
? { executionBinding: mission.execution_binding }
|
|
130
|
+
: {}),
|
|
131
|
+
...(work === undefined ? {} : { work }),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export async function installFrozenArtifacts(options) {
|
|
135
|
+
const frontierRoot = path.resolve(options.landingRepo, options.frontier);
|
|
136
|
+
const installed = [];
|
|
137
|
+
for (const entry of options.frozen) {
|
|
138
|
+
const target = path.resolve(frontierRoot, entry.artifact.path);
|
|
139
|
+
const relative = path.relative(frontierRoot, target);
|
|
140
|
+
if (relative === "" ||
|
|
141
|
+
relative === ".." ||
|
|
142
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
143
|
+
path.isAbsolute(relative)) {
|
|
144
|
+
throw new Error(`artifact path escapes the landing frontier: ${entry.artifact.path}`);
|
|
145
|
+
}
|
|
146
|
+
await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
147
|
+
try {
|
|
148
|
+
await copyFile(entry.frozenPath, target, constants.COPYFILE_EXCL);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (error.code !== "EEXIST")
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
const observed = await hashRegularFile(target, options.maxBytes);
|
|
155
|
+
if (observed.digest !== entry.artifact.digest || observed.bytes !== entry.artifact.bytes) {
|
|
156
|
+
throw new Error(`installed artifact ${entry.artifact.path} does not match frozen bytes`);
|
|
157
|
+
}
|
|
158
|
+
await chmod(target, 0o444);
|
|
159
|
+
installed.push(target);
|
|
160
|
+
}
|
|
161
|
+
return installed;
|
|
162
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { parseMission } from "../contracts/mission.js";
|
|
2
|
+
import { sha256At, stringAt } from "../contracts/validation.js";
|
|
3
|
+
import { contentDigest } from "../util/canonical.js";
|
|
4
|
+
export function planRepair(mission, parentCandidate, reason, remainingAttempts) {
|
|
5
|
+
const parent = sha256At(parentCandidate, "parent_candidate");
|
|
6
|
+
const repairReason = stringAt(reason, "repair_reason", { min: 1, max: 4096 });
|
|
7
|
+
if (!Number.isSafeInteger(remainingAttempts) || remainingAttempts < 1) {
|
|
8
|
+
throw new Error("repair requires at least one remaining attempt");
|
|
9
|
+
}
|
|
10
|
+
const suffix = contentDigest({ mission: mission.id, parent, repairReason }).slice(7, 19);
|
|
11
|
+
return parseMission({
|
|
12
|
+
...mission,
|
|
13
|
+
id: `mission_repair_${suffix}`,
|
|
14
|
+
objective: `Repair candidate ${parent}: ${repairReason}`.slice(0, 8192),
|
|
15
|
+
budgets: {
|
|
16
|
+
...mission.budgets,
|
|
17
|
+
max_attempts: Math.min(mission.budgets.max_attempts, remainingAttempts),
|
|
18
|
+
},
|
|
19
|
+
parent_candidate: parent,
|
|
20
|
+
repair_reason: repairReason,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const ROLE_INSTRUCTIONS = {
|
|
2
|
+
producer: "Construct the smallest candidate that meets the completion condition. Preserve a null result when the evidence does not support the claim.",
|
|
3
|
+
adversary: "Try to falsify or narrow the inherited candidate. Prefer a concrete counterexample or an explicit no-counterexample result over stylistic criticism.",
|
|
4
|
+
verifier: "Inspect whether the candidate and declared checks correspond. You do not replace the separate frozen verifier and you do not issue an acceptance verdict.",
|
|
5
|
+
fidelity: "Check that the claim says no more than the frozen artifacts and verifier facts support. Record mismatches as caveats or a failed candidate.",
|
|
6
|
+
};
|
|
7
|
+
export function roleInstruction(role) {
|
|
8
|
+
return ROLE_INSTRUCTIONS[role];
|
|
9
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Mission, MissionRoots, StrictBaseline } from "./contracts/mission.js";
|
|
2
|
+
import type { FrozenArtifact } from "./contracts/candidate.js";
|
|
3
|
+
import type { Engine } from "./engines/engine.js";
|
|
4
|
+
import { type RunProjection, type RunRecord } from "./projection/run.js";
|
|
5
|
+
import { type CommandRunner } from "./util/command.js";
|
|
6
|
+
import type { AuthoredReceiptInput, VelaClient, VelaLandCommandObservation } from "./vela/cli.js";
|
|
7
|
+
import type { LandResult, VelaCommandResponse, VelaInspection } from "./vela/types.js";
|
|
8
|
+
import { type WorkspacePaths } from "./workspace/prepare.js";
|
|
9
|
+
export interface VelaPort {
|
|
10
|
+
assertRoots(repoRoot: string, frontier: string, expected: MissionRoots, strictBaseline?: StrictBaseline): Promise<VelaInspection>;
|
|
11
|
+
inspect(repoRoot: string, frontier: string, strictBaseline?: StrictBaseline): Promise<VelaInspection>;
|
|
12
|
+
next(mission: Mission, repoRoot: string): Promise<VelaCommandResponse>;
|
|
13
|
+
work(mission: Mission, repoRoot: string, target: string, expected: MissionRoots): Promise<VelaCommandResponse>;
|
|
14
|
+
landAuthoredCommand(mission: Mission, repoRoot: string, input: AuthoredReceiptInput, expected: MissionRoots): Promise<VelaLandCommandObservation>;
|
|
15
|
+
parseLandCommand(observation: VelaLandCommandObservation): Record<string, unknown>;
|
|
16
|
+
validateLandResult(mission: Mission, raw: Record<string, unknown>): LandResult;
|
|
17
|
+
verifyReceiptBinding(mission: Mission, repoRoot: string, landing: LandResult, input: AuthoredReceiptInput, artifacts: readonly FrozenArtifact[]): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export interface CanopusRunOptions {
|
|
20
|
+
mission: Mission;
|
|
21
|
+
sourceRepo: string;
|
|
22
|
+
runRoot: string;
|
|
23
|
+
vela: VelaPort | VelaClient;
|
|
24
|
+
engine: Engine;
|
|
25
|
+
bundleRoot?: string;
|
|
26
|
+
dockerBinary?: string;
|
|
27
|
+
verifierRunner?: CommandRunner;
|
|
28
|
+
retainWithdrawalCapability?: (context: {
|
|
29
|
+
velaHome: string;
|
|
30
|
+
landingRepo: string;
|
|
31
|
+
mission: Mission;
|
|
32
|
+
landing: LandResult;
|
|
33
|
+
finalRoots: MissionRoots;
|
|
34
|
+
}) => Promise<void>;
|
|
35
|
+
noLand?: false;
|
|
36
|
+
}
|
|
37
|
+
export interface CanopusNoLandOptions extends Omit<CanopusRunOptions, "noLand"> {
|
|
38
|
+
noLand: true;
|
|
39
|
+
}
|
|
40
|
+
export interface CanopusRunResult {
|
|
41
|
+
record: RunRecord;
|
|
42
|
+
projection: RunProjection;
|
|
43
|
+
paths: WorkspacePaths;
|
|
44
|
+
}
|
|
45
|
+
export interface DiagnosticRunRecord {
|
|
46
|
+
schema: "canopus.diagnostic-run.v1";
|
|
47
|
+
run_id: string;
|
|
48
|
+
status: "completed";
|
|
49
|
+
mode: "no_land";
|
|
50
|
+
authority: "non_authoritative";
|
|
51
|
+
external_gate_credit: false;
|
|
52
|
+
mission: {
|
|
53
|
+
id: string;
|
|
54
|
+
target: string;
|
|
55
|
+
digest: string;
|
|
56
|
+
starting_roots: MissionRoots;
|
|
57
|
+
};
|
|
58
|
+
candidate: {
|
|
59
|
+
digest: string;
|
|
60
|
+
status: "success" | "null" | "failed";
|
|
61
|
+
claim: string;
|
|
62
|
+
artifacts: Array<{
|
|
63
|
+
path: string;
|
|
64
|
+
kind: string;
|
|
65
|
+
digest: string;
|
|
66
|
+
bytes: number;
|
|
67
|
+
}>;
|
|
68
|
+
caveats: string[];
|
|
69
|
+
};
|
|
70
|
+
verifier: RunRecord["verifier"];
|
|
71
|
+
landing: null;
|
|
72
|
+
reproduction: RunRecord["reproduction"];
|
|
73
|
+
budget: RunRecord["budget"];
|
|
74
|
+
}
|
|
75
|
+
export interface CanopusDiagnosticRunResult {
|
|
76
|
+
record: DiagnosticRunRecord;
|
|
77
|
+
projection: {
|
|
78
|
+
schema: "canopus.diagnostic-projection.v1";
|
|
79
|
+
authority: "read_only_projection";
|
|
80
|
+
run_id: string;
|
|
81
|
+
target: string;
|
|
82
|
+
candidate_digest: string;
|
|
83
|
+
verifier_status: "passed" | "failed" | "error";
|
|
84
|
+
landed: false;
|
|
85
|
+
clean_clone_reproduced: boolean;
|
|
86
|
+
};
|
|
87
|
+
paths: WorkspacePaths;
|
|
88
|
+
}
|
|
89
|
+
export declare function validateTargetOffer(target: string, response: VelaCommandResponse): {
|
|
90
|
+
index: number;
|
|
91
|
+
id: string;
|
|
92
|
+
};
|
|
93
|
+
export declare function runCanopus(options: CanopusNoLandOptions): Promise<CanopusDiagnosticRunResult>;
|
|
94
|
+
export declare function runCanopus(options: CanopusRunOptions): Promise<CanopusRunResult>;
|