harnery 0.26.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 (76) hide show
  1. package/dist/commands/harness.d.ts.map +1 -1
  2. package/dist/commands/harness.js +2 -0
  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 +9 -0
  9. package/dist/core/harnesses/attest.d.ts.map +1 -1
  10. package/dist/core/harnesses/attest.js +14 -1
  11. package/dist/core/harnesses/attestation.d.ts +1 -1
  12. package/dist/core/harnesses/attestation.d.ts.map +1 -1
  13. package/dist/core/harnesses/attestation.js +7 -1
  14. package/dist/core/harnesses/bench.d.ts.map +1 -1
  15. package/dist/core/harnesses/bench.js +13 -0
  16. package/dist/core/harnesses/profiles.d.ts +7 -0
  17. package/dist/core/harnesses/profiles.d.ts.map +1 -1
  18. package/dist/core/harnesses/profiles.js +9 -0
  19. package/dist/core/harnesses/types.d.ts +14 -1
  20. package/dist/core/harnesses/types.d.ts.map +1 -1
  21. package/dist/core/harnesses/types.js +1 -0
  22. package/dist/core/workflow/engine.d.ts.map +1 -1
  23. package/dist/core/workflow/engine.js +41 -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 +3 -1
  28. package/dist/core/workflow/proof.d.ts.map +1 -1
  29. package/dist/core/workflow/proof.js +1 -0
  30. package/dist/core/workflow/sandbox-projection.d.ts +57 -0
  31. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -0
  32. package/dist/core/workflow/sandbox-projection.js +95 -0
  33. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  34. package/dist/core/workflow/spawn-claude.js +6 -1
  35. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  36. package/dist/core/workflow/spawn-codex.js +10 -2
  37. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  38. package/dist/core/workflow/spawn-cursor.js +6 -1
  39. package/dist/core/workflow/types.d.ts +51 -0
  40. package/dist/core/workflow/types.d.ts.map +1 -1
  41. package/dist/core/workflow/workspaces/index.d.ts +2 -0
  42. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  43. package/dist/core/workflow/workspaces/index.js +1 -0
  44. package/dist/core/workflow/workspaces/inspect.d.ts.map +1 -1
  45. package/dist/core/workflow/workspaces/inspect.js +14 -0
  46. package/dist/core/workflow/workspaces/reclaim.d.ts +50 -0
  47. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -0
  48. package/dist/core/workflow/workspaces/reclaim.js +88 -0
  49. package/dist/lib/tunnel/error-page.d.ts +15 -0
  50. package/dist/lib/tunnel/error-page.d.ts.map +1 -0
  51. package/dist/lib/tunnel/error-page.js +98 -0
  52. package/dist/lib/tunnel/gate.d.ts +1 -12
  53. package/dist/lib/tunnel/gate.d.ts.map +1 -1
  54. package/dist/lib/tunnel/gate.js +59 -4
  55. package/package.json +1 -1
  56. package/src/commands/harness.ts +6 -0
  57. package/src/commands/workflow.ts +74 -0
  58. package/src/core/harnesses/attest-projection.ts +151 -0
  59. package/src/core/harnesses/attest.ts +23 -1
  60. package/src/core/harnesses/attestation.ts +7 -1
  61. package/src/core/harnesses/bench.ts +16 -0
  62. package/src/core/harnesses/profiles.ts +11 -0
  63. package/src/core/harnesses/types.ts +15 -0
  64. package/src/core/workflow/engine.ts +58 -0
  65. package/src/core/workflow/index.ts +1 -0
  66. package/src/core/workflow/proof.ts +4 -0
  67. package/src/core/workflow/sandbox-projection.ts +150 -0
  68. package/src/core/workflow/spawn-claude.ts +10 -1
  69. package/src/core/workflow/spawn-codex.ts +17 -2
  70. package/src/core/workflow/spawn-cursor.ts +10 -1
  71. package/src/core/workflow/types.ts +54 -0
  72. package/src/core/workflow/workspaces/index.ts +2 -0
  73. package/src/core/workflow/workspaces/inspect.ts +15 -0
  74. package/src/core/workflow/workspaces/reclaim.ts +109 -0
  75. package/src/lib/tunnel/error-page.ts +114 -0
  76. 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;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
+ {"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"}
@@ -69,6 +69,7 @@ export function registerHarnessCommand(program, emit) {
69
69
  .option("--yes", "Confirm that this spends real vendor tokens")
70
70
  .option("--subscription-only", "Scrub API-key vars so the child uses its stored login (repo default via config.jsonc workflow.subscriptionOnly)")
71
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")
72
73
  .option("--json", "Machine-readable attestation report")
73
74
  .action(async (harnesses, opts) => {
74
75
  if (!opts.yes) {
@@ -94,6 +95,7 @@ export function registerHarnessCommand(program, emit) {
94
95
  harnesses,
95
96
  timeoutMs,
96
97
  subscriptionOnly,
98
+ projection: opts.projection === true,
97
99
  });
98
100
  if (opts.json) {
99
101
  emit.config({ format: "json" });
@@ -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";
@@ -47,6 +48,14 @@ export interface RunHarnessAttestationOptions {
47
48
  spawn?: (harness: HarnessId, prompt: string, timeoutMs: number) => Promise<SpawnResult>;
48
49
  /** Test seam. Defaults to writing under the coord root. */
49
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;
50
59
  now?: () => Date;
51
60
  }
52
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;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"}
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,6 +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 { probeFilesystemProjection } from "./attest-projection.js";
8
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
@@ -88,6 +89,18 @@ export async function runHarnessAttestation(registry, opts = {}) {
88
89
  sessionId: result.sessionId ? "supported" : "unsupported",
89
90
  cost: result.costUsd !== undefined ? "supported" : "unsupported",
90
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
+ }
91
104
  const record = sealAttestation({
92
105
  schema_version: ATTESTATION_SCHEMA_VERSION,
93
106
  harness: id,
@@ -107,7 +120,7 @@ export async function runHarnessAttestation(registry, opts = {}) {
107
120
  binaryVersion,
108
121
  observations,
109
122
  durationMs: result.durationMs,
110
- note: `observed on ${binaryVersion}`,
123
+ note: `observed on ${binaryVersion}${projectionNote}`,
111
124
  });
112
125
  }
113
126
  return {
@@ -16,7 +16,7 @@ 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;
@@ -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;;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"}
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"}
@@ -19,7 +19,13 @@ 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)
@@ -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;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"}
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"}
@@ -186,6 +186,18 @@ function observeAdapter(adapter) {
186
186
  catch {
187
187
  planningFailed = true;
188
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
+ }
189
201
  let normalized = null;
190
202
  try {
191
203
  normalized = adapter.normalizeResult(adapter.fixture.raw);
@@ -221,6 +233,7 @@ function observeAdapter(adapter) {
221
233
  : costObserved),
222
234
  toolEvidence: fromAdapter(normalized && "toolEvidence" in normalized ? "supported" : "unsupported"),
223
235
  policyMapping: NOT_CHECKED,
236
+ filesystemPolicyProjection: fromAdapter(projectionRendered ? "supported" : "unsupported"),
224
237
  interruption: NOT_CHECKED,
225
238
  streaming: NOT_CHECKED,
226
239
  steering: NOT_CHECKED,
@@ -34,6 +34,13 @@ export declare const BUILTIN_HARNESS_PROFILES: {
34
34
  readonly date: "2026-07-25";
35
35
  readonly version: "codex-cli 0.144.5";
36
36
  };
37
+ readonly sandboxProjection: {
38
+ readonly modes: {
39
+ readonly "read-only": "read-only";
40
+ readonly "workspace-write": "workspace-write";
41
+ };
42
+ readonly writableRoots: true;
43
+ };
37
44
  readonly capabilities: HarnessCapabilities;
38
45
  };
39
46
  readonly cursor: {
@@ -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"}
@@ -13,6 +13,7 @@ function capabilities(overrides) {
13
13
  cost: unsupported(),
14
14
  toolEvidence: unsupported("The final-result adapter does not retain tool events."),
15
15
  policyMapping: unsupported("No ALLOW/DENY/ASK translation at the workflow boundary."),
16
+ filesystemPolicyProjection: unsupported("The adapter declares no sandbox projection."),
16
17
  interruption: partial("Timeout kills the subprocess; no caller-driven interrupt handle."),
17
18
  streaming: unsupported("Workflow children return one normalized final result."),
18
19
  steering: unsupported("One prompt is fixed at subprocess launch."),
@@ -62,8 +63,16 @@ export const BUILTIN_HARNESS_PROFILES = {
62
63
  modelFamily: "gpt",
63
64
  effortValues: ["none", "minimal", "low", "medium", "high", "xhigh"],
64
65
  verified: { date: "2026-07-25", version: "codex-cli 0.144.5" },
66
+ // Verified against codex-cli 0.144.5: `--sandbox <mode>` plus
67
+ // `sandbox_workspace_write.writable_roots`. Without the writable-root entry
68
+ // a workspace-write child still cannot write a repository's .git directory.
69
+ sandboxProjection: {
70
+ modes: { "read-only": "read-only", "workspace-write": "workspace-write" },
71
+ writableRoots: true,
72
+ },
65
73
  capabilities: capabilities({
66
74
  effortSelection: supported('Mapped to `-c model_reasoning_effort="<level>"`.'),
75
+ filesystemPolicyProjection: supported("Mode renders to --sandbox; writable roots to sandbox_workspace_write.writable_roots."),
67
76
  maxTurns: unsupported("codex exec exposes no turn-ceiling flag."),
68
77
  sessionId: unsupported("--output-last-message carries no session id."),
69
78
  cost: unsupported("The final-message path carries no usage or cost."),
@@ -3,7 +3,7 @@ import type { Spawner, SpawnRequest, SpawnResult } from "../workflow/types.js";
3
3
  * repeated through closed unions in every consumer. */
4
4
  export type HarnessId = string;
5
5
  export type CapabilitySupport = "supported" | "partial" | "unsupported" | "unknown";
6
- export declare const HARNESS_CAPABILITY_DIMENSIONS: readonly ["invocation", "modelSelection", "effortSelection", "maxTurns", "finalResult", "sessionId", "cost", "toolEvidence", "policyMapping", "interruption", "streaming", "steering", "resume", "images", "contextTelemetry", "preCompactionSignal", "postCompactionSignal", "compaction"];
6
+ export declare const HARNESS_CAPABILITY_DIMENSIONS: readonly ["invocation", "modelSelection", "effortSelection", "maxTurns", "finalResult", "sessionId", "cost", "toolEvidence", "policyMapping", "filesystemPolicyProjection", "interruption", "streaming", "steering", "resume", "images", "contextTelemetry", "preCompactionSignal", "postCompactionSignal", "compaction"];
7
7
  export type HarnessCapabilityDimension = (typeof HARNESS_CAPABILITY_DIMENSIONS)[number];
8
8
  export interface CapabilityClaim {
9
9
  support: CapabilitySupport;
@@ -12,6 +12,16 @@ export interface CapabilityClaim {
12
12
  note?: string;
13
13
  }
14
14
  export type HarnessCapabilities = Record<HarnessCapabilityDimension, CapabilityClaim>;
15
+ /** What one harness can represent of a filesystem-policy projection
16
+ * (ADR 0039). `null` means the harness does not distinguish that, which is a
17
+ * fact about the harness rather than something to paper over: a projection the
18
+ * adapter would silently drop must be refused instead. */
19
+ export interface HarnessSandboxProjection {
20
+ /** Native representation per canonical mode, or null when unrepresentable. */
21
+ modes: Record<"read-only" | "workspace-write", string | null>;
22
+ /** Whether the harness accepts an explicit writable-root set. */
23
+ writableRoots: boolean;
24
+ }
15
25
  export interface HarnessProfile {
16
26
  id: HarnessId;
17
27
  displayName: string;
@@ -29,6 +39,9 @@ export interface HarnessProfile {
29
39
  date: string;
30
40
  version: string;
31
41
  };
42
+ /** How this adapter projects host filesystem policy into the vendor's own
43
+ * sandbox (ADR 0039). Absent means it cannot project any of it. */
44
+ sandboxProjection?: HarnessSandboxProjection;
32
45
  }
33
46
  /** Fully planned child invocation. `resultFile` is used by adapters such as
34
47
  * Codex that deliver the authoritative final answer out-of-band. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE/E;uDACuD;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;AAEpF,eAAO,MAAM,6BAA6B,6RAmBhC,CAAC;AAEX,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AAExF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,iBAAiB,CAAC;IAC3B;iFAC6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,0BAA0B,EAAE,eAAe,CAAC,CAAC;AAEtF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,SAAS,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,gBAAgB,CAAC;IAClC,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAC;IACxC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,EAAE,mBAAmB,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED;oEACoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG;QAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED;;wDAEwD;AACxD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,iBAAiB,CAAC;IACnF,eAAe,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,WAAW,CAAC;IACxD,OAAO,EAAE,mBAAmB,CAAC;CAC9B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/harnesses/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE/E;uDACuD;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;AAEpF,eAAO,MAAM,6BAA6B,2TAoBhC,CAAC;AAEX,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAC;AAExF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,iBAAiB,CAAC;IAC3B;iFAC6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,0BAA0B,EAAE,eAAe,CAAC,CAAC;AAEtF;;;0DAG0D;AAC1D,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC,WAAW,GAAG,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9D,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,SAAS,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,gBAAgB,CAAC;IAClC,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAC;IACxC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,EAAE,mBAAmB,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C;uEACmE;IACnE,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C;AAED;oEACoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG;QAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED;;wDAEwD;AACxD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,iBAAiB,CAAC;IACnF,eAAe,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,WAAW,CAAC;IACxD,OAAO,EAAE,mBAAmB,CAAC;CAC9B"}
@@ -8,6 +8,7 @@ export const HARNESS_CAPABILITY_DIMENSIONS = [
8
8
  "cost",
9
9
  "toolEvidence",
10
10
  "policyMapping",
11
+ "filesystemPolicyProjection",
11
12
  "interruption",
12
13
  "streaming",
13
14
  "steering",
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAuCH,OAAO,KAAK,EAEV,UAAU,EAEV,SAAS,EAUV,MAAM,YAAY,CAAC;AAyBpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAOpF;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA6E1F"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAwCH,OAAO,KAAK,EAEV,UAAU,EAGV,SAAS,EAYV,MAAM,YAAY,CAAC;AAwCpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAOpF;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA6E1F"}
@@ -36,6 +36,7 @@ import { probeBilling } from "./billing.js";
36
36
  import { stableDigest } from "./durable-record.js";
37
37
  import { buildWorkflowProof, createEvidenceRecord, digestResult, normalizeWorkflowMeta, writeWorkflowProof, } from "./proof.js";
38
38
  import { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowScriptUnchanged, workflowScriptDigest, writeWorkflowRunManifest, } from "./run-state.js";
39
+ import { assertProjectionWithinWorkspace, resolveGitGrantRoots } from "./sandbox-projection.js";
39
40
  import { normalizeWorkflowSpecialists, resolveSpecialistAssignment } from "./specialists.js";
40
41
  import { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.js";
41
42
  import { parseStageOutput, validateAgainstSchema } from "./validate.js";
@@ -43,6 +44,18 @@ import { freezeWorkflowWorkContext } from "./work-context.js";
43
44
  import { attestTerminal, attestWorkspaceFailure, resolveWorkspaceBinding, } from "./workspaces/execution.js";
44
45
  import { appendWorkflowJournalEvent } from "./workspaces/state.js";
45
46
  import { isWorkspaceAttestation } from "./workspaces/validate.js";
47
+ /** Proof record of the filesystem projection a run applied (ADR 0039/0040).
48
+ * Absent when no policy was in force, which reads as "no projection applied"
49
+ * rather than "an empty one". */
50
+ function sandboxProjectionEvidence(policy, gitWrite) {
51
+ if (!policy)
52
+ return undefined;
53
+ return {
54
+ mode: policy.mode,
55
+ writable_roots: [...(policy.writableRoots ?? [])],
56
+ git_grant: gitWrite,
57
+ };
58
+ }
46
59
  const DEFAULT_MAX_AGENTS = 50;
47
60
  const DEFAULT_CONCURRENCY = 4;
48
61
  const DEFAULT_MAX_ATTEMPTS = 2;
@@ -246,6 +259,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
246
259
  evidence: [],
247
260
  harnessEvidence: opts.harnessEvidence,
248
261
  harnessAttestations: opts.harnessAttestations,
262
+ sandboxProjection: sandboxProjectionEvidence(opts.filesystemPolicy, opts.gitWrite ?? "none"),
249
263
  policy: policy
250
264
  ? {
251
265
  config: policy,
@@ -267,6 +281,30 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
267
281
  }
268
282
  const effectiveIsolation = workspaceFallback?.effective_isolation ?? isolation;
269
283
  const executionCwd = workspaceBinding?.active_root ?? cwd;
284
+ // Validate the projection once, before any child launches, so a policy that
285
+ // would reach outside the provider's validated root fails the run rather than
286
+ // silently widening one child's write access (ADR 0039).
287
+ const requestedPolicy = opts.filesystemPolicy;
288
+ if (requestedPolicy?.writableRoots?.length) {
289
+ if (!workspaceBinding) {
290
+ throw new Error("a filesystem policy with writable roots requires an isolated workspace; none is bound to this run");
291
+ }
292
+ assertProjectionWithinWorkspace("workflow", workspaceBinding.writable_root.realpath, requestedPolicy.writableRoots);
293
+ }
294
+ // The Git grant is resolved after containment on purpose: caller-supplied
295
+ // roots must stay inside the workspace, while these come from the verified
296
+ // binding and are the one sanctioned way out of it (ADR 0040).
297
+ const gitWrite = opts.gitWrite ?? "none";
298
+ const gitGrantRoots = resolveGitGrantRoots(gitWrite, workspaceBinding);
299
+ if (gitGrantRoots.length > 0 && !requestedPolicy) {
300
+ throw new Error(`gitWrite "${gitWrite}" has no effect without a filesystem policy to carry it; set filesystemPolicy or drop the grant`);
301
+ }
302
+ const filesystemPolicy = requestedPolicy
303
+ ? {
304
+ ...requestedPolicy,
305
+ writableRoots: [...(requestedPolicy.writableRoots ?? []), ...gitGrantRoots],
306
+ }
307
+ : undefined;
270
308
  const executionRepoBefore = resumeState?.manifest.repository_before ?? snapshotRepo(executionCwd);
271
309
  if (!resumeState) {
272
310
  writeWorkflowRunManifest({
@@ -726,6 +764,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
726
764
  cwd: executionCwd,
727
765
  runId,
728
766
  subscriptionOnly,
767
+ filesystemPolicy,
729
768
  });
730
769
  agentProof.attempts = attempt;
731
770
  agentProof.duration_ms += last.durationMs;
@@ -927,6 +966,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
927
966
  evidence: evidenceRecords,
928
967
  harnessEvidence: opts.harnessEvidence,
929
968
  harnessAttestations: opts.harnessAttestations,
969
+ sandboxProjection: sandboxProjectionEvidence(filesystemPolicy, gitWrite),
930
970
  policy: policy
931
971
  ? {
932
972
  config: policy,
@@ -1005,6 +1045,7 @@ async function executeWorkflow(scriptPath, absScript, opts, resumeState, workCon
1005
1045
  evidence: evidenceRecords,
1006
1046
  harnessEvidence: opts.harnessEvidence,
1007
1047
  harnessAttestations: opts.harnessAttestations,
1048
+ sandboxProjection: sandboxProjectionEvidence(filesystemPolicy, gitWrite),
1008
1049
  policy: policy
1009
1050
  ? {
1010
1051
  config: policy,
@@ -8,5 +8,5 @@ export { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowS
8
8
  export type { AcceptanceCriterion, AcceptanceResult, AcceptanceStatus, AcceptanceSummary, AgentOpts, EngineOpts, EvidenceKind, EvidenceSource, EvidenceStatus, HarnessEvidenceCapability, HarnessEvidenceCoverage, HarnessName, ResultDigest, RunReport, Spawner, SpawnRequest, SpawnResult, StageSchema, WorkflowAgentProof, WorkflowApprovalMode, WorkflowAttemptContext, WorkflowAttemptFailureCause, WorkflowAttemptPriorContext, WorkflowAttemptUnresolvedCriterion, WorkflowContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowModule, WorkflowPolicyProof, WorkflowProof, WorkflowProofUnknown, WorkflowRepoEvidence, WorkflowRepoSnapshot, WorkflowRunStatus, WorkflowSpecialistProfile, WorkflowWorkContext, } from "./types.js";
9
9
  export { WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION, WORKFLOW_PROOF_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "./types.js";
10
10
  export type { ApplyIntegrationInput, AuthorizedIntegrationPlan, AuthorizedProviderIntegrationInput, CancelWorkspaceInput, CleanupWorkspaceInput, FilesystemIdentity, IntegrationApplyAttempt, IntegrationApplyReceipt, IntegrationAuthorization, IntegrationPlan, IntegrationReviewRecord, IntegrationTarget, LocalGitWorktreeProviderOptions, PrepareIntegrationInput, ProviderIntegrationInput, ProviderIntegrationPreview, ProviderIntegrationResult, ProviderUnknown, ProviderUnsupportedReason, RepositoryBinding, ValidatedFilesystemPath, WorkflowWorkspaceInspection, WorkflowWorkspaceSelection, WorkflowWorkspaceStatus, WorkflowWorkspaceVerification, WorkspaceAllocationRequest, WorkspaceAttestation, WorkspaceBinding, WorkspaceBoundExecutionEvidence, WorkspaceCancellationReceipt, WorkspaceCancellationResult, WorkspaceCleanupAttempt, WorkspaceCleanupIntent, WorkspaceCleanupMode, WorkspaceCleanupReceipt, WorkspaceCleanupResult, WorkspaceCompatibilityExecutionEvidence, WorkspaceExecutionEvidence, WorkspaceIntegrationState, WorkspaceIsolation, WorkspaceLifecycleProjection, WorkspaceLifecycleProjectionInput, WorkspaceLifecycleState, WorkspaceObservation, WorkspaceOwner, WorkspaceProbeInput, WorkspaceProbeResult, WorkspaceProofOutcome, WorkspaceProvider, WorkspaceProviderCapabilities, WorkspaceProviderRef, WorkspaceResourceState, WorkspaceUnsupportedExecutionEvidence, } from "./workspaces/index.js";
11
- export { applyIntegration, assertWorkspaceLifecycleTransition, cancelWorkspace, cleanupWorkspace, createLocalGitWorktreeProvider, deriveWorkspaceLifecycle, IntegrationPrepareParkedError, inspectWorkflowWorkspace, isWorkspaceLifecycleState, listWorkflowWorkspaceInspections, prepareIntegration, probeLocalGitWorktreeProvider, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, validateProviderEventChain, WORKSPACE_BINDING_SCHEMA_VERSION, WORKSPACE_RECEIPT_SCHEMA_VERSION, } from "./workspaces/index.js";
11
+ export { applyIntegration, assertWorkspaceLifecycleTransition, cancelWorkspace, cleanupWorkspace, createLocalGitWorktreeProvider, deriveWorkspaceLifecycle, IntegrationPrepareParkedError, inspectWorkflowWorkspace, isWorkspaceLifecycleState, listWorkflowWorkspaceInspections, prepareIntegration, prepareReclaim, probeLocalGitWorktreeProvider, readWorkflowWorkspaceStatus, renderWorkflowWorkspaceStatus, validateProviderEventChain, WORKSPACE_BINDING_SCHEMA_VERSION, WORKSPACE_RECEIPT_SCHEMA_VERSION, } from "./workspaces/index.js";
12
12
  //# sourceMappingURL=index.d.ts.map