harnery 0.31.6 → 0.32.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 (218) hide show
  1. package/dist/commander.d.ts.map +1 -1
  2. package/dist/commander.js +2 -0
  3. package/dist/commands/agents.d.ts +0 -9
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +70 -95
  6. package/dist/commands/browse-session.d.ts +21 -0
  7. package/dist/commands/browse-session.d.ts.map +1 -0
  8. package/dist/commands/browse-session.js +157 -0
  9. package/dist/commands/browse.d.ts.map +1 -1
  10. package/dist/commands/browse.js +205 -37
  11. package/dist/commands/checkpoint.js +1 -1
  12. package/dist/commands/deinit.d.ts.map +1 -1
  13. package/dist/commands/deinit.js +4 -0
  14. package/dist/commands/docs.d.ts.map +1 -1
  15. package/dist/commands/docs.js +40 -0
  16. package/dist/commands/doctor.d.ts.map +1 -1
  17. package/dist/commands/doctor.js +100 -17
  18. package/dist/commands/init.d.ts +1 -2
  19. package/dist/commands/init.d.ts.map +1 -1
  20. package/dist/commands/init.js +16 -5
  21. package/dist/commands/tunnel.d.ts.map +1 -1
  22. package/dist/commands/tunnel.js +2 -3
  23. package/dist/core/agents/canonical-emit.d.ts +1 -2
  24. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  25. package/dist/core/agents/canonical-emit.js +1 -2
  26. package/dist/core/agents/cli.js +123 -88
  27. package/dist/core/agents/coord-client.d.ts +7 -3
  28. package/dist/core/agents/coord-client.d.ts.map +1 -1
  29. package/dist/core/agents/coord-client.js +5 -4
  30. package/dist/core/agents/finalization.d.ts +68 -0
  31. package/dist/core/agents/finalization.d.ts.map +1 -0
  32. package/dist/core/agents/finalization.js +443 -0
  33. package/dist/core/agents/git-hook.d.ts +51 -0
  34. package/dist/core/agents/git-hook.d.ts.map +1 -0
  35. package/dist/core/agents/git-hook.js +118 -0
  36. package/dist/core/agents/render/prompt-context.d.ts +6 -5
  37. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  38. package/dist/core/agents/render/prompt-context.js +26 -13
  39. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  40. package/dist/core/agents/render/session-context.js +15 -2
  41. package/dist/core/agents/rules/claim-conflict.js +3 -3
  42. package/dist/core/agents/rules/commit-conflict.d.ts +15 -6
  43. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  44. package/dist/core/agents/rules/commit-conflict.js +21 -5
  45. package/dist/core/agents/rules/stop-hook.d.ts +3 -0
  46. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  47. package/dist/core/agents/rules/stop-hook.js +59 -23
  48. package/dist/core/agents/session-events.d.ts +8 -16
  49. package/dist/core/agents/session-events.d.ts.map +1 -1
  50. package/dist/core/agents/session-events.js +12 -26
  51. package/dist/core/agents/state/heartbeat-projector.d.ts +3 -0
  52. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  53. package/dist/core/agents/state/heartbeat-projector.js +44 -1
  54. package/dist/core/agents/state/heartbeat-writer.d.ts +32 -5
  55. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  56. package/dist/core/agents/state/heartbeat-writer.js +54 -17
  57. package/dist/core/agents/state/names.d.ts +22 -1
  58. package/dist/core/agents/state/names.d.ts.map +1 -1
  59. package/dist/core/agents/state/names.js +40 -2
  60. package/dist/core/config.d.ts +30 -2
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +74 -11
  63. package/dist/core/governor/planning.d.ts.map +1 -1
  64. package/dist/core/governor/planning.js +12 -13
  65. package/dist/core/hooks/adapter/detect.d.ts +2 -4
  66. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  67. package/dist/core/hooks/adapter/detect.js +4 -14
  68. package/dist/core/hooks/adapter/output.d.ts +1 -1
  69. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  70. package/dist/core/hooks/adapter/output.js +5 -4
  71. package/dist/core/hooks/adapter/wiring.d.ts +23 -0
  72. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  73. package/dist/core/hooks/adapter/wiring.js +32 -0
  74. package/dist/core/hooks/cli.d.ts +1 -2
  75. package/dist/core/hooks/cli.d.ts.map +1 -1
  76. package/dist/core/hooks/cli.js +169 -27
  77. package/dist/core/hooks/codex-wsl-bridge.d.ts +46 -0
  78. package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -0
  79. package/dist/core/hooks/codex-wsl-bridge.js +137 -0
  80. package/dist/core/hooks/effects/index.d.ts +1 -1
  81. package/dist/core/hooks/effects/index.js +3 -3
  82. package/dist/core/hooks/events/schema.d.ts +27 -0
  83. package/dist/core/hooks/events/schema.d.ts.map +1 -1
  84. package/dist/core/hooks/resolve/owner.d.ts +7 -1
  85. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  86. package/dist/core/hooks/resolve/owner.js +15 -0
  87. package/dist/core/hooks/resolve/transcript.d.ts +44 -0
  88. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  89. package/dist/core/hooks/resolve/transcript.js +176 -1
  90. package/dist/core/hooks/session-name-presence.d.ts +30 -0
  91. package/dist/core/hooks/session-name-presence.d.ts.map +1 -0
  92. package/dist/core/hooks/session-name-presence.js +43 -0
  93. package/dist/core/hooks/unsafe-cross-shell.d.ts +13 -0
  94. package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -0
  95. package/dist/core/hooks/unsafe-cross-shell.js +151 -0
  96. package/dist/core/work/state.d.ts +4 -5
  97. package/dist/core/work/state.d.ts.map +1 -1
  98. package/dist/core/work/state.js +5 -8
  99. package/dist/core/workflow/index.d.ts +1 -1
  100. package/dist/core/workflow/index.d.ts.map +1 -1
  101. package/dist/core/workflow/proof.d.ts +2 -2
  102. package/dist/core/workflow/proof.d.ts.map +1 -1
  103. package/dist/core/workflow/proof.js +2 -2
  104. package/dist/core/workflow/run-state.d.ts +2 -2
  105. package/dist/core/workflow/run-state.d.ts.map +1 -1
  106. package/dist/core/workflow/run-state.js +2 -2
  107. package/dist/core/workflow/types.d.ts +3 -3
  108. package/dist/core/workflow/types.d.ts.map +1 -1
  109. package/dist/core/workflow/workspaces/execution.d.ts +2 -2
  110. package/dist/core/workflow/workspaces/execution.d.ts.map +1 -1
  111. package/dist/core/workflow/workspaces/execution.js +5 -0
  112. package/dist/core/workflow/workspaces/index.d.ts +1 -1
  113. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  114. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  115. package/dist/core/workflow/workspaces/local-git.js +1 -2
  116. package/dist/core/workflow/workspaces/paths.d.ts +2 -2
  117. package/dist/core/workflow/workspaces/types.d.ts +0 -6
  118. package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
  119. package/dist/core/workflow/workspaces/validate.d.ts +0 -2
  120. package/dist/core/workflow/workspaces/validate.d.ts.map +1 -1
  121. package/dist/core/workflow/workspaces/validate.js +0 -2
  122. package/dist/lib/browser/client.d.ts +118 -1
  123. package/dist/lib/browser/client.d.ts.map +1 -1
  124. package/dist/lib/browser/client.js +435 -6
  125. package/dist/lib/browser/geometry.d.ts.map +1 -1
  126. package/dist/lib/browser/geometry.js +180 -37
  127. package/dist/lib/browser/index.d.ts +5 -1
  128. package/dist/lib/browser/index.d.ts.map +1 -1
  129. package/dist/lib/browser/index.js +5 -1
  130. package/dist/lib/browser/netscape-cookies.d.ts +6 -0
  131. package/dist/lib/browser/netscape-cookies.d.ts.map +1 -0
  132. package/dist/lib/browser/netscape-cookies.js +31 -0
  133. package/dist/lib/browser/proxy.d.ts +24 -0
  134. package/dist/lib/browser/proxy.d.ts.map +1 -0
  135. package/dist/lib/browser/proxy.js +84 -0
  136. package/dist/lib/browser/runts.d.ts +6 -0
  137. package/dist/lib/browser/runts.d.ts.map +1 -1
  138. package/dist/lib/browser/runts.js +21 -4
  139. package/dist/lib/browser/session-control.d.ts +112 -0
  140. package/dist/lib/browser/session-control.d.ts.map +1 -0
  141. package/dist/lib/browser/session-control.js +670 -0
  142. package/dist/lib/docs-links.d.ts +108 -0
  143. package/dist/lib/docs-links.d.ts.map +1 -0
  144. package/dist/lib/docs-links.js +555 -0
  145. package/dist/lib/exec.d.ts +1 -1
  146. package/dist/lib/exec.js +1 -1
  147. package/dist/lib/identities/assume.d.ts +4 -2
  148. package/dist/lib/identities/assume.d.ts.map +1 -1
  149. package/dist/lib/identities/assume.js +15 -2
  150. package/dist/lib/instructions/git-hooks.d.ts +75 -0
  151. package/dist/lib/instructions/git-hooks.d.ts.map +1 -0
  152. package/dist/lib/instructions/git-hooks.js +238 -0
  153. package/dist/lib/instructions/splice.d.ts +10 -4
  154. package/dist/lib/instructions/splice.d.ts.map +1 -1
  155. package/dist/lib/instructions/splice.js +19 -11
  156. package/package.json +1 -1
  157. package/schemas/config.schema.json +32 -0
  158. package/src/commander.ts +2 -0
  159. package/src/commands/agents.ts +96 -115
  160. package/src/commands/browse-session.ts +245 -0
  161. package/src/commands/browse.ts +287 -44
  162. package/src/commands/checkpoint.ts +1 -1
  163. package/src/commands/deinit.ts +7 -1
  164. package/src/commands/docs.ts +56 -0
  165. package/src/commands/doctor.ts +100 -18
  166. package/src/commands/init.ts +17 -9
  167. package/src/commands/tunnel.ts +2 -5
  168. package/src/core/agents/canonical-emit.ts +1 -2
  169. package/src/core/agents/cli.ts +137 -88
  170. package/src/core/agents/coord-client.ts +10 -5
  171. package/src/core/agents/finalization.ts +595 -0
  172. package/src/core/agents/git-hook.ts +126 -0
  173. package/src/core/agents/render/prompt-context.ts +28 -13
  174. package/src/core/agents/render/session-context.ts +15 -2
  175. package/src/core/agents/rules/claim-conflict.ts +3 -3
  176. package/src/core/agents/rules/commit-conflict.ts +35 -8
  177. package/src/core/agents/rules/stop-hook.ts +71 -23
  178. package/src/core/agents/session-events.ts +14 -41
  179. package/src/core/agents/state/heartbeat-projector.ts +45 -1
  180. package/src/core/agents/state/heartbeat-writer.ts +78 -16
  181. package/src/core/agents/state/names.ts +54 -2
  182. package/src/core/config.ts +92 -12
  183. package/src/core/governor/planning.ts +17 -12
  184. package/src/core/hooks/adapter/detect.ts +4 -13
  185. package/src/core/hooks/adapter/output.ts +9 -4
  186. package/src/core/hooks/adapter/wiring.ts +43 -0
  187. package/src/core/hooks/cli.ts +201 -23
  188. package/src/core/hooks/codex-wsl-bridge.ts +188 -0
  189. package/src/core/hooks/effects/index.ts +5 -5
  190. package/src/core/hooks/events/schema.ts +27 -0
  191. package/src/core/hooks/resolve/owner.ts +22 -1
  192. package/src/core/hooks/resolve/transcript.ts +164 -1
  193. package/src/core/hooks/session-name-presence.ts +52 -0
  194. package/src/core/hooks/unsafe-cross-shell.ts +160 -0
  195. package/src/core/work/state.ts +8 -12
  196. package/src/core/workflow/engine.ts +2 -2
  197. package/src/core/workflow/index.ts +0 -1
  198. package/src/core/workflow/proof.ts +4 -4
  199. package/src/core/workflow/run-state.ts +5 -5
  200. package/src/core/workflow/types.ts +3 -3
  201. package/src/core/workflow/workspaces/execution.ts +11 -4
  202. package/src/core/workflow/workspaces/index.ts +0 -3
  203. package/src/core/workflow/workspaces/local-git.ts +1 -2
  204. package/src/core/workflow/workspaces/paths.ts +2 -2
  205. package/src/core/workflow/workspaces/types.ts +0 -9
  206. package/src/core/workflow/workspaces/validate.ts +1 -5
  207. package/src/lib/browser/client.ts +528 -6
  208. package/src/lib/browser/geometry.ts +197 -37
  209. package/src/lib/browser/index.ts +39 -0
  210. package/src/lib/browser/netscape-cookies.ts +39 -0
  211. package/src/lib/browser/proxy.ts +105 -0
  212. package/src/lib/browser/runts.ts +27 -3
  213. package/src/lib/browser/session-control.ts +892 -0
  214. package/src/lib/docs-links.ts +674 -0
  215. package/src/lib/exec.ts +1 -1
  216. package/src/lib/identities/assume.ts +22 -1
  217. package/src/lib/instructions/git-hooks.ts +259 -0
  218. package/src/lib/instructions/splice.ts +41 -11
@@ -0,0 +1,160 @@
1
+ import { isWslUncPath } from "./codex-wsl-bridge.ts";
2
+ import type { Adapter } from "./events/schema.ts";
3
+
4
+ export interface CrossShellInspection {
5
+ adapter: Adapter;
6
+ cwd: unknown;
7
+ toolName: unknown;
8
+ toolInput: unknown;
9
+ }
10
+
11
+ const DENY_REASON =
12
+ "Unsafe Windows-to-WSL shell-string construction blocked. Use the host's argv-preserving " +
13
+ "WSL bridge for a single executable, or its literal-script mode for compound Bash. Do not " +
14
+ "invoke wsl.exe with bash -c or bash -lc.";
15
+
16
+ function shellCommand(toolName: unknown, toolInput: unknown): string | null {
17
+ if (typeof toolName !== "string") return null;
18
+ const normalized = toolName.toLowerCase();
19
+ if (
20
+ normalized !== "bash" &&
21
+ normalized !== "shell" &&
22
+ normalized !== "shell_command" &&
23
+ normalized !== "functions.shell_command"
24
+ ) {
25
+ return null;
26
+ }
27
+
28
+ let input = toolInput;
29
+ if (typeof input === "string") {
30
+ try {
31
+ input = JSON.parse(input) as unknown;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ if (!input || typeof input !== "object") return null;
37
+ const command = (input as Record<string, unknown>).command;
38
+ return typeof command === "string" ? command : null;
39
+ }
40
+
41
+ function quotedToken(command: string, start: number): { token: string; end: number } {
42
+ const quote = command[start]!;
43
+ let token = "";
44
+ for (let index = start + 1; index < command.length; index += 1) {
45
+ const char = command[index]!;
46
+ if (char === "`" && quote === '"' && index + 1 < command.length) {
47
+ token += command[index + 1]!;
48
+ index += 1;
49
+ continue;
50
+ }
51
+ if (char === quote) {
52
+ if (quote === "'" && command[index + 1] === "'") {
53
+ token += "'";
54
+ index += 1;
55
+ continue;
56
+ }
57
+ return { token, end: index + 1 };
58
+ }
59
+ token += char;
60
+ }
61
+ return { token, end: command.length };
62
+ }
63
+
64
+ /** Find direct PowerShell command invocations of wsl/wsl.exe outside quoted strings. */
65
+ function wslInvocationOffsets(command: string): number[] {
66
+ const offsets: number[] = [];
67
+ let expectCommand = true;
68
+ let callOperator = false;
69
+
70
+ for (let index = 0; index < command.length; ) {
71
+ const char = command[index]!;
72
+ if (/\s/.test(char)) {
73
+ if (char === "\n" || char === "\r") {
74
+ expectCommand = true;
75
+ callOperator = false;
76
+ }
77
+ index += 1;
78
+ continue;
79
+ }
80
+ if (char === "#" && expectCommand) {
81
+ const newline = command.indexOf("\n", index + 1);
82
+ index = newline === -1 ? command.length : newline + 1;
83
+ expectCommand = true;
84
+ callOperator = false;
85
+ continue;
86
+ }
87
+ if (";|{}=()".includes(char)) {
88
+ expectCommand = true;
89
+ callOperator = false;
90
+ index += 1;
91
+ continue;
92
+ }
93
+ if (char === "&") {
94
+ expectCommand = true;
95
+ callOperator = true;
96
+ index += command[index + 1] === "&" ? 2 : 1;
97
+ continue;
98
+ }
99
+ if (char === "'" || char === '"') {
100
+ const parsed = quotedToken(command, index);
101
+ if (expectCommand && callOperator && /^wsl(?:\.exe)?$/i.test(parsed.token)) {
102
+ offsets.push(index);
103
+ }
104
+ expectCommand = false;
105
+ callOperator = false;
106
+ index = parsed.end;
107
+ continue;
108
+ }
109
+
110
+ const start = index;
111
+ while (index < command.length && !/[\s;|{}=()&]/.test(command[index]!)) index += 1;
112
+ const token = command.slice(start, index);
113
+ if (expectCommand && /^wsl(?:\.exe)?$/i.test(token)) offsets.push(start);
114
+ expectCommand = false;
115
+ callOperator = false;
116
+ }
117
+
118
+ return offsets;
119
+ }
120
+
121
+ function statementEnd(command: string, start: number): number {
122
+ let quote: string | null = null;
123
+ for (let index = start; index < command.length; index += 1) {
124
+ const char = command[index]!;
125
+ if (quote) {
126
+ if (char === "`" && quote === '"') {
127
+ index += 1;
128
+ } else if (char === quote) {
129
+ if (quote === "'" && command[index + 1] === "'") index += 1;
130
+ else quote = null;
131
+ }
132
+ continue;
133
+ }
134
+ if (char === "'" || char === '"') quote = char;
135
+ else if (char === ";" || char === "\n" || char === "\r" || char === "|") return index;
136
+ else if (char === "&" && command[index + 1] === "&") return index;
137
+ }
138
+ return command.length;
139
+ }
140
+
141
+ function invokesBashCommandString(command: string): boolean {
142
+ for (const offset of wslInvocationOffsets(command)) {
143
+ const segment = command.slice(offset, statementEnd(command, offset));
144
+ if (/(?:^|\s)(?:\/bin\/)?bash(?=\s|$)[\s\S]*?\s-[a-z]*c[a-z]*(?=\s|$)/i.test(segment)) {
145
+ return true;
146
+ }
147
+ }
148
+ return false;
149
+ }
150
+
151
+ /**
152
+ * Deny only the unsafe hybrid shape: Windows-native Codex, WSL UNC workspace,
153
+ * shell tool, and a direct wsl.exe -> bash -c/-lc command-string invocation.
154
+ */
155
+ export function unsafeCrossShellReason(input: CrossShellInspection): string | null {
156
+ if (input.adapter !== "codex" || !isWslUncPath(input.cwd)) return null;
157
+ const command = shellCommand(input.toolName, input.toolInput);
158
+ if (!command || !invokesBashCommandString(command)) return null;
159
+ return DENY_REASON;
160
+ }
@@ -21,7 +21,7 @@ import { readWorkflowRunManifest, workflowScriptDigest } from "../workflow/run-s
21
21
  import { WORKFLOW_TRANSCRIPT_EVENT_BYTES, workflowTranscriptPath } from "../workflow/transcript.ts";
22
22
  import { isWorkspaceBoundExecutionEvidence } from "../workflow/workspaces/validate.ts";
23
23
 
24
- export const WORK_INTENT_SCHEMA_VERSION = 1 as const;
24
+ export const WORK_INTENT_SCHEMA_VERSION = 2 as const;
25
25
  export const WORK_EVENT_SCHEMA_VERSION = 1 as const;
26
26
 
27
27
  const WORK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
@@ -77,12 +77,11 @@ export interface WorkIntent {
77
77
  dependencies: string[];
78
78
  workflow: { path: string; sha256: string };
79
79
  max_attempts: number;
80
- /** Ceiling on CONSECUTIVE uncharged attempts (ADR 0046), separate from
80
+ /** Ceiling on consecutive uncharged attempts (ADR 0046), separate from
81
81
  * max_attempts. An upstream outage produces uncharged attempt after uncharged
82
82
  * attempt; without a bound it would retry forever. At the bound the item stops
83
- * and reports it is blocked on an outside service. Optional for back-compat:
84
- * an intent written before ADR 0046 has none and falls back to the default. */
85
- max_uncharged_attempts?: number;
83
+ * and reports it is blocked on an outside service. */
84
+ max_uncharged_attempts: number;
86
85
  source?: { kind: "human" | "workflow" | "external"; ref?: string };
87
86
  created_at: string;
88
87
  }
@@ -551,7 +550,7 @@ function deriveWorkProjection(
551
550
  // Count trailing consecutive uncharged attempts in the current window. The
552
551
  // bound is the only brake on an outage that never ends, so at the limit the
553
552
  // item stops and names the outside service — distinct from work-blocked.
554
- const maxUncharged = intent.max_uncharged_attempts ?? DEFAULT_MAX_UNCHARGED_ATTEMPTS;
553
+ const maxUncharged = intent.max_uncharged_attempts;
555
554
  let trailingUncharged = 0;
556
555
  for (let index = currentAttempts.length - 1; index >= 0; index--) {
557
556
  if (currentAttempts[index]!.uncharged === undefined) break;
@@ -1041,12 +1040,9 @@ function validateWorkIntent(intent: WorkIntent, workId: string): void {
1041
1040
  !Number.isSafeInteger(intent.max_attempts) ||
1042
1041
  intent.max_attempts < 1 ||
1043
1042
  intent.max_attempts > 100 ||
1044
- // Optional for back-compat: absent on pre-ADR-0046 intents. When present it
1045
- // must be a valid bound.
1046
- (intent.max_uncharged_attempts !== undefined &&
1047
- (!Number.isSafeInteger(intent.max_uncharged_attempts) ||
1048
- intent.max_uncharged_attempts < 1 ||
1049
- intent.max_uncharged_attempts > 100))
1043
+ !Number.isSafeInteger(intent.max_uncharged_attempts) ||
1044
+ intent.max_uncharged_attempts < 1 ||
1045
+ intent.max_uncharged_attempts > 100
1050
1046
  ) {
1051
1047
  throw new Error(`work intent ${workId} has an unsupported or mismatched schema`);
1052
1048
  }
@@ -87,7 +87,7 @@ import {
87
87
  import type {
88
88
  WorkspaceAttestation,
89
89
  WorkspaceBinding,
90
- WorkspaceUnsupportedExecutionEvidence,
90
+ WorkspaceCompatibilityExecutionEvidence,
91
91
  } from "./workspaces/types.ts";
92
92
  import { isWorkspaceAttestation } from "./workspaces/validate.ts";
93
93
 
@@ -324,7 +324,7 @@ async function executeWorkflow(
324
324
  const fallbackMeta = normalizeWorkflowMeta(undefined, fallbackName);
325
325
  const workspaceProvider = isolation === "shared" ? undefined : opts.workspace?.provider;
326
326
  let workspaceBinding: WorkspaceBinding | undefined;
327
- let workspaceFallback: WorkspaceUnsupportedExecutionEvidence | undefined =
327
+ let workspaceFallback: WorkspaceCompatibilityExecutionEvidence | undefined =
328
328
  resumeState?.manifest.execution.workspace_fallback;
329
329
  try {
330
330
  workspaceBinding = await resolveWorkspaceBinding({
@@ -143,7 +143,6 @@ export type {
143
143
  WorkspaceProviderCapabilities,
144
144
  WorkspaceProviderRef,
145
145
  WorkspaceResourceState,
146
- WorkspaceUnsupportedExecutionEvidence,
147
146
  } from "./workspaces/index.ts";
148
147
  export {
149
148
  applyIntegration,
@@ -40,13 +40,13 @@ import { isCanonicalWorkflowWorkContext } from "./work-context.ts";
40
40
  import type {
41
41
  WorkspaceAttestation,
42
42
  WorkspaceBinding,
43
+ WorkspaceCompatibilityExecutionEvidence,
43
44
  WorkspaceExecutionEvidence,
44
- WorkspaceUnsupportedExecutionEvidence,
45
45
  } from "./workspaces/index.ts";
46
46
  import {
47
47
  isWorkspaceBoundExecutionEvidence,
48
+ isWorkspaceCompatibilityExecutionEvidence,
48
49
  isWorkspaceExecutionEvidence,
49
- isWorkspaceUnsupportedExecutionEvidence,
50
50
  workspaceProofLifecycle,
51
51
  } from "./workspaces/validate.ts";
52
52
 
@@ -101,7 +101,7 @@ export interface BuildWorkflowProofInput {
101
101
  blocked?: { reason: string; decisionId?: string };
102
102
  workspaceBinding?: WorkspaceBinding;
103
103
  workspaceAttestation?: WorkspaceAttestation;
104
- workspaceFallback?: WorkspaceUnsupportedExecutionEvidence;
104
+ workspaceFallback?: WorkspaceCompatibilityExecutionEvidence;
105
105
  result?: unknown;
106
106
  error?: string;
107
107
  }
@@ -401,7 +401,7 @@ export function readWorkflowProof(coordRoot: string, runId: string): WorkflowPro
401
401
  ? isWorkspaceBoundExecutionEvidence(proofExecution, runId) &&
402
402
  stableDigest(proofExecution.binding) === stableDigest(manifestBinding)
403
403
  : manifestFallback !== undefined
404
- ? isWorkspaceUnsupportedExecutionEvidence(proofExecution, runId) &&
404
+ ? isWorkspaceCompatibilityExecutionEvidence(proofExecution, runId) &&
405
405
  stableDigest(proofExecution) === stableDigest(manifestFallback)
406
406
  : proofExecution === undefined;
407
407
  if (!executionMatches) {
@@ -33,12 +33,12 @@ import { isCanonicalWorkflowWorkContext } from "./work-context.ts";
33
33
  import type {
34
34
  WorkspaceAllocationRequest,
35
35
  WorkspaceBinding,
36
- WorkspaceUnsupportedExecutionEvidence,
36
+ WorkspaceCompatibilityExecutionEvidence,
37
37
  } from "./workspaces/index.ts";
38
38
  import {
39
39
  assertWorkspaceManifestAuthority,
40
40
  isWorkspaceBinding,
41
- isWorkspaceUnsupportedExecutionEvidence,
41
+ isWorkspaceCompatibilityExecutionEvidence,
42
42
  } from "./workspaces/validate.ts";
43
43
 
44
44
  export const WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION = 1 as const;
@@ -70,7 +70,7 @@ export interface WorkflowRunManifest {
70
70
  policy?: NormalizedPolicy;
71
71
  specialists?: Record<string, WorkflowSpecialistProfile>;
72
72
  workspace_binding?: WorkspaceBinding;
73
- workspace_fallback?: WorkspaceUnsupportedExecutionEvidence;
73
+ workspace_fallback?: WorkspaceCompatibilityExecutionEvidence;
74
74
  };
75
75
  }
76
76
 
@@ -307,9 +307,9 @@ function validWorkspaceBinding(binding: WorkspaceBinding | undefined): boolean {
307
307
  }
308
308
 
309
309
  function validWorkspaceFallback(
310
- fallback: WorkspaceUnsupportedExecutionEvidence | undefined,
310
+ fallback: WorkspaceCompatibilityExecutionEvidence | undefined,
311
311
  ): boolean {
312
- return fallback === undefined || isWorkspaceUnsupportedExecutionEvidence(fallback);
312
+ return fallback === undefined || isWorkspaceCompatibilityExecutionEvidence(fallback);
313
313
  }
314
314
 
315
315
  function validSpecialists(
@@ -425,11 +425,11 @@ export type Spawner = (req: SpawnRequest) => Promise<SpawnResult>;
425
425
  /** The API surface injected into a workflow script's default export. Explicit
426
426
  * injection (no ambient globals): keeps scripts portable and unit-testable. */
427
427
  export interface WorkflowContext {
428
- /** Frozen durable-work assignment for work-linked runs. Standalone and
429
- * legacy-resumed workflows have no work context. */
428
+ /** Frozen durable-work assignment for work-linked runs. Standalone workflows
429
+ * have no work context. */
430
430
  work?: Readonly<WorkflowWorkContext>;
431
431
  /** Frozen attempt identity and, on retry, a bounded synopsis of prior
432
- * terminal evidence. Absent for standalone and legacy-resumed workflows. */
432
+ * terminal evidence. Absent for standalone workflows. */
433
433
  attempt?: Readonly<WorkflowAttemptContext>;
434
434
  /** Spawn one subagent; resolves to validated JSON (schema) or reply text. */
435
435
  agent: (prompt: string, opts?: AgentOpts) => Promise<unknown>;
@@ -14,9 +14,9 @@ import { appendWorkflowTranscriptEvent, stableDigest, writeWorkspaceRequest } fr
14
14
  import type {
15
15
  WorkspaceAttestation,
16
16
  WorkspaceBinding,
17
+ WorkspaceCompatibilityExecutionEvidence,
17
18
  WorkspaceIsolation,
18
19
  WorkspaceProvider,
19
- WorkspaceUnsupportedExecutionEvidence,
20
20
  } from "./types.ts";
21
21
  import { isWorkspaceAttestation, isWorkspaceBinding } from "./validate.ts";
22
22
 
@@ -36,7 +36,7 @@ export async function resolveWorkspaceBinding(input: {
36
36
  workContext: Readonly<WorkflowWorkContext> | undefined;
37
37
  attemptContext: Readonly<WorkflowAttemptContext> | undefined;
38
38
  policy: NormalizedPolicy | undefined;
39
- onUnsupportedFallback?: (evidence: WorkspaceUnsupportedExecutionEvidence) => void;
39
+ onUnsupportedFallback?: (evidence: WorkspaceCompatibilityExecutionEvidence) => void;
40
40
  }): Promise<WorkspaceBinding | undefined> {
41
41
  if (input.isolation === "shared") return undefined;
42
42
  if (
@@ -47,6 +47,13 @@ export async function resolveWorkspaceBinding(input: {
47
47
  throw new Error(`unsupported workflow isolation ${JSON.stringify(input.isolation)}`);
48
48
  }
49
49
  if (input.resumeState && !input.resumeState.manifest.execution.workspace_binding) {
50
+ const frozenFallback = input.resumeState.manifest.execution.workspace_fallback;
51
+ if (!frozenFallback) {
52
+ throw new Error(
53
+ `workflow run ${input.runId} requested ${input.isolation} isolation without frozen workspace evidence`,
54
+ );
55
+ }
56
+ input.onUnsupportedFallback?.(frozenFallback);
50
57
  return undefined;
51
58
  }
52
59
  const capability = input.isolation as WorkspaceIsolation;
@@ -55,7 +62,7 @@ export async function resolveWorkspaceBinding(input: {
55
62
  if (input.resumeState?.manifest.execution.workspace_binding) {
56
63
  throw new Error("isolated workflow resume requires the frozen workspace provider");
57
64
  }
58
- const fallback: WorkspaceUnsupportedExecutionEvidence = {
65
+ const fallback: WorkspaceCompatibilityExecutionEvidence = {
59
66
  schema_version: 1,
60
67
  run_id: input.runId,
61
68
  requested_isolation: capability,
@@ -163,7 +170,7 @@ export async function resolveWorkspaceBinding(input: {
163
170
  message: `provider does not support ${capability} isolation`,
164
171
  },
165
172
  ];
166
- const fallback: WorkspaceUnsupportedExecutionEvidence = {
173
+ const fallback: WorkspaceCompatibilityExecutionEvidence = {
167
174
  schema_version: 1,
168
175
  run_id: input.runId,
169
176
  requested_isolation: capability,
@@ -56,7 +56,6 @@ export type {
56
56
  RepositoryBinding,
57
57
  RepositoryObservation,
58
58
  ValidatedFilesystemPath,
59
- ValidatedFilesystemRoot,
60
59
  WorkspaceAllocationRequest,
61
60
  WorkspaceAttestation,
62
61
  WorkspaceBinding,
@@ -77,13 +76,11 @@ export type {
77
76
  WorkspaceOwner,
78
77
  WorkspaceProbeInput,
79
78
  WorkspaceProbeResult,
80
- WorkspaceProofLifecycleState,
81
79
  WorkspaceProofOutcome,
82
80
  WorkspaceProvider,
83
81
  WorkspaceProviderCapabilities,
84
82
  WorkspaceProviderRef,
85
83
  WorkspaceResourceState,
86
- WorkspaceUnsupportedExecutionEvidence,
87
84
  } from "./types.ts";
88
85
  export {
89
86
  WORKSPACE_BINDING_SCHEMA_VERSION,
@@ -76,7 +76,6 @@ import { isWorkspaceAttestation } from "./validate.ts";
76
76
  const PROVIDER_ID = "local-git-worktree";
77
77
  const PROVIDER_VERSION = "1";
78
78
  const WORKSPACE_PARENT_SEGMENT = ".harnery-workspaces";
79
- const LEGACY_WORKSPACE_PARENT_SEGMENT = "harnery-workspaces";
80
79
  const LOCK_STALE_MS = 5 * 60 * 1_000;
81
80
  const OBJECT_FORMAT = /^[a-f0-9]{40,64}$/;
82
81
  const REF_FORMAT = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._/-]{0,180}$/;
@@ -308,7 +307,7 @@ async function allocate(
308
307
  */
309
308
  function claimedParentSegment(claim: WorkspaceClaim): string {
310
309
  const segment = basename(dirname(claim.workspace_root));
311
- if (segment !== WORKSPACE_PARENT_SEGMENT && segment !== LEGACY_WORKSPACE_PARENT_SEGMENT) {
310
+ if (segment !== WORKSPACE_PARENT_SEGMENT) {
312
311
  throw new Error("claimed workspace path is not under a provider-owned workspace parent");
313
312
  }
314
313
  return segment;
@@ -9,11 +9,11 @@ import {
9
9
  statSync,
10
10
  } from "node:fs";
11
11
  import { isAbsolute, join, relative, resolve } from "node:path";
12
- import type { FilesystemIdentity, ValidatedFilesystemRoot } from "./types.ts";
12
+ import type { FilesystemIdentity, ValidatedFilesystemPath } from "./types.ts";
13
13
 
14
14
  const WINDOWS_AMBIGUOUS = /[<>:"|?*]|[ .]$/;
15
15
 
16
- export type ValidatedRoot = ValidatedFilesystemRoot;
16
+ export type ValidatedRoot = ValidatedFilesystemPath;
17
17
 
18
18
  export interface OpenContainedDirectory {
19
19
  path: string;
@@ -21,9 +21,6 @@ export interface ValidatedFilesystemPath {
21
21
  identity: FilesystemIdentity;
22
22
  }
23
23
 
24
- /** @deprecated Use ValidatedFilesystemPath. */
25
- export type ValidatedFilesystemRoot = ValidatedFilesystemPath;
26
-
27
24
  export interface WorkspaceProviderRef {
28
25
  id: string;
29
26
  version: string;
@@ -184,9 +181,6 @@ export type WorkspaceProofOutcome =
184
181
  | "blocked"
185
182
  | "lost";
186
183
 
187
- /** @deprecated Use WorkspaceProofOutcome. */
188
- export type WorkspaceProofLifecycleState = WorkspaceProofOutcome;
189
-
190
184
  export type WorkspaceResourceState = "active" | "preserved_dirty" | "released" | "blocked" | "lost";
191
185
 
192
186
  export type WorkspaceIntegrationState = "none" | "planned" | "applying" | "applied";
@@ -492,9 +486,6 @@ export interface WorkspaceCompatibilityExecutionEvidence {
492
486
  receipts: Record<string, never>;
493
487
  }
494
488
 
495
- /** @deprecated Use WorkspaceCompatibilityExecutionEvidence. */
496
- export type WorkspaceUnsupportedExecutionEvidence = WorkspaceCompatibilityExecutionEvidence;
497
-
498
489
  export type WorkspaceExecutionEvidence =
499
490
  | WorkspaceBoundExecutionEvidence
500
491
  | WorkspaceCompatibilityExecutionEvidence;
@@ -11,7 +11,6 @@ import type {
11
11
  WorkspaceCompatibilityExecutionEvidence,
12
12
  WorkspaceExecutionEvidence,
13
13
  WorkspaceOwner,
14
- WorkspaceProofLifecycleState,
15
14
  WorkspaceProofOutcome,
16
15
  } from "./types.ts";
17
16
 
@@ -376,7 +375,7 @@ function workspaceCwdMatchesBinding(
376
375
  }
377
376
 
378
377
  function validWorkspaceProofOutcome(
379
- outcome: WorkspaceProofLifecycleState,
378
+ outcome: WorkspaceProofOutcome,
380
379
  attestation: WorkspaceAttestation,
381
380
  workflowStatus?: "succeeded" | "failed",
382
381
  ): boolean {
@@ -420,9 +419,6 @@ export function isWorkspaceCompatibilityExecutionEvidence(
420
419
  );
421
420
  }
422
421
 
423
- /** @deprecated Use isWorkspaceCompatibilityExecutionEvidence. */
424
- export const isWorkspaceUnsupportedExecutionEvidence = isWorkspaceCompatibilityExecutionEvidence;
425
-
426
422
  function sameOwner(left: WorkspaceOwner, right: WorkspaceOwner): boolean {
427
423
  return (
428
424
  left.kind === right.kind &&