harnery 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/harness.d.ts +2 -1
  3. package/dist/commands/harness.d.ts.map +1 -1
  4. package/dist/commands/harness.js +105 -3
  5. package/dist/commands/supervisor.d.ts.map +1 -1
  6. package/dist/commands/supervisor.js +3 -13
  7. package/dist/commands/work.d.ts.map +1 -1
  8. package/dist/commands/work.js +2 -7
  9. package/dist/commands/workflow.d.ts.map +1 -1
  10. package/dist/commands/workflow.js +215 -17
  11. package/dist/core/harnesses/attest.d.ts +48 -0
  12. package/dist/core/harnesses/attest.d.ts.map +1 -0
  13. package/dist/core/harnesses/attest.js +110 -0
  14. package/dist/core/harnesses/attestation.d.ts +75 -0
  15. package/dist/core/harnesses/attestation.d.ts.map +1 -0
  16. package/dist/core/harnesses/attestation.js +168 -0
  17. package/dist/core/harnesses/bench.d.ts +23 -1
  18. package/dist/core/harnesses/bench.d.ts.map +1 -1
  19. package/dist/core/harnesses/bench.js +135 -32
  20. package/dist/core/harnesses/index.d.ts +6 -2
  21. package/dist/core/harnesses/index.d.ts.map +1 -1
  22. package/dist/core/harnesses/index.js +3 -1
  23. package/dist/core/workflow/engine.js +3 -0
  24. package/dist/core/workflow/index.d.ts +1 -1
  25. package/dist/core/workflow/index.d.ts.map +1 -1
  26. package/dist/core/workflow/index.js +1 -1
  27. package/dist/core/workflow/proof.d.ts +4 -1
  28. package/dist/core/workflow/proof.d.ts.map +1 -1
  29. package/dist/core/workflow/proof.js +3 -2
  30. package/dist/core/workflow/spawn-cursor.d.ts +3 -0
  31. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  32. package/dist/core/workflow/spawn-cursor.js +8 -4
  33. package/dist/core/workflow/types.d.ts +14 -0
  34. package/dist/core/workflow/types.d.ts.map +1 -1
  35. package/dist/core/workflow/workspaces/index.d.ts +1 -1
  36. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  37. package/dist/core/workflow/workspaces/index.js +1 -1
  38. package/dist/core/workflow/workspaces/integration.d.ts +8 -0
  39. package/dist/core/workflow/workspaces/integration.d.ts.map +1 -1
  40. package/dist/core/workflow/workspaces/integration.js +15 -1
  41. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  42. package/dist/core/workflow/workspaces/leases.js +19 -2
  43. package/package.json +1 -1
  44. package/src/commands/harness.ts +123 -2
  45. package/src/commands/supervisor.ts +11 -15
  46. package/src/commands/work.ts +8 -8
  47. package/src/commands/workflow.ts +268 -20
  48. package/src/core/harnesses/attest.ts +162 -0
  49. package/src/core/harnesses/attestation.ts +231 -0
  50. package/src/core/harnesses/bench.ts +187 -32
  51. package/src/core/harnesses/index.ts +26 -1
  52. package/src/core/workflow/engine.ts +3 -0
  53. package/src/core/workflow/index.ts +1 -0
  54. package/src/core/workflow/proof.ts +7 -1
  55. package/src/core/workflow/spawn-cursor.ts +8 -4
  56. package/src/core/workflow/types.ts +15 -0
  57. package/src/core/workflow/workspaces/index.ts +5 -1
  58. package/src/core/workflow/workspaces/integration.ts +17 -1
  59. package/src/core/workflow/workspaces/leases.ts +24 -2
@@ -3,7 +3,11 @@ import { dirname, isAbsolute, resolve } from "node:path";
3
3
  import type { Command } from "commander";
4
4
  import type { EmitContext } from "../commander.ts";
5
5
  import { workflowSubscriptionOnly } from "../core/config.ts";
6
- import { createBuiltinHarnessRegistry } from "../core/harnesses/index.ts";
6
+ import {
7
+ createBuiltinHarnessRegistry,
8
+ harnessProofInputs,
9
+ probeBinaryVersion,
10
+ } from "../core/harnesses/index.ts";
7
11
  import { findCoordRoot } from "../core/hooks/resolve/coord-root.ts";
8
12
  import type { PolicyIsolation } from "../core/policy/index.ts";
9
13
  import { loadPolicyFile } from "../core/policy/index.ts";
@@ -470,13 +474,9 @@ function serviceEngine(
470
474
  cwd: config.engine.cwd,
471
475
  subscriptionOnly: config.engine.subscription_only,
472
476
  allowApiBilling: config.engine.allow_api_billing,
473
- harnessEvidence: Object.fromEntries(
474
- registry
475
- .list()
476
- .map((adapter) => [
477
- adapter.profile.id,
478
- { toolEvidence: adapter.profile.capabilities.toolEvidence },
479
- ]),
477
+ ...harnessProofInputs(
478
+ registry.list().map((adapter) => adapter.profile),
479
+ { versionProbe: probeBinaryVersion },
480
480
  ),
481
481
  policy: config.engine.policy,
482
482
  approvalMode: "park" as const,
@@ -566,13 +566,9 @@ function registerRunCommand(
566
566
  subscriptionOnly:
567
567
  opts.subscriptionOnly === true ? true : workflowSubscriptionOnly(coordRoot),
568
568
  allowApiBilling: opts.allowApiBilling,
569
- harnessEvidence: Object.fromEntries(
570
- registry
571
- .list()
572
- .map((adapter) => [
573
- adapter.profile.id,
574
- { toolEvidence: adapter.profile.capabilities.toolEvidence },
575
- ]),
569
+ ...harnessProofInputs(
570
+ registry.list().map((adapter) => adapter.profile),
571
+ { versionProbe: probeBinaryVersion },
576
572
  ),
577
573
  policy: opts.policy ? loadPolicyFile(opts.policy) : undefined,
578
574
  approvalMode: "park",
@@ -1,7 +1,11 @@
1
1
  import type { Command } from "commander";
2
2
  import type { EmitContext } from "../commander.ts";
3
3
  import { workflowSubscriptionOnly } from "../core/config.ts";
4
- import { createBuiltinHarnessRegistry } from "../core/harnesses/index.ts";
4
+ import {
5
+ createBuiltinHarnessRegistry,
6
+ harnessProofInputs,
7
+ probeBinaryVersion,
8
+ } from "../core/harnesses/index.ts";
5
9
  import { findCoordRoot } from "../core/hooks/resolve/coord-root.ts";
6
10
  import type { PolicyIsolation } from "../core/policy/index.ts";
7
11
  import { loadPolicyFile } from "../core/policy/index.ts";
@@ -192,13 +196,9 @@ function registerRunCommand(
192
196
  subscriptionOnly:
193
197
  opts.subscriptionOnly === true ? true : workflowSubscriptionOnly(coordRoot),
194
198
  allowApiBilling: opts.allowApiBilling,
195
- harnessEvidence: Object.fromEntries(
196
- registry
197
- .list()
198
- .map((adapter) => [
199
- adapter.profile.id,
200
- { toolEvidence: adapter.profile.capabilities.toolEvidence },
201
- ]),
199
+ ...harnessProofInputs(
200
+ registry.list().map((adapter) => adapter.profile),
201
+ { versionProbe: probeBinaryVersion },
202
202
  ),
203
203
  policy: opts.policy ? loadPolicyFile(opts.policy) : undefined,
204
204
  approvalMode: "park",
@@ -1,13 +1,19 @@
1
- import { resolve } from "node:path";
1
+ import { existsSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
2
3
  import type { Command } from "commander";
3
4
  import type { EmitContext } from "../commander.ts";
4
- import { workflowSubscriptionOnly } from "../core/config.ts";
5
- import { createBuiltinHarnessRegistry } from "../core/harnesses/index.ts";
5
+ import { resolveBinName, workflowSubscriptionOnly } from "../core/config.ts";
6
+ import {
7
+ createBuiltinHarnessRegistry,
8
+ harnessProofInputs,
9
+ probeBinaryVersion,
10
+ } from "../core/harnesses/index.ts";
6
11
  import { findCoordRoot } from "../core/hooks/resolve/coord-root.ts";
7
12
  import type { PolicyIsolation } from "../core/policy/index.ts";
8
13
  import { loadPolicyFile } from "../core/policy/index.ts";
9
- import type { WorkflowApprovalStatus } from "../core/workflow/approvals.ts";
14
+ import type { WorkflowApproval, WorkflowApprovalStatus } from "../core/workflow/approvals.ts";
10
15
  import { WorkflowParkedError } from "../core/workflow/engine.ts";
16
+ import type { WorkspaceProvider } from "../core/workflow/index.ts";
11
17
 
12
18
  /**
13
19
  * `workflow run <script>`: execute a workflow script — bounded, schema-gated,
@@ -49,6 +55,21 @@ interface WorkflowApprovalDecisionOpts {
49
55
  json?: boolean;
50
56
  }
51
57
 
58
+ interface WorkflowIntegrationPrepareOpts {
59
+ policy?: string;
60
+ reviewer?: string;
61
+ reason?: string;
62
+ targetRoot?: string;
63
+ acceptUnknown?: string[];
64
+ approvalTo?: string;
65
+ json?: boolean;
66
+ }
67
+
68
+ interface WorkflowConfirmedMutationOpts {
69
+ yes?: boolean;
70
+ json?: boolean;
71
+ }
72
+
52
73
  export function registerWorkflowCommand(program: Command, emit: EmitContext): void {
53
74
  const registry = createBuiltinHarnessRegistry();
54
75
  const harnesses = registry.ids();
@@ -161,13 +182,9 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
161
182
  maxAgents: opts.maxAgents ? Number.parseInt(opts.maxAgents, 10) : undefined,
162
183
  concurrency: opts.concurrency ? Number.parseInt(opts.concurrency, 10) : undefined,
163
184
  cwd: opts.cwd,
164
- harnessEvidence: Object.fromEntries(
165
- registry
166
- .list()
167
- .map((adapter) => [
168
- adapter.profile.id,
169
- { toolEvidence: adapter.profile.capabilities.toolEvidence },
170
- ]),
185
+ ...harnessProofInputs(
186
+ registry.list().map((adapter) => adapter.profile),
187
+ { versionProbe: probeBinaryVersion },
171
188
  ),
172
189
  policy: opts.policy ? loadPolicyFile(opts.policy) : undefined,
173
190
  approvalMode: "park",
@@ -211,7 +228,7 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
211
228
  emit.text(
212
229
  `run ${err.runId} parked\napproval: ${err.approvalId}\n` +
213
230
  `journal: ${err.journalPath}\n` +
214
- `resume after resolution: harn workflow resume ${err.runId}\n`,
231
+ `resume after resolution: ${resolveBinName()} workflow resume ${err.runId}\n`,
215
232
  );
216
233
  }
217
234
  return;
@@ -261,13 +278,9 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
261
278
  writableRoots: [binding.writable_root.configured],
262
279
  }
263
280
  : undefined,
264
- harnessEvidence: Object.fromEntries(
265
- registry
266
- .list()
267
- .map((adapter) => [
268
- adapter.profile.id,
269
- { toolEvidence: adapter.profile.capabilities.toolEvidence },
270
- ]),
281
+ ...harnessProofInputs(
282
+ registry.list().map((adapter) => adapter.profile),
283
+ { versionProbe: probeBinaryVersion },
271
284
  ),
272
285
  });
273
286
  if (opts.json) {
@@ -387,6 +400,200 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
387
400
  );
388
401
  });
389
402
 
403
+ const integration = workflow
404
+ .command("integration")
405
+ .description("Prepare and apply proof-gated isolated-workspace integration.");
406
+
407
+ integration
408
+ .command("prepare <run-id>")
409
+ .description("Preview integration and write exact review and policy authority.")
410
+ .option("--policy <file>", "Current host policy JSON/JSONC for the Git mutation")
411
+ .option("--reviewer <name>", "Explicit reviewer for a standalone workflow")
412
+ .option("--reason <text>", "Reason recorded with the standalone review")
413
+ .option("--target-root <dir>", "Explicit target checkout (default: frozen source checkout)")
414
+ .option(
415
+ "--accept-unknown <code>",
416
+ "Accept one verification unknown by code (repeatable)",
417
+ collectOption,
418
+ [],
419
+ )
420
+ .option("--approval-to <address>", "Address a durable policy ASK request")
421
+ .option("--json", "Emit the durable integration plan as JSON")
422
+ .action(async (runId: string, opts: WorkflowIntegrationPrepareOpts) => {
423
+ const coordRoot = findCoordRoot();
424
+ if (!coordRoot) {
425
+ emit.error({
426
+ code: "no_coord_root",
427
+ message: "no .harnery/ coordination root found; run `init` first",
428
+ });
429
+ process.exit(1);
430
+ }
431
+ if (!opts.policy) {
432
+ emit.error({
433
+ code: "integration_policy_required",
434
+ message: "integration prepare requires --policy <file>",
435
+ });
436
+ process.exit(1);
437
+ }
438
+ if (opts.reason && !opts.reviewer) {
439
+ emit.error({
440
+ code: "integration_reviewer_required",
441
+ message: "--reason requires --reviewer <name>",
442
+ });
443
+ process.exit(1);
444
+ }
445
+ try {
446
+ const provider = await builtInProviderForRun(coordRoot, runId);
447
+ const { IntegrationPrepareParkedError, prepareIntegration } = await import(
448
+ "../core/workflow/index.ts"
449
+ );
450
+ try {
451
+ const plan = await prepareIntegration({
452
+ coordRoot,
453
+ runId,
454
+ provider,
455
+ policy: loadPolicyFile(opts.policy),
456
+ targetRoot: opts.targetRoot,
457
+ review: opts.reviewer ? { actor: opts.reviewer, reason: opts.reason } : undefined,
458
+ acceptedUnknowns: opts.acceptUnknown,
459
+ approvalAddressee: opts.approvalTo,
460
+ });
461
+ if (opts.json) {
462
+ emit.config({ format: "json" });
463
+ emit.data(plan);
464
+ return;
465
+ }
466
+ emit.text(
467
+ `integration plan ${plan.plan_id} authorized\n` +
468
+ `source: ${plan.provider_preview.source_commit}\n` +
469
+ `target: ${plan.provider_preview.target_ref} at ${plan.provider_preview.target_commit}\n` +
470
+ `changes: ${plan.provider_preview.changed_paths.length}\n` +
471
+ `apply: ${resolveBinName()} workflow integration apply ${runId} --yes\n`,
472
+ );
473
+ } catch (error) {
474
+ if (error instanceof IntegrationPrepareParkedError) {
475
+ if (opts.json) {
476
+ emit.config({ format: "json" });
477
+ emit.data({
478
+ status: "parked",
479
+ runId: error.runId,
480
+ planId: error.planId,
481
+ approvalId: error.approvalId,
482
+ });
483
+ } else {
484
+ emit.text(
485
+ `integration preparation parked\n` +
486
+ `run: ${error.runId}\n` +
487
+ `plan: ${error.planId}\n` +
488
+ `approval: ${error.approvalId}\n` +
489
+ `approve: ${resolveBinName()} workflow approvals approve ${error.approvalId}\n`,
490
+ );
491
+ }
492
+ return;
493
+ }
494
+ throw error;
495
+ }
496
+ } catch (error) {
497
+ emit.error({
498
+ code: "workflow_integration_prepare_failed",
499
+ message: error instanceof Error ? error.message : String(error),
500
+ });
501
+ process.exit(1);
502
+ }
503
+ });
504
+
505
+ integration
506
+ .command("apply <run-id>")
507
+ .description("Apply an authorized durable integration plan.")
508
+ .option("--yes", "Confirm the target-branch mutation")
509
+ .option("--json", "Emit the durable integration receipt as JSON")
510
+ .action(async (runId: string, opts: WorkflowConfirmedMutationOpts) => {
511
+ const coordRoot = findCoordRoot();
512
+ if (!coordRoot) {
513
+ emit.error({
514
+ code: "no_coord_root",
515
+ message: "no .harnery/ coordination root found; run `init` first",
516
+ });
517
+ process.exit(1);
518
+ }
519
+ if (!opts.yes) {
520
+ emit.error({
521
+ code: "integration_confirmation_required",
522
+ message: "integration apply changes the target branch; pass --yes to confirm",
523
+ });
524
+ process.exit(1);
525
+ }
526
+ try {
527
+ const provider = await builtInProviderForRun(coordRoot, runId);
528
+ const { applyIntegration } = await import("../core/workflow/index.ts");
529
+ const receipt = await applyIntegration({ coordRoot, runId, provider });
530
+ if (opts.json) {
531
+ emit.config({ format: "json" });
532
+ emit.data(receipt);
533
+ return;
534
+ }
535
+ emit.text(
536
+ `integration ${receipt.status}: ${receipt.target_ref} at ${receipt.target_commit}\n` +
537
+ `receipt: ${receipt.receipt_id}\n` +
538
+ `cleanup: ${resolveBinName()} workflow cleanup ${runId} --yes\n`,
539
+ );
540
+ } catch (error) {
541
+ emit.error({
542
+ code: "workflow_integration_apply_failed",
543
+ message: error instanceof Error ? error.message : String(error),
544
+ });
545
+ process.exit(1);
546
+ }
547
+ });
548
+
549
+ workflow
550
+ .command("cleanup <run-id>")
551
+ .description("Conservatively release a provider-owned isolated workspace.")
552
+ .option("--yes", "Confirm the worktree and provider-branch removal attempt")
553
+ .option("--json", "Emit the cleanup attempt or receipt as JSON")
554
+ .action(async (runId: string, opts: WorkflowConfirmedMutationOpts) => {
555
+ const coordRoot = findCoordRoot();
556
+ if (!coordRoot) {
557
+ emit.error({
558
+ code: "no_coord_root",
559
+ message: "no .harnery/ coordination root found; run `init` first",
560
+ });
561
+ process.exit(1);
562
+ }
563
+ if (!opts.yes) {
564
+ emit.error({
565
+ code: "cleanup_confirmation_required",
566
+ message: "workspace cleanup may remove a worktree and branch; pass --yes to confirm",
567
+ });
568
+ process.exit(1);
569
+ }
570
+ try {
571
+ const provider = await builtInProviderForRun(coordRoot, runId);
572
+ const { cleanupWorkspace } = await import("../core/workflow/index.ts");
573
+ const result = await cleanupWorkspace({ coordRoot, runId, provider });
574
+ if (opts.json) {
575
+ emit.config({ format: "json" });
576
+ emit.data(result);
577
+ return;
578
+ }
579
+ const receipt =
580
+ "receipt_id" in result && typeof result.receipt_id === "string"
581
+ ? `\nreceipt: ${result.receipt_id}`
582
+ : "";
583
+ const reason =
584
+ "reason" in result && typeof result.reason === "string"
585
+ ? `\nreason: ${result.reason}`
586
+ : "";
587
+ emit.text(`workspace cleanup ${result.status}: ${result.binding_id}${receipt}${reason}\n`);
588
+ } catch (error) {
589
+ emit.error({
590
+ code: "workflow_cleanup_failed",
591
+ message: error instanceof Error ? error.message : String(error),
592
+ });
593
+ process.exit(1);
594
+ }
595
+ });
596
+
390
597
  const approvals = workflow
391
598
  .command("approvals")
392
599
  .description("Inspect and resolve durable workflow policy approvals.");
@@ -488,7 +695,7 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
488
695
  emit.text(
489
696
  `${renderWorkflowApproval(resolved.approval)}` +
490
697
  `${resolved.applied ? "decision recorded" : "decision already recorded"}\n` +
491
- `resume: harn workflow resume ${resolved.approval.request.run_id}\n`,
698
+ approvalNextActionHint(coordRoot, resolved.approval),
492
699
  );
493
700
  }
494
701
  } catch (err) {
@@ -533,3 +740,44 @@ export function registerWorkflowCommand(program: Command, emit: EmitContext): vo
533
740
  }
534
741
  });
535
742
  }
743
+
744
+ async function builtInProviderForRun(coordRoot: string, runId: string): Promise<WorkspaceProvider> {
745
+ const { createLocalGitWorktreeProvider, readWorkflowRunManifest } = await import(
746
+ "../core/workflow/index.ts"
747
+ );
748
+ const manifest = readWorkflowRunManifest(coordRoot, runId);
749
+ const binding = manifest.execution.workspace_binding;
750
+ if (!binding) {
751
+ throw new Error(`workflow run ${runId} has no isolated workspace binding`);
752
+ }
753
+ if (binding.provider.id !== "local-git-worktree") {
754
+ throw new Error(
755
+ `workflow run ${runId} requires unavailable workspace provider ${binding.provider.id}`,
756
+ );
757
+ }
758
+ return createLocalGitWorktreeProvider({ coordRoot });
759
+ }
760
+
761
+ function collectOption(value: string, previous: string[]): string[] {
762
+ return [...previous, value];
763
+ }
764
+
765
+ /** Next operator command after resolving an approval. Mid-run parks resume the
766
+ * workflow; integration prepare parks after the run is already terminal, so
767
+ * the durable plan is authorized by re-running prepare (resume would fail). */
768
+ function approvalNextActionHint(coordRoot: string, approval: WorkflowApproval): string {
769
+ const bin = resolveBinName();
770
+ const runId = approval.request.run_id;
771
+ const integrationPlanPath = join(
772
+ coordRoot,
773
+ ".harnery",
774
+ "workflows",
775
+ runId,
776
+ "integration",
777
+ "plan.json",
778
+ );
779
+ if (existsSync(integrationPlanPath)) {
780
+ return `prepare: ${bin} workflow integration prepare ${runId}\n`;
781
+ }
782
+ return `resume: ${bin} workflow resume ${runId}\n`;
783
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * The opt-in live probe that produces an attestation (ADR 0038).
3
+ *
4
+ * One bounded turn per harness, through the same `spawn` the workflow engine
5
+ * uses, so what gets attested is the path production takes rather than a
6
+ * parallel test rig.
7
+ */
8
+
9
+ import type { SpawnResult } from "../workflow/types.ts";
10
+ import type { AttestableDimension, HarnessAttestation } from "./attestation.ts";
11
+ import { profileDigest, sealAttestation, writeAttestation } from "./attestation.ts";
12
+ import { probeBinaryVersion } from "./bench.ts";
13
+ import type { HarnessRegistry } from "./registry.ts";
14
+ import type { CapabilitySupport, HarnessId } from "./types.ts";
15
+
16
+ /** Fixed and content-free, so nothing user-supplied can reach the record and
17
+ * the turn stays as cheap as a turn can be. */
18
+ export const ATTESTATION_PROMPT = "Reply with the single word: ok";
19
+
20
+ export const DEFAULT_ATTESTATION_TIMEOUT_MS = 120_000;
21
+
22
+ /** Vendor failures arrive as whole console transcripts, banner and prompt echo
23
+ * included. Reports are bounded evidence, so the reason is collapsed to one
24
+ * short line and the echoed prompt is removed. */
25
+ const MAX_NOTE_REASON_CHARS = 200;
26
+
27
+ function boundedReason(reason: string | undefined): string {
28
+ if (!reason) return "no error reported";
29
+ const collapsed = reason.split(ATTESTATION_PROMPT).join("<prompt>").replace(/\s+/g, " ").trim();
30
+ return collapsed.length > MAX_NOTE_REASON_CHARS
31
+ ? `${collapsed.slice(0, MAX_NOTE_REASON_CHARS)}…`
32
+ : collapsed;
33
+ }
34
+
35
+ export type AttestationOutcome = "recorded" | "skipped" | "unreachable" | "failed";
36
+
37
+ export interface HarnessAttestationResult {
38
+ harness: HarnessId;
39
+ outcome: AttestationOutcome;
40
+ binaryVersion?: string;
41
+ observations?: Partial<Record<AttestableDimension, CapabilitySupport>>;
42
+ durationMs?: number;
43
+ note: string;
44
+ }
45
+
46
+ export interface HarnessAttestationReport {
47
+ generatedAt: string;
48
+ harnesses: HarnessId[];
49
+ results: HarnessAttestationResult[];
50
+ recorded: number;
51
+ /** True when at least one selected harness could not be attested, so a
52
+ * caller can tell a partial sweep from a complete one. */
53
+ incomplete: boolean;
54
+ }
55
+
56
+ export interface RunHarnessAttestationOptions {
57
+ harnesses?: readonly string[];
58
+ timeoutMs?: number;
59
+ cwd?: string;
60
+ coordRoot?: string;
61
+ /** Test seam and alternate host probe. A null version means unavailable. */
62
+ versionProbe?: (binary: string) => string | null;
63
+ /** Test seam. Defaults to the adapter's production spawner. */
64
+ spawn?: (harness: HarnessId, prompt: string, timeoutMs: number) => Promise<SpawnResult>;
65
+ /** Test seam. Defaults to writing under the coord root. */
66
+ persist?: (record: HarnessAttestation) => void;
67
+ now?: () => Date;
68
+ }
69
+
70
+ export async function runHarnessAttestation(
71
+ registry: HarnessRegistry,
72
+ opts: RunHarnessAttestationOptions = {},
73
+ ): Promise<HarnessAttestationReport> {
74
+ const ids = opts.harnesses?.length ? [...opts.harnesses] : registry.ids();
75
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_ATTESTATION_TIMEOUT_MS;
76
+ const cwd = opts.cwd ?? process.cwd();
77
+ const versionProbe = opts.versionProbe ?? probeBinaryVersion;
78
+ const now = opts.now ?? (() => new Date());
79
+ const results: HarnessAttestationResult[] = [];
80
+
81
+ for (const id of ids) {
82
+ const adapter = registry.require(id);
83
+ const binaryVersion = versionProbe(adapter.profile.binary);
84
+ if (!binaryVersion) {
85
+ results.push({
86
+ harness: id,
87
+ outcome: "skipped",
88
+ note: `${adapter.profile.binary} not found on PATH`,
89
+ });
90
+ continue;
91
+ }
92
+
93
+ let result: SpawnResult;
94
+ try {
95
+ result = opts.spawn
96
+ ? await opts.spawn(id, ATTESTATION_PROMPT, timeoutMs)
97
+ : await adapter.spawn({
98
+ prompt: ATTESTATION_PROMPT,
99
+ timeoutMs,
100
+ maxTurns: 1,
101
+ cwd,
102
+ });
103
+ } catch (error) {
104
+ results.push({
105
+ harness: id,
106
+ outcome: "failed",
107
+ binaryVersion,
108
+ note: `probe threw: ${boundedReason((error as Error).message)}`,
109
+ });
110
+ continue;
111
+ }
112
+
113
+ // Prerequisite rule: an unreachable subject cannot evidence anything, so a
114
+ // failed turn records nothing at all rather than a page of `unsupported`.
115
+ if (!result.ok) {
116
+ results.push({
117
+ harness: id,
118
+ outcome: "unreachable",
119
+ binaryVersion,
120
+ durationMs: result.durationMs,
121
+ note: `the probe turn did not complete (${boundedReason(result.error)}); nothing recorded`,
122
+ });
123
+ continue;
124
+ }
125
+
126
+ const observations: Partial<Record<AttestableDimension, CapabilitySupport>> = {
127
+ invocation: "supported",
128
+ finalResult: result.text.trim().length > 0 ? "supported" : "unsupported",
129
+ sessionId: result.sessionId ? "supported" : "unsupported",
130
+ cost: result.costUsd !== undefined ? "supported" : "unsupported",
131
+ };
132
+
133
+ const record = sealAttestation({
134
+ schema_version: 1,
135
+ harness: id,
136
+ binary_version: binaryVersion,
137
+ profile_digest: profileDigest(adapter.profile),
138
+ observed_at: now().toISOString(),
139
+ observations,
140
+ });
141
+
142
+ if (opts.persist) opts.persist(record);
143
+ else writeAttestation(record, { coordRoot: opts.coordRoot });
144
+
145
+ results.push({
146
+ harness: id,
147
+ outcome: "recorded",
148
+ binaryVersion,
149
+ observations,
150
+ durationMs: result.durationMs,
151
+ note: `observed on ${binaryVersion}`,
152
+ });
153
+ }
154
+
155
+ return {
156
+ generatedAt: now().toISOString(),
157
+ harnesses: ids,
158
+ results,
159
+ recorded: results.filter((row) => row.outcome === "recorded").length,
160
+ incomplete: results.some((row) => row.outcome !== "recorded"),
161
+ };
162
+ }