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.
- package/dist/commands/harness.d.ts +2 -1
- package/dist/commands/harness.d.ts.map +1 -1
- package/dist/commands/harness.js +113 -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 +3 -13
- package/dist/core/harnesses/attest.d.ts +53 -0
- package/dist/core/harnesses/attest.d.ts.map +1 -0
- package/dist/core/harnesses/attest.js +120 -0
- package/dist/core/harnesses/attestation.d.ts +82 -0
- package/dist/core/harnesses/attestation.d.ts.map +1 -0
- package/dist/core/harnesses/attestation.js +171 -0
- package/dist/core/harnesses/bench.d.ts +26 -1
- package/dist/core/harnesses/bench.d.ts.map +1 -1
- package/dist/core/harnesses/bench.js +137 -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/harnesses/profiles.d.ts +6 -6
- package/dist/core/harnesses/profiles.js +3 -3
- package/dist/core/workflow/engine.js +3 -0
- 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-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +2 -1
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +2 -1
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +2 -1
- package/dist/core/workflow/spawn-failure.d.ts +18 -0
- package/dist/core/workflow/spawn-failure.d.ts.map +1 -0
- package/dist/core/workflow/spawn-failure.js +24 -0
- package/dist/core/workflow/types.d.ts +14 -0
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/commands/harness.ts +135 -2
- package/src/commands/supervisor.ts +11 -15
- package/src/commands/work.ts +8 -8
- package/src/commands/workflow.ts +11 -15
- package/src/core/harnesses/attest.ts +181 -0
- package/src/core/harnesses/attestation.ts +249 -0
- package/src/core/harnesses/bench.ts +192 -32
- package/src/core/harnesses/index.ts +26 -1
- package/src/core/harnesses/profiles.ts +3 -3
- package/src/core/workflow/engine.ts +3 -0
- package/src/core/workflow/proof.ts +7 -1
- package/src/core/workflow/spawn-claude.ts +2 -1
- package/src/core/workflow/spawn-codex.ts +2 -1
- package/src/core/workflow/spawn-cursor.ts +2 -1
- package/src/core/workflow/spawn-failure.ts +28 -0
- 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
|
+
}
|