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
@@ -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."),
@@ -39,7 +40,7 @@ export const BUILTIN_HARNESS_PROFILES = {
39
40
  authModel: "own-auth",
40
41
  modelFamily: "claude",
41
42
  effortValues: ["low", "medium", "high", "xhigh", "max"],
42
- verified: { date: "2026-07-20", version: "current CLI contract" },
43
+ verified: { date: "2026-07-25", version: "2.1.197 (Claude Code)" },
43
44
  capabilities: capabilities({
44
45
  effortSelection: supported("Mapped to `--effort <level>`."),
45
46
  maxTurns: supported("Mapped to `--max-turns <n>`."),
@@ -61,9 +62,17 @@ export const BUILTIN_HARNESS_PROFILES = {
61
62
  authModel: "own-auth",
62
63
  modelFamily: "gpt",
63
64
  effortValues: ["none", "minimal", "low", "medium", "high", "xhigh"],
64
- verified: { date: "2026-07-21", version: "codex-cli 0.145.0-alpha.18" },
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."),
@@ -83,7 +92,7 @@ export const BUILTIN_HARNESS_PROFILES = {
83
92
  authModel: "own-auth",
84
93
  modelFamily: "multi",
85
94
  effortValues: [],
86
- verified: { date: "2026-07-21", version: "2026.07.16-899851b" },
95
+ verified: { date: "2026-07-25", version: "2026.07.23-e383d2b" },
87
96
  capabilities: capabilities({
88
97
  effortSelection: unsupported("Cursor embeds effort in some parameterized model ids; Harnery does not rewrite model ids."),
89
98
  maxTurns: unsupported("cursor-agent exposes no turn-ceiling flag."),
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,2BAA2B,EAC3B,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,4BAA4B,EAC5B,iCAAiC,EACjC,+BAA+B,GAChC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,uBAAuB,EACvB,oCAAoC,EACpC,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,EACX,YAAY,EACZ,SAAS,EACT,OAAO,EACP,YAAY,EACZ,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAC3B,kCAAkC,EAClC,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,uCAAuC,EACvC,6BAA6B,EAC7B,oCAAoC,GACrC,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,qBAAqB,EACrB,yBAAyB,EACzB,kCAAkC,EAClC,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,EACxB,eAAe,EACf,uBAAuB,EACvB,iBAAiB,EACjB,+BAA+B,EAC/B,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,uBAAuB,EACvB,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,EACvB,6BAA6B,EAC7B,0BAA0B,EAC1B,oBAAoB,EACpB,gBAAgB,EAChB,+BAA+B,EAC/B,4BAA4B,EAC5B,2BAA2B,EAC3B,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,uCAAuC,EACvC,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,EAClB,4BAA4B,EAC5B,iCAAiC,EACjC,uBAAuB,EACvB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,6BAA6B,EAC7B,oBAAoB,EACpB,sBAAsB,EACtB,qCAAqC,GACtC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,gBAAgB,EAChB,kCAAkC,EAClC,eAAe,EACf,gBAAgB,EAChB,8BAA8B,EAC9B,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,EACxB,yBAAyB,EACzB,gCAAgC,EAChC,kBAAkB,EAClB,cAAc,EACd,6BAA6B,EAC7B,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,EAC1B,gCAAgC,EAChC,gCAAgC,GACjC,MAAM,uBAAuB,CAAC"}
@@ -4,4 +4,4 @@ export { runWorkflow, WorkflowParkedError, WorkflowRunError } from "./engine.js"
4
4
  export { buildWorkflowProof, createEvidenceRecord, digestResult, normalizeWorkflowMeta, readWorkflowProof, renderWorkflowProof, rollupAcceptance, writeWorkflowProof, } from "./proof.js";
5
5
  export { acquireWorkflowResumeLease, assertWorkflowRunResumable, assertWorkflowScriptUnchanged, readWorkflowRunManifest, WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION, workflowRunManifestPath, workflowScriptDigest, writeWorkflowRunManifest, } from "./run-state.js";
6
6
  export { WORKFLOW_ATTEMPT_CONTEXT_SCHEMA_VERSION, WORKFLOW_PROOF_SCHEMA_VERSION, WORKFLOW_WORK_CONTEXT_SCHEMA_VERSION, } from "./types.js";
7
- 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";
7
+ 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";
@@ -1,6 +1,6 @@
1
1
  import type { RepoSnapshot } from "../context/index.js";
2
2
  import type { NormalizedPolicy, PolicyDecision, PolicyIsolation, PolicyNetworkAccess } from "../policy/index.js";
3
- import type { AcceptanceCriterion, AcceptanceResult, AcceptanceSummary, HarnessAttestationCitation, HarnessEvidenceCapability, ResultDigest, WorkflowAgentProof, WorkflowAttemptContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowProof, WorkflowWorkContext } from "./types.js";
3
+ import type { AcceptanceCriterion, AcceptanceResult, AcceptanceSummary, HarnessAttestationCitation, HarnessEvidenceCapability, ResultDigest, WorkflowAgentProof, WorkflowAttemptContext, WorkflowEvidenceInput, WorkflowEvidenceRecord, WorkflowMeta, WorkflowProof, WorkflowSandboxProjectionEvidence, WorkflowWorkContext } from "./types.js";
4
4
  import type { WorkspaceAttestation, WorkspaceBinding, WorkspaceUnsupportedExecutionEvidence } from "./workspaces/index.js";
5
5
  export interface NormalizedWorkflowMeta {
6
6
  name: string;
@@ -24,6 +24,8 @@ export interface BuildWorkflowProofInput {
24
24
  agents: WorkflowAgentProof[];
25
25
  evidence: WorkflowEvidenceRecord[];
26
26
  harnessEvidence?: Readonly<Record<string, HarnessEvidenceCapability | undefined>>;
27
+ /** Filesystem policy actually projected into children (ADR 0039). */
28
+ sandboxProjection?: WorkflowSandboxProjectionEvidence;
27
29
  /** Live attestations backing each harness's claims (ADR 0038). Injected by
28
30
  * the caller so proof stays free of filesystem lookups. */
29
31
  harnessAttestations?: Readonly<Record<string, HarnessAttestationCitation | undefined>>;
@@ -1 +1 @@
1
- {"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/proof.ts"],"names":[],"mappings":"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"}
@@ -146,6 +146,7 @@ export function buildWorkflowProof(input) {
146
146
  ? buildExecutionEvidence(input.status, input.workspaceBinding, input.workspaceAttestation)
147
147
  : input.workspaceFallback,
148
148
  repository,
149
+ ...(input.sandboxProjection ? { sandbox_projection: input.sandboxProjection } : {}),
149
150
  harnesses,
150
151
  unknowns,
151
152
  integrity: {
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Project the host's filesystem policy into a harness's own vendor sandbox
3
+ * (ADR 0039).
4
+ *
5
+ * Harnery decides where a workflow child may write. Until this existed it never
6
+ * told the child, so a child working in a provider-owned Git worktree could
7
+ * edit files and could not commit: the vendor excludes a repository's
8
+ * administrative directory from its writable set by policy, and Harnery had no
9
+ * way to name it as an exception.
10
+ *
11
+ * The one rule that matters here is that a projection an adapter cannot
12
+ * represent is refused before launch. Passing a policy that gets silently
13
+ * dropped would leave an operator believing a child was constrained when it was
14
+ * not, which is worse than refusing and worse than never offering the feature.
15
+ */
16
+ import type { HarnessSandboxProjection } from "../harnesses/types.js";
17
+ import type { GitAdministrativeGrant, SpawnFilesystemPolicy } from "./types.js";
18
+ import type { WorkspaceBinding } from "./workspaces/types.js";
19
+ export declare class SandboxProjectionError extends Error {
20
+ readonly harness: string;
21
+ readonly reason: "mode_unrepresentable" | "writable_roots_unrepresentable" | "no_projection" | "writable_root_escapes_workspace" | "git_grant_unavailable";
22
+ constructor(harness: string, reason: SandboxProjectionError["reason"], message: string);
23
+ }
24
+ export interface ResolvedSandboxProjection {
25
+ /** Vendor-native name for the requested mode. */
26
+ nativeMode: string;
27
+ writableRoots: readonly string[];
28
+ }
29
+ /**
30
+ * Resolve a requested policy against what the adapter declares it can carry.
31
+ * Throws rather than degrading; see the module note.
32
+ */
33
+ export declare function resolveSandboxProjection(harness: string, declaration: HarnessSandboxProjection | undefined, policy: SpawnFilesystemPolicy): ResolvedSandboxProjection;
34
+ /**
35
+ * Refuse a projection that would grant write access outside the root the
36
+ * workspace provider already validated (ADR 0039).
37
+ *
38
+ * The renderer alone cannot do this: it never sees the binding. Granting a path
39
+ * the provider never sanctioned would let a projection quietly widen the blast
40
+ * radius of a run that the workspace lifecycle believes it has contained.
41
+ */
42
+ export declare function assertProjectionWithinWorkspace(harness: string, allowedRootRealpath: string, writableRoots: readonly string[]): void;
43
+ /**
44
+ * Resolve a named Git administrative grant into concrete writable roots
45
+ * (ADR 0040).
46
+ *
47
+ * These are the only paths a run may write outside its workspace, and the
48
+ * caller never names them: they come from the binding the provider verified.
49
+ * A caller-supplied path is checked by `assertProjectionWithinWorkspace` and can
50
+ * never reach here, so asking for the grant is the whole of the widening.
51
+ *
52
+ * In a linked worktree both halves of the administrative directory live under
53
+ * the source repository, and a commit needs the shared half regardless, so the
54
+ * grant returns both rather than pretending the private half is useful alone.
55
+ */
56
+ export declare function resolveGitGrantRoots(grant: GitAdministrativeGrant, binding: WorkspaceBinding | undefined): readonly string[];
57
+ //# sourceMappingURL=sandbox-projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-projection.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/sandbox-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EACX,sBAAsB,GACtB,gCAAgC,GAChC,eAAe,GACf,iCAAiC,GACjC,uBAAuB,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM;CAMvF;AAED,MAAM,WAAW,yBAAyB;IACxC,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,wBAAwB,GAAG,SAAS,EACjD,MAAM,EAAE,qBAAqB,GAC5B,yBAAyB,CAoC3B;AASD;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,MAAM,EAC3B,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,IAAI,CAUN;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,sBAAsB,EAC7B,OAAO,EAAE,gBAAgB,GAAG,SAAS,GACpC,SAAS,MAAM,EAAE,CAanB"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Project the host's filesystem policy into a harness's own vendor sandbox
3
+ * (ADR 0039).
4
+ *
5
+ * Harnery decides where a workflow child may write. Until this existed it never
6
+ * told the child, so a child working in a provider-owned Git worktree could
7
+ * edit files and could not commit: the vendor excludes a repository's
8
+ * administrative directory from its writable set by policy, and Harnery had no
9
+ * way to name it as an exception.
10
+ *
11
+ * The one rule that matters here is that a projection an adapter cannot
12
+ * represent is refused before launch. Passing a policy that gets silently
13
+ * dropped would leave an operator believing a child was constrained when it was
14
+ * not, which is worse than refusing and worse than never offering the feature.
15
+ */
16
+ export class SandboxProjectionError extends Error {
17
+ harness;
18
+ reason;
19
+ constructor(harness, reason, message) {
20
+ super(message);
21
+ this.name = "SandboxProjectionError";
22
+ this.harness = harness;
23
+ this.reason = reason;
24
+ }
25
+ }
26
+ /**
27
+ * Resolve a requested policy against what the adapter declares it can carry.
28
+ * Throws rather than degrading; see the module note.
29
+ */
30
+ export function resolveSandboxProjection(harness, declaration, policy) {
31
+ if (!declaration) {
32
+ throw new SandboxProjectionError(harness, "no_projection", `${harness} cannot project a filesystem policy into its sandbox; remove the policy or use a harness that can`);
33
+ }
34
+ const nativeMode = declaration.modes[policy.mode];
35
+ if (!nativeMode) {
36
+ throw new SandboxProjectionError(harness, "mode_unrepresentable", `${harness} does not distinguish the "${policy.mode}" filesystem mode, so it cannot be enforced`);
37
+ }
38
+ const writableRoots = policy.writableRoots ?? [];
39
+ if (writableRoots.length > 0 && !declaration.writableRoots) {
40
+ throw new SandboxProjectionError(harness, "writable_roots_unrepresentable", `${harness} does not accept an explicit writable-root set, so ${writableRoots.length} declared path(s) could not be enforced`);
41
+ }
42
+ for (const root of writableRoots) {
43
+ // Relative paths cannot be validated against the provider's roots and are
44
+ // resolved differently by every vendor, so they are refused outright.
45
+ if (typeof root !== "string" || !root.startsWith("/")) {
46
+ throw new SandboxProjectionError(harness, "writable_roots_unrepresentable", `writable root ${JSON.stringify(root)} must be an absolute path`);
47
+ }
48
+ }
49
+ return { nativeMode, writableRoots };
50
+ }
51
+ /** True when `candidate` is `root` or lies beneath it, comparing whole path
52
+ * segments so `/a/bc` is not treated as inside `/a/b`. */
53
+ function isWithin(root, candidate) {
54
+ const normalizedRoot = root.endsWith("/") ? root.slice(0, -1) : root;
55
+ return candidate === normalizedRoot || candidate.startsWith(`${normalizedRoot}/`);
56
+ }
57
+ /**
58
+ * Refuse a projection that would grant write access outside the root the
59
+ * workspace provider already validated (ADR 0039).
60
+ *
61
+ * The renderer alone cannot do this: it never sees the binding. Granting a path
62
+ * the provider never sanctioned would let a projection quietly widen the blast
63
+ * radius of a run that the workspace lifecycle believes it has contained.
64
+ */
65
+ export function assertProjectionWithinWorkspace(harness, allowedRootRealpath, writableRoots) {
66
+ for (const root of writableRoots) {
67
+ if (!isWithin(allowedRootRealpath, root)) {
68
+ throw new SandboxProjectionError(harness, "writable_root_escapes_workspace", `writable root ${JSON.stringify(root)} is outside the workspace root ${JSON.stringify(allowedRootRealpath)} the provider validated`);
69
+ }
70
+ }
71
+ }
72
+ /**
73
+ * Resolve a named Git administrative grant into concrete writable roots
74
+ * (ADR 0040).
75
+ *
76
+ * These are the only paths a run may write outside its workspace, and the
77
+ * caller never names them: they come from the binding the provider verified.
78
+ * A caller-supplied path is checked by `assertProjectionWithinWorkspace` and can
79
+ * never reach here, so asking for the grant is the whole of the widening.
80
+ *
81
+ * In a linked worktree both halves of the administrative directory live under
82
+ * the source repository, and a commit needs the shared half regardless, so the
83
+ * grant returns both rather than pretending the private half is useful alone.
84
+ */
85
+ export function resolveGitGrantRoots(grant, binding) {
86
+ if (grant === "none")
87
+ return [];
88
+ const repository = binding?.repository;
89
+ if (!repository) {
90
+ throw new SandboxProjectionError("workflow", "git_grant_unavailable", `gitWrite "${grant}" needs a Git repository binding; this run has ${binding ? "a workspace with no repository" : "no isolated workspace"}`);
91
+ }
92
+ // Deduplicated: a full-clone topology reports the same path for both, and a
93
+ // repeated writable root is noise in the rendered argv and in proof.
94
+ return [...new Set([repository.gitdir.realpath, repository.common_dir.realpath])];
95
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYrE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAc1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAgDxE;AAED,eAAO,MAAM,iBAAiB,EAAE,OAc/B,CAAC"}
1
+ {"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYrE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAsB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAgDxE;AAED,eAAO,MAAM,iBAAiB,EAAE,OAc/B,CAAC"}
@@ -18,11 +18,17 @@
18
18
  * (stop-hook.ts), keeping heartbeats + events on.
19
19
  */
20
20
  import { exec } from "../../lib/exec.js";
21
- import { validateHarnessEffort } from "../harnesses/profiles.js";
21
+ import { builtinHarnessProfile, validateHarnessEffort } from "../harnesses/profiles.js";
22
22
  import { buildChildEnv } from "./child-env.js";
23
23
  import { notFoundError } from "./harnesses.js";
24
+ import { resolveSandboxProjection } from "./sandbox-projection.js";
25
+ import { vendorFailureText } from "./spawn-failure.js";
24
26
  export function buildClaudeInvocation(req) {
25
27
  validateHarnessEffort("claude-code", req.effort);
28
+ if (req.filesystemPolicy) {
29
+ // Declared unrepresentable: refuse rather than drop it silently (ADR 0039).
30
+ resolveSandboxProjection("claude-code", builtinHarnessProfile("claude-code")?.sandboxProjection, req.filesystemPolicy);
31
+ }
26
32
  const argv = [
27
33
  "claude",
28
34
  "-p",
@@ -52,7 +58,7 @@ export function normalizeClaudeResult(raw) {
52
58
  ok: false,
53
59
  text: "",
54
60
  durationMs: raw.durationMs,
55
- error: `claude exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
61
+ error: `claude exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
56
62
  };
57
63
  }
58
64
  let envelope;
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAgB9F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAiBvE;AAED,eAAO,MAAM,YAAY,EAAE,OA2B1B,CAAC"}
1
+ {"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CA8B9F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAiBvE;AAED,eAAO,MAAM,YAAY,EAAE,OA2B1B,CAAC"}
@@ -19,13 +19,19 @@ import { existsSync, readFileSync, rmSync } from "node:fs";
19
19
  import { tmpdir } from "node:os";
20
20
  import { join } from "node:path";
21
21
  import { exec } from "../../lib/exec.js";
22
- import { validateHarnessEffort } from "../harnesses/profiles.js";
22
+ import { builtinHarnessProfile, validateHarnessEffort } from "../harnesses/profiles.js";
23
23
  import { buildChildEnv } from "./child-env.js";
24
24
  import { notFoundError } from "./harnesses.js";
25
+ import { resolveSandboxProjection } from "./sandbox-projection.js";
26
+ import { vendorFailureText } from "./spawn-failure.js";
25
27
  export function buildCodexInvocation(req, resultFile) {
26
28
  validateHarnessEffort("codex", req.effort);
27
29
  if (!resultFile)
28
30
  throw new Error("codex adapter requires a final-message result file");
31
+ // Default stays workspace-write so an unprojected request is unchanged.
32
+ const projection = req.filesystemPolicy
33
+ ? resolveSandboxProjection("codex", builtinHarnessProfile("codex")?.sandboxProjection, req.filesystemPolicy)
34
+ : undefined;
29
35
  const argv = [
30
36
  "codex",
31
37
  "exec",
@@ -34,8 +40,11 @@ export function buildCodexInvocation(req, resultFile) {
34
40
  resultFile,
35
41
  "--skip-git-repo-check",
36
42
  "--sandbox",
37
- "workspace-write",
43
+ projection?.nativeMode ?? "workspace-write",
38
44
  ];
45
+ if (projection && projection.writableRoots.length > 0) {
46
+ argv.push("-c", `sandbox_workspace_write.writable_roots=${JSON.stringify(projection.writableRoots)}`);
47
+ }
39
48
  if (req.model)
40
49
  argv.push("--model", req.model);
41
50
  if (req.effort)
@@ -51,7 +60,7 @@ export function normalizeCodexResult(raw) {
51
60
  ok: false,
52
61
  text: "",
53
62
  durationMs: raw.durationMs,
54
- error: `codex exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
63
+ error: `codex exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
55
64
  };
56
65
  }
57
66
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAU1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA6BxE;AAED,eAAO,MAAM,aAAa,EAAE,OAe3B,CAAC"}
1
+ {"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAkB1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA6BxE;AAED,eAAO,MAAM,aAAa,EAAE,OAe3B,CAAC"}
@@ -19,9 +19,11 @@
19
19
  * accepted and ignored (documented in the CLI docs page).
20
20
  */
21
21
  import { exec } from "../../lib/exec.js";
22
- import { validateHarnessEffort } from "../harnesses/profiles.js";
22
+ import { builtinHarnessProfile, validateHarnessEffort } from "../harnesses/profiles.js";
23
23
  import { buildChildEnv } from "./child-env.js";
24
24
  import { notFoundError } from "./harnesses.js";
25
+ import { resolveSandboxProjection } from "./sandbox-projection.js";
26
+ import { vendorFailureText } from "./spawn-failure.js";
25
27
  /** Exported for unit tests (no live binary to test against). */
26
28
  export function parseCursorOutput(stdout) {
27
29
  try {
@@ -38,6 +40,10 @@ export function parseCursorOutput(stdout) {
38
40
  }
39
41
  export function buildCursorInvocation(req) {
40
42
  validateHarnessEffort("cursor", req.effort);
43
+ if (req.filesystemPolicy) {
44
+ // Declared unrepresentable: refuse rather than drop it silently (ADR 0039).
45
+ resolveSandboxProjection("cursor", builtinHarnessProfile("cursor")?.sandboxProjection, req.filesystemPolicy);
46
+ }
41
47
  // --trust: headless cursor-agent refuses untrusted workspaces (exit 1,
42
48
  // "Workspace Trust Required"). --force: a print-mode child has no interactive
43
49
  // approval channel, so host-authorized workflow dispatch must let it run
@@ -57,7 +63,7 @@ export function normalizeCursorResult(raw) {
57
63
  ok: false,
58
64
  text: "",
59
65
  durationMs: raw.durationMs,
60
- error: `cursor-agent exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
66
+ error: `cursor-agent exited ${raw.exitCode}: ${vendorFailureText(raw)}`,
61
67
  };
62
68
  }
63
69
  const parsed = parseCursorOutput(raw.stdout);
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Shared failure-text extraction for the harness spawn adapters.
3
+ *
4
+ * A vendor CLI prints its banner, resolved config, and startup warnings first,
5
+ * and the reason it actually failed last. Every adapter used to keep the FIRST
6
+ * 500 characters of the transcript, which reliably preserved the banner and
7
+ * discarded the answer: a child that died with "your workspace is out of
8
+ * credits" reported a cosmetic startup warning instead.
9
+ *
10
+ * Two rules follow. Keep the tail. Include both streams, because which one
11
+ * carries the reason varies by vendor and by failure.
12
+ */
13
+ /** Bounded failure text from a finished child, tail-preserving. */
14
+ export declare function vendorFailureText(raw: {
15
+ stdout?: string;
16
+ stderr?: string;
17
+ }, maxChars?: number): string;
18
+ //# sourceMappingURL=spawn-failure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-failure.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,mEAAmE;AACnE,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EACzC,QAAQ,SAAoB,GAC3B,MAAM,CAQR"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared failure-text extraction for the harness spawn adapters.
3
+ *
4
+ * A vendor CLI prints its banner, resolved config, and startup warnings first,
5
+ * and the reason it actually failed last. Every adapter used to keep the FIRST
6
+ * 500 characters of the transcript, which reliably preserved the banner and
7
+ * discarded the answer: a child that died with "your workspace is out of
8
+ * credits" reported a cosmetic startup warning instead.
9
+ *
10
+ * Two rules follow. Keep the tail. Include both streams, because which one
11
+ * carries the reason varies by vendor and by failure.
12
+ */
13
+ const DEFAULT_MAX_CHARS = 500;
14
+ /** Bounded failure text from a finished child, tail-preserving. */
15
+ export function vendorFailureText(raw, maxChars = DEFAULT_MAX_CHARS) {
16
+ const parts = [raw.stderr, raw.stdout]
17
+ .map((part) => part?.trim())
18
+ .filter((part) => !!part);
19
+ if (parts.length === 0)
20
+ return "";
21
+ // Both streams, stderr first, so a reason on the unexpected stream survives.
22
+ const combined = parts.join("\n");
23
+ return combined.length > maxChars ? `…${combined.slice(-maxChars)}` : combined;
24
+ }