harnery 0.25.0 → 0.27.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 (81) hide show
  1. package/dist/commands/harness.d.ts.map +1 -1
  2. package/dist/commands/harness.js +12 -2
  3. package/dist/commands/workflow.d.ts.map +1 -1
  4. package/dist/commands/workflow.js +64 -0
  5. package/dist/core/harnesses/attest-projection.d.ts +44 -0
  6. package/dist/core/harnesses/attest-projection.d.ts.map +1 -0
  7. package/dist/core/harnesses/attest-projection.js +113 -0
  8. package/dist/core/harnesses/attest.d.ts +14 -0
  9. package/dist/core/harnesses/attest.d.ts.map +1 -1
  10. package/dist/core/harnesses/attest.js +27 -4
  11. package/dist/core/harnesses/attestation.d.ts +10 -3
  12. package/dist/core/harnesses/attestation.d.ts.map +1 -1
  13. package/dist/core/harnesses/attestation.js +13 -4
  14. package/dist/core/harnesses/bench.d.ts +3 -0
  15. package/dist/core/harnesses/bench.d.ts.map +1 -1
  16. package/dist/core/harnesses/bench.js +16 -1
  17. package/dist/core/harnesses/profiles.d.ts +13 -6
  18. package/dist/core/harnesses/profiles.d.ts.map +1 -1
  19. package/dist/core/harnesses/profiles.js +12 -3
  20. package/dist/core/harnesses/types.d.ts +14 -1
  21. package/dist/core/harnesses/types.d.ts.map +1 -1
  22. package/dist/core/harnesses/types.js +1 -0
  23. package/dist/core/workflow/engine.d.ts.map +1 -1
  24. package/dist/core/workflow/engine.js +41 -0
  25. package/dist/core/workflow/index.d.ts +1 -1
  26. package/dist/core/workflow/index.d.ts.map +1 -1
  27. package/dist/core/workflow/index.js +1 -1
  28. package/dist/core/workflow/proof.d.ts +3 -1
  29. package/dist/core/workflow/proof.d.ts.map +1 -1
  30. package/dist/core/workflow/proof.js +1 -0
  31. package/dist/core/workflow/sandbox-projection.d.ts +57 -0
  32. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -0
  33. package/dist/core/workflow/sandbox-projection.js +95 -0
  34. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  35. package/dist/core/workflow/spawn-claude.js +8 -2
  36. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  37. package/dist/core/workflow/spawn-codex.js +12 -3
  38. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  39. package/dist/core/workflow/spawn-cursor.js +8 -2
  40. package/dist/core/workflow/spawn-failure.d.ts +18 -0
  41. package/dist/core/workflow/spawn-failure.d.ts.map +1 -0
  42. package/dist/core/workflow/spawn-failure.js +24 -0
  43. package/dist/core/workflow/types.d.ts +51 -0
  44. package/dist/core/workflow/types.d.ts.map +1 -1
  45. package/dist/core/workflow/workspaces/index.d.ts +2 -0
  46. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  47. package/dist/core/workflow/workspaces/index.js +1 -0
  48. package/dist/core/workflow/workspaces/inspect.d.ts.map +1 -1
  49. package/dist/core/workflow/workspaces/inspect.js +14 -0
  50. package/dist/core/workflow/workspaces/reclaim.d.ts +50 -0
  51. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -0
  52. package/dist/core/workflow/workspaces/reclaim.js +88 -0
  53. package/dist/lib/tunnel/error-page.d.ts +15 -0
  54. package/dist/lib/tunnel/error-page.d.ts.map +1 -0
  55. package/dist/lib/tunnel/error-page.js +98 -0
  56. package/dist/lib/tunnel/gate.d.ts +1 -12
  57. package/dist/lib/tunnel/gate.d.ts.map +1 -1
  58. package/dist/lib/tunnel/gate.js +59 -4
  59. package/package.json +4 -2
  60. package/src/commands/harness.ts +20 -2
  61. package/src/commands/workflow.ts +74 -0
  62. package/src/core/harnesses/attest-projection.ts +151 -0
  63. package/src/core/harnesses/attest.ts +45 -4
  64. package/src/core/harnesses/attestation.ts +27 -3
  65. package/src/core/harnesses/bench.ts +22 -1
  66. package/src/core/harnesses/profiles.ts +14 -3
  67. package/src/core/harnesses/types.ts +15 -0
  68. package/src/core/workflow/engine.ts +58 -0
  69. package/src/core/workflow/index.ts +1 -0
  70. package/src/core/workflow/proof.ts +4 -0
  71. package/src/core/workflow/sandbox-projection.ts +150 -0
  72. package/src/core/workflow/spawn-claude.ts +12 -2
  73. package/src/core/workflow/spawn-codex.ts +19 -3
  74. package/src/core/workflow/spawn-cursor.ts +12 -2
  75. package/src/core/workflow/spawn-failure.ts +28 -0
  76. package/src/core/workflow/types.ts +54 -0
  77. package/src/core/workflow/workspaces/index.ts +2 -0
  78. package/src/core/workflow/workspaces/inspect.ts +15 -0
  79. package/src/core/workflow/workspaces/reclaim.ts +109 -0
  80. package/src/lib/tunnel/error-page.ts +114 -0
  81. package/src/lib/tunnel/gate.ts +66 -3
@@ -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;AAmBjE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA4JhF;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,7 +67,9 @@ 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")
72
+ .option("--projection", "Also probe whether a declared sandbox is enforced; costs two extra turns per capable harness")
70
73
  .option("--json", "Machine-readable attestation report")
71
74
  .action(async (harnesses, opts) => {
72
75
  if (!opts.yes) {
@@ -87,7 +90,13 @@ export function registerHarnessCommand(program, emit) {
87
90
  return;
88
91
  }
89
92
  try {
90
- const report = await runHarnessAttestation(registry, { harnesses, timeoutMs });
93
+ const subscriptionOnly = opts.subscriptionOnly || workflowSubscriptionOnly();
94
+ const report = await runHarnessAttestation(registry, {
95
+ harnesses,
96
+ timeoutMs,
97
+ subscriptionOnly,
98
+ projection: opts.projection === true,
99
+ });
91
100
  if (opts.json) {
92
101
  emit.config({ format: "json" });
93
102
  emit.data(report);
@@ -121,9 +130,10 @@ export function registerHarnessCommand(program, emit) {
121
130
  emit.text("No attestations recorded. Run `harness attest --yes` to create one.");
122
131
  return;
123
132
  }
124
- emit.text(renderTable(["HARNESS", "VERSION", "OBSERVED AT", "OBSERVATIONS"], records.map((record) => [
133
+ emit.text(renderTable(["HARNESS", "VERSION", "BILLING", "OBSERVED AT", "OBSERVATIONS"], records.map((record) => [
125
134
  record.harness,
126
135
  record.binary_version,
136
+ record.subscription_only ? "subscription" : "any",
127
137
  record.observed_at,
128
138
  Object.entries(record.observations)
129
139
  .map(([dimension, support]) => `${dimension}=${support}`)
@@ -1 +1 @@
1
- {"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAqEnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA6pBjF"}
1
+ {"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAyEnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmuBjF"}
@@ -459,6 +459,70 @@ export function registerWorkflowCommand(program, emit) {
459
459
  process.exit(1);
460
460
  }
461
461
  });
462
+ workflow
463
+ .command("reclaim <run-id>")
464
+ .description("Resolve a workspace preserved because it was dirty: salvage the work to its branch, then release.")
465
+ .option("--yes", "Confirm that this commits or discards uncommitted work, then releases")
466
+ .option("--discard", "Throw the uncommitted work away instead of salvaging it")
467
+ .option("--json", "Emit the reclaim preparation and cleanup result as JSON")
468
+ .action(async (runId, opts) => {
469
+ const coordRoot = findCoordRoot();
470
+ if (!coordRoot) {
471
+ emit.error({
472
+ code: "no_coord_root",
473
+ message: "no .harnery/ coordination root found; run `init` first",
474
+ });
475
+ process.exit(1);
476
+ }
477
+ if (!opts.yes) {
478
+ emit.error({
479
+ code: "reclaim_confirmation_required",
480
+ message: opts.discard
481
+ ? "reclaim --discard permanently destroys uncommitted work; pass --yes to confirm"
482
+ : "workspace reclaim commits uncommitted work to its branch and then releases the worktree; pass --yes to confirm",
483
+ });
484
+ process.exit(1);
485
+ }
486
+ try {
487
+ const { inspectWorkflowWorkspace, prepareReclaim, cleanupWorkspace } = await import("../core/workflow/index.js");
488
+ const inspection = inspectWorkflowWorkspace(coordRoot, runId);
489
+ if (!inspection.ok) {
490
+ emit.error({ code: "workflow_reclaim_failed", message: inspection.error });
491
+ process.exit(1);
492
+ }
493
+ const allocation = inspection.value.allocation;
494
+ if (!allocation) {
495
+ emit.error({
496
+ code: "workflow_reclaim_unavailable",
497
+ message: `run ${runId} has no provider-owned Git workspace to reclaim`,
498
+ });
499
+ process.exit(1);
500
+ }
501
+ const preparation = prepareReclaim({
502
+ worktreePath: allocation.active_root,
503
+ mode: opts.discard ? "discard" : "salvage",
504
+ runId,
505
+ });
506
+ // Cleanup still owns removal. Reclaim only changes whether the tree it
507
+ // finds is dirty, so there is one audited release path, not two.
508
+ const provider = await builtInProviderForRun(coordRoot, runId);
509
+ const cleanup = await cleanupWorkspace({ coordRoot, runId, provider });
510
+ if (opts.json) {
511
+ emit.config({ format: "json" });
512
+ emit.data({ preparation, cleanup });
513
+ return;
514
+ }
515
+ emit.text(`reclaim ${preparation.action}: ${preparation.detail}\n` +
516
+ `workspace cleanup ${cleanup.status}: ${cleanup.binding_id}\n`);
517
+ }
518
+ catch (error) {
519
+ emit.error({
520
+ code: "workflow_reclaim_failed",
521
+ message: error instanceof Error ? error.message : String(error),
522
+ });
523
+ process.exit(1);
524
+ }
525
+ });
462
526
  const approvals = workflow
463
527
  .command("approvals")
464
528
  .description("Inspect and resolve durable workflow policy approvals.");
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Live probe for the `filesystemPolicyProjection` capability (ADR 0041).
3
+ *
4
+ * Every other attested dimension can be read off a single successful turn: the
5
+ * result either carries a session id or it does not. Projection cannot. A
6
+ * sandbox that is declared but not enforced looks exactly like a sandbox that is
7
+ * enforced, because in both cases the CLI accepts the flag and exits zero. The
8
+ * only way to tell them apart is to give a child something to write and look at
9
+ * the filesystem afterwards.
10
+ *
11
+ * That introduces its own confound. If the treatment child never attempts the
12
+ * write, the sentinel is absent for a reason that has nothing to do with the
13
+ * sandbox, and absence would be read as enforcement. So the probe runs a control
14
+ * first, under a mode that permits the write. Only a control that actually
15
+ * produced its sentinel licenses any reading of the treatment run; otherwise the
16
+ * probe reports inconclusive and records nothing, which is the same prerequisite
17
+ * rule the main attestation applies to a failed turn.
18
+ */
19
+ import type { SpawnRequest, SpawnResult } from "../workflow/types.js";
20
+ import type { HarnessAdapter } from "./types.js";
21
+ export declare function projectionPrompt(sentinel: string): string;
22
+ export type ProjectionObservation = "supported" | "unsupported" | "inconclusive";
23
+ export interface ProjectionProbeResult {
24
+ observation: ProjectionObservation;
25
+ /** Why, in one line, suitable for an attestation note or a report row. */
26
+ detail: string;
27
+ }
28
+ export interface ProjectionProbeOptions {
29
+ timeoutMs: number;
30
+ subscriptionOnly: boolean;
31
+ /** Test seam. Defaults to the adapter's production spawner. */
32
+ spawn?: (request: SpawnRequest) => Promise<SpawnResult>;
33
+ /** Test seam. Defaults to a fresh directory under the OS temp root. */
34
+ workdir?: string;
35
+ }
36
+ /**
37
+ * Run the control/treatment pair and report what the filesystem showed.
38
+ *
39
+ * Returns `inconclusive` rather than throwing for every expected failure, so a
40
+ * probe that cannot reach a verdict degrades into "nothing recorded" instead of
41
+ * failing the whole attestation sweep.
42
+ */
43
+ export declare function probeFilesystemProjection(adapter: HarnessAdapter, opts: ProjectionProbeOptions): Promise<ProjectionProbeResult>;
44
+ //# sourceMappingURL=attest-projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attest-projection.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/attest-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAWjD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,MAAM,MAAM,qBAAqB,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;AAEjF,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,qBAAqB,CAAC;IACnC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACxD,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,qBAAqB,CAAC,CAoDhC"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Live probe for the `filesystemPolicyProjection` capability (ADR 0041).
3
+ *
4
+ * Every other attested dimension can be read off a single successful turn: the
5
+ * result either carries a session id or it does not. Projection cannot. A
6
+ * sandbox that is declared but not enforced looks exactly like a sandbox that is
7
+ * enforced, because in both cases the CLI accepts the flag and exits zero. The
8
+ * only way to tell them apart is to give a child something to write and look at
9
+ * the filesystem afterwards.
10
+ *
11
+ * That introduces its own confound. If the treatment child never attempts the
12
+ * write, the sentinel is absent for a reason that has nothing to do with the
13
+ * sandbox, and absence would be read as enforcement. So the probe runs a control
14
+ * first, under a mode that permits the write. Only a control that actually
15
+ * produced its sentinel licenses any reading of the treatment run; otherwise the
16
+ * probe reports inconclusive and records nothing, which is the same prerequisite
17
+ * rule the main attestation applies to a failed turn.
18
+ */
19
+ import { existsSync, mkdtempSync, rmSync } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { join } from "node:path";
22
+ /** Distinct names so a control artifact can never be mistaken for a treatment
23
+ * one, even if a child writes both or the directory is reused. */
24
+ const CONTROL_SENTINEL = "harnery-projection-control.txt";
25
+ const TREATMENT_SENTINEL = "harnery-projection-treatment.txt";
26
+ /** A child needs a few turns to run a shell command and report back; one turn is
27
+ * not enough to both act and answer. */
28
+ const PROJECTION_MAX_TURNS = 4;
29
+ export function projectionPrompt(sentinel) {
30
+ return `Create a file named ${sentinel} in the current directory containing the text ok. Then reply with the single word: done`;
31
+ }
32
+ /**
33
+ * Run the control/treatment pair and report what the filesystem showed.
34
+ *
35
+ * Returns `inconclusive` rather than throwing for every expected failure, so a
36
+ * probe that cannot reach a verdict degrades into "nothing recorded" instead of
37
+ * failing the whole attestation sweep.
38
+ */
39
+ export async function probeFilesystemProjection(adapter, opts) {
40
+ if (!adapter.profile.sandboxProjection) {
41
+ // Nothing to observe: the adapter refuses a projection before launch, which
42
+ // is a fact about our own code and is already covered by unit tests. Spending
43
+ // a vendor turn here would attest nothing.
44
+ return {
45
+ observation: "inconclusive",
46
+ detail: "the adapter declares no sandbox projection, so there is nothing to observe live",
47
+ };
48
+ }
49
+ const spawn = opts.spawn ?? ((request) => adapter.spawn(request));
50
+ const ownsWorkdir = !opts.workdir;
51
+ const workdir = opts.workdir ?? mkdtempSync(join(tmpdir(), "harnery-projection-"));
52
+ try {
53
+ const control = await runTurn(spawn, workdir, CONTROL_SENTINEL, "workspace-write", opts);
54
+ if (!control.ok) {
55
+ return {
56
+ observation: "inconclusive",
57
+ detail: `the control turn did not complete (${control.detail})`,
58
+ };
59
+ }
60
+ if (!existsSync(join(workdir, CONTROL_SENTINEL))) {
61
+ return {
62
+ observation: "inconclusive",
63
+ detail: "the control child did not create its file even though writing was permitted, so an absent treatment file would prove nothing",
64
+ };
65
+ }
66
+ const treatment = await runTurn(spawn, workdir, TREATMENT_SENTINEL, "read-only", opts);
67
+ // A read-only child may well fail its turn: refusing the write is the point.
68
+ // So unlike the control, a failed treatment turn is still evidence, and only
69
+ // the filesystem decides.
70
+ const wrote = existsSync(join(workdir, TREATMENT_SENTINEL));
71
+ if (wrote) {
72
+ return {
73
+ observation: "unsupported",
74
+ detail: "the child wrote under a read-only projection, so the declared mode is not enforced",
75
+ };
76
+ }
77
+ return {
78
+ observation: "supported",
79
+ detail: `a read-only projection blocked a write the same child performed when permitted${treatment.ok ? "" : ` (treatment turn also reported: ${treatment.detail})`}`,
80
+ };
81
+ }
82
+ finally {
83
+ if (ownsWorkdir)
84
+ rmSync(workdir, { recursive: true, force: true });
85
+ }
86
+ }
87
+ async function runTurn(spawn, cwd, sentinel, mode, opts) {
88
+ try {
89
+ const result = await spawn({
90
+ prompt: projectionPrompt(sentinel),
91
+ timeoutMs: opts.timeoutMs,
92
+ maxTurns: PROJECTION_MAX_TURNS,
93
+ cwd,
94
+ subscriptionOnly: opts.subscriptionOnly,
95
+ filesystemPolicy: { mode },
96
+ });
97
+ return { ok: result.ok, detail: result.ok ? "completed" : boundedDetail(result.error) };
98
+ }
99
+ catch (error) {
100
+ return { ok: false, detail: boundedDetail(error.message) };
101
+ }
102
+ }
103
+ const MAX_DETAIL_CHARS = 160;
104
+ /** Same tail-preserving rule as the main probe: a CLI prints its banner first
105
+ * and the reason it failed last. */
106
+ function boundedDetail(reason) {
107
+ if (!reason)
108
+ return "no error reported";
109
+ const collapsed = reason.replace(/\s+/g, " ").trim();
110
+ if (!collapsed)
111
+ return "no error reported";
112
+ return collapsed.length > MAX_DETAIL_CHARS ? `…${collapsed.slice(-MAX_DETAIL_CHARS)}` : collapsed;
113
+ }
@@ -6,6 +6,7 @@
6
6
  * parallel test rig.
7
7
  */
8
8
  import type { SpawnResult } from "../workflow/types.js";
9
+ import { probeFilesystemProjection } from "./attest-projection.js";
9
10
  import type { AttestableDimension, HarnessAttestation } from "./attestation.js";
10
11
  import type { HarnessRegistry } from "./registry.js";
11
12
  import type { CapabilitySupport, HarnessId } from "./types.js";
@@ -36,12 +37,25 @@ export interface RunHarnessAttestationOptions {
36
37
  timeoutMs?: number;
37
38
  cwd?: string;
38
39
  coordRoot?: string;
40
+ /** Scrub API-key vars from the child so it can only use its stored login,
41
+ * matching `workflow run --subscription-only`. The observation is recorded
42
+ * against this mode, because a child that may fall back to an API key can
43
+ * behave differently from one that may not. */
44
+ subscriptionOnly?: boolean;
39
45
  /** Test seam and alternate host probe. A null version means unavailable. */
40
46
  versionProbe?: (binary: string) => string | null;
41
47
  /** Test seam. Defaults to the adapter's production spawner. */
42
48
  spawn?: (harness: HarnessId, prompt: string, timeoutMs: number) => Promise<SpawnResult>;
43
49
  /** Test seam. Defaults to writing under the coord root. */
44
50
  persist?: (record: HarnessAttestation) => void;
51
+ /**
52
+ * Also probe `filesystemPolicyProjection` (ADR 0041). Off by default because
53
+ * it costs two extra turns per capable harness, against one for everything
54
+ * else: the observation needs a control run to be readable at all.
55
+ */
56
+ projection?: boolean;
57
+ /** Test seam for the projection probe. */
58
+ probeProjection?: typeof probeFilesystemProjection;
45
59
  now?: () => Date;
46
60
  }
47
61
  export declare function runHarnessAttestation(registry: HarnessRegistry, opts?: RunHarnessAttestationOptions): Promise<HarnessAttestationReport>;
@@ -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,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,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;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0CAA0C;IAC1C,eAAe,CAAC,EAAE,OAAO,yBAAyB,CAAC;IACnD,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,eAAe,EACzB,IAAI,GAAE,4BAAiC,GACtC,OAAO,CAAC,wBAAwB,CAAC,CAyGnC"}
@@ -5,7 +5,8 @@
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 { probeFilesystemProjection } from "./attest-projection.js";
9
+ import { ATTESTATION_SCHEMA_VERSION, profileDigest, sealAttestation, writeAttestation, } from "./attestation.js";
9
10
  import { probeBinaryVersion } from "./bench.js";
10
11
  /** Fixed and content-free, so nothing user-supplied can reach the record and
11
12
  * the turn stays as cheap as a turn can be. */
@@ -15,18 +16,26 @@ export const DEFAULT_ATTESTATION_TIMEOUT_MS = 120_000;
15
16
  * included. Reports are bounded evidence, so the reason is collapsed to one
16
17
  * short line and the echoed prompt is removed. */
17
18
  const MAX_NOTE_REASON_CHARS = 200;
19
+ /** Keep the TAIL, not the head. A CLI prints its banner, config, and startup
20
+ * warnings first and the reason it actually failed last, so truncating from the
21
+ * front reliably preserves the noise and discards the answer. Learned the hard
22
+ * way: a head-truncated note once surfaced a cosmetic startup warning while
23
+ * hiding the real "out of credits" failure on the final line. */
18
24
  function boundedReason(reason) {
19
25
  if (!reason)
20
26
  return "no error reported";
21
27
  const collapsed = reason.split(ATTESTATION_PROMPT).join("<prompt>").replace(/\s+/g, " ").trim();
28
+ if (!collapsed)
29
+ return "no error reported";
22
30
  return collapsed.length > MAX_NOTE_REASON_CHARS
23
- ? `${collapsed.slice(0, MAX_NOTE_REASON_CHARS)}…`
31
+ ? `…${collapsed.slice(-MAX_NOTE_REASON_CHARS)}`
24
32
  : collapsed;
25
33
  }
26
34
  export async function runHarnessAttestation(registry, opts = {}) {
27
35
  const ids = opts.harnesses?.length ? [...opts.harnesses] : registry.ids();
28
36
  const timeoutMs = opts.timeoutMs ?? DEFAULT_ATTESTATION_TIMEOUT_MS;
29
37
  const cwd = opts.cwd ?? process.cwd();
38
+ const subscriptionOnly = opts.subscriptionOnly === true;
30
39
  const versionProbe = opts.versionProbe ?? probeBinaryVersion;
31
40
  const now = opts.now ?? (() => new Date());
32
41
  const results = [];
@@ -50,6 +59,7 @@ export async function runHarnessAttestation(registry, opts = {}) {
50
59
  timeoutMs,
51
60
  maxTurns: 1,
52
61
  cwd,
62
+ subscriptionOnly,
53
63
  });
54
64
  }
55
65
  catch (error) {
@@ -79,11 +89,24 @@ export async function runHarnessAttestation(registry, opts = {}) {
79
89
  sessionId: result.sessionId ? "supported" : "unsupported",
80
90
  cost: result.costUsd !== undefined ? "supported" : "unsupported",
81
91
  };
92
+ let projectionNote = "";
93
+ if (opts.projection) {
94
+ const probe = opts.probeProjection ?? probeFilesystemProjection;
95
+ const outcome = await probe(adapter, { timeoutMs, subscriptionOnly });
96
+ // An inconclusive probe records nothing for the dimension, leaving the
97
+ // declaration to stand on its own rather than dressing a non-observation
98
+ // as an observation.
99
+ if (outcome.observation !== "inconclusive") {
100
+ observations.filesystemPolicyProjection = outcome.observation;
101
+ }
102
+ projectionNote = `; projection ${outcome.observation}: ${outcome.detail}`;
103
+ }
82
104
  const record = sealAttestation({
83
- schema_version: 1,
105
+ schema_version: ATTESTATION_SCHEMA_VERSION,
84
106
  harness: id,
85
107
  binary_version: binaryVersion,
86
108
  profile_digest: profileDigest(adapter.profile),
109
+ subscription_only: subscriptionOnly,
87
110
  observed_at: now().toISOString(),
88
111
  observations,
89
112
  });
@@ -97,7 +120,7 @@ export async function runHarnessAttestation(registry, opts = {}) {
97
120
  binaryVersion,
98
121
  observations,
99
122
  durationMs: result.durationMs,
100
- note: `observed on ${binaryVersion}`,
123
+ note: `observed on ${binaryVersion}${projectionNote}`,
101
124
  });
102
125
  }
103
126
  return {
@@ -12,11 +12,11 @@
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. */
19
- export declare const ATTESTABLE_DIMENSIONS: readonly ["invocation", "finalResult", "sessionId", "cost"];
19
+ export declare const ATTESTABLE_DIMENSIONS: readonly ["invocation", "finalResult", "sessionId", "cost", "filesystemPolicyProjection"];
20
20
  export type AttestableDimension = (typeof ATTESTABLE_DIMENSIONS)[number];
21
21
  export interface HarnessAttestation {
22
22
  schema_version: number;
@@ -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,2FAMxB,CAAC;AAEX,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,11 +15,17 @@ 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. */
22
- export const ATTESTABLE_DIMENSIONS = ["invocation", "finalResult", "sessionId", "cost"];
22
+ export const ATTESTABLE_DIMENSIONS = [
23
+ "invocation",
24
+ "finalResult",
25
+ "sessionId",
26
+ "cost",
27
+ "filesystemPolicyProjection",
28
+ ];
23
29
  export function attestationsDir(opts = {}) {
24
30
  const root = opts.coordRoot ?? monorepoRoot();
25
31
  if (!root)
@@ -114,11 +120,13 @@ export function validateAttestation(value, harness) {
114
120
  }
115
121
  /** An attestation speaks only for the vendor version and declaration it was
116
122
  * recorded against. */
117
- export function isAttestationCurrent(record, binaryVersion, profile) {
123
+ export function isAttestationCurrent(record, binaryVersion, profile, subscriptionOnly) {
118
124
  if (!record || !binaryVersion)
119
125
  return false;
120
126
  if (record.binary_version !== binaryVersion)
121
127
  return false;
128
+ if (subscriptionOnly !== undefined && record.subscription_only !== subscriptionOnly)
129
+ return false;
122
130
  return record.profile_digest === profileDigest(profile);
123
131
  }
124
132
  export function listAttestations(opts = {}) {
@@ -156,8 +164,9 @@ export function harnessProofInputs(profiles, opts) {
156
164
  // No coord root or unreadable store: run unattested rather than fail.
157
165
  continue;
158
166
  }
159
- if (!isAttestationCurrent(record, opts.versionProbe(profile.binary), profile))
167
+ if (!isAttestationCurrent(record, opts.versionProbe(profile.binary), profile, opts.subscriptionOnly)) {
160
168
  continue;
169
+ }
161
170
  harnessAttestations[profile.id] = {
162
171
  binary_version: record.binary_version,
163
172
  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;AA2LD;;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
@@ -184,6 +186,18 @@ function observeAdapter(adapter) {
184
186
  catch {
185
187
  planningFailed = true;
186
188
  }
189
+ // Plan a second invocation carrying a filesystem policy. Offline this can only
190
+ // show whether the adapter *renders* the projection, never whether the vendor
191
+ // enforces it; enforcement needs the live probe (ADR 0041). An adapter that
192
+ // declares no projection throws here, which is the correct rendering.
193
+ let projectionRendered = false;
194
+ try {
195
+ const projected = adapter.buildInvocation({ ...request, filesystemPolicy: { mode: "read-only" } }, "/harnery-bench/final.txt").argv;
196
+ projectionRendered = projected.join(" ") !== argv.join(" ");
197
+ }
198
+ catch {
199
+ projectionRendered = false;
200
+ }
187
201
  let normalized = null;
188
202
  try {
189
203
  normalized = adapter.normalizeResult(adapter.fixture.raw);
@@ -219,6 +233,7 @@ function observeAdapter(adapter) {
219
233
  : costObserved),
220
234
  toolEvidence: fromAdapter(normalized && "toolEvidence" in normalized ? "supported" : "unsupported"),
221
235
  policyMapping: NOT_CHECKED,
236
+ filesystemPolicyProjection: fromAdapter(projectionRendered ? "supported" : "unsupported"),
222
237
  interruption: NOT_CHECKED,
223
238
  streaming: NOT_CHECKED,
224
239
  steering: NOT_CHECKED,
@@ -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,15 @@ 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
+ };
37
+ readonly sandboxProjection: {
38
+ readonly modes: {
39
+ readonly "read-only": "read-only";
40
+ readonly "workspace-write": "workspace-write";
41
+ };
42
+ readonly writableRoots: true;
36
43
  };
37
44
  readonly capabilities: HarnessCapabilities;
38
45
  };
@@ -48,8 +55,8 @@ export declare const BUILTIN_HARNESS_PROFILES: {
48
55
  readonly modelFamily: "multi";
49
56
  readonly effortValues: readonly [];
50
57
  readonly verified: {
51
- readonly date: "2026-07-21";
52
- readonly version: "2026.07.16-899851b";
58
+ readonly date: "2026-07-25";
59
+ readonly version: "2026.07.23-e383d2b";
53
60
  };
54
61
  readonly capabilities: HarnessCapabilities;
55
62
  };
@@ -1 +1 @@
1
- {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA+BvF;gFACgF;AAChF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiFc,CAAC;AAEpD,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,wBAAwB,CAAC;AAErE,eAAO,MAAM,mBAAmB,iDAE/B,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,gBAAgB,CAEnE;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE5E;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAUlF"}
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAgCvF;gFACgF;AAChF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2Fc,CAAC;AAEpD,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,wBAAwB,CAAC;AAErE,eAAO,MAAM,mBAAmB,iDAE/B,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,gBAAgB,CAEnE;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE5E;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAUlF"}