harnery 0.24.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.
Files changed (39) 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 +105 -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 +48 -0
  11. package/dist/core/harnesses/attest.d.ts.map +1 -0
  12. package/dist/core/harnesses/attest.js +110 -0
  13. package/dist/core/harnesses/attestation.d.ts +75 -0
  14. package/dist/core/harnesses/attestation.d.ts.map +1 -0
  15. package/dist/core/harnesses/attestation.js +168 -0
  16. package/dist/core/harnesses/bench.d.ts +23 -1
  17. package/dist/core/harnesses/bench.d.ts.map +1 -1
  18. package/dist/core/harnesses/bench.js +135 -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/workflow/engine.js +3 -0
  23. package/dist/core/workflow/proof.d.ts +4 -1
  24. package/dist/core/workflow/proof.d.ts.map +1 -1
  25. package/dist/core/workflow/proof.js +3 -2
  26. package/dist/core/workflow/types.d.ts +14 -0
  27. package/dist/core/workflow/types.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/src/commands/harness.ts +123 -2
  30. package/src/commands/supervisor.ts +11 -15
  31. package/src/commands/work.ts +8 -8
  32. package/src/commands/workflow.ts +11 -15
  33. package/src/core/harnesses/attest.ts +162 -0
  34. package/src/core/harnesses/attestation.ts +231 -0
  35. package/src/core/harnesses/bench.ts +187 -32
  36. package/src/core/harnesses/index.ts +26 -1
  37. package/src/core/workflow/engine.ts +3 -0
  38. package/src/core/workflow/proof.ts +7 -1
  39. package/src/core/workflow/types.ts +15 -0
@@ -0,0 +1,168 @@
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
+ import { mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
15
+ import { resolve } from "node:path";
16
+ import { monorepoRoot } from "../agents/coord-client.js";
17
+ import { stableDigest } from "../workflow/durable-record.js";
18
+ export const ATTESTATION_SCHEMA_VERSION = 1;
19
+ /** Dimensions one minimal live turn can honestly establish. Everything else
20
+ * needs a purpose-built scenario and stays outside the record rather than
21
+ * being guessed at. */
22
+ export const ATTESTABLE_DIMENSIONS = ["invocation", "finalResult", "sessionId", "cost"];
23
+ export function attestationsDir(opts = {}) {
24
+ const root = opts.coordRoot ?? monorepoRoot();
25
+ if (!root)
26
+ throw new Error("Not in a coord-aware repo (coord root resolved to null).");
27
+ return resolve(root, ".harnery", "harnesses", "attestations");
28
+ }
29
+ /** Stable identity of a declaration, so editing a claim invalidates the
30
+ * attestation that was recorded against the old one. */
31
+ export function profileDigest(profile) {
32
+ return stableDigest({
33
+ id: profile.id,
34
+ binary: profile.binary,
35
+ capabilities: profile.capabilities,
36
+ });
37
+ }
38
+ function digestOf(record) {
39
+ return stableDigest(record);
40
+ }
41
+ export function sealAttestation(record) {
42
+ return { ...record, record_digest: digestOf(record) };
43
+ }
44
+ function attestationPath(harness, opts) {
45
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(harness)) {
46
+ throw new Error(`unsafe harness id for an attestation path: ${harness}`);
47
+ }
48
+ return resolve(attestationsDir(opts), `${harness}.json`);
49
+ }
50
+ /** Replace-in-place write. Unlike a workflow record an attestation is meant to
51
+ * be re-recorded, so this is a mutable atomic swap rather than an immutable
52
+ * create. */
53
+ export function writeAttestation(record, opts = {}) {
54
+ const path = attestationPath(record.harness, opts);
55
+ mkdirSync(attestationsDir(opts), { recursive: true, mode: 0o700 });
56
+ const temporary = `${path}.tmp-${process.pid}`;
57
+ try {
58
+ writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\n`, {
59
+ encoding: "utf8",
60
+ mode: 0o600,
61
+ });
62
+ renameSync(temporary, path);
63
+ }
64
+ catch (error) {
65
+ try {
66
+ unlinkSync(temporary);
67
+ }
68
+ catch {
69
+ // The temp file may never have been created; the original error wins.
70
+ }
71
+ throw error;
72
+ }
73
+ return path;
74
+ }
75
+ /** Null for absent, unreadable, malformed, wrong-schema, or tampered records.
76
+ * A record that fails its own digest is discarded rather than trusted, because
77
+ * the whole point of the file is that it was not hand-written. */
78
+ export function readAttestation(harness, opts = {}) {
79
+ let raw;
80
+ try {
81
+ raw = readFileSync(attestationPath(harness, opts), "utf8");
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ let parsed;
87
+ try {
88
+ parsed = JSON.parse(raw);
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ return validateAttestation(parsed, harness);
94
+ }
95
+ export function validateAttestation(value, harness) {
96
+ if (!value || typeof value !== "object")
97
+ return null;
98
+ const record = value;
99
+ if (record.schema_version !== ATTESTATION_SCHEMA_VERSION)
100
+ return null;
101
+ if (typeof record.harness !== "string" || (harness && record.harness !== harness))
102
+ return null;
103
+ if (typeof record.binary_version !== "string" || typeof record.profile_digest !== "string") {
104
+ return null;
105
+ }
106
+ if (typeof record.observed_at !== "string" || typeof record.record_digest !== "string")
107
+ return null;
108
+ if (!record.observations || typeof record.observations !== "object")
109
+ return null;
110
+ const { record_digest, ...body } = record;
111
+ if (digestOf(body) !== record_digest)
112
+ return null;
113
+ return record;
114
+ }
115
+ /** An attestation speaks only for the vendor version and declaration it was
116
+ * recorded against. */
117
+ export function isAttestationCurrent(record, binaryVersion, profile) {
118
+ if (!record || !binaryVersion)
119
+ return false;
120
+ if (record.binary_version !== binaryVersion)
121
+ return false;
122
+ return record.profile_digest === profileDigest(profile);
123
+ }
124
+ export function listAttestations(opts = {}) {
125
+ let names;
126
+ try {
127
+ names = readdirSync(attestationsDir(opts));
128
+ }
129
+ catch {
130
+ return [];
131
+ }
132
+ const records = [];
133
+ for (const name of names) {
134
+ if (!name.endsWith(".json"))
135
+ continue;
136
+ const record = readAttestation(name.slice(0, -".json".length), opts);
137
+ if (record)
138
+ records.push(record);
139
+ }
140
+ return records.sort((a, b) => a.harness.localeCompare(b.harness));
141
+ }
142
+ /** Both harness-derived proof inputs, read once, for a workflow run
143
+ * (ADR 0038). Callers inject the result so the engine performs no capability
144
+ * lookups of its own. A harness with no current attestation simply has no
145
+ * citation; that absence is not a proof unknown. */
146
+ export function harnessProofInputs(profiles, opts) {
147
+ const harnessEvidence = {};
148
+ const harnessAttestations = {};
149
+ for (const profile of profiles) {
150
+ harnessEvidence[profile.id] = { toolEvidence: profile.capabilities.toolEvidence };
151
+ let record = null;
152
+ try {
153
+ record = readAttestation(profile.id, opts);
154
+ }
155
+ catch {
156
+ // No coord root or unreadable store: run unattested rather than fail.
157
+ continue;
158
+ }
159
+ if (!isAttestationCurrent(record, opts.versionProbe(profile.binary), profile))
160
+ continue;
161
+ harnessAttestations[profile.id] = {
162
+ binary_version: record.binary_version,
163
+ observed_at: record.observed_at,
164
+ record_digest: record.record_digest,
165
+ };
166
+ }
167
+ return { harnessEvidence, harnessAttestations };
168
+ }
@@ -1,13 +1,23 @@
1
+ import type { HarnessAttestation } from "./attestation.js";
1
2
  import type { HarnessRegistry } from "./registry.js";
2
3
  import type { CapabilitySupport, HarnessCapabilityDimension, HarnessId } from "./types.js";
3
4
  export type BenchVerdict = "supported" | "partial" | "unsupported" | "not_applicable" | "unknown" | "skipped" | "drift";
4
- export type BenchDimension = "registration" | "binary" | HarnessCapabilityDimension;
5
+ export type BenchDimension = "registration" | "binary" | "contract" | HarnessCapabilityDimension;
6
+ /** How a result was established (ADR 0037). Orthogonal to the verdict: this
7
+ * answers "how do we know", not "what is true".
8
+ *
9
+ * - `adapter`: checked against Harnery's own planner, normalizer, or fixture.
10
+ * Proves the adapter contract, says nothing about the installed vendor CLI.
11
+ * - `attested`: checked by observing the installed vendor CLI on this host.
12
+ * - `declared`: not checked. The value is the declaration, repeated. */
13
+ export type BenchBasis = "adapter" | "attested" | "declared";
5
14
  export interface BenchResult {
6
15
  harness: HarnessId;
7
16
  dimension: BenchDimension;
8
17
  declared: CapabilitySupport | "not_applicable";
9
18
  observed: BenchVerdict;
10
19
  verdict: BenchVerdict;
20
+ basis: BenchBasis;
11
21
  note?: string;
12
22
  }
13
23
  export interface HarnessBenchReport {
@@ -16,6 +26,9 @@ export interface HarnessBenchReport {
16
26
  harnesses: HarnessId[];
17
27
  results: BenchResult[];
18
28
  summary: Record<BenchVerdict, number>;
29
+ /** Result counts per basis, so a caller can tell a clean report from an
30
+ * unmeasured one without walking every result. */
31
+ basisSummary: Record<BenchBasis, number>;
19
32
  drift: boolean;
20
33
  skipped: boolean;
21
34
  }
@@ -24,6 +37,15 @@ export interface HarnessBenchOptions {
24
37
  dimensions?: readonly HarnessCapabilityDimension[];
25
38
  /** Test seam and alternate host probe. A null version means unavailable. */
26
39
  versionProbe?: (binary: string) => string | null;
40
+ /** Where to look for live attestations (ADR 0038). Defaults to the coord
41
+ * root. Reading them never runs a model turn. */
42
+ coordRoot?: string;
43
+ /** Test seam. Defaults to reading the attestation store. */
44
+ attestationReader?: (harness: HarnessId) => HarnessAttestation | null;
27
45
  }
28
46
  export declare function runHarnessBench(registry: HarnessRegistry, opts?: HarnessBenchOptions): HarnessBenchReport;
47
+ /** Ask an installed vendor CLI what version it is. Null when it is absent or
48
+ * refuses to answer. Shared with the live attestation probe so both halves of
49
+ * the capability story key on the same string. */
50
+ export declare function probeBinaryVersion(binary: string): string | null;
29
51
  //# sourceMappingURL=bench.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bench.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/bench.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EACV,iBAAiB,EAEjB,0BAA0B,EAC1B,SAAS,EACV,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,SAAS,GACT,aAAa,GACb,gBAAgB,GAChB,SAAS,GACT,SAAS,GACT,OAAO,CAAC;AAEZ,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEpF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,SAAS,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,iBAAiB,GAAG,gBAAgB,CAAC;IAC/C,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,UAAU,CAAC,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACnD,4EAA4E;IAC5E,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAClD;AAYD,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,eAAe,EACzB,IAAI,GAAE,mBAAwB,GAC7B,kBAAkB,CAuDpB"}
1
+ {"version":3,"file":"bench.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/bench.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAuB,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EACV,iBAAiB,EAEjB,0BAA0B,EAC1B,SAAS,EACV,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,SAAS,GACT,aAAa,GACb,gBAAgB,GAChB,SAAS,GACT,SAAS,GACT,OAAO,CAAC;AAEZ,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,QAAQ,GAAG,UAAU,GAAG,0BAA0B,CAAC;AAEjG;;;;;;wEAMwE;AACxE,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,SAAS,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;IAC1B,QAAQ,EAAE,iBAAiB,GAAG,gBAAgB,CAAC;IAC/C,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACtC;sDACkD;IAClD,YAAY,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACzC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,UAAU,CAAC,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACnD,4EAA4E;IAC5E,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD;qDACiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,KAAK,kBAAkB,GAAG,IAAI,CAAC;CACvE;AAgDD,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,eAAe,EACzB,IAAI,GAAE,mBAAwB,GAC7B,kBAAkB,CAwEpB;AA2KD;;kDAEkD;AAClD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIhE"}
@@ -1,5 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { HARNESS_SPECS } from "../hooks/harness/events.js";
3
+ import { isAttestationCurrent, readAttestation } from "./attestation.js";
3
4
  import { HARNESS_CAPABILITY_DIMENSIONS } from "./types.js";
4
5
  const EMPTY_SUMMARY = {
5
6
  supported: 0,
@@ -10,12 +11,42 @@ const EMPTY_SUMMARY = {
10
11
  skipped: 0,
11
12
  drift: 0,
12
13
  };
14
+ const EMPTY_BASIS_SUMMARY = {
15
+ adapter: 0,
16
+ attested: 0,
17
+ declared: 0,
18
+ };
19
+ /** A stored attestation only counts when it still matches the installed
20
+ * version and the current declaration. A stale one is ignored rather than
21
+ * trusted, so an upgraded vendor CLI silently drops back to adapter basis. */
22
+ function loadAttestation(id, version, adapter, opts) {
23
+ let record = null;
24
+ try {
25
+ record = opts.attestationReader
26
+ ? opts.attestationReader(id)
27
+ : readAttestation(id, { coordRoot: opts.coordRoot });
28
+ }
29
+ catch {
30
+ // No coord root, unreadable store: the bench still runs, just unattested.
31
+ return null;
32
+ }
33
+ return isAttestationCurrent(record, version, adapter.profile) ? record : null;
34
+ }
35
+ /** First version-shaped token in a string, or null when there is none.
36
+ * Tolerates the vendor prefixes and suffixes real CLIs print, so
37
+ * `codex-cli 0.144.5` and `2.1.197 (Claude Code)` both reduce to a token. */
38
+ function versionToken(value) {
39
+ if (!value)
40
+ return null;
41
+ const match = value.match(/\d+(?:\.\d+)+(?:[-.][0-9a-z]+)*/i);
42
+ return match ? match[0].toLowerCase() : null;
43
+ }
13
44
  export function runHarnessBench(registry, opts = {}) {
14
45
  const ids = opts.harnesses?.length ? [...opts.harnesses] : registry.ids();
15
46
  const dimensions = opts.dimensions?.length
16
47
  ? [...new Set(opts.dimensions)]
17
48
  : [...HARNESS_CAPABILITY_DIMENSIONS];
18
- const versionProbe = opts.versionProbe ?? probeVersion;
49
+ const versionProbe = opts.versionProbe ?? probeBinaryVersion;
19
50
  const results = [];
20
51
  for (const id of ids) {
21
52
  const adapter = registry.require(id);
@@ -25,6 +56,7 @@ export function runHarnessBench(registry, opts = {}) {
25
56
  declared: "supported",
26
57
  observed: "supported",
27
58
  verdict: "supported",
59
+ basis: "adapter",
28
60
  note: `${adapter.profile.integrationMode}; ${adapter.profile.authModel}`,
29
61
  });
30
62
  const version = versionProbe(adapter.profile.binary);
@@ -34,35 +66,105 @@ export function runHarnessBench(registry, opts = {}) {
34
66
  declared: "supported",
35
67
  observed: version ? "supported" : "skipped",
36
68
  verdict: version ? "supported" : "skipped",
69
+ // A skip establishes nothing about the vendor, so it is not an attestation.
70
+ basis: version ? "attested" : "declared",
37
71
  note: version ?? `${adapter.profile.binary} not found on PATH`,
38
72
  });
73
+ results.push(contractResult(id, adapter, version));
39
74
  const observations = observeAdapter(adapter);
75
+ const attestation = loadAttestation(id, version, adapter, opts);
40
76
  for (const dimension of dimensions) {
41
77
  const claim = adapter.profile.capabilities[dimension];
42
- const observed = observations[dimension];
78
+ const live = attestation?.observations[dimension];
79
+ // A live observation of the installed CLI outranks a fixture check of
80
+ // Harnery's own normalizer. Everything else keeps its adapter basis.
81
+ const { observed, basis } = live
82
+ ? { observed: live, basis: "attested" }
83
+ : observations[dimension];
43
84
  results.push({
44
85
  harness: id,
45
86
  dimension,
46
87
  declared: claim.support,
47
88
  observed,
48
89
  verdict: reconcile(claim.support, observed),
49
- note: claim.note,
90
+ basis,
91
+ note: live ? `attested on ${attestation?.binary_version}` : claim.note,
50
92
  });
51
93
  }
52
94
  }
53
95
  const summary = { ...EMPTY_SUMMARY };
54
- for (const result of results)
96
+ const basisSummary = { ...EMPTY_BASIS_SUMMARY };
97
+ for (const result of results) {
55
98
  summary[result.verdict]++;
99
+ basisSummary[result.basis]++;
100
+ }
56
101
  return {
57
102
  generatedAt: new Date().toISOString(),
58
103
  mode: "offline",
59
104
  harnesses: ids,
60
105
  results,
61
106
  summary,
107
+ basisSummary,
62
108
  drift: summary.drift > 0,
63
109
  skipped: summary.skipped > 0,
64
110
  };
65
111
  }
112
+ /** Compare the vendor contract a declaration was validated against with the
113
+ * one actually installed (ADR 0037). This is the only dimension attested
114
+ * without a model turn, because a version string costs nothing to read.
115
+ *
116
+ * A mismatch is `drift`, not failure: a newer vendor CLI is not automatically
117
+ * broken, but the declaration is no longer backed by an observation. An
118
+ * unrecorded or unparseable declared version stays `unknown` rather than being
119
+ * inferred from the installed one. */
120
+ function contractResult(id, adapter, version) {
121
+ const recorded = adapter.profile.verified;
122
+ const base = { harness: id, dimension: "contract", declared: "supported" };
123
+ if (!version) {
124
+ return {
125
+ ...base,
126
+ observed: "skipped",
127
+ verdict: "skipped",
128
+ basis: "declared",
129
+ note: `${adapter.profile.binary} not found on PATH; installed contract unobservable`,
130
+ };
131
+ }
132
+ const declaredToken = versionToken(recorded?.version);
133
+ if (!declaredToken) {
134
+ return {
135
+ ...base,
136
+ observed: "unknown",
137
+ verdict: "unknown",
138
+ basis: "declared",
139
+ note: recorded
140
+ ? `verified.version "${recorded.version}" is not a version; installed ${version}`
141
+ : `no verified vendor contract recorded; installed ${version}`,
142
+ };
143
+ }
144
+ const installedToken = versionToken(version) ?? version.trim().toLowerCase();
145
+ if (installedToken === declaredToken) {
146
+ return {
147
+ ...base,
148
+ observed: "supported",
149
+ verdict: "supported",
150
+ basis: "attested",
151
+ note: `declaration validated against ${recorded?.version} on ${recorded?.date}`,
152
+ };
153
+ }
154
+ return {
155
+ ...base,
156
+ observed: "drift",
157
+ verdict: "drift",
158
+ basis: "attested",
159
+ note: `declaration validated against ${recorded?.version} (${recorded?.date}); installed ${version}`,
160
+ };
161
+ }
162
+ /** Checked against Harnery's planner, normalizer, or fixture. */
163
+ function fromAdapter(observed) {
164
+ return { observed, basis: "adapter" };
165
+ }
166
+ /** No check exists for this dimension yet. */
167
+ const NOT_CHECKED = { observed: "unknown", basis: "declared" };
66
168
  function observeAdapter(adapter) {
67
169
  const profile = adapter.profile;
68
170
  const effort = profile.effortValues[0];
@@ -98,43 +200,41 @@ function observeAdapter(adapter) {
98
200
  : undefined;
99
201
  const hookSubcommands = new Set(hookSpec?.events.map((event) => event.subcommand) ?? []);
100
202
  return {
101
- invocation: !planningFailed && argv[0] === profile.binary && argv.includes(request.prompt)
203
+ invocation: fromAdapter(!planningFailed && argv[0] === profile.binary && argv.includes(request.prompt)
102
204
  ? "supported"
103
- : "unsupported",
104
- modelSelection: !planningFailed && argv.includes(request.model ?? "") ? "supported" : "unsupported",
105
- effortSelection: effort === undefined
205
+ : "unsupported"),
206
+ modelSelection: fromAdapter(!planningFailed && argv.includes(request.model ?? "") ? "supported" : "unsupported"),
207
+ effortSelection: fromAdapter(effort === undefined
106
208
  ? "unsupported"
107
209
  : !planningFailed && argv.some((arg) => arg.includes(effort))
108
210
  ? "supported"
109
- : "unsupported",
110
- maxTurns: !planningFailed && argv.includes(String(request.maxTurns)) ? "supported" : "unsupported",
111
- finalResult: finalMatches ? "supported" : "unsupported",
112
- sessionId: sessionObserved === "supported" && normalized?.sessionId !== fixture.sessionId
211
+ : "unsupported"),
212
+ maxTurns: fromAdapter(!planningFailed && argv.includes(String(request.maxTurns)) ? "supported" : "unsupported"),
213
+ finalResult: fromAdapter(finalMatches ? "supported" : "unsupported"),
214
+ sessionId: fromAdapter(sessionObserved === "supported" && normalized?.sessionId !== fixture.sessionId
113
215
  ? "unsupported"
114
- : sessionObserved,
115
- cost: costObserved === "supported" && normalized?.costUsd !== fixture.costUsd
216
+ : sessionObserved),
217
+ cost: fromAdapter(costObserved === "supported" && normalized?.costUsd !== fixture.costUsd
116
218
  ? "unsupported"
117
- : costObserved,
118
- toolEvidence: normalized && "toolEvidence" in normalized ? "supported" : "unsupported",
119
- policyMapping: "unknown",
120
- interruption: "unknown",
121
- streaming: "unknown",
122
- steering: "unknown",
123
- resume: "unknown",
124
- images: "unknown",
125
- contextTelemetry: "unknown",
219
+ : costObserved),
220
+ toolEvidence: fromAdapter(normalized && "toolEvidence" in normalized ? "supported" : "unsupported"),
221
+ policyMapping: NOT_CHECKED,
222
+ interruption: NOT_CHECKED,
223
+ streaming: NOT_CHECKED,
224
+ steering: NOT_CHECKED,
225
+ resume: NOT_CHECKED,
226
+ images: NOT_CHECKED,
227
+ contextTelemetry: NOT_CHECKED,
126
228
  preCompactionSignal: hookSpec
127
- ? hookSubcommands.has("pre-compact")
128
- ? "supported"
129
- : "unsupported"
130
- : "unknown",
229
+ ? fromAdapter(hookSubcommands.has("pre-compact") ? "supported" : "unsupported")
230
+ : NOT_CHECKED,
131
231
  postCompactionSignal: hookSpec
132
- ? hookSubcommands.has("post-compact") ||
232
+ ? fromAdapter(hookSubcommands.has("post-compact") ||
133
233
  (adapter.profile.id === "claude-code" && hookSubcommands.has("session-start"))
134
234
  ? "supported"
135
- : "unsupported"
136
- : "unknown",
137
- compaction: "unknown",
235
+ : "unsupported")
236
+ : NOT_CHECKED,
237
+ compaction: NOT_CHECKED,
138
238
  };
139
239
  }
140
240
  function reconcile(declared, observed) {
@@ -143,7 +243,10 @@ function reconcile(declared, observed) {
143
243
  }
144
244
  return declared === observed ? observed : "drift";
145
245
  }
146
- function probeVersion(binary) {
246
+ /** Ask an installed vendor CLI what version it is. Null when it is absent or
247
+ * refuses to answer. Shared with the live attestation probe so both halves of
248
+ * the capability story key on the same string. */
249
+ export function probeBinaryVersion(binary) {
147
250
  const result = spawnSync(binary, ["--version"], { encoding: "utf8", timeout: 5_000 });
148
251
  if (result.error || result.status !== 0)
149
252
  return null;
@@ -1,6 +1,10 @@
1
1
  export type { Spawner, SpawnRequest, SpawnResult } from "../workflow/types.js";
2
- export type { BenchDimension, BenchResult, BenchVerdict, HarnessBenchOptions, HarnessBenchReport, } from "./bench.js";
3
- export { runHarnessBench } from "./bench.js";
2
+ export type { AttestationOutcome, HarnessAttestationReport, HarnessAttestationResult, RunHarnessAttestationOptions, } from "./attest.js";
3
+ export { ATTESTATION_PROMPT, runHarnessAttestation } from "./attest.js";
4
+ export type { AttestableDimension, AttestationStoreOptions, HarnessAttestation, } from "./attestation.js";
5
+ export { ATTESTABLE_DIMENSIONS, ATTESTATION_SCHEMA_VERSION, attestationsDir, harnessProofInputs, isAttestationCurrent, listAttestations, profileDigest, readAttestation, validateAttestation, writeAttestation, } from "./attestation.js";
6
+ export type { BenchBasis, BenchDimension, BenchResult, BenchVerdict, HarnessBenchOptions, HarnessBenchReport, } from "./bench.js";
7
+ export { probeBinaryVersion, runHarnessBench } from "./bench.js";
4
8
  export type { BuiltinHarnessId } from "./profiles.js";
5
9
  export { BUILTIN_HARNESS_IDS, BUILTIN_HARNESS_PROFILES, builtinHarnessProfile, isBuiltinHarness, validateHarnessEffort, } from "./profiles.js";
6
10
  export { createBuiltinHarnessRegistry, HarnessRegistry } from "./registry.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC/E,YAAY,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,4BAA4B,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC9E,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,SAAS,EACT,iBAAiB,EACjB,cAAc,EACd,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC/E,YAAY,EACV,kBAAkB,EAClB,wBAAwB,EACxB,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACjE,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,4BAA4B,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC9E,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,SAAS,EACT,iBAAiB,EACjB,cAAc,EACd,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC"}
@@ -1,4 +1,6 @@
1
- export { runHarnessBench } from "./bench.js";
1
+ export { ATTESTATION_PROMPT, runHarnessAttestation } from "./attest.js";
2
+ export { ATTESTABLE_DIMENSIONS, ATTESTATION_SCHEMA_VERSION, attestationsDir, harnessProofInputs, isAttestationCurrent, listAttestations, profileDigest, readAttestation, validateAttestation, writeAttestation, } from "./attestation.js";
3
+ export { probeBinaryVersion, runHarnessBench } from "./bench.js";
2
4
  export { BUILTIN_HARNESS_IDS, BUILTIN_HARNESS_PROFILES, builtinHarnessProfile, isBuiltinHarness, validateHarnessEffort, } from "./profiles.js";
3
5
  export { createBuiltinHarnessRegistry, HarnessRegistry } from "./registry.js";
4
6
  export { HARNESS_CAPABILITY_DIMENSIONS } from "./types.js";
@@ -245,6 +245,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
245
245
  agents: [],
246
246
  evidence: [],
247
247
  harnessEvidence: opts.harnessEvidence,
248
+ harnessAttestations: opts.harnessAttestations,
248
249
  policy: policy
249
250
  ? {
250
251
  config: policy,
@@ -925,6 +926,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
925
926
  agents: Array.from(agentProofs.values()),
926
927
  evidence: evidenceRecords,
927
928
  harnessEvidence: opts.harnessEvidence,
929
+ harnessAttestations: opts.harnessAttestations,
928
930
  policy: policy
929
931
  ? {
930
932
  config: policy,
@@ -1002,6 +1004,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
1002
1004
  agents: Array.from(agentProofs.values()),
1003
1005
  evidence: evidenceRecords,
1004
1006
  harnessEvidence: opts.harnessEvidence,
1007
+ harnessAttestations: opts.harnessAttestations,
1005
1008
  policy: policy
1006
1009
  ? {
1007
1010
  config: policy,
@@ -1,6 +1,6 @@
1
1
  import type { RepoSnapshot } from "../context/index.js";
2
2
  import type { NormalizedPolicy, PolicyDecision, PolicyIsolation, PolicyNetworkAccess } from "../policy/index.js";
3
- import type { AcceptanceCriterion, AcceptanceResult, AcceptanceSummary, HarnessEvidenceCapability, ResultDigest, WorkflowAgentProof, WorkflowAttemptContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowProof, WorkflowWorkContext } from "./types.js";
3
+ import type { AcceptanceCriterion, AcceptanceResult, AcceptanceSummary, HarnessAttestationCitation, HarnessEvidenceCapability, ResultDigest, WorkflowAgentProof, WorkflowAttemptContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowProof, WorkflowWorkContext } from "./types.js";
4
4
  import type { WorkspaceAttestation, WorkspaceBinding, WorkspaceUnsupportedExecutionEvidence } from "./workspaces/index.js";
5
5
  export interface NormalizedWorkflowMeta {
6
6
  name: string;
@@ -24,6 +24,9 @@ export interface BuildWorkflowProofInput {
24
24
  agents: WorkflowAgentProof[];
25
25
  evidence: WorkflowEvidenceRecord[];
26
26
  harnessEvidence?: Readonly<Record<string, HarnessEvidenceCapability | undefined>>;
27
+ /** Live attestations backing each harness's claims (ADR 0038). Injected by
28
+ * the caller so proof stays free of filesystem lookups. */
29
+ harnessAttestations?: Readonly<Record<string, HarnessAttestationCitation | undefined>>;
27
30
  policy?: {
28
31
  config: Readonly<NormalizedPolicy>;
29
32
  decisions: readonly PolicyDecision[];
@@ -1 +1 @@
1
- {"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,0BAA0B,EAC1B,yBAAyB,EAEzB,YAAY,EACZ,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EAEZ,aAAa,EAIb,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EAEhB,qCAAqC,EACtC,MAAM,uBAAuB,CAAC;AAoB/B,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,mBAAmB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IAC5C,cAAc,CAAC,EAAE,QAAQ,CAAC,sBAAsB,CAAC,CAAC;IAClD,IAAI,EAAE,sBAAsB,CAAC;IAC7B,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,GAAG,SAAS,CAAC,CAAC,CAAC;IAClF;+DAC2D;IAC3D,mBAAmB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,0BAA0B,GAAG,SAAS,CAAC,CAAC,CAAC;IACvF,MAAM,CAAC,EAAE;QACP,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACnC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;QACrC,SAAS,EAAE,eAAe,CAAC;QAC3B,aAAa,EAAE,mBAAmB,CAAC;KACpC,CAAC;IACF,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,iBAAiB,CAAC,EAAE,qCAAqC,CAAC;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,YAAY,GAAG,SAAS,EAC9B,YAAY,EAAE,MAAM,GACnB,sBAAsB,CAqCxB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,KAAK,EAAE,qBAAqB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,sBAAsB,CA6BzB;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,QAAQ,EAAE,sBAAsB,EAAE,GACjC;IAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,CAuB9D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAQjF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,aAAa,CAiDhF;AAgCD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAO3E;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAgDjF;AA2BD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAoChE"}
@@ -119,7 +119,7 @@ export function buildWorkflowProof(input) {
119
119
  session_id: clippedOptional(agent.session_id, MAX_REF_CHARS),
120
120
  error: clippedOptional(agent.error, MAX_SUMMARY_CHARS),
121
121
  }));
122
- const harnesses = buildHarnessCoverage(agents, input.harnessEvidence);
122
+ const harnesses = buildHarnessCoverage(agents, input.harnessEvidence, input.harnessAttestations);
123
123
  const unknowns = buildUnknowns(agents, harnesses, repository);
124
124
  const journal = readFileSync(input.journalPath);
125
125
  return {
@@ -328,7 +328,7 @@ function normalizeRepoSnapshot(snapshot) {
328
328
  dirty_paths_truncated: snapshot.dirty_paths_truncated,
329
329
  };
330
330
  }
331
- function buildHarnessCoverage(agents, claims) {
331
+ function buildHarnessCoverage(agents, claims, attestations) {
332
332
  return [...new Set(agents.map((agent) => agent.harness))].map((harness) => {
333
333
  const harnessAgents = agents.filter((agent) => agent.harness === harness);
334
334
  return {
@@ -342,6 +342,7 @@ function buildHarnessCoverage(agents, claims) {
342
342
  session_ids: harnessAgents.filter((agent) => agent.session_id).length,
343
343
  costs: harnessAgents.filter((agent) => agent.cost_usd !== undefined).length,
344
344
  },
345
+ ...(attestations?.[harness] ? { attestation: attestations[harness] } : {}),
345
346
  };
346
347
  });
347
348
  }
@@ -100,6 +100,13 @@ export interface WorkflowRepoEvidence {
100
100
  note?: string;
101
101
  };
102
102
  }
103
+ /** Bounded pointer to the live attestation backing a harness's claims
104
+ * (ADR 0038). Structural facts only: no prompt text, no host paths. */
105
+ export interface HarnessAttestationCitation {
106
+ binary_version: string;
107
+ observed_at: string;
108
+ record_digest: string;
109
+ }
103
110
  export interface HarnessEvidenceCoverage {
104
111
  harness: HarnessName;
105
112
  tool_evidence: {
@@ -111,6 +118,10 @@ export interface HarnessEvidenceCoverage {
111
118
  session_ids: number;
112
119
  costs: number;
113
120
  };
121
+ /** What backs this harness's capability claims (ADR 0038). Absent when the
122
+ * host recorded no live attestation, which is the common case and is not by
123
+ * itself a proof unknown. */
124
+ attestation?: HarnessAttestationCitation;
114
125
  }
115
126
  export interface WorkflowProofUnknown {
116
127
  code: "tool_evidence_unavailable" | "harness_capability_unregistered" | "agent_cost_unreported" | "agent_session_unreported" | "repository_drift_incomplete";
@@ -374,6 +385,9 @@ export interface EngineOpts {
374
385
  /** Capability claims used to state whether adapter-native tool evidence was
375
386
  * available. Missing claims remain unknown. */
376
387
  harnessEvidence?: Readonly<Record<HarnessName, HarnessEvidenceCapability | undefined>>;
388
+ /** Live attestations backing each harness's claims (ADR 0038). Read once by
389
+ * the host and injected, so the engine performs no capability lookups. */
390
+ harnessAttestations?: Readonly<Record<HarnessName, HarnessAttestationCitation | undefined>>;
377
391
  /** Immutable host policy. Workflow scripts and model prompts cannot replace it. */
378
392
  policy?: PolicySpec | NormalizedPolicy;
379
393
  /** Host callback for ASK. Missing, invalid, throwing, or timed-out resolution denies. */