harnery 0.25.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 (32) hide show
  1. package/dist/commands/harness.d.ts.map +1 -1
  2. package/dist/commands/harness.js +10 -2
  3. package/dist/core/harnesses/attest.d.ts +5 -0
  4. package/dist/core/harnesses/attest.d.ts.map +1 -1
  5. package/dist/core/harnesses/attest.js +13 -3
  6. package/dist/core/harnesses/attestation.d.ts +9 -2
  7. package/dist/core/harnesses/attestation.d.ts.map +1 -1
  8. package/dist/core/harnesses/attestation.js +6 -3
  9. package/dist/core/harnesses/bench.d.ts +3 -0
  10. package/dist/core/harnesses/bench.d.ts.map +1 -1
  11. package/dist/core/harnesses/bench.js +3 -1
  12. package/dist/core/harnesses/profiles.d.ts +6 -6
  13. package/dist/core/harnesses/profiles.js +3 -3
  14. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  15. package/dist/core/workflow/spawn-claude.js +2 -1
  16. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  17. package/dist/core/workflow/spawn-codex.js +2 -1
  18. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  19. package/dist/core/workflow/spawn-cursor.js +2 -1
  20. package/dist/core/workflow/spawn-failure.d.ts +18 -0
  21. package/dist/core/workflow/spawn-failure.d.ts.map +1 -0
  22. package/dist/core/workflow/spawn-failure.js +24 -0
  23. package/package.json +4 -2
  24. package/src/commands/harness.ts +14 -2
  25. package/src/core/harnesses/attest.ts +22 -3
  26. package/src/core/harnesses/attestation.ts +20 -2
  27. package/src/core/harnesses/bench.ts +6 -1
  28. package/src/core/harnesses/profiles.ts +3 -3
  29. package/src/core/workflow/spawn-claude.ts +2 -1
  30. package/src/core/workflow/spawn-codex.ts +2 -1
  31. package/src/core/workflow/spawn-cursor.ts +2 -1
  32. package/src/core/workflow/spawn-failure.ts +28 -0
@@ -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,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"}
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;AAEnD,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EAIxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAkBjE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAuJhF;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"}
@@ -1,3 +1,4 @@
1
+ import { workflowSubscriptionOnly } from "../core/config.js";
1
2
  import { createBuiltinHarnessRegistry, listAttestations, runHarnessAttestation, runHarnessBench, } from "../core/harnesses/index.js";
2
3
  const registry = createBuiltinHarnessRegistry();
3
4
  export function registerHarnessCommand(program, emit) {
@@ -66,6 +67,7 @@ export function registerHarnessCommand(program, emit) {
66
67
  .command("attest [harnesses...]")
67
68
  .description("Record what the installed vendor CLIs actually do. Runs one real model turn each; needs --yes.")
68
69
  .option("--yes", "Confirm that this spends real vendor tokens")
70
+ .option("--subscription-only", "Scrub API-key vars so the child uses its stored login (repo default via config.jsonc workflow.subscriptionOnly)")
69
71
  .option("--timeout <ms>", "Per-harness probe timeout in milliseconds")
70
72
  .option("--json", "Machine-readable attestation report")
71
73
  .action(async (harnesses, opts) => {
@@ -87,7 +89,12 @@ export function registerHarnessCommand(program, emit) {
87
89
  return;
88
90
  }
89
91
  try {
90
- const report = await runHarnessAttestation(registry, { harnesses, timeoutMs });
92
+ const subscriptionOnly = opts.subscriptionOnly || workflowSubscriptionOnly();
93
+ const report = await runHarnessAttestation(registry, {
94
+ harnesses,
95
+ timeoutMs,
96
+ subscriptionOnly,
97
+ });
91
98
  if (opts.json) {
92
99
  emit.config({ format: "json" });
93
100
  emit.data(report);
@@ -121,9 +128,10 @@ export function registerHarnessCommand(program, emit) {
121
128
  emit.text("No attestations recorded. Run `harness attest --yes` to create one.");
122
129
  return;
123
130
  }
124
- emit.text(renderTable(["HARNESS", "VERSION", "OBSERVED AT", "OBSERVATIONS"], records.map((record) => [
131
+ emit.text(renderTable(["HARNESS", "VERSION", "BILLING", "OBSERVED AT", "OBSERVATIONS"], records.map((record) => [
125
132
  record.harness,
126
133
  record.binary_version,
134
+ record.subscription_only ? "subscription" : "any",
127
135
  record.observed_at,
128
136
  Object.entries(record.observations)
129
137
  .map(([dimension, support]) => `${dimension}=${support}`)
@@ -36,6 +36,11 @@ export interface RunHarnessAttestationOptions {
36
36
  timeoutMs?: number;
37
37
  cwd?: string;
38
38
  coordRoot?: string;
39
+ /** Scrub API-key vars from the child so it can only use its stored login,
40
+ * matching `workflow run --subscription-only`. The observation is recorded
41
+ * against this mode, because a child that may fall back to an API key can
42
+ * behave differently from one that may not. */
43
+ subscriptionOnly?: boolean;
39
44
  /** Test seam and alternate host probe. A null version means unavailable. */
40
45
  versionProbe?: (binary: string) => string | null;
41
46
  /** Test seam. Defaults to the adapter's production spawner. */
@@ -1 +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"}
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;AAQhF,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;AAqBtD,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;;;mDAG+C;IAC/C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,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,CA4FnC"}
@@ -5,7 +5,7 @@
5
5
  * uses, so what gets attested is the path production takes rather than a
6
6
  * parallel test rig.
7
7
  */
8
- import { profileDigest, sealAttestation, writeAttestation } from "./attestation.js";
8
+ import { ATTESTATION_SCHEMA_VERSION, profileDigest, sealAttestation, writeAttestation, } from "./attestation.js";
9
9
  import { probeBinaryVersion } from "./bench.js";
10
10
  /** Fixed and content-free, so nothing user-supplied can reach the record and
11
11
  * the turn stays as cheap as a turn can be. */
@@ -15,18 +15,26 @@ export const DEFAULT_ATTESTATION_TIMEOUT_MS = 120_000;
15
15
  * included. Reports are bounded evidence, so the reason is collapsed to one
16
16
  * short line and the echoed prompt is removed. */
17
17
  const MAX_NOTE_REASON_CHARS = 200;
18
+ /** Keep the TAIL, not the head. A CLI prints its banner, config, and startup
19
+ * warnings first and the reason it actually failed last, so truncating from the
20
+ * front reliably preserves the noise and discards the answer. Learned the hard
21
+ * way: a head-truncated note once surfaced a cosmetic startup warning while
22
+ * hiding the real "out of credits" failure on the final line. */
18
23
  function boundedReason(reason) {
19
24
  if (!reason)
20
25
  return "no error reported";
21
26
  const collapsed = reason.split(ATTESTATION_PROMPT).join("<prompt>").replace(/\s+/g, " ").trim();
27
+ if (!collapsed)
28
+ return "no error reported";
22
29
  return collapsed.length > MAX_NOTE_REASON_CHARS
23
- ? `${collapsed.slice(0, MAX_NOTE_REASON_CHARS)}…`
30
+ ? `…${collapsed.slice(-MAX_NOTE_REASON_CHARS)}`
24
31
  : collapsed;
25
32
  }
26
33
  export async function runHarnessAttestation(registry, opts = {}) {
27
34
  const ids = opts.harnesses?.length ? [...opts.harnesses] : registry.ids();
28
35
  const timeoutMs = opts.timeoutMs ?? DEFAULT_ATTESTATION_TIMEOUT_MS;
29
36
  const cwd = opts.cwd ?? process.cwd();
37
+ const subscriptionOnly = opts.subscriptionOnly === true;
30
38
  const versionProbe = opts.versionProbe ?? probeBinaryVersion;
31
39
  const now = opts.now ?? (() => new Date());
32
40
  const results = [];
@@ -50,6 +58,7 @@ export async function runHarnessAttestation(registry, opts = {}) {
50
58
  timeoutMs,
51
59
  maxTurns: 1,
52
60
  cwd,
61
+ subscriptionOnly,
53
62
  });
54
63
  }
55
64
  catch (error) {
@@ -80,10 +89,11 @@ export async function runHarnessAttestation(registry, opts = {}) {
80
89
  cost: result.costUsd !== undefined ? "supported" : "unsupported",
81
90
  };
82
91
  const record = sealAttestation({
83
- schema_version: 1,
92
+ schema_version: ATTESTATION_SCHEMA_VERSION,
84
93
  harness: id,
85
94
  binary_version: binaryVersion,
86
95
  profile_digest: profileDigest(adapter.profile),
96
+ subscription_only: subscriptionOnly,
87
97
  observed_at: now().toISOString(),
88
98
  observations,
89
99
  });
@@ -12,7 +12,7 @@
12
12
  * published.
13
13
  */
14
14
  import type { CapabilitySupport, HarnessId, HarnessProfile } from "./types.js";
15
- export declare const ATTESTATION_SCHEMA_VERSION = 1;
15
+ export declare const ATTESTATION_SCHEMA_VERSION = 2;
16
16
  /** Dimensions one minimal live turn can honestly establish. Everything else
17
17
  * needs a purpose-built scenario and stays outside the record rather than
18
18
  * being guessed at. */
@@ -27,6 +27,10 @@ export interface HarnessAttestation {
27
27
  /** Digest of the capability declaration at record time, so an edited
28
28
  * declaration also invalidates the record. */
29
29
  profile_digest: string;
30
+ /** The billing policy the probe ran under. A child launched with API keys
31
+ * scrubbed can behave differently from one that can fall back to them, so an
32
+ * observation only speaks for the mode it was made in. */
33
+ subscription_only: boolean;
30
34
  observed_at: string;
31
35
  /** Only what the probe actually saw. A dimension absent from this map was
32
36
  * not observed, which is not the same as unsupported. */
@@ -54,7 +58,7 @@ export declare function readAttestation(harness: HarnessId, opts?: AttestationSt
54
58
  export declare function validateAttestation(value: unknown, harness?: HarnessId): HarnessAttestation | null;
55
59
  /** An attestation speaks only for the vendor version and declaration it was
56
60
  * recorded against. */
57
- export declare function isAttestationCurrent(record: HarnessAttestation | null, binaryVersion: string | null, profile: HarnessProfile): record is HarnessAttestation;
61
+ export declare function isAttestationCurrent(record: HarnessAttestation | null, binaryVersion: string | null, profile: HarnessProfile, subscriptionOnly?: boolean): record is HarnessAttestation;
58
62
  export declare function listAttestations(opts?: AttestationStoreOptions): HarnessAttestation[];
59
63
  /** Both harness-derived proof inputs, read once, for a workflow run
60
64
  * (ADR 0038). Callers inject the result so the engine performs no capability
@@ -62,6 +66,9 @@ export declare function listAttestations(opts?: AttestationStoreOptions): Harnes
62
66
  * citation; that absence is not a proof unknown. */
63
67
  export declare function harnessProofInputs(profiles: readonly HarnessProfile[], opts: AttestationStoreOptions & {
64
68
  versionProbe: (binary: string) => string | null;
69
+ /** Billing policy this run will use, so a record made under the other mode
70
+ * is not cited as if it applied. */
71
+ subscriptionOnly?: boolean;
65
72
  }): {
66
73
  harnessEvidence: Record<string, {
67
74
  toolEvidence: HarnessProfile["capabilities"]["toolEvidence"];
@@ -1 +1 @@
1
- {"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/attestation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE/E,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;uBAEuB;AACvB,eAAO,MAAM,qBAAqB,6DAA8D,CAAC;AAEjG,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,SAAS,CAAC;IACnB;kFAC8E;IAC9E,cAAc,EAAE,MAAM,CAAC;IACvB;kDAC8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB;6DACyD;IACzD,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACtE,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,IAAI,GAAE,uBAA4B,GAAG,MAAM,CAI1E;AAED;wDACwD;AACxD,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAM7D;AAMD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,eAAe,CAAC,GAChD,kBAAkB,CAEpB;AASD;;aAEa;AACb,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,kBAAkB,EAC1B,IAAI,GAAE,uBAA4B,GACjC,MAAM,CAmBR;AAED;;kEAEkE;AAClE,wBAAgB,eAAe,CAC7B,OAAO,EAAE,SAAS,EAClB,IAAI,GAAE,uBAA4B,GACjC,kBAAkB,GAAG,IAAI,CAc3B;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,SAAS,GAClB,kBAAkB,GAAG,IAAI,CAe3B;AAED;uBACuB;AACvB,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,kBAAkB,GAAG,IAAI,EACjC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,OAAO,EAAE,cAAc,GACtB,MAAM,IAAI,kBAAkB,CAI9B;AAED,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,uBAA4B,GAAG,kBAAkB,EAAE,CAczF;AAED;;;oDAGoD;AACpD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,IAAI,EAAE,uBAAuB,GAAG;IAC9B,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACjD,GACA;IACD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,cAAc,CAAC,cAAc,CAAC,CAAC,cAAc,CAAC,CAAA;KAAE,CAAC,CAAC;IAClG,mBAAmB,EAAE,MAAM,CACzB,MAAM,EACN;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CACvE,CAAC;CACH,CA2BA"}
1
+ {"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/attestation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE/E,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;uBAEuB;AACvB,eAAO,MAAM,qBAAqB,6DAA8D,CAAC;AAEjG,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,SAAS,CAAC;IACnB;kFAC8E;IAC9E,cAAc,EAAE,MAAM,CAAC;IACvB;kDAC8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB;;8DAE0D;IAC1D,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB;6DACyD;IACzD,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACtE,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,IAAI,GAAE,uBAA4B,GAAG,MAAM,CAI1E;AAED;wDACwD;AACxD,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAM7D;AAMD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,eAAe,CAAC,GAChD,kBAAkB,CAEpB;AASD;;aAEa;AACb,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,kBAAkB,EAC1B,IAAI,GAAE,uBAA4B,GACjC,MAAM,CAmBR;AAED;;kEAEkE;AAClE,wBAAgB,eAAe,CAC7B,OAAO,EAAE,SAAS,EAClB,IAAI,GAAE,uBAA4B,GACjC,kBAAkB,GAAG,IAAI,CAc3B;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,SAAS,GAClB,kBAAkB,GAAG,IAAI,CAe3B;AAED;uBACuB;AACvB,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,kBAAkB,GAAG,IAAI,EACjC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,OAAO,EAAE,cAAc,EACvB,gBAAgB,CAAC,EAAE,OAAO,GACzB,MAAM,IAAI,kBAAkB,CAK9B;AAED,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,uBAA4B,GAAG,kBAAkB,EAAE,CAczF;AAED;;;oDAGoD;AACpD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,IAAI,EAAE,uBAAuB,GAAG;IAC9B,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAChD;wCACoC;IACpC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GACA;IACD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,cAAc,CAAC,cAAc,CAAC,CAAC,cAAc,CAAC,CAAA;KAAE,CAAC,CAAC;IAClG,mBAAmB,EAAE,MAAM,CACzB,MAAM,EACN;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CACvE,CAAC;CACH,CAoCA"}
@@ -15,7 +15,7 @@ import { mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFile
15
15
  import { resolve } from "node:path";
16
16
  import { monorepoRoot } from "../agents/coord-client.js";
17
17
  import { stableDigest } from "../workflow/durable-record.js";
18
- export const ATTESTATION_SCHEMA_VERSION = 1;
18
+ export const ATTESTATION_SCHEMA_VERSION = 2;
19
19
  /** Dimensions one minimal live turn can honestly establish. Everything else
20
20
  * needs a purpose-built scenario and stays outside the record rather than
21
21
  * being guessed at. */
@@ -114,11 +114,13 @@ export function validateAttestation(value, harness) {
114
114
  }
115
115
  /** An attestation speaks only for the vendor version and declaration it was
116
116
  * recorded against. */
117
- export function isAttestationCurrent(record, binaryVersion, profile) {
117
+ export function isAttestationCurrent(record, binaryVersion, profile, subscriptionOnly) {
118
118
  if (!record || !binaryVersion)
119
119
  return false;
120
120
  if (record.binary_version !== binaryVersion)
121
121
  return false;
122
+ if (subscriptionOnly !== undefined && record.subscription_only !== subscriptionOnly)
123
+ return false;
122
124
  return record.profile_digest === profileDigest(profile);
123
125
  }
124
126
  export function listAttestations(opts = {}) {
@@ -156,8 +158,9 @@ export function harnessProofInputs(profiles, opts) {
156
158
  // No coord root or unreadable store: run unattested rather than fail.
157
159
  continue;
158
160
  }
159
- if (!isAttestationCurrent(record, opts.versionProbe(profile.binary), profile))
161
+ if (!isAttestationCurrent(record, opts.versionProbe(profile.binary), profile, opts.subscriptionOnly)) {
160
162
  continue;
163
+ }
161
164
  harnessAttestations[profile.id] = {
162
165
  binary_version: record.binary_version,
163
166
  observed_at: record.observed_at,
@@ -42,6 +42,9 @@ export interface HarnessBenchOptions {
42
42
  coordRoot?: string;
43
43
  /** Test seam. Defaults to reading the attestation store. */
44
44
  attestationReader?: (harness: HarnessId) => HarnessAttestation | null;
45
+ /** Only cite attestations recorded under this billing mode. Omitted means
46
+ * any mode is acceptable for reporting purposes. */
47
+ subscriptionOnly?: boolean;
45
48
  }
46
49
  export declare function runHarnessBench(registry: HarnessRegistry, opts?: HarnessBenchOptions): HarnessBenchReport;
47
50
  /** Ask an installed vendor CLI what version it is. Null when it is absent or
@@ -1 +1 @@
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
+ {"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;IACtE;wDACoD;IACpD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAkDD,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"}
@@ -30,7 +30,9 @@ function loadAttestation(id, version, adapter, opts) {
30
30
  // No coord root, unreadable store: the bench still runs, just unattested.
31
31
  return null;
32
32
  }
33
- return isAttestationCurrent(record, version, adapter.profile) ? record : null;
33
+ return isAttestationCurrent(record, version, adapter.profile, opts.subscriptionOnly)
34
+ ? record
35
+ : null;
34
36
  }
35
37
  /** First version-shaped token in a string, or null when there is none.
36
38
  * Tolerates the vendor prefixes and suffixes real CLIs print, so
@@ -14,8 +14,8 @@ export declare const BUILTIN_HARNESS_PROFILES: {
14
14
  readonly modelFamily: "claude";
15
15
  readonly effortValues: readonly ["low", "medium", "high", "xhigh", "max"];
16
16
  readonly verified: {
17
- readonly date: "2026-07-20";
18
- readonly version: "current CLI contract";
17
+ readonly date: "2026-07-25";
18
+ readonly version: "2.1.197 (Claude Code)";
19
19
  };
20
20
  readonly capabilities: HarnessCapabilities;
21
21
  };
@@ -31,8 +31,8 @@ export declare const BUILTIN_HARNESS_PROFILES: {
31
31
  readonly modelFamily: "gpt";
32
32
  readonly effortValues: readonly ["none", "minimal", "low", "medium", "high", "xhigh"];
33
33
  readonly verified: {
34
- readonly date: "2026-07-21";
35
- readonly version: "codex-cli 0.145.0-alpha.18";
34
+ readonly date: "2026-07-25";
35
+ readonly version: "codex-cli 0.144.5";
36
36
  };
37
37
  readonly capabilities: HarnessCapabilities;
38
38
  };
@@ -48,8 +48,8 @@ export declare const BUILTIN_HARNESS_PROFILES: {
48
48
  readonly modelFamily: "multi";
49
49
  readonly effortValues: readonly [];
50
50
  readonly verified: {
51
- readonly date: "2026-07-21";
52
- readonly version: "2026.07.16-899851b";
51
+ readonly date: "2026-07-25";
52
+ readonly version: "2026.07.23-e383d2b";
53
53
  };
54
54
  readonly capabilities: HarnessCapabilities;
55
55
  };
@@ -39,7 +39,7 @@ export const BUILTIN_HARNESS_PROFILES = {
39
39
  authModel: "own-auth",
40
40
  modelFamily: "claude",
41
41
  effortValues: ["low", "medium", "high", "xhigh", "max"],
42
- verified: { date: "2026-07-20", version: "current CLI contract" },
42
+ verified: { date: "2026-07-25", version: "2.1.197 (Claude Code)" },
43
43
  capabilities: capabilities({
44
44
  effortSelection: supported("Mapped to `--effort <level>`."),
45
45
  maxTurns: supported("Mapped to `--max-turns <n>`."),
@@ -61,7 +61,7 @@ export const BUILTIN_HARNESS_PROFILES = {
61
61
  authModel: "own-auth",
62
62
  modelFamily: "gpt",
63
63
  effortValues: ["none", "minimal", "low", "medium", "high", "xhigh"],
64
- verified: { date: "2026-07-21", version: "codex-cli 0.145.0-alpha.18" },
64
+ verified: { date: "2026-07-25", version: "codex-cli 0.144.5" },
65
65
  capabilities: capabilities({
66
66
  effortSelection: supported('Mapped to `-c model_reasoning_effort="<level>"`.'),
67
67
  maxTurns: unsupported("codex exec exposes no turn-ceiling flag."),
@@ -83,7 +83,7 @@ export const BUILTIN_HARNESS_PROFILES = {
83
83
  authModel: "own-auth",
84
84
  modelFamily: "multi",
85
85
  effortValues: [],
86
- verified: { date: "2026-07-21", version: "2026.07.16-899851b" },
86
+ verified: { date: "2026-07-25", version: "2026.07.23-e383d2b" },
87
87
  capabilities: capabilities({
88
88
  effortSelection: unsupported("Cursor embeds effort in some parameterized model ids; Harnery does not rewrite model ids."),
89
89
  maxTurns: unsupported("cursor-agent exposes no turn-ceiling flag."),
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYrE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAc1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAgDxE;AAED,eAAO,MAAM,iBAAiB,EAAE,OAc/B,CAAC"}
1
+ {"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYrE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAc1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAgDxE;AAED,eAAO,MAAM,iBAAiB,EAAE,OAc/B,CAAC"}
@@ -21,6 +21,7 @@ import { exec } from "../../lib/exec.js";
21
21
  import { validateHarnessEffort } from "../harnesses/profiles.js";
22
22
  import { buildChildEnv } from "./child-env.js";
23
23
  import { notFoundError } from "./harnesses.js";
24
+ import { vendorFailureText } from "./spawn-failure.js";
24
25
  export function buildClaudeInvocation(req) {
25
26
  validateHarnessEffort("claude-code", req.effort);
26
27
  const argv = [
@@ -52,7 +53,7 @@ export function normalizeClaudeResult(raw) {
52
53
  ok: false,
53
54
  text: "",
54
55
  durationMs: raw.durationMs,
55
- error: `claude exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
56
+ error: `claude exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
56
57
  };
57
58
  }
58
59
  let envelope;
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAgB9F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAiBvE;AAED,eAAO,MAAM,YAAY,EAAE,OA2B1B,CAAC"}
1
+ {"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAgB9F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAiBvE;AAED,eAAO,MAAM,YAAY,EAAE,OA2B1B,CAAC"}
@@ -22,6 +22,7 @@ import { exec } from "../../lib/exec.js";
22
22
  import { validateHarnessEffort } from "../harnesses/profiles.js";
23
23
  import { buildChildEnv } from "./child-env.js";
24
24
  import { notFoundError } from "./harnesses.js";
25
+ import { vendorFailureText } from "./spawn-failure.js";
25
26
  export function buildCodexInvocation(req, resultFile) {
26
27
  validateHarnessEffort("codex", req.effort);
27
28
  if (!resultFile)
@@ -51,7 +52,7 @@ export function normalizeCodexResult(raw) {
51
52
  ok: false,
52
53
  text: "",
53
54
  durationMs: raw.durationMs,
54
- error: `codex exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
55
+ error: `codex exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
55
56
  };
56
57
  }
57
58
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAU1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA6BxE;AAED,eAAO,MAAM,aAAa,EAAE,OAe3B,CAAC"}
1
+ {"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAU1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA6BxE;AAED,eAAO,MAAM,aAAa,EAAE,OAe3B,CAAC"}
@@ -22,6 +22,7 @@ import { exec } from "../../lib/exec.js";
22
22
  import { validateHarnessEffort } from "../harnesses/profiles.js";
23
23
  import { buildChildEnv } from "./child-env.js";
24
24
  import { notFoundError } from "./harnesses.js";
25
+ import { vendorFailureText } from "./spawn-failure.js";
25
26
  /** Exported for unit tests (no live binary to test against). */
26
27
  export function parseCursorOutput(stdout) {
27
28
  try {
@@ -57,7 +58,7 @@ export function normalizeCursorResult(raw) {
57
58
  ok: false,
58
59
  text: "",
59
60
  durationMs: raw.durationMs,
60
- error: `cursor-agent exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
61
+ error: `cursor-agent exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
61
62
  };
62
63
  }
63
64
  const parsed = parseCursorOutput(raw.stdout);
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Shared failure-text extraction for the harness spawn adapters.
3
+ *
4
+ * A vendor CLI prints its banner, resolved config, and startup warnings first,
5
+ * and the reason it actually failed last. Every adapter used to keep the FIRST
6
+ * 500 characters of the transcript, which reliably preserved the banner and
7
+ * discarded the answer: a child that died with "your workspace is out of
8
+ * credits" reported a cosmetic startup warning instead.
9
+ *
10
+ * Two rules follow. Keep the tail. Include both streams, because which one
11
+ * carries the reason varies by vendor and by failure.
12
+ */
13
+ /** Bounded failure text from a finished child, tail-preserving. */
14
+ export declare function vendorFailureText(raw: {
15
+ stdout?: string;
16
+ stderr?: string;
17
+ }, maxChars?: number): string;
18
+ //# sourceMappingURL=spawn-failure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-failure.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,mEAAmE;AACnE,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EACzC,QAAQ,SAAoB,GAC3B,MAAM,CAQR"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared failure-text extraction for the harness spawn adapters.
3
+ *
4
+ * A vendor CLI prints its banner, resolved config, and startup warnings first,
5
+ * and the reason it actually failed last. Every adapter used to keep the FIRST
6
+ * 500 characters of the transcript, which reliably preserved the banner and
7
+ * discarded the answer: a child that died with "your workspace is out of
8
+ * credits" reported a cosmetic startup warning instead.
9
+ *
10
+ * Two rules follow. Keep the tail. Include both streams, because which one
11
+ * carries the reason varies by vendor and by failure.
12
+ */
13
+ const DEFAULT_MAX_CHARS = 500;
14
+ /** Bounded failure text from a finished child, tail-preserving. */
15
+ export function vendorFailureText(raw, maxChars = DEFAULT_MAX_CHARS) {
16
+ const parts = [raw.stderr, raw.stdout]
17
+ .map((part) => part?.trim())
18
+ .filter((part) => !!part);
19
+ if (parts.length === 0)
20
+ return "";
21
+ // Both streams, stderr first, so a reason on the unexpected stream survives.
22
+ const combined = parts.join("\n");
23
+ return combined.length > maxChars ? `…${combined.slice(-maxChars)}` : combined;
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harnery",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Multi-agent coordination for AI coding agents - Claude Code, Cursor, and Codex.",
5
5
  "license": "MIT",
6
6
  "author": "Ryan Kelly",
@@ -186,7 +186,9 @@
186
186
  "smoke": "node scripts/smoke-test.mjs",
187
187
  "docs:dev": "bun run --cwd docs dev",
188
188
  "docs:build": "bun run --cwd docs build",
189
- "brand:showcase": "node scripts/build-brand-showcase.mjs"
189
+ "brand:showcase": "node scripts/build-brand-showcase.mjs",
190
+ "verify:contracts": "bun run scripts/sync-verified-contracts.ts",
191
+ "verify:contracts:check": "bun run scripts/sync-verified-contracts.ts --check"
190
192
  },
191
193
  "dependencies": {
192
194
  "@mozilla/readability": "^0.6.0",
@@ -1,5 +1,6 @@
1
1
  import type { Command } from "commander";
2
2
  import type { EmitContext } from "../commander.ts";
3
+ import { workflowSubscriptionOnly } from "../core/config.ts";
3
4
  import {
4
5
  type BenchResult,
5
6
  createBuiltinHarnessRegistry,
@@ -22,6 +23,7 @@ interface BenchOpts extends FormatOpts {
22
23
  interface AttestOpts extends FormatOpts {
23
24
  timeout?: string;
24
25
  yes?: boolean;
26
+ subscriptionOnly?: boolean;
25
27
  }
26
28
 
27
29
  const registry = createBuiltinHarnessRegistry();
@@ -94,6 +96,10 @@ export function registerHarnessCommand(program: Command, emit: EmitContext): voi
94
96
  "Record what the installed vendor CLIs actually do. Runs one real model turn each; needs --yes.",
95
97
  )
96
98
  .option("--yes", "Confirm that this spends real vendor tokens")
99
+ .option(
100
+ "--subscription-only",
101
+ "Scrub API-key vars so the child uses its stored login (repo default via config.jsonc workflow.subscriptionOnly)",
102
+ )
97
103
  .option("--timeout <ms>", "Per-harness probe timeout in milliseconds")
98
104
  .option("--json", "Machine-readable attestation report")
99
105
  .action(async (harnesses: string[], opts: AttestOpts) => {
@@ -116,7 +122,12 @@ export function registerHarnessCommand(program: Command, emit: EmitContext): voi
116
122
  return;
117
123
  }
118
124
  try {
119
- const report = await runHarnessAttestation(registry, { harnesses, timeoutMs });
125
+ const subscriptionOnly = opts.subscriptionOnly || workflowSubscriptionOnly();
126
+ const report = await runHarnessAttestation(registry, {
127
+ harnesses,
128
+ timeoutMs,
129
+ subscriptionOnly,
130
+ });
120
131
  if (opts.json) {
121
132
  emit.config({ format: "json" });
122
133
  emit.data(report);
@@ -151,10 +162,11 @@ export function registerHarnessCommand(program: Command, emit: EmitContext): voi
151
162
  }
152
163
  emit.text(
153
164
  renderTable(
154
- ["HARNESS", "VERSION", "OBSERVED AT", "OBSERVATIONS"],
165
+ ["HARNESS", "VERSION", "BILLING", "OBSERVED AT", "OBSERVATIONS"],
155
166
  records.map((record) => [
156
167
  record.harness,
157
168
  record.binary_version,
169
+ record.subscription_only ? "subscription" : "any",
158
170
  record.observed_at,
159
171
  Object.entries(record.observations)
160
172
  .map(([dimension, support]) => `${dimension}=${support}`)
@@ -8,7 +8,12 @@
8
8
 
9
9
  import type { SpawnResult } from "../workflow/types.ts";
10
10
  import type { AttestableDimension, HarnessAttestation } from "./attestation.ts";
11
- import { profileDigest, sealAttestation, writeAttestation } from "./attestation.ts";
11
+ import {
12
+ ATTESTATION_SCHEMA_VERSION,
13
+ profileDigest,
14
+ sealAttestation,
15
+ writeAttestation,
16
+ } from "./attestation.ts";
12
17
  import { probeBinaryVersion } from "./bench.ts";
13
18
  import type { HarnessRegistry } from "./registry.ts";
14
19
  import type { CapabilitySupport, HarnessId } from "./types.ts";
@@ -24,11 +29,17 @@ export const DEFAULT_ATTESTATION_TIMEOUT_MS = 120_000;
24
29
  * short line and the echoed prompt is removed. */
25
30
  const MAX_NOTE_REASON_CHARS = 200;
26
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. */
27
37
  function boundedReason(reason: string | undefined): string {
28
38
  if (!reason) return "no error reported";
29
39
  const collapsed = reason.split(ATTESTATION_PROMPT).join("<prompt>").replace(/\s+/g, " ").trim();
40
+ if (!collapsed) return "no error reported";
30
41
  return collapsed.length > MAX_NOTE_REASON_CHARS
31
- ? `${collapsed.slice(0, MAX_NOTE_REASON_CHARS)}…`
42
+ ? `…${collapsed.slice(-MAX_NOTE_REASON_CHARS)}`
32
43
  : collapsed;
33
44
  }
34
45
 
@@ -58,6 +69,11 @@ export interface RunHarnessAttestationOptions {
58
69
  timeoutMs?: number;
59
70
  cwd?: string;
60
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;
61
77
  /** Test seam and alternate host probe. A null version means unavailable. */
62
78
  versionProbe?: (binary: string) => string | null;
63
79
  /** Test seam. Defaults to the adapter's production spawner. */
@@ -74,6 +90,7 @@ export async function runHarnessAttestation(
74
90
  const ids = opts.harnesses?.length ? [...opts.harnesses] : registry.ids();
75
91
  const timeoutMs = opts.timeoutMs ?? DEFAULT_ATTESTATION_TIMEOUT_MS;
76
92
  const cwd = opts.cwd ?? process.cwd();
93
+ const subscriptionOnly = opts.subscriptionOnly === true;
77
94
  const versionProbe = opts.versionProbe ?? probeBinaryVersion;
78
95
  const now = opts.now ?? (() => new Date());
79
96
  const results: HarnessAttestationResult[] = [];
@@ -99,6 +116,7 @@ export async function runHarnessAttestation(
99
116
  timeoutMs,
100
117
  maxTurns: 1,
101
118
  cwd,
119
+ subscriptionOnly,
102
120
  });
103
121
  } catch (error) {
104
122
  results.push({
@@ -131,10 +149,11 @@ export async function runHarnessAttestation(
131
149
  };
132
150
 
133
151
  const record = sealAttestation({
134
- schema_version: 1,
152
+ schema_version: ATTESTATION_SCHEMA_VERSION,
135
153
  harness: id,
136
154
  binary_version: binaryVersion,
137
155
  profile_digest: profileDigest(adapter.profile),
156
+ subscription_only: subscriptionOnly,
138
157
  observed_at: now().toISOString(),
139
158
  observations,
140
159
  });
@@ -25,7 +25,7 @@ import { monorepoRoot } from "../agents/coord-client.ts";
25
25
  import { stableDigest } from "../workflow/durable-record.ts";
26
26
  import type { CapabilitySupport, HarnessId, HarnessProfile } from "./types.ts";
27
27
 
28
- export const ATTESTATION_SCHEMA_VERSION = 1;
28
+ export const ATTESTATION_SCHEMA_VERSION = 2;
29
29
 
30
30
  /** Dimensions one minimal live turn can honestly establish. Everything else
31
31
  * needs a purpose-built scenario and stays outside the record rather than
@@ -43,6 +43,10 @@ export interface HarnessAttestation {
43
43
  /** Digest of the capability declaration at record time, so an edited
44
44
  * declaration also invalidates the record. */
45
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;
46
50
  observed_at: string;
47
51
  /** Only what the probe actually saw. A dimension absent from this map was
48
52
  * not observed, which is not the same as unsupported. */
@@ -164,9 +168,11 @@ export function isAttestationCurrent(
164
168
  record: HarnessAttestation | null,
165
169
  binaryVersion: string | null,
166
170
  profile: HarnessProfile,
171
+ subscriptionOnly?: boolean,
167
172
  ): record is HarnessAttestation {
168
173
  if (!record || !binaryVersion) return false;
169
174
  if (record.binary_version !== binaryVersion) return false;
175
+ if (subscriptionOnly !== undefined && record.subscription_only !== subscriptionOnly) return false;
170
176
  return record.profile_digest === profileDigest(profile);
171
177
  }
172
178
 
@@ -194,6 +200,9 @@ export function harnessProofInputs(
194
200
  profiles: readonly HarnessProfile[],
195
201
  opts: AttestationStoreOptions & {
196
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;
197
206
  },
198
207
  ): {
199
208
  harnessEvidence: Record<string, { toolEvidence: HarnessProfile["capabilities"]["toolEvidence"] }>;
@@ -220,7 +229,16 @@ export function harnessProofInputs(
220
229
  // No coord root or unreadable store: run unattested rather than fail.
221
230
  continue;
222
231
  }
223
- if (!isAttestationCurrent(record, opts.versionProbe(profile.binary), profile)) continue;
232
+ if (
233
+ !isAttestationCurrent(
234
+ record,
235
+ opts.versionProbe(profile.binary),
236
+ profile,
237
+ opts.subscriptionOnly,
238
+ )
239
+ ) {
240
+ continue;
241
+ }
224
242
  harnessAttestations[profile.id] = {
225
243
  binary_version: record.binary_version,
226
244
  observed_at: record.observed_at,
@@ -65,6 +65,9 @@ export interface HarnessBenchOptions {
65
65
  coordRoot?: string;
66
66
  /** Test seam. Defaults to reading the attestation store. */
67
67
  attestationReader?: (harness: HarnessId) => HarnessAttestation | null;
68
+ /** Only cite attestations recorded under this billing mode. Omitted means
69
+ * any mode is acceptable for reporting purposes. */
70
+ subscriptionOnly?: boolean;
68
71
  }
69
72
 
70
73
  const EMPTY_SUMMARY: Record<BenchVerdict, number> = {
@@ -101,7 +104,9 @@ function loadAttestation(
101
104
  // No coord root, unreadable store: the bench still runs, just unattested.
102
105
  return null;
103
106
  }
104
- return isAttestationCurrent(record, version, adapter.profile) ? record : null;
107
+ return isAttestationCurrent(record, version, adapter.profile, opts.subscriptionOnly)
108
+ ? record
109
+ : null;
105
110
  }
106
111
 
107
112
  /** First version-shaped token in a string, or null when there is none.
@@ -43,7 +43,7 @@ export const BUILTIN_HARNESS_PROFILES = {
43
43
  authModel: "own-auth",
44
44
  modelFamily: "claude",
45
45
  effortValues: ["low", "medium", "high", "xhigh", "max"],
46
- verified: { date: "2026-07-20", version: "current CLI contract" },
46
+ verified: { date: "2026-07-25", version: "2.1.197 (Claude Code)" },
47
47
  capabilities: capabilities({
48
48
  effortSelection: supported("Mapped to `--effort <level>`."),
49
49
  maxTurns: supported("Mapped to `--max-turns <n>`."),
@@ -69,7 +69,7 @@ export const BUILTIN_HARNESS_PROFILES = {
69
69
  authModel: "own-auth",
70
70
  modelFamily: "gpt",
71
71
  effortValues: ["none", "minimal", "low", "medium", "high", "xhigh"],
72
- verified: { date: "2026-07-21", version: "codex-cli 0.145.0-alpha.18" },
72
+ verified: { date: "2026-07-25", version: "codex-cli 0.144.5" },
73
73
  capabilities: capabilities({
74
74
  effortSelection: supported('Mapped to `-c model_reasoning_effort="<level>"`.'),
75
75
  maxTurns: unsupported("codex exec exposes no turn-ceiling flag."),
@@ -95,7 +95,7 @@ export const BUILTIN_HARNESS_PROFILES = {
95
95
  authModel: "own-auth",
96
96
  modelFamily: "multi",
97
97
  effortValues: [],
98
- verified: { date: "2026-07-21", version: "2026.07.16-899851b" },
98
+ verified: { date: "2026-07-25", version: "2026.07.23-e383d2b" },
99
99
  capabilities: capabilities({
100
100
  effortSelection: unsupported(
101
101
  "Cursor embeds effort in some parameterized model ids; Harnery does not rewrite model ids.",
@@ -23,6 +23,7 @@ import { validateHarnessEffort } from "../harnesses/profiles.ts";
23
23
  import type { HarnessInvocation, HarnessRawResult } from "../harnesses/types.ts";
24
24
  import { buildChildEnv } from "./child-env.ts";
25
25
  import { notFoundError } from "./harnesses.ts";
26
+ import { vendorFailureText } from "./spawn-failure.ts";
26
27
  import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
27
28
 
28
29
  interface ClaudeEnvelope {
@@ -65,7 +66,7 @@ export function normalizeClaudeResult(raw: HarnessRawResult): SpawnResult {
65
66
  ok: false,
66
67
  text: "",
67
68
  durationMs: raw.durationMs,
68
- error: `claude exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
69
+ error: `claude exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
69
70
  };
70
71
  }
71
72
 
@@ -24,6 +24,7 @@ import { validateHarnessEffort } from "../harnesses/profiles.ts";
24
24
  import type { HarnessInvocation, HarnessRawResult } from "../harnesses/types.ts";
25
25
  import { buildChildEnv } from "./child-env.ts";
26
26
  import { notFoundError } from "./harnesses.ts";
27
+ import { vendorFailureText } from "./spawn-failure.ts";
27
28
  import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
28
29
 
29
30
  export function buildCodexInvocation(req: SpawnRequest, resultFile?: string): HarnessInvocation {
@@ -53,7 +54,7 @@ export function normalizeCodexResult(raw: HarnessRawResult): SpawnResult {
53
54
  ok: false,
54
55
  text: "",
55
56
  durationMs: raw.durationMs,
56
- error: `codex exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
57
+ error: `codex exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
57
58
  };
58
59
  }
59
60
  return {
@@ -24,6 +24,7 @@ import { validateHarnessEffort } from "../harnesses/profiles.ts";
24
24
  import type { HarnessInvocation, HarnessRawResult } from "../harnesses/types.ts";
25
25
  import { buildChildEnv } from "./child-env.ts";
26
26
  import { notFoundError } from "./harnesses.ts";
27
+ import { vendorFailureText } from "./spawn-failure.ts";
27
28
  import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
28
29
 
29
30
  interface CursorEnvelope {
@@ -72,7 +73,7 @@ export function normalizeCursorResult(raw: HarnessRawResult): SpawnResult {
72
73
  ok: false,
73
74
  text: "",
74
75
  durationMs: raw.durationMs,
75
- error: `cursor-agent exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
76
+ error: `cursor-agent exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
76
77
  };
77
78
  }
78
79
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared failure-text extraction for the harness spawn adapters.
3
+ *
4
+ * A vendor CLI prints its banner, resolved config, and startup warnings first,
5
+ * and the reason it actually failed last. Every adapter used to keep the FIRST
6
+ * 500 characters of the transcript, which reliably preserved the banner and
7
+ * discarded the answer: a child that died with "your workspace is out of
8
+ * credits" reported a cosmetic startup warning instead.
9
+ *
10
+ * Two rules follow. Keep the tail. Include both streams, because which one
11
+ * carries the reason varies by vendor and by failure.
12
+ */
13
+
14
+ const DEFAULT_MAX_CHARS = 500;
15
+
16
+ /** Bounded failure text from a finished child, tail-preserving. */
17
+ export function vendorFailureText(
18
+ raw: { stdout?: string; stderr?: string },
19
+ maxChars = DEFAULT_MAX_CHARS,
20
+ ): string {
21
+ const parts = [raw.stderr, raw.stdout]
22
+ .map((part) => part?.trim())
23
+ .filter((part): part is string => !!part);
24
+ if (parts.length === 0) return "";
25
+ // Both streams, stderr first, so a reason on the unexpected stream survives.
26
+ const combined = parts.join("\n");
27
+ return combined.length > maxChars ? `…${combined.slice(-maxChars)}` : combined;
28
+ }