@mikenguyen69/harness 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +43 -0
- package/dist/build-identity.json +1 -0
- package/dist/cancellation.js +138 -0
- package/dist/cli.js +47 -0
- package/dist/dispatch.js +70 -0
- package/dist/help.js +47 -0
- package/dist/internal/agent/adapter/conformance.js +53 -0
- package/dist/internal/agent/adapter/hook.js +113 -0
- package/dist/internal/agent/adapter/install.js +30 -0
- package/dist/internal/agent/adapter/scope.js +55 -0
- package/dist/internal/agent/adapter/session.js +63 -0
- package/dist/internal/agent/adapter/settings.js +51 -0
- package/dist/internal/agent/cli.js +79 -0
- package/dist/internal/agent/evals/index.js +111 -0
- package/dist/internal/agent/index.js +15 -0
- package/dist/internal/agent/ledger-read.js +27 -0
- package/dist/internal/agent/routing/cli.js +104 -0
- package/dist/internal/agent/routing/index.js +123 -0
- package/dist/internal/agent/telemetry/index.js +97 -0
- package/dist/internal/orchestration/activity/index.js +111 -0
- package/dist/internal/orchestration/analyze/index.js +237 -0
- package/dist/internal/orchestration/cli.js +475 -0
- package/dist/internal/orchestration/config/index.js +66 -0
- package/dist/internal/orchestration/doctor/index.js +202 -0
- package/dist/internal/orchestration/forge/index.js +121 -0
- package/dist/internal/orchestration/git/index.js +181 -0
- package/dist/internal/orchestration/harness/cli.js +378 -0
- package/dist/internal/orchestration/harness/index.js +186 -0
- package/dist/internal/orchestration/harness/shim.js +182 -0
- package/dist/internal/orchestration/index.js +29 -0
- package/dist/internal/orchestration/loop/index.js +1107 -0
- package/dist/internal/orchestration/prompts/index.js +199 -0
- package/dist/internal/orchestration/report/index.js +125 -0
- package/dist/internal/orchestration/routes/index.js +85 -0
- package/dist/internal/orchestration/run/index.js +221 -0
- package/dist/internal/orchestration/runner/index.js +90 -0
- package/dist/internal/orchestration/runners/claude/index.js +246 -0
- package/dist/internal/orchestration/runners/claude/sdk.js +198 -0
- package/dist/internal/orchestration/runners/claude/wiring.js +98 -0
- package/dist/internal/orchestration/runners/codex/exec.js +114 -0
- package/dist/internal/orchestration/runners/codex/index.js +72 -0
- package/dist/internal/orchestration/runners/conformance/index.js +169 -0
- package/dist/internal/orchestration/runners/cursor/exec.js +531 -0
- package/dist/internal/orchestration/runners/cursor/index.js +184 -0
- package/dist/internal/orchestration/runners/stub/index.js +168 -0
- package/dist/internal/orchestration/runners/telemetry.js +36 -0
- package/dist/internal/orchestration/runstate/index.js +213 -0
- package/dist/internal/orchestration/runstate/publish.js +115 -0
- package/dist/internal/orchestration/worktree/index.js +89 -0
- package/dist/internal/system/adapters/index.js +19 -0
- package/dist/internal/system/agentops/index.js +16 -0
- package/dist/internal/system/agentops/scope.js +67 -0
- package/dist/internal/system/assets.js +45 -0
- package/dist/internal/system/board/cli.js +56 -0
- package/dist/internal/system/board/index.js +276 -0
- package/dist/internal/system/board/live.js +277 -0
- package/dist/internal/system/calibration/cli.js +45 -0
- package/dist/internal/system/calibration/index.js +141 -0
- package/dist/internal/system/cli.js +174 -0
- package/dist/internal/system/corpus/replay.js +79 -0
- package/dist/internal/system/digest/cli.js +23 -0
- package/dist/internal/system/digest/index.js +145 -0
- package/dist/internal/system/doors/classify.js +94 -0
- package/dist/internal/system/doors/cli.js +184 -0
- package/dist/internal/system/doors/index.js +249 -0
- package/dist/internal/system/doors/metrics.js +47 -0
- package/dist/internal/system/escapes/cli.js +45 -0
- package/dist/internal/system/escapes/index.js +81 -0
- package/dist/internal/system/explore/cli.js +77 -0
- package/dist/internal/system/explore/index.js +91 -0
- package/dist/internal/system/gates/cli.js +43 -0
- package/dist/internal/system/gates/detectors.js +55 -0
- package/dist/internal/system/gates/pre.js +51 -0
- package/dist/internal/system/hub/cli.js +51 -0
- package/dist/internal/system/hub/index.js +146 -0
- package/dist/internal/system/init/index.js +83 -0
- package/dist/internal/system/ledger/cli.js +54 -0
- package/dist/internal/system/ledger/index.js +176 -0
- package/dist/internal/system/manifest/index.js +207 -0
- package/dist/internal/system/provenance/index.js +13 -0
- package/dist/internal/system/report/index.js +15 -0
- package/dist/internal/system/runner/index.js +74 -0
- package/dist/internal/system/runstatus/cli.js +86 -0
- package/dist/internal/system/runstatus/index.js +168 -0
- package/dist/internal/system/sequencer/briefing.js +75 -0
- package/dist/internal/system/sequencer/cli.js +170 -0
- package/dist/internal/system/sequencer/index.js +156 -0
- package/dist/internal/system/spec/cli.js +110 -0
- package/dist/internal/system/spec/index.js +212 -0
- package/dist/internal/system/spec/materialize.js +111 -0
- package/dist/internal/system/spec/openspec-parse.js +83 -0
- package/dist/internal/system/spec/project.js +208 -0
- package/dist/internal/system/substrate/index.js +14 -0
- package/dist/internal/system/system/index.js +159 -0
- package/dist/internal/system/units/index.js +87 -0
- package/dist/internal/system/verify/cli.js +103 -0
- package/dist/internal/system/verify/declined-reasons.js +61 -0
- package/dist/internal/system/verify/index.js +188 -0
- package/dist/internal/system/verify/signing.js +79 -0
- package/dist/paths.js +50 -0
- package/dist/routes.js +93 -0
- package/dist/version.js +30 -0
- package/dist/win32-process-group.js +360 -0
- package/package.json +41 -0
- package/profiles/python/bindings.toml +59 -0
- package/profiles/terraform/bindings.toml +66 -0
- package/profiles/typescript/bindings.toml +64 -0
- package/profiles/typescript/smoke/README.md +8 -0
- package/schema/baseline.schema.json +26 -0
- package/schema/decision-classes.yaml +45 -0
- package/schema/envelope.schema.json +186 -0
- package/schema/ledger-event.schema.json +235 -0
- package/schema/manifest.schema.json +119 -0
- package/schema/routes.schema.json +55 -0
- package/schema/run-status.schema.json +162 -0
- package/schema/session-event.schema.json +74 -0
- package/schema/spec.schema.json +114 -0
- package/schema/system.schema.json +142 -0
- package/templates/ci/verify.yml +23 -0
- package/templates/target-kit/README.md +78 -0
- package/templates/target-kit/component.toml +20 -0
- package/templates/target-kit/orchestration.json +9 -0
- package/templates/target-kit/profiles/target/bindings.toml +6 -0
- package/templates/target-kit/routes.toml +5 -0
- package/templates/target-kit/specs/first-unit.yaml +11 -0
- package/templates/target-kit/system.toml +7 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* exec — the ONLY module that shells `codex exec`. Everything else in
|
|
3
|
+
* `src/runners/codex/` is plain glue over the agent-neutral `Runner` contract.
|
|
4
|
+
* If the Codex CLI's flag surface shifts, this file is the blast radius.
|
|
5
|
+
*
|
|
6
|
+
* `codex exec` runs one non-interactive turn. Surface used (verify against the
|
|
7
|
+
* installed `codex` at integration time — do not guess further):
|
|
8
|
+
* codex exec "<prompt>" --model <m> --cd <cwd> --sandbox <mode> --json
|
|
9
|
+
* `--json` streams JSONL events on stdout; the last assistant/message event is
|
|
10
|
+
* the result. Exit 0 = the turn completed.
|
|
11
|
+
*
|
|
12
|
+
* `execImpl` is injectable so the runner's tests never spawn `codex`.
|
|
13
|
+
*/
|
|
14
|
+
import { execFile } from "node:child_process";
|
|
15
|
+
export async function execCodexProcess(bin, args, opts) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
// Stay in the parent's process group / console (execFile is not detached)
|
|
18
|
+
// so the facade's process-group interrupt reaches Codex.
|
|
19
|
+
const child = execFile(bin, args, {
|
|
20
|
+
cwd: opts.cwd,
|
|
21
|
+
env: opts.env,
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
24
|
+
...(opts.timeoutMs !== undefined ? { timeout: opts.timeoutMs } : {}),
|
|
25
|
+
}, (error, stdout, stderr) => resolve(foldProcessExit(error, stdout, stderr, opts.timeoutMs)));
|
|
26
|
+
// `codex exec` treats piped stdin as additional prompt input. execFile
|
|
27
|
+
// otherwise leaves this pipe open, so Codex waits forever before starting.
|
|
28
|
+
child.stdin?.end();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function foldProcessExit(error, stdout, stderr, timeoutMs) {
|
|
32
|
+
if (!error)
|
|
33
|
+
return { code: 0, stdout, stderr };
|
|
34
|
+
const timedOut = timeoutMs !== undefined &&
|
|
35
|
+
(error.killed === true || error.signal != null || error.code === "ETIMEDOUT");
|
|
36
|
+
const timeoutError = timedOut ? `codex exec timed out after ${timeoutMs}ms` : "";
|
|
37
|
+
return {
|
|
38
|
+
code: typeof error.code === "number" ? error.code : 1,
|
|
39
|
+
stdout,
|
|
40
|
+
stderr: [stderr.trim(), timeoutError].filter(Boolean).join("\n"),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function runCodexExec(params) {
|
|
44
|
+
const bin = params.bin ?? "codex";
|
|
45
|
+
const args = [
|
|
46
|
+
"exec",
|
|
47
|
+
params.prompt,
|
|
48
|
+
"--model",
|
|
49
|
+
params.model,
|
|
50
|
+
"--cd",
|
|
51
|
+
params.cwd,
|
|
52
|
+
"--sandbox",
|
|
53
|
+
params.sandbox,
|
|
54
|
+
"--json",
|
|
55
|
+
];
|
|
56
|
+
const exec = params.execImpl
|
|
57
|
+
? params.execImpl
|
|
58
|
+
: execCodexProcess.bind(undefined, bin);
|
|
59
|
+
const { code, stdout, stderr } = await exec(args, {
|
|
60
|
+
cwd: params.cwd,
|
|
61
|
+
env: params.env,
|
|
62
|
+
...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs } : {}),
|
|
63
|
+
});
|
|
64
|
+
return foldCodexStream(stdout, code, stderr);
|
|
65
|
+
}
|
|
66
|
+
/** Parse `codex exec --json` JSONL into a flat result. Tolerant of unknown event shapes. */
|
|
67
|
+
export function foldCodexStream(stdout, code, stderr) {
|
|
68
|
+
let resultText = "";
|
|
69
|
+
let costUsd = 0;
|
|
70
|
+
for (const line of stdout.split("\n")) {
|
|
71
|
+
const t = line.trim();
|
|
72
|
+
if (!t)
|
|
73
|
+
continue;
|
|
74
|
+
let ev;
|
|
75
|
+
try {
|
|
76
|
+
ev = JSON.parse(t);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// last message-ish event wins as the result text.
|
|
82
|
+
const msg = pickString(ev, ["message", "text", "content", "last_message"]) ??
|
|
83
|
+
(ev.type === "message" ? pickString(ev, ["data"]) : undefined) ??
|
|
84
|
+
pickAgentMessage(ev);
|
|
85
|
+
if (msg)
|
|
86
|
+
resultText = msg;
|
|
87
|
+
// token usage → a rough cost, if present.
|
|
88
|
+
const usage = ev.usage;
|
|
89
|
+
if (usage) {
|
|
90
|
+
costUsd +=
|
|
91
|
+
((usage.input_tokens ?? 0) / 1e6) * 3 + ((usage.output_tokens ?? 0) / 1e6) * 15;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return code === 0
|
|
95
|
+
? { ok: true, resultText, costUsd }
|
|
96
|
+
: { ok: false, resultText, costUsd, error: (stderr || stdout).trim() || `codex exec exit ${code}` };
|
|
97
|
+
}
|
|
98
|
+
function pickAgentMessage(ev) {
|
|
99
|
+
if (ev.type !== "item.completed" || !isRecord(ev.item))
|
|
100
|
+
return undefined;
|
|
101
|
+
return ev.item.type === "agent_message"
|
|
102
|
+
? pickString(ev.item, ["text", "message"])
|
|
103
|
+
: undefined;
|
|
104
|
+
}
|
|
105
|
+
function isRecord(value) {
|
|
106
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
107
|
+
}
|
|
108
|
+
function pickString(o, keys) {
|
|
109
|
+
for (const k of keys)
|
|
110
|
+
if (typeof o[k] === "string")
|
|
111
|
+
return o[k];
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=exec.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parseVerdict, renderImplementerPrompt, renderVerifierPrompt, } from "../../prompts/index.js";
|
|
4
|
+
import { runCodexExec } from "./exec.js";
|
|
5
|
+
/** the agent has escalated when its output carries this sentinel (codex has no escalate verb). */
|
|
6
|
+
const ESCALATE_RE = /ESCALATE:\s*(.+?)(?:\s+WANTS:\s*(.+))?$/im;
|
|
7
|
+
export class CodexRunner {
|
|
8
|
+
opts;
|
|
9
|
+
name = "codex";
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
this.opts = opts;
|
|
12
|
+
}
|
|
13
|
+
capabilities() {
|
|
14
|
+
return { scopeHook: false, sessionTelemetry: false };
|
|
15
|
+
}
|
|
16
|
+
async run(briefing, policy, kind, workspace, _onActivity) {
|
|
17
|
+
const isVerifier = kind === "verifier";
|
|
18
|
+
const prompt = isVerifier
|
|
19
|
+
? renderVerifierPrompt(briefing, (await this.opts.diffFor?.(briefing.unitId)) ?? "")
|
|
20
|
+
: renderImplementerPrompt(briefing, this.opts.retryFor?.(briefing.unitId, "implementer"));
|
|
21
|
+
const r = await runCodexExec({
|
|
22
|
+
prompt,
|
|
23
|
+
model: policy.model,
|
|
24
|
+
cwd: workspace?.path ?? this.opts.cwd,
|
|
25
|
+
sandbox: isVerifier ? "read-only" : "workspace-write",
|
|
26
|
+
env: {
|
|
27
|
+
...withCodexHome(this.opts.env ?? process.env),
|
|
28
|
+
HARNESS_BIN: this.opts.harnessBin,
|
|
29
|
+
HARNESS_UNIT_ID: briefing.unitId,
|
|
30
|
+
},
|
|
31
|
+
...(this.opts.deadlineAt !== undefined
|
|
32
|
+
? { timeoutMs: Math.max(1, this.opts.deadlineAt - Date.now()) }
|
|
33
|
+
: {}),
|
|
34
|
+
...(this.opts.bin ? { bin: this.opts.bin } : {}),
|
|
35
|
+
...(this.opts.execImpl ? { execImpl: this.opts.execImpl } : {}),
|
|
36
|
+
});
|
|
37
|
+
if (isVerifier) {
|
|
38
|
+
return {
|
|
39
|
+
ok: r.ok,
|
|
40
|
+
filesTouched: [], // read-only sandbox; the loop's post-hoc diff is the backstop
|
|
41
|
+
cost: r.costUsd,
|
|
42
|
+
transcriptRef: codexTranscriptRef(briefing.unitId, "verifier", r.error),
|
|
43
|
+
verdict: parseVerdict(r.resultText),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const esc = ESCALATE_RE.exec(r.resultText);
|
|
47
|
+
const result = {
|
|
48
|
+
ok: r.ok && !esc,
|
|
49
|
+
// codex exec reports no file list — the loop cross-checks with `git diff`.
|
|
50
|
+
filesTouched: [],
|
|
51
|
+
cost: r.costUsd,
|
|
52
|
+
transcriptRef: codexTranscriptRef(briefing.unitId, "implementer", r.error),
|
|
53
|
+
};
|
|
54
|
+
if (esc) {
|
|
55
|
+
result.escalation = {
|
|
56
|
+
reason: esc[1]?.trim() ?? "codex session escalated",
|
|
57
|
+
wants: esc[2]?.trim() ?? "operator decision",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function codexTranscriptRef(unitId, kind, error) {
|
|
64
|
+
const ref = `codex-exec://${unitId}/${kind}`;
|
|
65
|
+
return error ? `${ref}#error=${encodeURIComponent(error)}` : ref;
|
|
66
|
+
}
|
|
67
|
+
function withCodexHome(env) {
|
|
68
|
+
if (env.CODEX_HOME?.trim())
|
|
69
|
+
return env;
|
|
70
|
+
return { ...env, CODEX_HOME: join(homedir(), ".codex") };
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* conformance — the shared scenario set every `Runner` implementation must
|
|
3
|
+
* satisfy, mirroring agent-harness's adapter conformance suite (AH P2.4). A
|
|
4
|
+
* scenario names a `RunnerBehavior` and asserts the loop outcome it must
|
|
5
|
+
* produce; the runner's identity never appears in the assertions.
|
|
6
|
+
*
|
|
7
|
+
* `checkRunnerConformance(makeRunner, applyBehavior)` runs the scenarios against
|
|
8
|
+
* any runner: `applyBehavior` translates the abstract behaviour into that
|
|
9
|
+
* runner's own scripting (a `FakeRunner`'s `.on()`, a `CodexRunner`'s mock
|
|
10
|
+
* `execImpl`, a replay `queryImpl` for `ClaudeRunner`). The FakeRunner applier
|
|
11
|
+
* is the default.
|
|
12
|
+
*/
|
|
13
|
+
import { FakeHarness } from "../../harness/index.js";
|
|
14
|
+
import { DEFAULT_CONFIG, FakeGit, runLoop, } from "../../loop/index.js";
|
|
15
|
+
import { FakeRouteSource } from "../../routes/index.js";
|
|
16
|
+
import { RunLog } from "../../runstate/index.js";
|
|
17
|
+
import { FakeRunner } from "../../runner/index.js";
|
|
18
|
+
export const SCENARIOS = [
|
|
19
|
+
{
|
|
20
|
+
name: "clean run — implementer ok, verifier approves, unit merges",
|
|
21
|
+
units: [{ id: "a", mergeFacts: { door: "two-way" } }],
|
|
22
|
+
behavior: { kind: "clean" },
|
|
23
|
+
check: (s) => {
|
|
24
|
+
assert(s.outcome === "completed", `outcome ${s.outcome} != completed`);
|
|
25
|
+
assert(s.counts.merged === 1, `merged ${s.counts.merged} != 1`);
|
|
26
|
+
assert(s.counts.inFlight === 0, "a lane was left in flight");
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "gate failure then recovery — one implementer retry, then merge",
|
|
31
|
+
units: [{ id: "a", mergeFacts: { door: "two-way" } }],
|
|
32
|
+
config: { retryBudget: 2 },
|
|
33
|
+
behavior: { kind: "fail-then-ok" },
|
|
34
|
+
check: (s) => {
|
|
35
|
+
assert(s.outcome === "completed", `outcome ${s.outcome}`);
|
|
36
|
+
assert(s.counts.merged === 1, `merged ${s.counts.merged}`);
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "gate failure past budget — halts with retry-exhausted",
|
|
41
|
+
units: [{ id: "a", mergeFacts: { door: "two-way" } }],
|
|
42
|
+
config: { retryBudget: 1 },
|
|
43
|
+
behavior: { kind: "fail-always" },
|
|
44
|
+
check: (s) => {
|
|
45
|
+
assert(s.outcome === "halted", `outcome ${s.outcome}`);
|
|
46
|
+
assert(s.needsYou.some((n) => n.kind === "retry-exhausted"), "no retry-exhausted needs-you entry");
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "escalation — parks the unit, never spends the retry budget",
|
|
51
|
+
units: [{ id: "a" }],
|
|
52
|
+
config: { retryBudget: 3 },
|
|
53
|
+
behavior: { kind: "escalate", reason: "scope too narrow", wants: "widen scope" },
|
|
54
|
+
check: (s) => {
|
|
55
|
+
assert(s.outcome === "halted", `outcome ${s.outcome}`);
|
|
56
|
+
assert(s.counts.escalated === 1, `escalated ${s.counts.escalated}`);
|
|
57
|
+
assert(s.needsYou.some((n) => n.kind === "escalation"), "no escalation needs-you entry");
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "scope violation (degraded tier) — rejected, never a merge candidate",
|
|
62
|
+
units: [{ id: "a", scope: ["src/a/**"] }],
|
|
63
|
+
config: { retryBudget: 0 },
|
|
64
|
+
behavior: {
|
|
65
|
+
kind: "scope-violation",
|
|
66
|
+
filesTouched: ["src/a/ok.ts", "src/other/leak.ts"],
|
|
67
|
+
},
|
|
68
|
+
check: (s) => {
|
|
69
|
+
assert(s.outcome === "halted", `outcome ${s.outcome}`);
|
|
70
|
+
assert(s.counts.merged === 0, "a scope-violating unit merged");
|
|
71
|
+
assert(s.needsYou.some((n) => n.kind === "retry-exhausted"), "scope violation did not exhaust into a halt");
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "dead session — a stranded in-review unit halts the startup sweep",
|
|
76
|
+
units: [{ id: "a", state: "in-review" }],
|
|
77
|
+
behavior: { kind: "none" },
|
|
78
|
+
check: (s) => {
|
|
79
|
+
assert(s.outcome === "halted", `outcome ${s.outcome}`);
|
|
80
|
+
assert(s.needsYou.some((n) => n.kind === "stranded-in-review"), "no stranded-in-review needs-you entry");
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
function assert(cond, msg) {
|
|
85
|
+
if (!cond)
|
|
86
|
+
throw new Error(msg);
|
|
87
|
+
}
|
|
88
|
+
/** The default applier: script a `FakeRunner` from a `RunnerBehavior`. */
|
|
89
|
+
export function applyFakeBehavior(runner, behavior, unitId) {
|
|
90
|
+
switch (behavior.kind) {
|
|
91
|
+
case "clean":
|
|
92
|
+
case "none":
|
|
93
|
+
return;
|
|
94
|
+
case "fail-then-ok":
|
|
95
|
+
runner.on(unitId, "implementer", { kind: "fail" }, 1);
|
|
96
|
+
runner.on(unitId, "implementer", { kind: "ok" }, 2);
|
|
97
|
+
return;
|
|
98
|
+
case "fail-always":
|
|
99
|
+
runner.on(unitId, "implementer", { kind: "fail" });
|
|
100
|
+
return;
|
|
101
|
+
case "escalate":
|
|
102
|
+
runner.on(unitId, "implementer", {
|
|
103
|
+
kind: "escalate",
|
|
104
|
+
reason: behavior.reason,
|
|
105
|
+
wants: behavior.wants,
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
case "scope-violation":
|
|
109
|
+
runner.on(unitId, "implementer", {
|
|
110
|
+
kind: "scope-violation",
|
|
111
|
+
filesTouched: behavior.filesTouched,
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Run every scenario against a runner built by `makeRunner`. `applyBehavior`
|
|
118
|
+
* scripts the runner for each scenario — defaults to the `FakeRunner` applier.
|
|
119
|
+
* For the scope-violation scenario the runner is forced to the degraded tier.
|
|
120
|
+
*/
|
|
121
|
+
export async function checkRunnerConformance(makeRunner, applyBehavior = (r, b, u) => {
|
|
122
|
+
if (r instanceof FakeRunner)
|
|
123
|
+
applyFakeBehavior(r, b, u);
|
|
124
|
+
}) {
|
|
125
|
+
const out = [];
|
|
126
|
+
for (const sc of SCENARIOS) {
|
|
127
|
+
const degraded = sc.behavior.kind === "scope-violation";
|
|
128
|
+
const runner = degraded
|
|
129
|
+
? makeRunner({ scopeHook: false, sessionTelemetry: false })
|
|
130
|
+
: makeRunner();
|
|
131
|
+
const target = sc.target ?? sc.units[0].id;
|
|
132
|
+
await applyBehavior(runner, sc.behavior, target, sc);
|
|
133
|
+
const git = new FakeGit();
|
|
134
|
+
if (sc.behavior.kind === "scope-violation") {
|
|
135
|
+
// the degraded tier's cross-check reads `git diff` — make it see the leak.
|
|
136
|
+
git.setChangedFiles(`unit/${target}`, sc.behavior.filesTouched);
|
|
137
|
+
}
|
|
138
|
+
const deps = {
|
|
139
|
+
harness: new FakeHarness(sc.units),
|
|
140
|
+
routes: new FakeRouteSource({
|
|
141
|
+
runner: runner.name,
|
|
142
|
+
model: "conformance-model",
|
|
143
|
+
mode: "auto",
|
|
144
|
+
}),
|
|
145
|
+
runners: new Map([[runner.name, runner]]),
|
|
146
|
+
git,
|
|
147
|
+
config: { ...DEFAULT_CONFIG, runnersEnabled: [runner.name], ...sc.config },
|
|
148
|
+
log: new RunLog(),
|
|
149
|
+
mintSession: (() => {
|
|
150
|
+
let n = 0;
|
|
151
|
+
return () => `s-${++n}`;
|
|
152
|
+
})(),
|
|
153
|
+
};
|
|
154
|
+
try {
|
|
155
|
+
const state = await runLoop(deps, {
|
|
156
|
+
specRef: "conformance",
|
|
157
|
+
systemId: "conformance",
|
|
158
|
+
dagSize: sc.units.length,
|
|
159
|
+
});
|
|
160
|
+
sc.check(state);
|
|
161
|
+
out.push({ name: sc.name, ok: true });
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
out.push({ name: sc.name, ok: false, error: e.message });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=index.js.map
|