harnery 0.23.0 → 0.25.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 +1 -1
- package/dist/commands/harness.d.ts +2 -1
- package/dist/commands/harness.d.ts.map +1 -1
- package/dist/commands/harness.js +105 -3
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +3 -13
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +2 -7
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +215 -17
- package/dist/core/harnesses/attest.d.ts +48 -0
- package/dist/core/harnesses/attest.d.ts.map +1 -0
- package/dist/core/harnesses/attest.js +110 -0
- package/dist/core/harnesses/attestation.d.ts +75 -0
- package/dist/core/harnesses/attestation.d.ts.map +1 -0
- package/dist/core/harnesses/attestation.js +168 -0
- package/dist/core/harnesses/bench.d.ts +23 -1
- package/dist/core/harnesses/bench.d.ts.map +1 -1
- package/dist/core/harnesses/bench.js +135 -32
- package/dist/core/harnesses/index.d.ts +6 -2
- package/dist/core/harnesses/index.d.ts.map +1 -1
- package/dist/core/harnesses/index.js +3 -1
- package/dist/core/workflow/engine.js +3 -0
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +1 -1
- package/dist/core/workflow/proof.d.ts +4 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +3 -2
- package/dist/core/workflow/spawn-cursor.d.ts +3 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +8 -4
- package/dist/core/workflow/types.d.ts +14 -0
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.d.ts +1 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.js +1 -1
- package/dist/core/workflow/workspaces/integration.d.ts +8 -0
- package/dist/core/workflow/workspaces/integration.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/integration.js +15 -1
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +19 -2
- package/package.json +1 -1
- package/src/commands/harness.ts +123 -2
- package/src/commands/supervisor.ts +11 -15
- package/src/commands/work.ts +8 -8
- package/src/commands/workflow.ts +268 -20
- package/src/core/harnesses/attest.ts +162 -0
- package/src/core/harnesses/attestation.ts +231 -0
- package/src/core/harnesses/bench.ts +187 -32
- package/src/core/harnesses/index.ts +26 -1
- package/src/core/workflow/engine.ts +3 -0
- package/src/core/workflow/index.ts +1 -0
- package/src/core/workflow/proof.ts +7 -1
- package/src/core/workflow/spawn-cursor.ts +8 -4
- package/src/core/workflow/types.ts +15 -0
- package/src/core/workflow/workspaces/index.ts +5 -1
- package/src/core/workflow/workspaces/integration.ts +17 -1
- package/src/core/workflow/workspaces/leases.ts +24 -2
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- **Multi-agent coordination:** per-agent heartbeats in `.harnery/active/`, claim-time and commit-time guards, the canonical event stream, councils, a decision docket, per-agent scratchpads, and harness adapters for Claude Code / Cursor / Codex.
|
|
23
23
|
- **Cross-machine presence:** sessions on every machine that shares a repo see each other, labeled by machine. Zero-config over the repo's own git remote (presence refs), with an optional live-socket upgrade via an end-to-end-encrypted relay — the public `relay.harnery.com`, or self-host with `harn relay serve` / the bundled Cloudflare Worker (`relay/worker/`). The relay sees only ciphertext in opaquely-named rooms.
|
|
24
24
|
- **Coordination-aware workflows:** `harn workflow run <script>` executes bounded, schema-gated multi-subagent workflow scripts — plain JS stages fanning out to headless harness-CLI subagents that are born coordination-registered (heartbeats, events, claim guards), with deterministic code deciding the routing between stages. An optional host-owned policy gates dispatch and declared external mutations with `ALLOW`, `ASK`, and `DENY`; CLI `ASK` requests park durably for an explicit approve/deny and same-run resume, while library callers remain fail-closed by default.
|
|
25
|
-
- **Optional isolated workspaces:** add `--isolation worktree --workspace-root <dir>` to run a workflow in a host-owned local Git worktree. Harnery freezes the allocation and provider identity, verifies the workspace before execution and resume, requires review and policy authority before fast-forward integration, and keeps cleanup conservative. `harn workflow workspace
|
|
25
|
+
- **Optional isolated workspaces:** add `--isolation worktree --workspace-root <dir>` to run a workflow in a host-owned local Git worktree. Harnery freezes the allocation and provider identity, verifies the workspace before execution and resume, requires review and policy authority before fast-forward integration, and keeps cleanup conservative. Inspect with `harn workflow workspace`, then use the separate `workflow integration prepare`, `integration apply`, and `cleanup` phases; shared execution remains the default.
|
|
26
26
|
- **Durable work above attempts:** `harn work` preserves an objective across bounded workflow attempts, dependencies, approval parks, failures, and review. Reusable workflow code receives the exact work ID, title, objective, acceptance criteria, and attempt identity as frozen typed context. A retry also receives the prior run's bounded error and unresolved proof criteria, while retry authority and attempt ceilings remain unchanged. The manifest, proof, and append-only ledger bind the same context, and the one-shot reconciler reports the next explicit action without silently spawning, retrying, or accepting work.
|
|
27
27
|
- **Bounded goal supervision:** `harn supervisor` freezes a mission or existing root graph, specialist team, and safety limits, then runs scheduling cycles until reviewed mission success, human attention, no progress, or budget exhaustion. Its optional planner can create the first milestone, reassess accepted milestone proof, or recover a terminal graph using only a frozen workflow-template catalog. Plans can require independent bounded specialist review before ordinary approval or auto-apply authority, old generations remain immutable evidence, and all attempts still consume the goal budget. Use explicit foreground ticks or enroll goals in the optional heartbeat-backed background service.
|
|
28
28
|
- **Standalone web UI:** `harn web up` boots a local Next.js dashboard for the coord layer, councils, per-project state, and a local coding-agent status view (Claude Code / Codex / Cursor usage, backed by `harn devtools`). Ships with the git clone, not the npm package (see [Install](#install)).
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import type { EmitContext } from "../commander.js";
|
|
3
|
-
import { type BenchResult, type HarnessBenchReport } from "../core/harnesses/index.js";
|
|
3
|
+
import { type BenchResult, type HarnessAttestationReport, type HarnessBenchReport } from "../core/harnesses/index.js";
|
|
4
4
|
import type { HarnessProfile } from "../core/harnesses/types.js";
|
|
5
5
|
export declare function registerHarnessCommand(program: Command, emit: EmitContext): void;
|
|
6
|
+
export declare function renderAttestationReport(report: HarnessAttestationReport): string;
|
|
6
7
|
export declare function renderProfileTable(profiles: readonly HarnessProfile[]): string;
|
|
7
8
|
export declare function renderProfile(profile: HarnessProfile): string;
|
|
8
9
|
export declare function renderBenchReport(report: HarnessBenchReport): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/commands/harness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,kBAAkB,
|
|
1
|
+
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/commands/harness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EAIxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAiBjE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA6IhF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM,CAgBhF;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAU9E;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAkB7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CA6BpE;AAgBD,mFAAmF;AACnF,YAAY,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/commands/harness.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createBuiltinHarnessRegistry, runHarnessBench, } from "../core/harnesses/index.js";
|
|
1
|
+
import { createBuiltinHarnessRegistry, listAttestations, runHarnessAttestation, runHarnessBench, } from "../core/harnesses/index.js";
|
|
2
2
|
const registry = createBuiltinHarnessRegistry();
|
|
3
3
|
export function registerHarnessCommand(program, emit) {
|
|
4
4
|
const command = program
|
|
@@ -62,6 +62,96 @@ export function registerHarnessCommand(program, emit) {
|
|
|
62
62
|
emit.setExitCode(1);
|
|
63
63
|
}
|
|
64
64
|
});
|
|
65
|
+
command
|
|
66
|
+
.command("attest [harnesses...]")
|
|
67
|
+
.description("Record what the installed vendor CLIs actually do. Runs one real model turn each; needs --yes.")
|
|
68
|
+
.option("--yes", "Confirm that this spends real vendor tokens")
|
|
69
|
+
.option("--timeout <ms>", "Per-harness probe timeout in milliseconds")
|
|
70
|
+
.option("--json", "Machine-readable attestation report")
|
|
71
|
+
.action(async (harnesses, opts) => {
|
|
72
|
+
if (!opts.yes) {
|
|
73
|
+
emit.error({
|
|
74
|
+
code: "harness_attest_unconfirmed",
|
|
75
|
+
message: "harness attest runs one real model turn per harness and spends vendor tokens. Re-run with --yes.",
|
|
76
|
+
});
|
|
77
|
+
emit.setExitCode(1);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const timeoutMs = opts.timeout ? Number(opts.timeout) : undefined;
|
|
81
|
+
if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
|
|
82
|
+
emit.error({
|
|
83
|
+
code: "harness_attest_bad_timeout",
|
|
84
|
+
message: "--timeout must be a positive number",
|
|
85
|
+
});
|
|
86
|
+
emit.setExitCode(1);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const report = await runHarnessAttestation(registry, { harnesses, timeoutMs });
|
|
91
|
+
if (opts.json) {
|
|
92
|
+
emit.config({ format: "json" });
|
|
93
|
+
emit.data(report);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
emit.text(renderAttestationReport(report));
|
|
97
|
+
}
|
|
98
|
+
// A sweep that recorded nothing at all is a failure; a partial sweep is
|
|
99
|
+
// reported honestly and still exits 0, because a host is not required
|
|
100
|
+
// to install every vendor CLI.
|
|
101
|
+
emit.setExitCode(report.recorded === 0 ? 1 : 0);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
emit.error({ code: "harness_attest_failed", message: error.message });
|
|
105
|
+
emit.setExitCode(1);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
command
|
|
109
|
+
.command("attestations")
|
|
110
|
+
.description("Show the recorded live attestations without running any model turns.")
|
|
111
|
+
.option("--json", "Machine-readable attestation list")
|
|
112
|
+
.action((opts) => {
|
|
113
|
+
try {
|
|
114
|
+
const records = listAttestations();
|
|
115
|
+
if (opts.json) {
|
|
116
|
+
emit.config({ format: "json" });
|
|
117
|
+
emit.data({ attestations: records });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (records.length === 0) {
|
|
121
|
+
emit.text("No attestations recorded. Run `harness attest --yes` to create one.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
emit.text(renderTable(["HARNESS", "VERSION", "OBSERVED AT", "OBSERVATIONS"], records.map((record) => [
|
|
125
|
+
record.harness,
|
|
126
|
+
record.binary_version,
|
|
127
|
+
record.observed_at,
|
|
128
|
+
Object.entries(record.observations)
|
|
129
|
+
.map(([dimension, support]) => `${dimension}=${support}`)
|
|
130
|
+
.join(" "),
|
|
131
|
+
])));
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
emit.error({ code: "harness_attestations_failed", message: error.message });
|
|
135
|
+
emit.setExitCode(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
export function renderAttestationReport(report) {
|
|
140
|
+
const rows = report.results.map((result) => [
|
|
141
|
+
result.harness,
|
|
142
|
+
result.outcome,
|
|
143
|
+
result.binaryVersion ?? "",
|
|
144
|
+
result.observations
|
|
145
|
+
? Object.entries(result.observations)
|
|
146
|
+
.map(([dimension, support]) => `${dimension}=${support}`)
|
|
147
|
+
.join(" ")
|
|
148
|
+
: result.note,
|
|
149
|
+
]);
|
|
150
|
+
const table = renderTable(["HARNESS", "OUTCOME", "VERSION", "OBSERVED"], rows);
|
|
151
|
+
const tail = report.incomplete
|
|
152
|
+
? "Some harnesses were not attested; their bench rows keep an adapter basis."
|
|
153
|
+
: "Every selected harness was attested.";
|
|
154
|
+
return `${table}\n\nrecorded: ${report.recorded}/${report.results.length}\n${tail}`;
|
|
65
155
|
}
|
|
66
156
|
export function renderProfileTable(profiles) {
|
|
67
157
|
const rows = profiles.map((profile) => [
|
|
@@ -100,13 +190,25 @@ export function renderBenchReport(report) {
|
|
|
100
190
|
result.declared,
|
|
101
191
|
result.observed,
|
|
102
192
|
result.verdict,
|
|
193
|
+
result.basis,
|
|
103
194
|
]);
|
|
104
|
-
const table = renderTable(["HARNESS", "DIMENSION", "DECLARED", "OBSERVED", "VERDICT"], rows);
|
|
195
|
+
const table = renderTable(["HARNESS", "DIMENSION", "DECLARED", "OBSERVED", "VERDICT", "BASIS"], rows);
|
|
105
196
|
const summary = Object.entries(report.summary)
|
|
106
197
|
.filter(([, count]) => count > 0)
|
|
107
198
|
.map(([verdict, count]) => `${verdict}=${count}`)
|
|
108
199
|
.join(", ");
|
|
109
|
-
|
|
200
|
+
const basis = Object.entries(report.basisSummary)
|
|
201
|
+
.filter(([, count]) => count > 0)
|
|
202
|
+
.map(([name, count]) => `${name}=${count}`)
|
|
203
|
+
.join(", ");
|
|
204
|
+
return [
|
|
205
|
+
table,
|
|
206
|
+
"",
|
|
207
|
+
`mode: ${report.mode} (no model calls)`,
|
|
208
|
+
summary,
|
|
209
|
+
`basis: ${basis}`,
|
|
210
|
+
"adapter = checked against Harnery's planner/normalizer/fixture, not the installed CLI",
|
|
211
|
+
].join("\n");
|
|
110
212
|
}
|
|
111
213
|
function renderTable(headers, rows) {
|
|
112
214
|
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0)));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../src/commands/supervisor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../src/commands/supervisor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA0EnD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAgGnF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
3
|
import { workflowSubscriptionOnly } from "../core/config.js";
|
|
4
|
-
import { createBuiltinHarnessRegistry } from "../core/harnesses/index.js";
|
|
4
|
+
import { createBuiltinHarnessRegistry, harnessProofInputs, probeBinaryVersion, } from "../core/harnesses/index.js";
|
|
5
5
|
import { findCoordRoot } from "../core/hooks/resolve/coord-root.js";
|
|
6
6
|
import { loadPolicyFile } from "../core/policy/index.js";
|
|
7
7
|
import { approveSupervisorPlan, configureSupervisorService, createSupervisor, listSupervisors, readSupervisor, readSupervisorPlan, readSupervisorServiceConfig, readSupervisorServiceStatus, rejectSupervisorPlan, requestSupervisorServiceStop, retrySupervisorPlan, runSupervisor, runSupervisorServiceDaemon, spawnSupervisorService, supervisorServiceLogPath, } from "../core/supervisor/index.js";
|
|
@@ -332,12 +332,7 @@ function serviceEngine(config, registry) {
|
|
|
332
332
|
cwd: config.engine.cwd,
|
|
333
333
|
subscriptionOnly: config.engine.subscription_only,
|
|
334
334
|
allowApiBilling: config.engine.allow_api_billing,
|
|
335
|
-
|
|
336
|
-
.list()
|
|
337
|
-
.map((adapter) => [
|
|
338
|
-
adapter.profile.id,
|
|
339
|
-
{ toolEvidence: adapter.profile.capabilities.toolEvidence },
|
|
340
|
-
])),
|
|
335
|
+
...harnessProofInputs(registry.list().map((adapter) => adapter.profile), { versionProbe: probeBinaryVersion }),
|
|
341
336
|
policy: config.engine.policy,
|
|
342
337
|
approvalMode: "park",
|
|
343
338
|
approvalAddressee: config.engine.approval_addressee,
|
|
@@ -413,12 +408,7 @@ function registerRunCommand(supervisor, mode, registry, emit) {
|
|
|
413
408
|
cwd: opts.cwd,
|
|
414
409
|
subscriptionOnly: opts.subscriptionOnly === true ? true : workflowSubscriptionOnly(coordRoot),
|
|
415
410
|
allowApiBilling: opts.allowApiBilling,
|
|
416
|
-
|
|
417
|
-
.list()
|
|
418
|
-
.map((adapter) => [
|
|
419
|
-
adapter.profile.id,
|
|
420
|
-
{ toolEvidence: adapter.profile.capabilities.toolEvidence },
|
|
421
|
-
])),
|
|
411
|
+
...harnessProofInputs(registry.list().map((adapter) => adapter.profile), { versionProbe: probeBinaryVersion }),
|
|
422
412
|
policy: opts.policy ? loadPolicyFile(opts.policy) : undefined,
|
|
423
413
|
approvalMode: "park",
|
|
424
414
|
approvalAddressee: opts.approvalTo,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/commands/work.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAuDnD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA8F7E"}
|
package/dist/commands/work.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { workflowSubscriptionOnly } from "../core/config.js";
|
|
2
|
-
import { createBuiltinHarnessRegistry } from "../core/harnesses/index.js";
|
|
2
|
+
import { createBuiltinHarnessRegistry, harnessProofInputs, probeBinaryVersion, } from "../core/harnesses/index.js";
|
|
3
3
|
import { findCoordRoot } from "../core/hooks/resolve/coord-root.js";
|
|
4
4
|
import { loadPolicyFile } from "../core/policy/index.js";
|
|
5
5
|
import { acceptWorkItem, cancelWorkItem, createWorkItem, listWorkItems, readWorkItem, reconcileAllWorkItems, reconcileWorkItem, reopenWorkItem, runWorkItem, } from "../core/work/index.js";
|
|
@@ -132,12 +132,7 @@ function registerRunCommand(work, name, retry, registry, harnesses, emit) {
|
|
|
132
132
|
cwd: opts.cwd,
|
|
133
133
|
subscriptionOnly: opts.subscriptionOnly === true ? true : workflowSubscriptionOnly(coordRoot),
|
|
134
134
|
allowApiBilling: opts.allowApiBilling,
|
|
135
|
-
|
|
136
|
-
.list()
|
|
137
|
-
.map((adapter) => [
|
|
138
|
-
adapter.profile.id,
|
|
139
|
-
{ toolEvidence: adapter.profile.capabilities.toolEvidence },
|
|
140
|
-
])),
|
|
135
|
+
...harnessProofInputs(registry.list().map((adapter) => adapter.profile), { versionProbe: probeBinaryVersion }),
|
|
141
136
|
policy: opts.policy ? loadPolicyFile(opts.policy) : undefined,
|
|
142
137
|
approvalMode: "park",
|
|
143
138
|
approvalAddressee: opts.approvalTo,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAqEnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA6pBjF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { resolveBinName, workflowSubscriptionOnly } from "../core/config.js";
|
|
4
|
+
import { createBuiltinHarnessRegistry, harnessProofInputs, probeBinaryVersion, } from "../core/harnesses/index.js";
|
|
4
5
|
import { findCoordRoot } from "../core/hooks/resolve/coord-root.js";
|
|
5
6
|
import { loadPolicyFile } from "../core/policy/index.js";
|
|
6
7
|
import { WorkflowParkedError } from "../core/workflow/engine.js";
|
|
@@ -87,12 +88,7 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
87
88
|
maxAgents: opts.maxAgents ? Number.parseInt(opts.maxAgents, 10) : undefined,
|
|
88
89
|
concurrency: opts.concurrency ? Number.parseInt(opts.concurrency, 10) : undefined,
|
|
89
90
|
cwd: opts.cwd,
|
|
90
|
-
|
|
91
|
-
.list()
|
|
92
|
-
.map((adapter) => [
|
|
93
|
-
adapter.profile.id,
|
|
94
|
-
{ toolEvidence: adapter.profile.capabilities.toolEvidence },
|
|
95
|
-
])),
|
|
91
|
+
...harnessProofInputs(registry.list().map((adapter) => adapter.profile), { versionProbe: probeBinaryVersion }),
|
|
96
92
|
policy: opts.policy ? loadPolicyFile(opts.policy) : undefined,
|
|
97
93
|
approvalMode: "park",
|
|
98
94
|
approvalAddressee: opts.approvalTo,
|
|
@@ -134,7 +130,7 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
134
130
|
else {
|
|
135
131
|
emit.text(`run ${err.runId} parked\napproval: ${err.approvalId}\n` +
|
|
136
132
|
`journal: ${err.journalPath}\n` +
|
|
137
|
-
`resume after resolution:
|
|
133
|
+
`resume after resolution: ${resolveBinName()} workflow resume ${err.runId}\n`);
|
|
138
134
|
}
|
|
139
135
|
return;
|
|
140
136
|
}
|
|
@@ -178,12 +174,7 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
178
174
|
writableRoots: [binding.writable_root.configured],
|
|
179
175
|
}
|
|
180
176
|
: undefined,
|
|
181
|
-
|
|
182
|
-
.list()
|
|
183
|
-
.map((adapter) => [
|
|
184
|
-
adapter.profile.id,
|
|
185
|
-
{ toolEvidence: adapter.profile.capabilities.toolEvidence },
|
|
186
|
-
])),
|
|
177
|
+
...harnessProofInputs(registry.list().map((adapter) => adapter.profile), { versionProbe: probeBinaryVersion }),
|
|
187
178
|
});
|
|
188
179
|
if (opts.json) {
|
|
189
180
|
emit.config({ format: "json" });
|
|
@@ -288,6 +279,186 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
288
279
|
: `${inspection.run_id}\tinvalid\t${inspection.error}`)
|
|
289
280
|
.join("\n")}\n`);
|
|
290
281
|
});
|
|
282
|
+
const integration = workflow
|
|
283
|
+
.command("integration")
|
|
284
|
+
.description("Prepare and apply proof-gated isolated-workspace integration.");
|
|
285
|
+
integration
|
|
286
|
+
.command("prepare <run-id>")
|
|
287
|
+
.description("Preview integration and write exact review and policy authority.")
|
|
288
|
+
.option("--policy <file>", "Current host policy JSON/JSONC for the Git mutation")
|
|
289
|
+
.option("--reviewer <name>", "Explicit reviewer for a standalone workflow")
|
|
290
|
+
.option("--reason <text>", "Reason recorded with the standalone review")
|
|
291
|
+
.option("--target-root <dir>", "Explicit target checkout (default: frozen source checkout)")
|
|
292
|
+
.option("--accept-unknown <code>", "Accept one verification unknown by code (repeatable)", collectOption, [])
|
|
293
|
+
.option("--approval-to <address>", "Address a durable policy ASK request")
|
|
294
|
+
.option("--json", "Emit the durable integration plan as JSON")
|
|
295
|
+
.action(async (runId, opts) => {
|
|
296
|
+
const coordRoot = findCoordRoot();
|
|
297
|
+
if (!coordRoot) {
|
|
298
|
+
emit.error({
|
|
299
|
+
code: "no_coord_root",
|
|
300
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
301
|
+
});
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
if (!opts.policy) {
|
|
305
|
+
emit.error({
|
|
306
|
+
code: "integration_policy_required",
|
|
307
|
+
message: "integration prepare requires --policy <file>",
|
|
308
|
+
});
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
if (opts.reason && !opts.reviewer) {
|
|
312
|
+
emit.error({
|
|
313
|
+
code: "integration_reviewer_required",
|
|
314
|
+
message: "--reason requires --reviewer <name>",
|
|
315
|
+
});
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const provider = await builtInProviderForRun(coordRoot, runId);
|
|
320
|
+
const { IntegrationPrepareParkedError, prepareIntegration } = await import("../core/workflow/index.js");
|
|
321
|
+
try {
|
|
322
|
+
const plan = await prepareIntegration({
|
|
323
|
+
coordRoot,
|
|
324
|
+
runId,
|
|
325
|
+
provider,
|
|
326
|
+
policy: loadPolicyFile(opts.policy),
|
|
327
|
+
targetRoot: opts.targetRoot,
|
|
328
|
+
review: opts.reviewer ? { actor: opts.reviewer, reason: opts.reason } : undefined,
|
|
329
|
+
acceptedUnknowns: opts.acceptUnknown,
|
|
330
|
+
approvalAddressee: opts.approvalTo,
|
|
331
|
+
});
|
|
332
|
+
if (opts.json) {
|
|
333
|
+
emit.config({ format: "json" });
|
|
334
|
+
emit.data(plan);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
emit.text(`integration plan ${plan.plan_id} authorized\n` +
|
|
338
|
+
`source: ${plan.provider_preview.source_commit}\n` +
|
|
339
|
+
`target: ${plan.provider_preview.target_ref} at ${plan.provider_preview.target_commit}\n` +
|
|
340
|
+
`changes: ${plan.provider_preview.changed_paths.length}\n` +
|
|
341
|
+
`apply: ${resolveBinName()} workflow integration apply ${runId} --yes\n`);
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
if (error instanceof IntegrationPrepareParkedError) {
|
|
345
|
+
if (opts.json) {
|
|
346
|
+
emit.config({ format: "json" });
|
|
347
|
+
emit.data({
|
|
348
|
+
status: "parked",
|
|
349
|
+
runId: error.runId,
|
|
350
|
+
planId: error.planId,
|
|
351
|
+
approvalId: error.approvalId,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
emit.text(`integration preparation parked\n` +
|
|
356
|
+
`run: ${error.runId}\n` +
|
|
357
|
+
`plan: ${error.planId}\n` +
|
|
358
|
+
`approval: ${error.approvalId}\n` +
|
|
359
|
+
`approve: ${resolveBinName()} workflow approvals approve ${error.approvalId}\n`);
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
emit.error({
|
|
368
|
+
code: "workflow_integration_prepare_failed",
|
|
369
|
+
message: error instanceof Error ? error.message : String(error),
|
|
370
|
+
});
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
integration
|
|
375
|
+
.command("apply <run-id>")
|
|
376
|
+
.description("Apply an authorized durable integration plan.")
|
|
377
|
+
.option("--yes", "Confirm the target-branch mutation")
|
|
378
|
+
.option("--json", "Emit the durable integration receipt as JSON")
|
|
379
|
+
.action(async (runId, opts) => {
|
|
380
|
+
const coordRoot = findCoordRoot();
|
|
381
|
+
if (!coordRoot) {
|
|
382
|
+
emit.error({
|
|
383
|
+
code: "no_coord_root",
|
|
384
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
385
|
+
});
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
if (!opts.yes) {
|
|
389
|
+
emit.error({
|
|
390
|
+
code: "integration_confirmation_required",
|
|
391
|
+
message: "integration apply changes the target branch; pass --yes to confirm",
|
|
392
|
+
});
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const provider = await builtInProviderForRun(coordRoot, runId);
|
|
397
|
+
const { applyIntegration } = await import("../core/workflow/index.js");
|
|
398
|
+
const receipt = await applyIntegration({ coordRoot, runId, provider });
|
|
399
|
+
if (opts.json) {
|
|
400
|
+
emit.config({ format: "json" });
|
|
401
|
+
emit.data(receipt);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
emit.text(`integration ${receipt.status}: ${receipt.target_ref} at ${receipt.target_commit}\n` +
|
|
405
|
+
`receipt: ${receipt.receipt_id}\n` +
|
|
406
|
+
`cleanup: ${resolveBinName()} workflow cleanup ${runId} --yes\n`);
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
emit.error({
|
|
410
|
+
code: "workflow_integration_apply_failed",
|
|
411
|
+
message: error instanceof Error ? error.message : String(error),
|
|
412
|
+
});
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
workflow
|
|
417
|
+
.command("cleanup <run-id>")
|
|
418
|
+
.description("Conservatively release a provider-owned isolated workspace.")
|
|
419
|
+
.option("--yes", "Confirm the worktree and provider-branch removal attempt")
|
|
420
|
+
.option("--json", "Emit the cleanup attempt or receipt as JSON")
|
|
421
|
+
.action(async (runId, opts) => {
|
|
422
|
+
const coordRoot = findCoordRoot();
|
|
423
|
+
if (!coordRoot) {
|
|
424
|
+
emit.error({
|
|
425
|
+
code: "no_coord_root",
|
|
426
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
427
|
+
});
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
if (!opts.yes) {
|
|
431
|
+
emit.error({
|
|
432
|
+
code: "cleanup_confirmation_required",
|
|
433
|
+
message: "workspace cleanup may remove a worktree and branch; pass --yes to confirm",
|
|
434
|
+
});
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
const provider = await builtInProviderForRun(coordRoot, runId);
|
|
439
|
+
const { cleanupWorkspace } = await import("../core/workflow/index.js");
|
|
440
|
+
const result = await cleanupWorkspace({ coordRoot, runId, provider });
|
|
441
|
+
if (opts.json) {
|
|
442
|
+
emit.config({ format: "json" });
|
|
443
|
+
emit.data(result);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const receipt = "receipt_id" in result && typeof result.receipt_id === "string"
|
|
447
|
+
? `\nreceipt: ${result.receipt_id}`
|
|
448
|
+
: "";
|
|
449
|
+
const reason = "reason" in result && typeof result.reason === "string"
|
|
450
|
+
? `\nreason: ${result.reason}`
|
|
451
|
+
: "";
|
|
452
|
+
emit.text(`workspace cleanup ${result.status}: ${result.binding_id}${receipt}${reason}\n`);
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
emit.error({
|
|
456
|
+
code: "workflow_cleanup_failed",
|
|
457
|
+
message: error instanceof Error ? error.message : String(error),
|
|
458
|
+
});
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
291
462
|
const approvals = workflow
|
|
292
463
|
.command("approvals")
|
|
293
464
|
.description("Inspect and resolve durable workflow policy approvals.");
|
|
@@ -379,7 +550,7 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
379
550
|
else {
|
|
380
551
|
emit.text(`${renderWorkflowApproval(resolved.approval)}` +
|
|
381
552
|
`${resolved.applied ? "decision recorded" : "decision already recorded"}\n` +
|
|
382
|
-
|
|
553
|
+
approvalNextActionHint(coordRoot, resolved.approval));
|
|
383
554
|
}
|
|
384
555
|
}
|
|
385
556
|
catch (err) {
|
|
@@ -422,3 +593,30 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
422
593
|
}
|
|
423
594
|
});
|
|
424
595
|
}
|
|
596
|
+
async function builtInProviderForRun(coordRoot, runId) {
|
|
597
|
+
const { createLocalGitWorktreeProvider, readWorkflowRunManifest } = await import("../core/workflow/index.js");
|
|
598
|
+
const manifest = readWorkflowRunManifest(coordRoot, runId);
|
|
599
|
+
const binding = manifest.execution.workspace_binding;
|
|
600
|
+
if (!binding) {
|
|
601
|
+
throw new Error(`workflow run ${runId} has no isolated workspace binding`);
|
|
602
|
+
}
|
|
603
|
+
if (binding.provider.id !== "local-git-worktree") {
|
|
604
|
+
throw new Error(`workflow run ${runId} requires unavailable workspace provider ${binding.provider.id}`);
|
|
605
|
+
}
|
|
606
|
+
return createLocalGitWorktreeProvider({ coordRoot });
|
|
607
|
+
}
|
|
608
|
+
function collectOption(value, previous) {
|
|
609
|
+
return [...previous, value];
|
|
610
|
+
}
|
|
611
|
+
/** Next operator command after resolving an approval. Mid-run parks resume the
|
|
612
|
+
* workflow; integration prepare parks after the run is already terminal, so
|
|
613
|
+
* the durable plan is authorized by re-running prepare (resume would fail). */
|
|
614
|
+
function approvalNextActionHint(coordRoot, approval) {
|
|
615
|
+
const bin = resolveBinName();
|
|
616
|
+
const runId = approval.request.run_id;
|
|
617
|
+
const integrationPlanPath = join(coordRoot, ".harnery", "workflows", runId, "integration", "plan.json");
|
|
618
|
+
if (existsSync(integrationPlanPath)) {
|
|
619
|
+
return `prepare: ${bin} workflow integration prepare ${runId}\n`;
|
|
620
|
+
}
|
|
621
|
+
return `resume: ${bin} workflow resume ${runId}\n`;
|
|
622
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The opt-in live probe that produces an attestation (ADR 0038).
|
|
3
|
+
*
|
|
4
|
+
* One bounded turn per harness, through the same `spawn` the workflow engine
|
|
5
|
+
* uses, so what gets attested is the path production takes rather than a
|
|
6
|
+
* parallel test rig.
|
|
7
|
+
*/
|
|
8
|
+
import type { SpawnResult } from "../workflow/types.js";
|
|
9
|
+
import type { AttestableDimension, HarnessAttestation } from "./attestation.js";
|
|
10
|
+
import type { HarnessRegistry } from "./registry.js";
|
|
11
|
+
import type { CapabilitySupport, HarnessId } from "./types.js";
|
|
12
|
+
/** Fixed and content-free, so nothing user-supplied can reach the record and
|
|
13
|
+
* the turn stays as cheap as a turn can be. */
|
|
14
|
+
export declare const ATTESTATION_PROMPT = "Reply with the single word: ok";
|
|
15
|
+
export declare const DEFAULT_ATTESTATION_TIMEOUT_MS = 120000;
|
|
16
|
+
export type AttestationOutcome = "recorded" | "skipped" | "unreachable" | "failed";
|
|
17
|
+
export interface HarnessAttestationResult {
|
|
18
|
+
harness: HarnessId;
|
|
19
|
+
outcome: AttestationOutcome;
|
|
20
|
+
binaryVersion?: string;
|
|
21
|
+
observations?: Partial<Record<AttestableDimension, CapabilitySupport>>;
|
|
22
|
+
durationMs?: number;
|
|
23
|
+
note: string;
|
|
24
|
+
}
|
|
25
|
+
export interface HarnessAttestationReport {
|
|
26
|
+
generatedAt: string;
|
|
27
|
+
harnesses: HarnessId[];
|
|
28
|
+
results: HarnessAttestationResult[];
|
|
29
|
+
recorded: number;
|
|
30
|
+
/** True when at least one selected harness could not be attested, so a
|
|
31
|
+
* caller can tell a partial sweep from a complete one. */
|
|
32
|
+
incomplete: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface RunHarnessAttestationOptions {
|
|
35
|
+
harnesses?: readonly string[];
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
cwd?: string;
|
|
38
|
+
coordRoot?: string;
|
|
39
|
+
/** Test seam and alternate host probe. A null version means unavailable. */
|
|
40
|
+
versionProbe?: (binary: string) => string | null;
|
|
41
|
+
/** Test seam. Defaults to the adapter's production spawner. */
|
|
42
|
+
spawn?: (harness: HarnessId, prompt: string, timeoutMs: number) => Promise<SpawnResult>;
|
|
43
|
+
/** Test seam. Defaults to writing under the coord root. */
|
|
44
|
+
persist?: (record: HarnessAttestation) => void;
|
|
45
|
+
now?: () => Date;
|
|
46
|
+
}
|
|
47
|
+
export declare function runHarnessAttestation(registry: HarnessRegistry, opts?: RunHarnessAttestationOptions): Promise<HarnessAttestationReport>;
|
|
48
|
+
//# sourceMappingURL=attest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attest.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/attest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAGhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE/D;+CAC+C;AAC/C,eAAO,MAAM,kBAAkB,mCAAmC,CAAC;AAEnE,eAAO,MAAM,8BAA8B,SAAU,CAAC;AAetD,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,QAAQ,CAAC;AAEnF,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,SAAS,CAAC;IACnB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB;8DAC0D;IAC1D,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD,+DAA+D;IAC/D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACxF,2DAA2D;IAC3D,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC/C,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,eAAe,EACzB,IAAI,GAAE,4BAAiC,GACtC,OAAO,CAAC,wBAAwB,CAAC,CAyFnC"}
|