harnery 0.24.0 → 0.26.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.
Files changed (55) hide show
  1. package/dist/commands/harness.d.ts +2 -1
  2. package/dist/commands/harness.d.ts.map +1 -1
  3. package/dist/commands/harness.js +113 -3
  4. package/dist/commands/supervisor.d.ts.map +1 -1
  5. package/dist/commands/supervisor.js +3 -13
  6. package/dist/commands/work.d.ts.map +1 -1
  7. package/dist/commands/work.js +2 -7
  8. package/dist/commands/workflow.d.ts.map +1 -1
  9. package/dist/commands/workflow.js +3 -13
  10. package/dist/core/harnesses/attest.d.ts +53 -0
  11. package/dist/core/harnesses/attest.d.ts.map +1 -0
  12. package/dist/core/harnesses/attest.js +120 -0
  13. package/dist/core/harnesses/attestation.d.ts +82 -0
  14. package/dist/core/harnesses/attestation.d.ts.map +1 -0
  15. package/dist/core/harnesses/attestation.js +171 -0
  16. package/dist/core/harnesses/bench.d.ts +26 -1
  17. package/dist/core/harnesses/bench.d.ts.map +1 -1
  18. package/dist/core/harnesses/bench.js +137 -32
  19. package/dist/core/harnesses/index.d.ts +6 -2
  20. package/dist/core/harnesses/index.d.ts.map +1 -1
  21. package/dist/core/harnesses/index.js +3 -1
  22. package/dist/core/harnesses/profiles.d.ts +6 -6
  23. package/dist/core/harnesses/profiles.js +3 -3
  24. package/dist/core/workflow/engine.js +3 -0
  25. package/dist/core/workflow/proof.d.ts +4 -1
  26. package/dist/core/workflow/proof.d.ts.map +1 -1
  27. package/dist/core/workflow/proof.js +3 -2
  28. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  29. package/dist/core/workflow/spawn-claude.js +2 -1
  30. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  31. package/dist/core/workflow/spawn-codex.js +2 -1
  32. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  33. package/dist/core/workflow/spawn-cursor.js +2 -1
  34. package/dist/core/workflow/spawn-failure.d.ts +18 -0
  35. package/dist/core/workflow/spawn-failure.d.ts.map +1 -0
  36. package/dist/core/workflow/spawn-failure.js +24 -0
  37. package/dist/core/workflow/types.d.ts +14 -0
  38. package/dist/core/workflow/types.d.ts.map +1 -1
  39. package/package.json +4 -2
  40. package/src/commands/harness.ts +135 -2
  41. package/src/commands/supervisor.ts +11 -15
  42. package/src/commands/work.ts +8 -8
  43. package/src/commands/workflow.ts +11 -15
  44. package/src/core/harnesses/attest.ts +181 -0
  45. package/src/core/harnesses/attestation.ts +249 -0
  46. package/src/core/harnesses/bench.ts +192 -32
  47. package/src/core/harnesses/index.ts +26 -1
  48. package/src/core/harnesses/profiles.ts +3 -3
  49. package/src/core/workflow/engine.ts +3 -0
  50. package/src/core/workflow/proof.ts +7 -1
  51. package/src/core/workflow/spawn-claude.ts +2 -1
  52. package/src/core/workflow/spawn-codex.ts +2 -1
  53. package/src/core/workflow/spawn-cursor.ts +2 -1
  54. package/src/core/workflow/spawn-failure.ts +28 -0
  55. package/src/core/workflow/types.ts +15 -0
@@ -0,0 +1,181 @@
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
+
9
+ import type { SpawnResult } from "../workflow/types.ts";
10
+ import type { AttestableDimension, HarnessAttestation } from "./attestation.ts";
11
+ import {
12
+ ATTESTATION_SCHEMA_VERSION,
13
+ profileDigest,
14
+ sealAttestation,
15
+ writeAttestation,
16
+ } from "./attestation.ts";
17
+ import { probeBinaryVersion } from "./bench.ts";
18
+ import type { HarnessRegistry } from "./registry.ts";
19
+ import type { CapabilitySupport, HarnessId } from "./types.ts";
20
+
21
+ /** Fixed and content-free, so nothing user-supplied can reach the record and
22
+ * the turn stays as cheap as a turn can be. */
23
+ export const ATTESTATION_PROMPT = "Reply with the single word: ok";
24
+
25
+ export const DEFAULT_ATTESTATION_TIMEOUT_MS = 120_000;
26
+
27
+ /** Vendor failures arrive as whole console transcripts, banner and prompt echo
28
+ * included. Reports are bounded evidence, so the reason is collapsed to one
29
+ * short line and the echoed prompt is removed. */
30
+ const MAX_NOTE_REASON_CHARS = 200;
31
+
32
+ /** Keep the TAIL, not the head. A CLI prints its banner, config, and startup
33
+ * warnings first and the reason it actually failed last, so truncating from the
34
+ * front reliably preserves the noise and discards the answer. Learned the hard
35
+ * way: a head-truncated note once surfaced a cosmetic startup warning while
36
+ * hiding the real "out of credits" failure on the final line. */
37
+ function boundedReason(reason: string | undefined): string {
38
+ if (!reason) return "no error reported";
39
+ const collapsed = reason.split(ATTESTATION_PROMPT).join("<prompt>").replace(/\s+/g, " ").trim();
40
+ if (!collapsed) return "no error reported";
41
+ return collapsed.length > MAX_NOTE_REASON_CHARS
42
+ ? `…${collapsed.slice(-MAX_NOTE_REASON_CHARS)}`
43
+ : collapsed;
44
+ }
45
+
46
+ export type AttestationOutcome = "recorded" | "skipped" | "unreachable" | "failed";
47
+
48
+ export interface HarnessAttestationResult {
49
+ harness: HarnessId;
50
+ outcome: AttestationOutcome;
51
+ binaryVersion?: string;
52
+ observations?: Partial<Record<AttestableDimension, CapabilitySupport>>;
53
+ durationMs?: number;
54
+ note: string;
55
+ }
56
+
57
+ export interface HarnessAttestationReport {
58
+ generatedAt: string;
59
+ harnesses: HarnessId[];
60
+ results: HarnessAttestationResult[];
61
+ recorded: number;
62
+ /** True when at least one selected harness could not be attested, so a
63
+ * caller can tell a partial sweep from a complete one. */
64
+ incomplete: boolean;
65
+ }
66
+
67
+ export interface RunHarnessAttestationOptions {
68
+ harnesses?: readonly string[];
69
+ timeoutMs?: number;
70
+ cwd?: string;
71
+ coordRoot?: string;
72
+ /** Scrub API-key vars from the child so it can only use its stored login,
73
+ * matching `workflow run --subscription-only`. The observation is recorded
74
+ * against this mode, because a child that may fall back to an API key can
75
+ * behave differently from one that may not. */
76
+ subscriptionOnly?: boolean;
77
+ /** Test seam and alternate host probe. A null version means unavailable. */
78
+ versionProbe?: (binary: string) => string | null;
79
+ /** Test seam. Defaults to the adapter's production spawner. */
80
+ spawn?: (harness: HarnessId, prompt: string, timeoutMs: number) => Promise<SpawnResult>;
81
+ /** Test seam. Defaults to writing under the coord root. */
82
+ persist?: (record: HarnessAttestation) => void;
83
+ now?: () => Date;
84
+ }
85
+
86
+ export async function runHarnessAttestation(
87
+ registry: HarnessRegistry,
88
+ opts: RunHarnessAttestationOptions = {},
89
+ ): Promise<HarnessAttestationReport> {
90
+ const ids = opts.harnesses?.length ? [...opts.harnesses] : registry.ids();
91
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_ATTESTATION_TIMEOUT_MS;
92
+ const cwd = opts.cwd ?? process.cwd();
93
+ const subscriptionOnly = opts.subscriptionOnly === true;
94
+ const versionProbe = opts.versionProbe ?? probeBinaryVersion;
95
+ const now = opts.now ?? (() => new Date());
96
+ const results: HarnessAttestationResult[] = [];
97
+
98
+ for (const id of ids) {
99
+ const adapter = registry.require(id);
100
+ const binaryVersion = versionProbe(adapter.profile.binary);
101
+ if (!binaryVersion) {
102
+ results.push({
103
+ harness: id,
104
+ outcome: "skipped",
105
+ note: `${adapter.profile.binary} not found on PATH`,
106
+ });
107
+ continue;
108
+ }
109
+
110
+ let result: SpawnResult;
111
+ try {
112
+ result = opts.spawn
113
+ ? await opts.spawn(id, ATTESTATION_PROMPT, timeoutMs)
114
+ : await adapter.spawn({
115
+ prompt: ATTESTATION_PROMPT,
116
+ timeoutMs,
117
+ maxTurns: 1,
118
+ cwd,
119
+ subscriptionOnly,
120
+ });
121
+ } catch (error) {
122
+ results.push({
123
+ harness: id,
124
+ outcome: "failed",
125
+ binaryVersion,
126
+ note: `probe threw: ${boundedReason((error as Error).message)}`,
127
+ });
128
+ continue;
129
+ }
130
+
131
+ // Prerequisite rule: an unreachable subject cannot evidence anything, so a
132
+ // failed turn records nothing at all rather than a page of `unsupported`.
133
+ if (!result.ok) {
134
+ results.push({
135
+ harness: id,
136
+ outcome: "unreachable",
137
+ binaryVersion,
138
+ durationMs: result.durationMs,
139
+ note: `the probe turn did not complete (${boundedReason(result.error)}); nothing recorded`,
140
+ });
141
+ continue;
142
+ }
143
+
144
+ const observations: Partial<Record<AttestableDimension, CapabilitySupport>> = {
145
+ invocation: "supported",
146
+ finalResult: result.text.trim().length > 0 ? "supported" : "unsupported",
147
+ sessionId: result.sessionId ? "supported" : "unsupported",
148
+ cost: result.costUsd !== undefined ? "supported" : "unsupported",
149
+ };
150
+
151
+ const record = sealAttestation({
152
+ schema_version: ATTESTATION_SCHEMA_VERSION,
153
+ harness: id,
154
+ binary_version: binaryVersion,
155
+ profile_digest: profileDigest(adapter.profile),
156
+ subscription_only: subscriptionOnly,
157
+ observed_at: now().toISOString(),
158
+ observations,
159
+ });
160
+
161
+ if (opts.persist) opts.persist(record);
162
+ else writeAttestation(record, { coordRoot: opts.coordRoot });
163
+
164
+ results.push({
165
+ harness: id,
166
+ outcome: "recorded",
167
+ binaryVersion,
168
+ observations,
169
+ durationMs: result.durationMs,
170
+ note: `observed on ${binaryVersion}`,
171
+ });
172
+ }
173
+
174
+ return {
175
+ generatedAt: now().toISOString(),
176
+ harnesses: ids,
177
+ results,
178
+ recorded: results.filter((row) => row.outcome === "recorded").length,
179
+ incomplete: results.some((row) => row.outcome !== "recorded"),
180
+ };
181
+ }
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Durable record of what an installed vendor CLI was actually observed doing
3
+ * (ADR 0038).
4
+ *
5
+ * The conformance bench proves Harnery's own planner and normalizer against a
6
+ * committed fixture. That is an adapter check, not a vendor check
7
+ * (ADR 0037). An attestation is the other half: one bounded live turn, its
8
+ * observations, and the vendor version they were observed on.
9
+ *
10
+ * A record stores structural facts only. No prompt text, no completion text,
11
+ * no host paths. It lives under the host's coordination directory and is never
12
+ * published.
13
+ */
14
+
15
+ import {
16
+ mkdirSync,
17
+ readdirSync,
18
+ readFileSync,
19
+ renameSync,
20
+ unlinkSync,
21
+ writeFileSync,
22
+ } from "node:fs";
23
+ import { resolve } from "node:path";
24
+ import { monorepoRoot } from "../agents/coord-client.ts";
25
+ import { stableDigest } from "../workflow/durable-record.ts";
26
+ import type { CapabilitySupport, HarnessId, HarnessProfile } from "./types.ts";
27
+
28
+ export const ATTESTATION_SCHEMA_VERSION = 2;
29
+
30
+ /** Dimensions one minimal live turn can honestly establish. Everything else
31
+ * needs a purpose-built scenario and stays outside the record rather than
32
+ * being guessed at. */
33
+ export const ATTESTABLE_DIMENSIONS = ["invocation", "finalResult", "sessionId", "cost"] as const;
34
+
35
+ export type AttestableDimension = (typeof ATTESTABLE_DIMENSIONS)[number];
36
+
37
+ export interface HarnessAttestation {
38
+ schema_version: number;
39
+ harness: HarnessId;
40
+ /** What the vendor binary reported when this was recorded. Staleness is
41
+ * keyed on this, so a vendor upgrade invalidates the record automatically. */
42
+ binary_version: string;
43
+ /** Digest of the capability declaration at record time, so an edited
44
+ * declaration also invalidates the record. */
45
+ profile_digest: string;
46
+ /** The billing policy the probe ran under. A child launched with API keys
47
+ * scrubbed can behave differently from one that can fall back to them, so an
48
+ * observation only speaks for the mode it was made in. */
49
+ subscription_only: boolean;
50
+ observed_at: string;
51
+ /** Only what the probe actually saw. A dimension absent from this map was
52
+ * not observed, which is not the same as unsupported. */
53
+ observations: Partial<Record<AttestableDimension, CapabilitySupport>>;
54
+ /** Over every field above. A hand-edited record fails to load. */
55
+ record_digest: string;
56
+ }
57
+
58
+ export interface AttestationStoreOptions {
59
+ /** Test seam and alternate host. Defaults to the resolved coord root. */
60
+ coordRoot?: string;
61
+ }
62
+
63
+ export function attestationsDir(opts: AttestationStoreOptions = {}): string {
64
+ const root = opts.coordRoot ?? monorepoRoot();
65
+ if (!root) throw new Error("Not in a coord-aware repo (coord root resolved to null).");
66
+ return resolve(root, ".harnery", "harnesses", "attestations");
67
+ }
68
+
69
+ /** Stable identity of a declaration, so editing a claim invalidates the
70
+ * attestation that was recorded against the old one. */
71
+ export function profileDigest(profile: HarnessProfile): string {
72
+ return stableDigest({
73
+ id: profile.id,
74
+ binary: profile.binary,
75
+ capabilities: profile.capabilities,
76
+ });
77
+ }
78
+
79
+ function digestOf(record: Omit<HarnessAttestation, "record_digest">): string {
80
+ return stableDigest(record);
81
+ }
82
+
83
+ export function sealAttestation(
84
+ record: Omit<HarnessAttestation, "record_digest">,
85
+ ): HarnessAttestation {
86
+ return { ...record, record_digest: digestOf(record) };
87
+ }
88
+
89
+ function attestationPath(harness: HarnessId, opts: AttestationStoreOptions): string {
90
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(harness)) {
91
+ throw new Error(`unsafe harness id for an attestation path: ${harness}`);
92
+ }
93
+ return resolve(attestationsDir(opts), `${harness}.json`);
94
+ }
95
+
96
+ /** Replace-in-place write. Unlike a workflow record an attestation is meant to
97
+ * be re-recorded, so this is a mutable atomic swap rather than an immutable
98
+ * create. */
99
+ export function writeAttestation(
100
+ record: HarnessAttestation,
101
+ opts: AttestationStoreOptions = {},
102
+ ): string {
103
+ const path = attestationPath(record.harness, opts);
104
+ mkdirSync(attestationsDir(opts), { recursive: true, mode: 0o700 });
105
+ const temporary = `${path}.tmp-${process.pid}`;
106
+ try {
107
+ writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\n`, {
108
+ encoding: "utf8",
109
+ mode: 0o600,
110
+ });
111
+ renameSync(temporary, path);
112
+ } catch (error) {
113
+ try {
114
+ unlinkSync(temporary);
115
+ } catch {
116
+ // The temp file may never have been created; the original error wins.
117
+ }
118
+ throw error;
119
+ }
120
+ return path;
121
+ }
122
+
123
+ /** Null for absent, unreadable, malformed, wrong-schema, or tampered records.
124
+ * A record that fails its own digest is discarded rather than trusted, because
125
+ * the whole point of the file is that it was not hand-written. */
126
+ export function readAttestation(
127
+ harness: HarnessId,
128
+ opts: AttestationStoreOptions = {},
129
+ ): HarnessAttestation | null {
130
+ let raw: string;
131
+ try {
132
+ raw = readFileSync(attestationPath(harness, opts), "utf8");
133
+ } catch {
134
+ return null;
135
+ }
136
+ let parsed: unknown;
137
+ try {
138
+ parsed = JSON.parse(raw);
139
+ } catch {
140
+ return null;
141
+ }
142
+ return validateAttestation(parsed, harness);
143
+ }
144
+
145
+ export function validateAttestation(
146
+ value: unknown,
147
+ harness?: HarnessId,
148
+ ): HarnessAttestation | null {
149
+ if (!value || typeof value !== "object") return null;
150
+ const record = value as HarnessAttestation;
151
+ if (record.schema_version !== ATTESTATION_SCHEMA_VERSION) return null;
152
+ if (typeof record.harness !== "string" || (harness && record.harness !== harness)) return null;
153
+ if (typeof record.binary_version !== "string" || typeof record.profile_digest !== "string") {
154
+ return null;
155
+ }
156
+ if (typeof record.observed_at !== "string" || typeof record.record_digest !== "string")
157
+ return null;
158
+ if (!record.observations || typeof record.observations !== "object") return null;
159
+
160
+ const { record_digest, ...body } = record;
161
+ if (digestOf(body) !== record_digest) return null;
162
+ return record;
163
+ }
164
+
165
+ /** An attestation speaks only for the vendor version and declaration it was
166
+ * recorded against. */
167
+ export function isAttestationCurrent(
168
+ record: HarnessAttestation | null,
169
+ binaryVersion: string | null,
170
+ profile: HarnessProfile,
171
+ subscriptionOnly?: boolean,
172
+ ): record is HarnessAttestation {
173
+ if (!record || !binaryVersion) return false;
174
+ if (record.binary_version !== binaryVersion) return false;
175
+ if (subscriptionOnly !== undefined && record.subscription_only !== subscriptionOnly) return false;
176
+ return record.profile_digest === profileDigest(profile);
177
+ }
178
+
179
+ export function listAttestations(opts: AttestationStoreOptions = {}): HarnessAttestation[] {
180
+ let names: string[];
181
+ try {
182
+ names = readdirSync(attestationsDir(opts));
183
+ } catch {
184
+ return [];
185
+ }
186
+ const records: HarnessAttestation[] = [];
187
+ for (const name of names) {
188
+ if (!name.endsWith(".json")) continue;
189
+ const record = readAttestation(name.slice(0, -".json".length), opts);
190
+ if (record) records.push(record);
191
+ }
192
+ return records.sort((a, b) => a.harness.localeCompare(b.harness));
193
+ }
194
+
195
+ /** Both harness-derived proof inputs, read once, for a workflow run
196
+ * (ADR 0038). Callers inject the result so the engine performs no capability
197
+ * lookups of its own. A harness with no current attestation simply has no
198
+ * citation; that absence is not a proof unknown. */
199
+ export function harnessProofInputs(
200
+ profiles: readonly HarnessProfile[],
201
+ opts: AttestationStoreOptions & {
202
+ versionProbe: (binary: string) => string | null;
203
+ /** Billing policy this run will use, so a record made under the other mode
204
+ * is not cited as if it applied. */
205
+ subscriptionOnly?: boolean;
206
+ },
207
+ ): {
208
+ harnessEvidence: Record<string, { toolEvidence: HarnessProfile["capabilities"]["toolEvidence"] }>;
209
+ harnessAttestations: Record<
210
+ string,
211
+ { binary_version: string; observed_at: string; record_digest: string }
212
+ >;
213
+ } {
214
+ const harnessEvidence: Record<
215
+ string,
216
+ { toolEvidence: HarnessProfile["capabilities"]["toolEvidence"] }
217
+ > = {};
218
+ const harnessAttestations: Record<
219
+ string,
220
+ { binary_version: string; observed_at: string; record_digest: string }
221
+ > = {};
222
+
223
+ for (const profile of profiles) {
224
+ harnessEvidence[profile.id] = { toolEvidence: profile.capabilities.toolEvidence };
225
+ let record: HarnessAttestation | null = null;
226
+ try {
227
+ record = readAttestation(profile.id, opts);
228
+ } catch {
229
+ // No coord root or unreadable store: run unattested rather than fail.
230
+ continue;
231
+ }
232
+ if (
233
+ !isAttestationCurrent(
234
+ record,
235
+ opts.versionProbe(profile.binary),
236
+ profile,
237
+ opts.subscriptionOnly,
238
+ )
239
+ ) {
240
+ continue;
241
+ }
242
+ harnessAttestations[profile.id] = {
243
+ binary_version: record.binary_version,
244
+ observed_at: record.observed_at,
245
+ record_digest: record.record_digest,
246
+ };
247
+ }
248
+ return { harnessEvidence, harnessAttestations };
249
+ }