@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.04a1e5b9e484

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 (223) hide show
  1. package/README.md +165 -1
  2. package/dist/bridge/bridge-chat.types.d.ts +111 -0
  3. package/dist/bridge/bridge-chat.types.js +401 -0
  4. package/dist/bridge/bridge.base.d.ts +9 -1
  5. package/dist/bridge/bridge.service.d.ts +129 -3
  6. package/dist/bridge/bridge.service.js +2427 -70
  7. package/dist/bridge/bridge.types.d.ts +218 -2
  8. package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
  9. package/dist/bridge/dispatch-result-outbox.js +62 -0
  10. package/dist/bridge/dispatch-result-worker.d.ts +2 -0
  11. package/dist/bridge/dispatch-result-worker.js +122 -0
  12. package/dist/bridge/internals.d.ts +2 -0
  13. package/dist/bridge/internals.js +1 -0
  14. package/dist/bridge/readiness-result-outbox.d.ts +21 -0
  15. package/dist/bridge/readiness-result-outbox.js +54 -0
  16. package/dist/bridge/readiness-result-worker.d.ts +2 -0
  17. package/dist/bridge/readiness-result-worker.js +121 -0
  18. package/dist/bridge/runner-host-capabilities.d.ts +15 -0
  19. package/dist/bridge/runner-host-capabilities.js +93 -0
  20. package/dist/cleanup/cleanup.service.d.ts +14 -2
  21. package/dist/cleanup/cleanup.service.js +496 -3
  22. package/dist/cleanup/cleanup.types.d.ts +10 -0
  23. package/dist/cleanup/index.d.ts +1 -1
  24. package/dist/cleanup/index.js +1 -1
  25. package/dist/cleanup/internals.d.ts +2 -2
  26. package/dist/cleanup/internals.js +1 -1
  27. package/dist/cli/cli.help.d.ts +2 -0
  28. package/dist/cli/cli.help.js +217 -0
  29. package/dist/cli/cli.service.d.ts +55 -7
  30. package/dist/cli/cli.service.js +429 -219
  31. package/dist/cli/local-config.d.ts +7 -0
  32. package/dist/cli/local-config.js +51 -3
  33. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  34. package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
  35. package/dist/runtime/attempt-database-broker.d.ts +89 -0
  36. package/dist/runtime/attempt-database-broker.js +390 -0
  37. package/dist/runtime/attempt-node-runtime-launcher.d.ts +18 -0
  38. package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
  39. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  40. package/dist/runtime/attempt-path-operation.js +94 -0
  41. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  42. package/dist/runtime/attempt-private-directory.js +54 -0
  43. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  44. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  45. package/dist/runtime/attempt-process-supervisor.d.ts +102 -0
  46. package/dist/runtime/attempt-process-supervisor.js +421 -0
  47. package/dist/runtime/canonical-source-file.d.ts +3 -0
  48. package/dist/runtime/canonical-source-file.js +54 -0
  49. package/dist/runtime/docker-engine-probe.d.ts +90 -0
  50. package/dist/runtime/docker-engine-probe.js +333 -0
  51. package/dist/runtime/generated-output-reset.d.ts +20 -0
  52. package/dist/runtime/generated-output-reset.js +157 -0
  53. package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
  54. package/dist/runtime/host-database-capability-boundary.js +62 -0
  55. package/dist/runtime/host-diagnostics.d.ts +11 -0
  56. package/dist/runtime/host-diagnostics.js +192 -0
  57. package/dist/runtime/host-execution-root.d.ts +4 -0
  58. package/dist/runtime/host-execution-root.js +39 -0
  59. package/dist/runtime/host-listener-inspector.d.ts +51 -0
  60. package/dist/runtime/host-listener-inspector.js +385 -0
  61. package/dist/runtime/host-port-broker.d.ts +76 -0
  62. package/dist/runtime/host-port-broker.js +333 -0
  63. package/dist/runtime/host-runtime-contract.d.ts +58 -0
  64. package/dist/runtime/host-runtime-contract.js +813 -0
  65. package/dist/runtime/host-runtime-lease.d.ts +217 -0
  66. package/dist/runtime/host-runtime-lease.js +2487 -0
  67. package/dist/runtime/host-runtime-promotion.d.ts +32 -0
  68. package/dist/runtime/host-runtime-promotion.js +206 -0
  69. package/dist/runtime/memory-capacity.d.ts +70 -0
  70. package/dist/runtime/memory-capacity.js +123 -0
  71. package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
  72. package/dist/runtime/native-test-postgres-fallback.js +91 -0
  73. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  74. package/dist/runtime/node-host-runtime-adapter.js +286 -0
  75. package/dist/runtime/pinned-directory-process.d.ts +9 -0
  76. package/dist/runtime/pinned-directory-process.js +61 -0
  77. package/dist/runtime/pinned-source-directory.d.ts +17 -0
  78. package/dist/runtime/pinned-source-directory.js +94 -0
  79. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  80. package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
  81. package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
  82. package/dist/runtime/prepared-runtime-identity.js +218 -0
  83. package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
  84. package/dist/runtime/prepared-runtime-readiness.js +285 -0
  85. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  86. package/dist/runtime/runtime-isolation-lease.js +161 -0
  87. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  88. package/dist/runtime/workspace-resource-estimator.js +174 -0
  89. package/dist/sandbox/command-runner.d.ts +4 -0
  90. package/dist/sandbox/command-runner.js +47 -0
  91. package/dist/sandbox/contract.d.ts +5 -0
  92. package/dist/sandbox/contract.js +276 -0
  93. package/dist/sandbox/event-budget.d.ts +11 -0
  94. package/dist/sandbox/event-budget.js +27 -0
  95. package/dist/sandbox/image-policy.d.ts +35 -0
  96. package/dist/sandbox/image-policy.js +247 -0
  97. package/dist/sandbox/index.d.ts +5 -0
  98. package/dist/sandbox/index.js +5 -0
  99. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  100. package/dist/sandbox/lease-watchdog.js +67 -0
  101. package/dist/sandbox/preflight.d.ts +84 -0
  102. package/dist/sandbox/preflight.js +521 -0
  103. package/dist/sandbox/process.d.ts +20 -0
  104. package/dist/sandbox/process.js +209 -0
  105. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  106. package/dist/sandbox/provider-auth/codex.js +212 -0
  107. package/dist/sandbox/repository-lock.d.ts +6 -0
  108. package/dist/sandbox/repository-lock.js +129 -0
  109. package/dist/sandbox/runtime.d.ts +302 -0
  110. package/dist/sandbox/runtime.js +2368 -0
  111. package/dist/sandbox/sandbox.types.d.ts +93 -0
  112. package/dist/sandbox/sandbox.types.js +1 -0
  113. package/dist/sandbox/smoke.d.ts +76 -0
  114. package/dist/sandbox/smoke.js +380 -0
  115. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  116. package/dist/sandbox/worker-supervisor.js +36 -0
  117. package/dist/sandbox/worker.d.ts +1 -0
  118. package/dist/sandbox/worker.js +225 -0
  119. package/dist/sandbox/workspace.service.d.ts +13 -0
  120. package/dist/sandbox/workspace.service.js +74 -0
  121. package/dist/session/managed-attempt-tools.d.ts +28 -0
  122. package/dist/session/managed-attempt-tools.js +1016 -0
  123. package/dist/session/managed-build-coordinator.d.ts +5 -0
  124. package/dist/session/managed-build-coordinator.js +106 -0
  125. package/dist/session/managed-build-receipt.d.ts +21 -0
  126. package/dist/session/managed-build-receipt.js +337 -0
  127. package/dist/session/managed-verification-timeouts.d.ts +8 -0
  128. package/dist/session/managed-verification-timeouts.js +17 -0
  129. package/dist/session/phase-completion.artifacts.d.ts +27 -0
  130. package/dist/session/phase-completion.artifacts.js +829 -0
  131. package/dist/session/phase-completion.d.ts +53 -42
  132. package/dist/session/phase-completion.helpers.d.ts +117 -0
  133. package/dist/session/phase-completion.helpers.js +707 -0
  134. package/dist/session/phase-completion.js +125 -3007
  135. package/dist/session/phase-completion.prompts.d.ts +14 -0
  136. package/dist/session/phase-completion.prompts.js +233 -0
  137. package/dist/session/phase-completion.runtime.d.ts +49 -0
  138. package/dist/session/phase-completion.runtime.js +840 -0
  139. package/dist/session/phase-completion.schemas.d.ts +3 -0
  140. package/dist/session/phase-completion.schemas.js +859 -0
  141. package/dist/session/phase-completion.validation.d.ts +38 -0
  142. package/dist/session/phase-completion.validation.js +918 -0
  143. package/dist/session/pre-publication-validation.d.ts +75 -0
  144. package/dist/session/pre-publication-validation.js +661 -0
  145. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
  146. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
  147. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  148. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  149. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
  150. package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
  151. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  152. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  153. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  154. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  155. package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
  156. package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
  157. package/dist/session/provider-cli/provider-cli.process.js +874 -0
  158. package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
  159. package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
  160. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  161. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
  162. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  163. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  164. package/dist/session/provider-cli/provider-cli.runtime.d.ts +22 -0
  165. package/dist/session/provider-cli/provider-cli.runtime.js +92 -0
  166. package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
  167. package/dist/session/provider-cli/provider-cli.service.js +1258 -2497
  168. package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
  169. package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
  170. package/dist/session/provider-cli/provider-output-state.js +817 -0
  171. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  172. package/dist/session/provider-cli/provider-usage.js +28 -5
  173. package/dist/session/session-redaction.d.ts +3 -0
  174. package/dist/session/session-redaction.js +448 -0
  175. package/dist/session/session.base.d.ts +6 -1
  176. package/dist/session/session.service.d.ts +8 -2
  177. package/dist/session/session.service.js +586 -59
  178. package/dist/session/session.types.d.ts +324 -3
  179. package/dist/session/session.types.js +939 -10
  180. package/dist/session/unreferenced-exports.d.ts +12 -0
  181. package/dist/session/unreferenced-exports.js +59 -0
  182. package/dist/shared/format/format-bytes.d.ts +1 -0
  183. package/dist/shared/format/format-bytes.js +9 -0
  184. package/dist/shared/git/git.js +2 -1
  185. package/dist/shared/path/path.d.ts +1 -0
  186. package/dist/shared/path/path.js +21 -1
  187. package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
  188. package/dist/shared/process/dotnet-toolchain.js +307 -0
  189. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  190. package/dist/shared/process/host-process-recovery.js +79 -0
  191. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  192. package/dist/shared/process/host-process-supervisor.js +331 -0
  193. package/dist/shared/process/process.d.ts +3 -0
  194. package/dist/shared/process/process.js +13 -7
  195. package/dist/shared/process/windows-launch.d.ts +8 -0
  196. package/dist/shared/process/windows-launch.js +41 -0
  197. package/dist/shared/provider-capabilities.d.ts +4 -0
  198. package/dist/shared/provider-capabilities.js +88 -0
  199. package/dist/shared/redaction/redaction.d.ts +1 -0
  200. package/dist/shared/redaction/redaction.js +47 -4
  201. package/dist/shared/time/monotonic.d.ts +1 -0
  202. package/dist/shared/time/monotonic.js +8 -0
  203. package/dist/version.d.ts +1 -1
  204. package/dist/version.js +1 -1
  205. package/dist/worktree/artifact-injector.d.ts +3 -2
  206. package/dist/worktree/artifact-injector.js +82 -10
  207. package/dist/worktree/dependency-warmer.d.ts +53 -1
  208. package/dist/worktree/dependency-warmer.js +619 -11
  209. package/dist/worktree/index.d.ts +2 -1
  210. package/dist/worktree/index.js +1 -0
  211. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  212. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  213. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  214. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  215. package/dist/worktree/worktree-paths.d.ts +4 -0
  216. package/dist/worktree/worktree-paths.js +30 -0
  217. package/dist/worktree/worktree.base.d.ts +3 -0
  218. package/dist/worktree/worktree.service.d.ts +16 -3
  219. package/dist/worktree/worktree.service.js +334 -54
  220. package/dist/worktree/worktree.types.d.ts +9 -1
  221. package/package.json +12 -7
  222. package/runtime/windows-job-supervisor.ps1 +722 -0
  223. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -1,12 +1,23 @@
1
1
  import path from "node:path";
2
+ import { redactText } from "../shared/redaction/redaction.js";
3
+ export class ForgeFailureError extends Error {
4
+ failureCode;
5
+ failureSource;
6
+ constructor(failureCode, message, failureSource = "bridge", options) {
7
+ super(message, options);
8
+ this.name = "ForgeFailureError";
9
+ this.failureCode = normalizeFailureCode(failureCode);
10
+ this.failureSource = failureSource;
11
+ }
12
+ }
2
13
  export function parseRunOnceDispatchPacket(value) {
3
14
  const packet = asObject(value, "dispatch packet");
4
15
  const schemaVersion = requiredNumber(packet, "schemaVersion");
5
- if (schemaVersion !== 1) {
16
+ if (schemaVersion !== 1 && schemaVersion !== 2) {
6
17
  throw new Error(`Unsupported dispatch schemaVersion: ${schemaVersion}`);
7
18
  }
8
19
  return {
9
- schemaVersion: 1,
20
+ schemaVersion,
10
21
  userId: requiredString(packet, "userId"),
11
22
  workItem: parseWorkItem(packet.workItem),
12
23
  repo: parseRepo(packet.repo),
@@ -14,30 +25,676 @@ export function parseRunOnceDispatchPacket(value) {
14
25
  session: parseSession(packet.session),
15
26
  lease: parseLease(packet.lease),
16
27
  execution: parseExecution(packet.execution),
28
+ runtimeIntent: parseRuntimeIntent(packet.runtimeIntent),
17
29
  serverLocal: parseServerLocal(packet.serverLocal),
30
+ sandbox: parseSandbox(packet.sandbox, schemaVersion),
18
31
  pullRequest: parsePullRequest(packet.pullRequest),
19
32
  apiKeySecretGrant: parseApiKeySecretGrant(packet.apiKeySecretGrant),
33
+ apiKeySecretGrantHandle: parseApiKeySecretGrantHandle(packet.apiKeySecretGrantHandle),
20
34
  artifacts: parseArtifacts(packet.artifacts),
35
+ priorPhaseArtifacts: parsePriorPhaseArtifacts(packet.priorPhaseArtifacts),
21
36
  warmPathOverrides: parseWarmPathOverrides(packet.warmPathOverrides),
22
37
  qaBrowser: parseQaBrowser(packet.qaBrowser),
38
+ qaScope: parseQaScope(packet.qaScope ?? packet.qa_scope),
23
39
  qaRework: parseQaRework(packet.qaRework),
24
40
  architectRevision: parseArchitectRevision(packet.architectRevision),
41
+ architectDependencies: parseArchitectDependencies(packet.architectDependencies),
25
42
  storyDevelop: parseStoryDevelop(packet.storyDevelop),
26
43
  storyQa: parseStoryQa(packet.storyQa),
44
+ designerRefs: parseDesignerRefs(packet.designerRefs ?? packet.designer_refs),
27
45
  draft: parseDraftPrompt(packet.draft)
28
46
  };
29
47
  }
30
- export function failureResult(summary, events = []) {
48
+ export function parseRunOnceResultPacket(value) {
49
+ const packet = asObject(value, "run-once result packet");
50
+ const schemaVersion = requiredNumber(packet, "schemaVersion");
51
+ if (schemaVersion !== 1 && schemaVersion !== 2) {
52
+ throw new Error(`Unsupported run-once result schemaVersion: ${schemaVersion}`);
53
+ }
54
+ const exitCode = requiredNumber(packet, "exitCode");
55
+ if (!Number.isSafeInteger(exitCode))
56
+ throw new Error("exitCode must be an integer.");
57
+ requiredString(packet, "summary");
58
+ if (!Array.isArray(packet.events))
59
+ throw new Error("events must be an array.");
60
+ for (const item of packet.events) {
61
+ if (typeof item !== "object" || item === null || !("wait" in item))
62
+ continue;
63
+ const event = asObject(item, "run-once result event");
64
+ if (event.wait !== undefined && event.wait !== null) {
65
+ parseWaitEnvelope(event.wait, requiredString(event, "eventType"));
66
+ }
67
+ }
68
+ if (schemaVersion === 1) {
69
+ if (packet.failure !== undefined && packet.failure !== null) {
70
+ throw new Error("run-once result schemaVersion 1 cannot contain a v2 failure envelope.");
71
+ }
72
+ return packet;
73
+ }
74
+ if (exitCode === 0 && packet.failure !== undefined && packet.failure !== null) {
75
+ throw new Error("run-once result cannot report both success and a failure envelope.");
76
+ }
77
+ if (exitCode !== 0 && (packet.failure === undefined || packet.failure === null)) {
78
+ throw new Error("run-once result schemaVersion 2 requires a failure envelope for a nonzero exit.");
79
+ }
80
+ if (packet.failure !== undefined && packet.failure !== null)
81
+ parseFailureEnvelope(packet.failure);
82
+ return packet;
83
+ }
84
+ const waitKinds = new Map([
85
+ ["host_runtime_admission_waiting", "host_runtime_admission"],
86
+ ["host_runtime_admission_acquired", "host_runtime_admission"],
87
+ ["host_runtime_serial_admission_waiting", "host_runtime_serial_admission"],
88
+ ["host_runtime_prepared_probe_started", "host_runtime_prepared_probe"],
89
+ ["host_runtime_prepared_probe_progress", "host_runtime_prepared_probe"],
90
+ ["host_runtime_prepared_probe_completed", "host_runtime_prepared_probe"],
91
+ ["host_runtime_prepared_admission_waiting", "host_runtime_prepared_admission"],
92
+ ["host_runtime_prepared_admission_acquired", "host_runtime_prepared_admission"],
93
+ ]);
94
+ export function waitKindForEvent(eventType) {
95
+ const kind = waitKinds.get(eventType);
96
+ if (!kind)
97
+ throw new Error(`Unsupported typed wait observation '${eventType}'.`);
98
+ return kind;
99
+ }
100
+ export function waitWakeCondition(kind) {
101
+ switch (kind) {
102
+ case "host_runtime_serial_admission":
103
+ return "project_singleton_released";
104
+ case "host_runtime_prepared_probe":
105
+ return "prepared_runtime_probe_completed";
106
+ default:
107
+ return "host_runtime_admission_available";
108
+ }
109
+ }
110
+ function parseWaitEnvelope(value, eventType) {
111
+ const wait = asObject(value, "wait envelope");
112
+ if (requiredNumber(wait, "schemaVersion") !== 2) {
113
+ throw new Error("Unsupported wait envelope schemaVersion.");
114
+ }
115
+ let expectedKind;
116
+ try {
117
+ expectedKind = waitKindForEvent(eventType);
118
+ }
119
+ catch {
120
+ throw new Error("wait envelope is only valid on a wait lifecycle observation.");
121
+ }
122
+ const kind = requiredString(wait, "kind");
123
+ const scope = requiredString(wait, "scope");
124
+ const wakeCondition = requiredString(wait, "wakeCondition");
125
+ if (kind !== expectedKind
126
+ || !isWaitCode(kind, 80)
127
+ || !isWaitCode(scope, 64)
128
+ || !isWaitCode(wakeCondition, 80)
129
+ || wakeCondition !== waitWakeCondition(kind)) {
130
+ throw new Error("wait envelope kind, scope, or wakeCondition is invalid.");
131
+ }
132
+ if (!Array.isArray(wait.resourceKeys)
133
+ || wait.resourceKeys.length > 16
134
+ || wait.resourceKeys.some(item => typeof item !== "string" || !isWaitCode(item, 256))
135
+ || new Set(wait.resourceKeys).size !== wait.resourceKeys.length) {
136
+ throw new Error("wait envelope resourceKeys are invalid, duplicated, or exceed their bound.");
137
+ }
138
+ const queuePosition = wait.queuePosition;
139
+ if (queuePosition !== undefined && queuePosition !== null
140
+ && (typeof queuePosition !== "number" || !Number.isSafeInteger(queuePosition) || queuePosition <= 0)) {
141
+ throw new Error("wait envelope queuePosition is invalid.");
142
+ }
143
+ const blockerSessionId = wait.blockerSessionId;
144
+ if (blockerSessionId !== undefined && blockerSessionId !== null
145
+ && (typeof blockerSessionId !== "string"
146
+ || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(blockerSessionId)
147
+ || /^0{8}-0{4}-0{4}-0{4}-0{12}$/u.test(blockerSessionId))) {
148
+ throw new Error("wait envelope blockerSessionId is invalid.");
149
+ }
150
+ const blockerTitle = wait.blockerTitle;
151
+ if (blockerTitle !== undefined && blockerTitle !== null
152
+ && (typeof blockerTitle !== "string" || blockerTitle.length > 500)) {
153
+ throw new Error("wait envelope blockerTitle is invalid.");
154
+ }
155
+ const retryAt = wait.retryAt;
156
+ if (retryAt !== undefined && retryAt !== null
157
+ && (typeof retryAt !== "string" || !Number.isFinite(Date.parse(retryAt)))) {
158
+ throw new Error("wait envelope retryAt is invalid.");
159
+ }
160
+ const firstObservedAt = requiredString(wait, "firstObservedAt");
161
+ if (!Number.isFinite(Date.parse(firstObservedAt))) {
162
+ throw new Error("wait envelope firstObservedAt is invalid.");
163
+ }
164
+ const observationVersion = requiredNumber(wait, "observationVersion");
165
+ if (!Number.isSafeInteger(observationVersion) || observationVersion <= 0) {
166
+ throw new Error("wait envelope observationVersion is invalid.");
167
+ }
168
+ if (requiredString(wait, "contractOrigin") !== "bridge_v2") {
169
+ throw new Error("wait envelope contractOrigin must identify the v2 Bridge writer.");
170
+ }
171
+ return value;
172
+ }
173
+ function isWaitCode(value, maximumLength) {
174
+ return value.length <= maximumLength && /^[a-z][a-z0-9_.:-]*$/u.test(value);
175
+ }
176
+ function parseRuntimeIntent(value) {
177
+ if (value === undefined || value === null) {
178
+ return { executionClass: "managed_runtime", applicationRuntimeRequired: true };
179
+ }
180
+ const intent = asObject(value, "runtimeIntent");
181
+ const executionClass = requiredString(intent, "executionClass").trim().toLowerCase();
182
+ if (executionClass !== "analysis_only" && executionClass !== "managed_runtime") {
183
+ throw new Error("runtimeIntent.executionClass must be analysis_only or managed_runtime.");
184
+ }
185
+ const applicationRuntimeRequired = requiredBoolean(intent, "applicationRuntimeRequired");
186
+ if ((executionClass === "analysis_only") === applicationRuntimeRequired) {
187
+ throw new Error("runtimeIntent execution class and application runtime requirement are inconsistent.");
188
+ }
189
+ return { executionClass, applicationRuntimeRequired };
190
+ }
191
+ function parseSandbox(value, schemaVersion) {
192
+ if (value === undefined || value === null) {
193
+ if (schemaVersion === 2) {
194
+ throw new Error("sandbox is required for dispatch schemaVersion 2.");
195
+ }
196
+ return null;
197
+ }
198
+ const sandbox = asObject(value, "sandbox");
199
+ const executionMode = requiredString(sandbox, "executionMode");
200
+ if (executionMode !== "host" && executionMode !== "docker") {
201
+ throw new Error("sandbox.executionMode must be host or docker.");
202
+ }
203
+ const executionPolicy = requiredString(sandbox, "executionPolicy");
204
+ if (executionPolicy !== "host_allowed" && executionPolicy !== "sandbox_preferred" && executionPolicy !== "sandbox_required") {
205
+ throw new Error("sandbox.executionPolicy is invalid.");
206
+ }
207
+ return {
208
+ dispatchAttemptId: requiredString(sandbox, "dispatchAttemptId"),
209
+ executionLeaseId: requiredString(sandbox, "executionLeaseId"),
210
+ runnerInstanceId: requiredString(sandbox, "runnerInstanceId"),
211
+ executionMode,
212
+ executionPolicy,
213
+ networkPolicy: parseSandboxNetworkPolicy(sandbox.networkPolicy),
214
+ requestedResources: parseSandboxResourceRequest(sandbox.requestedResources),
215
+ runtime: optionalString(sandbox, "runtime"),
216
+ sandboxConfigCommitSha: optionalString(sandbox, "sandboxConfigCommitSha"),
217
+ inputCommitSha: optionalString(sandbox, "inputCommitSha"),
218
+ contractDigest: optionalString(sandbox, "contractDigest"),
219
+ imageDigest: optionalString(sandbox, "imageDigest"),
220
+ publicationMode: parsePublicationMode(sandbox.publicationMode),
221
+ publishGrantHandle: parsePublishGrantHandle(sandbox.publishGrantHandle)
222
+ };
223
+ }
224
+ function parsePublicationMode(value) {
225
+ if (value === undefined || value === null)
226
+ return "shared_filesystem";
227
+ if (value !== "bridge_push" && value !== "shared_filesystem")
228
+ throw new Error("sandbox.publicationMode is invalid.");
229
+ return value;
230
+ }
231
+ function parsePublishGrantHandle(value) {
232
+ if (value === undefined || value === null)
233
+ return null;
234
+ const handle = asObject(value, "sandbox.publishGrantHandle");
235
+ const grantId = requiredString(handle, "grantId");
236
+ const branch = requiredString(handle, "branch");
237
+ const expiresAt = requiredString(handle, "expiresAt");
238
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(grantId)) {
239
+ throw new Error("sandbox.publishGrantHandle.grantId is invalid.");
240
+ }
241
+ if (branch.length > 240)
242
+ throw new Error("sandbox.publishGrantHandle.branch is too long.");
243
+ if (!Number.isFinite(Date.parse(expiresAt)))
244
+ throw new Error("sandbox.publishGrantHandle.expiresAt is invalid.");
245
+ return { grantId, branch, expiresAt };
246
+ }
247
+ function parseSandboxResourceRequest(value) {
248
+ if (value === undefined || value === null) {
249
+ return {
250
+ sandboxSlots: 1,
251
+ cpuMillis: 2000,
252
+ workerMemoryLimitBytes: 2 * 1024 ** 3,
253
+ serviceMemoryLimitBytes: 0,
254
+ runtimeOverheadBytes: 128 * 1024 ** 2,
255
+ memoryBytes: 2 * 1024 ** 3 + 128 * 1024 ** 2,
256
+ workspaceBytes: 10 * 1024 ** 3,
257
+ pids: 512,
258
+ buildSlots: 1
259
+ };
260
+ }
261
+ const resources = asObject(value, "sandbox.requestedResources");
262
+ const memoryBytes = boundedInteger(resources, "memoryBytes", 128 * 1024 ** 2, 512 * 1024 ** 3);
263
+ const hasMemoryBreakdown = ["workerMemoryLimitBytes", "serviceMemoryLimitBytes", "runtimeOverheadBytes"]
264
+ .some(key => resources[key] !== undefined && resources[key] !== null);
265
+ const workerMemoryLimitBytes = hasMemoryBreakdown
266
+ ? boundedInteger(resources, "workerMemoryLimitBytes", 128 * 1024 ** 2, 512 * 1024 ** 3)
267
+ : memoryBytes;
268
+ const serviceMemoryLimitBytes = hasMemoryBreakdown
269
+ ? boundedInteger(resources, "serviceMemoryLimitBytes", 0, 512 * 1024 ** 3)
270
+ : 0;
271
+ const runtimeOverheadBytes = hasMemoryBreakdown
272
+ ? boundedInteger(resources, "runtimeOverheadBytes", 0, 64 * 1024 ** 3)
273
+ : 0;
274
+ if (workerMemoryLimitBytes + serviceMemoryLimitBytes + runtimeOverheadBytes !== memoryBytes) {
275
+ throw new Error("sandbox.requestedResources memory breakdown must equal memoryBytes.");
276
+ }
277
+ return {
278
+ sandboxSlots: boundedInteger(resources, "sandboxSlots", 1, 4),
279
+ cpuMillis: boundedInteger(resources, "cpuMillis", 100, 64_000),
280
+ workerMemoryLimitBytes,
281
+ serviceMemoryLimitBytes,
282
+ runtimeOverheadBytes,
283
+ memoryBytes,
284
+ workspaceBytes: boundedInteger(resources, "workspaceBytes", 1024 ** 3, 10 * 1024 ** 4),
285
+ pids: boundedInteger(resources, "pids", 32, 65_536),
286
+ buildSlots: boundedInteger(resources, "buildSlots", 0, 4)
287
+ };
288
+ }
289
+ function parseSandboxNetworkPolicy(value) {
290
+ if (value === undefined || value === null)
291
+ return { mode: "offline", allowedHosts: [] };
292
+ const policy = asObject(value, "sandbox.networkPolicy");
293
+ const mode = requiredString(policy, "mode");
294
+ if (mode !== "offline" && mode !== "public_allowlist" && mode !== "open_public") {
295
+ throw new Error("sandbox.networkPolicy.mode is invalid.");
296
+ }
297
+ const rawHosts = policy.allowedHosts ?? [];
298
+ if (!Array.isArray(rawHosts) || rawHosts.some((host) => typeof host !== "string")) {
299
+ throw new Error("sandbox.networkPolicy.allowedHosts must be a string array.");
300
+ }
301
+ const allowedHosts = [...new Set(rawHosts.map(host => host.trim().toLowerCase()).filter(Boolean))];
302
+ if (allowedHosts.length > 128 || allowedHosts.some(host => !/^(?:\*\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(host))) {
303
+ throw new Error("sandbox.networkPolicy.allowedHosts contains an invalid hostname.");
304
+ }
305
+ if (mode !== "public_allowlist" && allowedHosts.length > 0) {
306
+ throw new Error("sandbox.networkPolicy.allowedHosts requires public_allowlist mode.");
307
+ }
308
+ if (mode === "public_allowlist" && allowedHosts.length === 0) {
309
+ return { mode: "offline", allowedHosts: [] };
310
+ }
311
+ return { mode, allowedHosts };
312
+ }
313
+ const failureCodePattern = /^[a-z][a-z0-9_.-]{0,79}$/u;
314
+ const failureCategories = new Set(["domain", "provider", "transport", "cancellation", "ownership", "resource", "unknown"]);
315
+ const failurePhases = new Set(["offer", "build", "prepare", "agent", "validation", "import", "teardown", "unknown"]);
316
+ const failureSources = new Set(["bridge", "provider", "sandbox", "unknown"]);
317
+ const failureRetryabilities = new Set(["retryable", "non_retryable", "policy_required", "unknown"]);
318
+ const failureOperatorActions = new Set(["none", "retry_dispatch", "retry_with_policy", "inspect_failure", "fix_input", "fix_environment", "reauthenticate", "restore_ownership"]);
319
+ const maximumFailureMessageLength = 2000;
320
+ const maximumFailureCausalChainLength = 8;
321
+ const waitConditionCodes = new Set([
322
+ "host_runtime_admission_waiting",
323
+ "host_runtime_serial_admission_waiting",
324
+ "host_runtime_prepared_probe_started",
325
+ "host_runtime_prepared_probe_progress",
326
+ "host_runtime_prepared_admission_waiting",
327
+ "waiting_for_capacity",
328
+ ]);
329
+ const stableFailureFamilyCodes = new Set([
330
+ "generated_output_reset_invalid",
331
+ "generated_output_reset_unsafe",
332
+ "sandbox_build_failed",
333
+ "sandbox_build_policy_failed",
334
+ "sandbox_builder_release_failed",
335
+ "sandbox_config_commit_invalid",
336
+ "sandbox_config_commit_missing",
337
+ "sandbox_config_commit_unapproved",
338
+ "contract_invalid",
339
+ "sandbox_input_failed",
340
+ "docker_capacity_unavailable",
341
+ "docker_engine_invalid_response",
342
+ "docker_engine_recovery_failed",
343
+ "docker_engine_recovery_timeout",
344
+ "docker_linux_required",
345
+ "docker_preflight_required",
346
+ "docker_rootless_required",
347
+ "docker_unavailable",
348
+ "docker_unreachable",
349
+ "sandbox_output_conflict",
350
+ "sandbox_output_invalid",
351
+ "sandbox_output_missing",
352
+ "sandbox_output_publish_forbidden",
353
+ "sandbox_output_rejected",
354
+ "sandbox_output_publish_rejected",
355
+ "sandbox_output_publish_stale",
356
+ "sandbox_output_publish_unavailable",
357
+ "sandbox_output_stale",
358
+ "sandbox_import_cancelled",
359
+ "sandbox_import_unavailable",
360
+ "teardown_failed",
361
+ "reaper_cleanup_degraded",
362
+ "reaper_runner_replaced",
363
+ "sandbox_egress_policy_invalid",
364
+ "sandbox_egress_proxy_unavailable",
365
+ "sandbox_event_flood",
366
+ "sandbox_event_invalid",
367
+ "sandbox_identity_invalid",
368
+ "sandbox_image_signature_failed",
369
+ "sandbox_log_limit",
370
+ "sandbox_preflight_failed",
371
+ "sandbox_security_profile_invalid",
372
+ "sandbox_security_profile_unavailable",
373
+ "sandbox_service_health_policy_invalid",
374
+ ]);
375
+ export function failureResult(summary, events = [], facts = failureFactsForCode("bridge.execution_failed")) {
31
376
  return {
32
- schemaVersion: 1,
377
+ schemaVersion: 2,
33
378
  exitCode: 1,
34
- summary,
35
- events
379
+ summary: boundedFailureText(summary),
380
+ events,
381
+ failure: createFailureEnvelope(summary, facts),
382
+ };
383
+ }
384
+ export function createFailureEnvelope(message, facts, occurredAt = new Date().toISOString()) {
385
+ const code = normalizeFailureCode(facts.code);
386
+ if (waitConditionCodes.has(code)) {
387
+ throw new Error(`Wait condition '${code}' must be emitted as lifecycle state, not a terminal failure.`);
388
+ }
389
+ assertFailureFactsMatchPolicy(code, facts);
390
+ return {
391
+ schemaVersion: 2,
392
+ code,
393
+ message: boundedFailureText(message),
394
+ category: facts.category,
395
+ phase: facts.phase,
396
+ source: facts.source,
397
+ retryability: facts.retryability,
398
+ operatorAction: facts.operatorAction,
399
+ causalChain: (facts.causalChain ?? []).slice(0, maximumFailureCausalChainLength).map(cause => ({
400
+ code: normalizeFailureCode(cause.code),
401
+ message: boundedFailureText(cause.message),
402
+ source: cause.source,
403
+ })),
404
+ occurredAt,
405
+ contractOrigin: "bridge_v2",
406
+ };
407
+ }
408
+ export function failureFactsFromError(error, options = {}) {
409
+ if (options.cancelled) {
410
+ const cancellationSource = options.source ?? "bridge";
411
+ return {
412
+ ...failureFactsForCode("bridge.dispatch_cancelled", cancellationSource),
413
+ causalChain: errorCauseChain(error, cancellationSource),
414
+ };
415
+ }
416
+ const source = error instanceof ForgeFailureError
417
+ ? error.failureSource
418
+ : options.source ?? "bridge";
419
+ const code = error instanceof ForgeFailureError
420
+ ? error.failureCode
421
+ : "bridge.execution_failed";
422
+ return {
423
+ ...failureFactsForCode(code, source),
424
+ causalChain: errorCauseChain(error, source),
36
425
  };
37
426
  }
427
+ export function failureFactsForCode(code, source = "bridge") {
428
+ const policy = failurePolicyForCode(code);
429
+ if (policy.known && !policy.allowedSources.includes(source)) {
430
+ throw new Error(`Failure code '${policy.code}' cannot be attributed to source '${source}'.`);
431
+ }
432
+ return {
433
+ code: policy.code,
434
+ category: policy.category,
435
+ phase: policy.phase,
436
+ source,
437
+ retryability: policy.retryability,
438
+ operatorAction: policy.operatorAction,
439
+ };
440
+ }
441
+ export function failurePolicyForCode(code) {
442
+ code = normalizeFailureCode(code);
443
+ const classification = unvalidatedFailureFactsForCode(code, "bridge");
444
+ const known = classification.category !== "unknown" || code === "legacy_failure_unknown";
445
+ return {
446
+ code,
447
+ category: classification.category,
448
+ phase: classification.phase,
449
+ retryability: classification.retryability,
450
+ operatorAction: classification.operatorAction,
451
+ allowedSources: allowedSourcesForCode(code, known),
452
+ known,
453
+ };
454
+ }
455
+ function unvalidatedFailureFactsForCode(code, source) {
456
+ code = normalizeFailureCode(code);
457
+ if (["bridge.dispatch_cancelled", "dispatch_cancelled", "dispatch_session_terminal", "dispatch.capacity_retargeted"].includes(code)) {
458
+ return facts(code, "cancellation", "teardown", source, "non_retryable", "none");
459
+ }
460
+ if (["bridge.dispatch_fence_conflict", "bridge.reservation_stale", "bridge.capacity_wait_lost", "bridge.dispatch_attempt_missing"].includes(code)) {
461
+ return facts(code, "ownership", "offer", source, "non_retryable", "restore_ownership");
462
+ }
463
+ if (["reservation_expired", "bridge.lease_expired", "bridge.stale_runner_instance", "bridge.runner_replaced"].includes(code)) {
464
+ return facts(code, "ownership", "offer", source, "retryable", "retry_dispatch");
465
+ }
466
+ if (["bridge.dispatch_failed", "bridge.execution_failed", "bridge.cli_execution_failed", "bridge_callback_timeout"].includes(code)) {
467
+ return facts(code, "transport", "unknown", source, "policy_required", "inspect_failure");
468
+ }
469
+ if (code === "bridge.unavailable") {
470
+ return facts(code, "transport", "offer", source, "retryable", "retry_dispatch");
471
+ }
472
+ if (["capacity_unavailable", "bridge.runner_draining", "docker_engine_unavailable", "docker_memory_low", "docker_memory_sample_unavailable", "docker_cpu_low", "docker_disk_low", "server_local.resource_pressure", "server_local.capacity_unavailable", "dispatch.preparation_resource_pressure", "sandbox_capacity_profile_unavailable", "host_runtime_admission_expired"].includes(code)) {
473
+ return facts(code, "resource", "offer", source, "retryable", "retry_dispatch");
474
+ }
475
+ if (["bridge.lease_retry_exhausted", "bridge.capacity_wait_exhausted"].includes(code)) {
476
+ return facts(code, "resource", "offer", source, "non_retryable", "inspect_failure");
477
+ }
478
+ if (["prepublication_infrastructure_failed", "prepublication_capability_unavailable"].includes(code)) {
479
+ return facts(code, "resource", "validation", source, "retryable", "retry_with_policy");
480
+ }
481
+ if (["prepublication_validation_failed", "prepublication_contract_invalid", "prepublication_execution_scope_invalid"].includes(code)) {
482
+ return facts(code, "domain", "validation", source, "non_retryable", "fix_input");
483
+ }
484
+ if (["generated_output_reset_failed", "generated_output_reset_unverified", "worktree_prepare_failed", "host_runtime_ledger_write_failed", "host_diagnostics_temporary_unavailable", "attempt_process_supervisor_start_failed", "attempt_process_supervisor_start_timeout"].includes(code)) {
485
+ return facts(code, "transport", "prepare", source, "retryable", "retry_dispatch");
486
+ }
487
+ if (stableFailureFamilyCodes.has(code) && code.startsWith("generated_output_reset_")) {
488
+ return facts(code, "domain", "prepare", source, "non_retryable", "fix_environment");
489
+ }
490
+ if (code === "provider_start_unavailable") {
491
+ return facts(code, "provider", "prepare", source, "retryable", "retry_dispatch");
492
+ }
493
+ if (code === "provider_configuration_invalid") {
494
+ return facts(code, "provider", "prepare", source, "non_retryable", "fix_environment");
495
+ }
496
+ if (["host_port_collision", "host_runtime_process_exited", "host_runtime_listener_timeout", "host_runtime_http_readiness_timeout", "host_runtime_prepared_source_changed", "host_runtime_prepared_probe_incomplete", "host_runtime_prepared_probe_cleanup_unconfirmed"].includes(code)) {
497
+ return facts(code, "resource", "prepare", source, "retryable", "retry_dispatch");
498
+ }
499
+ if (code === "host_runtime_project_start_failed") {
500
+ return facts(code, "domain", "prepare", source, "non_retryable", "fix_environment");
501
+ }
502
+ if (stableFailureFamilyCodes.has(code) && (code.startsWith("sandbox_build") || code.startsWith("image_"))) {
503
+ const policyFailure = code === "sandbox_build_policy_failed";
504
+ return facts(code, "resource", "build", source, policyFailure ? "non_retryable" : "retryable", policyFailure ? "fix_environment" : "retry_dispatch");
505
+ }
506
+ if (code === "provider_network_policy_denied"
507
+ || stableFailureFamilyCodes.has(code) && (code.startsWith("sandbox_config") || code.startsWith("contract_") || code.startsWith("sandbox_input"))) {
508
+ return facts(code, "domain", "prepare", source, "non_retryable", "fix_environment");
509
+ }
510
+ if (stableFailureFamilyCodes.has(code) && code.startsWith("docker_")) {
511
+ return facts(code, "resource", "prepare", source, "non_retryable", "fix_environment");
512
+ }
513
+ if (code === "provider_network_unavailable") {
514
+ return facts(code, "transport", "offer", source, "retryable", "retry_dispatch");
515
+ }
516
+ if (code === "provider_request_not_found") {
517
+ return facts(code, "provider", "offer", source, "retryable", "retry_dispatch");
518
+ }
519
+ if (code === "provider_capacity_unavailable") {
520
+ return facts(code, "resource", "offer", source, "retryable", "retry_dispatch");
521
+ }
522
+ if (code === "provider_context_window_exhausted") {
523
+ return facts(code, "provider", "agent", source, "retryable", "retry_dispatch");
524
+ }
525
+ if (stableFailureFamilyCodes.has(code) && (code.startsWith("sandbox_output") || code.startsWith("sandbox_import"))) {
526
+ const retryable = code === "sandbox_import_unavailable" || code === "sandbox_output_publish_unavailable";
527
+ return facts(code, "transport", "import", source, retryable ? "retryable" : "non_retryable", retryable ? "retry_with_policy" : "inspect_failure");
528
+ }
529
+ if (stableFailureFamilyCodes.has(code) && (code.startsWith("teardown") || code.startsWith("reaper"))) {
530
+ return facts(code, "transport", "teardown", source, "retryable", "retry_with_policy");
531
+ }
532
+ if (["invalid_dispatch"].includes(code)) {
533
+ return facts(code, "domain", "offer", source, "non_retryable", "fix_input");
534
+ }
535
+ if ([
536
+ "bridge.api_key_auth_required",
537
+ "bridge.api_key_secret_grant_missing",
538
+ "bridge.api_key_secret_grant_denied",
539
+ "bridge.api_key_secret_grant_provider_mismatch",
540
+ "bridge.api_key_secret_missing",
541
+ "contract_plaintext_secret_rejected",
542
+ ].includes(code)) {
543
+ return facts(code, "domain", "prepare", source, "non_retryable", "reauthenticate");
544
+ }
545
+ if (code === "sandbox_idle_timeout") {
546
+ return facts(code, "provider", "agent", source, "retryable", "retry_with_policy");
547
+ }
548
+ if (code === "sandbox_wall_timeout") {
549
+ return facts(code, "provider", "agent", source, "retryable", "retry_with_policy");
550
+ }
551
+ if (["sandbox_oom_killed", "sandbox_pid_exhausted", "sandbox_file_descriptor_exhausted", "sandbox_storage_exhausted"].includes(code)) {
552
+ return facts(code, "resource", "agent", source, "non_retryable", "inspect_failure");
553
+ }
554
+ if (code === "sandbox_auth_unavailable") {
555
+ return facts(code, "domain", "prepare", source, "non_retryable", "reauthenticate");
556
+ }
557
+ if (code === "sandbox_lease_expired") {
558
+ return facts(code, "ownership", "agent", source, "non_retryable", "restore_ownership");
559
+ }
560
+ if (code === "sandbox_runtime_failed") {
561
+ return facts(code, "provider", "agent", source, "non_retryable", "inspect_failure");
562
+ }
563
+ if (code === "worktree_branch_integration_conflict") {
564
+ return facts(code, "domain", "agent", source, "non_retryable", "fix_input");
565
+ }
566
+ if (stableFailureFamilyCodes.has(code) && code.startsWith("sandbox_")) {
567
+ return facts(code, "provider", "agent", source, "non_retryable", "inspect_failure");
568
+ }
569
+ return facts(code, "unknown", "unknown", source, "unknown", "inspect_failure");
570
+ }
571
+ function parseFailureEnvelope(value) {
572
+ const failure = asObject(value, "failure");
573
+ if (requiredNumber(failure, "schemaVersion") !== 2)
574
+ throw new Error("failure.schemaVersion must be 2.");
575
+ const code = requiredString(failure, "code");
576
+ const message = requiredString(failure, "message");
577
+ const category = requiredString(failure, "category");
578
+ const phase = requiredString(failure, "phase");
579
+ const source = requiredString(failure, "source");
580
+ const retryability = requiredString(failure, "retryability");
581
+ const operatorAction = requiredString(failure, "operatorAction");
582
+ const occurredAt = requiredString(failure, "occurredAt");
583
+ const contractOrigin = requiredString(failure, "contractOrigin");
584
+ if (!failureCodePattern.test(code))
585
+ throw new Error("failure.code is invalid.");
586
+ if (message.length > maximumFailureMessageLength)
587
+ throw new Error("failure.message is too long.");
588
+ if (!failureCategories.has(category) || !failurePhases.has(phase) || !failureSources.has(source)
589
+ || !failureRetryabilities.has(retryability) || !failureOperatorActions.has(operatorAction)) {
590
+ throw new Error("failure contains an unsupported category, phase, source, retryability, or operator action.");
591
+ }
592
+ assertFailureFactsMatchPolicy(code, { code, category, phase, source, retryability, operatorAction });
593
+ if (!Number.isFinite(Date.parse(occurredAt)))
594
+ throw new Error("failure.occurredAt is invalid.");
595
+ if (contractOrigin !== "bridge_v2")
596
+ throw new Error("failure.contractOrigin must be bridge_v2.");
597
+ if (!Array.isArray(failure.causalChain) || failure.causalChain.length > maximumFailureCausalChainLength) {
598
+ throw new Error("failure.causalChain is invalid or too long.");
599
+ }
600
+ const causalChain = failure.causalChain.map((candidate, index) => {
601
+ const cause = asObject(candidate, `failure.causalChain[${index}]`);
602
+ const causeCode = requiredString(cause, "code");
603
+ const causeMessage = requiredString(cause, "message");
604
+ const causeSource = requiredString(cause, "source");
605
+ if (!failureCodePattern.test(causeCode) || causeMessage.length > maximumFailureMessageLength || !failureSources.has(causeSource)) {
606
+ throw new Error(`failure.causalChain[${index}] is invalid.`);
607
+ }
608
+ return { code: causeCode, message: causeMessage, source: causeSource };
609
+ });
610
+ return { schemaVersion: 2, code, message, category, phase, source, retryability, operatorAction, causalChain, occurredAt, contractOrigin };
611
+ }
612
+ function facts(code, category, phase, source, retryability, operatorAction) {
613
+ return { code, category, phase, source, retryability, operatorAction };
614
+ }
615
+ function allowedSourcesForCode(code, known) {
616
+ if (!known)
617
+ return ["bridge", "provider", "sandbox", "unknown"];
618
+ if (code.startsWith("provider_"))
619
+ return ["bridge", "provider", "sandbox"];
620
+ if (code.startsWith("sandbox_")
621
+ || code.startsWith("docker_")
622
+ || code.startsWith("image_")
623
+ || code.startsWith("contract_")
624
+ || code.startsWith("generated_output_reset_")
625
+ || code.startsWith("teardown")
626
+ || code.startsWith("reaper")) {
627
+ return ["bridge", "sandbox"];
628
+ }
629
+ return ["bridge"];
630
+ }
631
+ function assertFailureFactsMatchPolicy(code, factsToValidate) {
632
+ const policy = failurePolicyForCode(code);
633
+ if (!policy.known)
634
+ return;
635
+ if (factsToValidate.category !== policy.category
636
+ || factsToValidate.phase !== policy.phase
637
+ || factsToValidate.retryability !== policy.retryability
638
+ || factsToValidate.operatorAction !== policy.operatorAction
639
+ || !policy.allowedSources.includes(factsToValidate.source)) {
640
+ throw new Error(`Failure code '${policy.code}' contradicts the failure taxonomy or allowed-source registry.`);
641
+ }
642
+ }
643
+ function normalizeFailureCode(value) {
644
+ const normalized = value.trim().toLowerCase().slice(0, 80);
645
+ return failureCodePattern.test(normalized) ? normalized : "bridge.execution_failed";
646
+ }
647
+ function boundedFailureText(value) {
648
+ const bounded = value.trim().slice(0, maximumFailureMessageLength);
649
+ return bounded || "Dispatch failed without a diagnostic message.";
650
+ }
651
+ /**
652
+ * Rebuilds an error graph at a privileged execution boundary without
653
+ * retaining the raw error or any raw recursive cause. Forge failure metadata
654
+ * survives so the P7 taxonomy remains authoritative after sanitization.
655
+ */
656
+ export function sanitizeFailureError(error, exactSecrets = []) {
657
+ return sanitizeFailureErrorAtDepth(error, exactSecrets, new Set(), 0);
658
+ }
659
+ function sanitizeFailureErrorAtDepth(error, exactSecrets, visited, depth) {
660
+ if (depth >= maximumFailureCausalChainLength) {
661
+ return new Error("Additional error causes were omitted.");
662
+ }
663
+ if (error !== null && typeof error === "object") {
664
+ if (visited.has(error))
665
+ return new Error("Circular error cause was omitted.");
666
+ visited.add(error);
667
+ }
668
+ const message = redactText(error instanceof Error ? error.message : String(error), exactSecrets);
669
+ const cause = error instanceof Error && error.cause !== undefined
670
+ ? sanitizeFailureErrorAtDepth(error.cause, exactSecrets, visited, depth + 1)
671
+ : undefined;
672
+ const options = cause === undefined ? undefined : { cause };
673
+ if (error instanceof ForgeFailureError) {
674
+ return new ForgeFailureError(error.failureCode, message, error.failureSource, options);
675
+ }
676
+ if (error instanceof AggregateError) {
677
+ const errors = [...error.errors].map(item => sanitizeFailureErrorAtDepth(item, exactSecrets, visited, depth + 1));
678
+ return new AggregateError(errors, message, options);
679
+ }
680
+ return new Error(message, options);
681
+ }
682
+ function errorCauseChain(error, source) {
683
+ const causes = [];
684
+ let current = error;
685
+ while (current !== undefined && current !== null && causes.length < maximumFailureCausalChainLength) {
686
+ const message = current instanceof Error ? current.message : String(current);
687
+ const code = current instanceof ForgeFailureError ? current.failureCode : "cause_unknown";
688
+ const causeSource = current instanceof ForgeFailureError ? current.failureSource : source;
689
+ causes.push({ code, message: boundedFailureText(message), source: causeSource });
690
+ current = current instanceof Error ? current.cause : undefined;
691
+ }
692
+ return causes;
693
+ }
38
694
  function parseWorkItem(value) {
39
695
  const item = asObject(value, "workItem");
40
696
  return {
697
+ projectId: optionalString(item, "projectId"),
41
698
  id: requiredString(item, "id"),
42
699
  kind: requiredString(item, "kind"),
43
700
  title: requiredString(item, "title"),
@@ -61,6 +718,7 @@ function parseRepo(value) {
61
718
  function parseCycleRun(value) {
62
719
  const cycle = asObject(value, "cycleRun");
63
720
  return {
721
+ effectivePlanAmendmentHash: optionalString(cycle, "effectivePlanAmendmentHash"),
64
722
  id: requiredString(cycle, "id"),
65
723
  ownerType: optionalString(cycle, "ownerType"),
66
724
  ownerId: optionalString(cycle, "ownerId"),
@@ -70,6 +728,7 @@ function parseCycleRun(value) {
70
728
  requestedBase: optionalString(cycle, "requestedBase") ?? "develop",
71
729
  baseBranch: optionalString(cycle, "baseBranch") ?? "develop",
72
730
  expectedCurrentBranch: optionalString(cycle, "expectedCurrentBranch"),
731
+ repairHeadSha: optionalString(cycle, "repairHeadSha"),
73
732
  attempt: requiredNumber(cycle, "attempt"),
74
733
  model: requiredString(cycle, "model")
75
734
  };
@@ -79,7 +738,9 @@ function parseSession(value) {
79
738
  return {
80
739
  id: requiredString(session, "id"),
81
740
  model: requiredString(session, "model"),
82
- lane: requiredString(session, "lane")
741
+ lane: requiredString(session, "lane"),
742
+ resumedFromSessionId: optionalString(session, "resumedFromSessionId"),
743
+ resumeProviderThreadId: optionalString(session, "resumeProviderThreadId")
83
744
  };
84
745
  }
85
746
  function parseLease(value) {
@@ -107,7 +768,8 @@ function parseExecution(value) {
107
768
  role: requiredString(execution, "role"),
108
769
  lane: requiredString(execution, "lane"),
109
770
  requiresApiKeySecretGrant: requiredBoolean(execution, "requiresApiKeySecretGrant"),
110
- runnerRepoLocalPath: optionalString(execution, "runnerRepoLocalPath")
771
+ runnerRepoLocalPath: optionalString(execution, "runnerRepoLocalPath"),
772
+ effort: optionalString(execution, "effort")
111
773
  };
112
774
  }
113
775
  function parseDraftPrompt(value) {
@@ -118,9 +780,45 @@ function parseDraftPrompt(value) {
118
780
  return {
119
781
  kind: requiredString(draft, "kind"),
120
782
  systemPrompt: requiredString(draft, "systemPrompt"),
121
- userPrompt: requiredString(draft, "userPrompt")
783
+ userPrompt: requiredString(draft, "userPrompt"),
784
+ projectName: optionalString(draft, "projectName"),
785
+ repoName: optionalString(draft, "repoName"),
786
+ attachments: parseDraftAttachments(draft.attachments)
122
787
  };
123
788
  }
789
+ function parseDraftAttachments(value) {
790
+ if (value === undefined || value === null) {
791
+ return [];
792
+ }
793
+ if (!Array.isArray(value)) {
794
+ throw new Error("draft.attachments must be an array when provided.");
795
+ }
796
+ return value.map((entry, index) => {
797
+ const attachment = asObject(entry, `draft.attachments[${index}]`);
798
+ const kind = requiredString(attachment, "kind").trim().toLowerCase();
799
+ if (kind !== "text" && kind !== "image") {
800
+ throw new Error(`draft.attachments[${index}].kind must be text or image.`);
801
+ }
802
+ const textContent = optionalString(attachment, "textContent");
803
+ const contentBase64 = optionalString(attachment, "contentBase64");
804
+ if (kind === "text" && textContent === null) {
805
+ throw new Error(`draft.attachments[${index}].textContent is required for text attachments.`);
806
+ }
807
+ if (kind === "image" && contentBase64 === null) {
808
+ throw new Error(`draft.attachments[${index}].contentBase64 is required for image attachments.`);
809
+ }
810
+ return {
811
+ index: optionalNonNegativeInteger(attachment, "index") ?? index,
812
+ fileName: requiredString(attachment, "fileName"),
813
+ contentType: optionalString(attachment, "contentType") ?? "",
814
+ kind,
815
+ byteCount: optionalNonNegativeInteger(attachment, "byteCount") ?? 0,
816
+ summary: optionalString(attachment, "summary") ?? "",
817
+ textContent,
818
+ contentBase64
819
+ };
820
+ });
821
+ }
124
822
  function parseApiKeySecretGrant(value) {
125
823
  if (value === undefined || value === null) {
126
824
  return null;
@@ -133,6 +831,16 @@ function parseApiKeySecretGrant(value) {
133
831
  expiresAt: requiredString(grant, "expiresAt")
134
832
  };
135
833
  }
834
+ function parseApiKeySecretGrantHandle(value) {
835
+ if (value === undefined || value === null)
836
+ return null;
837
+ const grant = asObject(value, "apiKeySecretGrantHandle");
838
+ return {
839
+ grantId: requiredString(grant, "grantId"),
840
+ provider: requiredString(grant, "provider"),
841
+ expiresAt: requiredString(grant, "expiresAt")
842
+ };
843
+ }
136
844
  function parseServerLocal(value) {
137
845
  const serverLocal = asObject(value, "serverLocal");
138
846
  return {
@@ -159,9 +867,49 @@ function parsePullRequest(value) {
159
867
  requiredChecksTotal: requiredNumber(pullRequest, "requiredChecksTotal"),
160
868
  unresolvedThreads: requiredNumber(pullRequest, "unresolvedThreads"),
161
869
  humanApproved: requiredBoolean(pullRequest, "humanApproved"),
162
- hasMergeConflicts: requiredBoolean(pullRequest, "hasMergeConflicts")
870
+ hasMergeConflicts: requiredBoolean(pullRequest, "hasMergeConflicts"),
871
+ headSha: optionalString(pullRequest, "headSha") ?? "",
872
+ reviewIssueKey: optionalString(pullRequest, "reviewIssueKey"),
873
+ reviewFindings: parsePullRequestReviewFindings(pullRequest.reviewFindings),
874
+ openWorkOrderStories: parseWorkOrderStories(pullRequest.openWorkOrderStories)
163
875
  };
164
876
  }
877
+ function parseWorkOrderStories(value) {
878
+ if (value === null || value === undefined)
879
+ return [];
880
+ if (!Array.isArray(value))
881
+ throw new Error("pullRequest.openWorkOrderStories must be an array when provided.");
882
+ return value.map((entry, index) => {
883
+ const story = asObject(entry, `pullRequest.openWorkOrderStories[${index}]`);
884
+ return {
885
+ workItemId: requiredString(story, "workItemId"),
886
+ title: optionalString(story, "title") ?? "",
887
+ acceptanceCriteria: optionalString(story, "acceptanceCriteria") ?? ""
888
+ };
889
+ });
890
+ }
891
+ function parsePullRequestReviewFindings(value) {
892
+ if (value === null || value === undefined)
893
+ return [];
894
+ if (!Array.isArray(value))
895
+ throw new Error("pullRequest.reviewFindings must be an array when provided.");
896
+ return value.map((entry, index) => {
897
+ const finding = asObject(entry, `pullRequest.reviewFindings[${index}]`);
898
+ return {
899
+ claimId: requiredString(finding, "claimId"),
900
+ kind: requiredString(finding, "kind"),
901
+ bodySha256: requiredString(finding, "bodySha256"),
902
+ body: optionalString(finding, "body") ?? "",
903
+ contentAvailable: requiredBoolean(finding, "contentAvailable"),
904
+ actionable: requiredBoolean(finding, "actionable"),
905
+ actionabilityReason: requiredString(finding, "actionabilityReason"),
906
+ author: optionalString(finding, "author"),
907
+ url: optionalString(finding, "url"),
908
+ path: optionalString(finding, "path"),
909
+ line: optionalPositiveInteger(finding, "line")
910
+ };
911
+ });
912
+ }
165
913
  function parseWarmPathOverrides(value) {
166
914
  if (value === undefined || value === null) {
167
915
  return [];
@@ -197,6 +945,28 @@ function parseQaBrowser(value) {
197
945
  missingTargetReason: optionalString(qaBrowser, "missingTargetReason") ?? optionalString(qaBrowser, "missing_target_reason")
198
946
  };
199
947
  }
948
+ function parseQaScope(value) {
949
+ if (value === undefined || value === null) {
950
+ return null;
951
+ }
952
+ const qaScope = asObject(value, "qaScope");
953
+ return {
954
+ schema_version: requiredNumber(qaScope, "schema_version"),
955
+ surface: parseOptionalStringArray(qaScope.surface, "qaScope.surface"),
956
+ required_evidence: parseOptionalStringArray(qaScope.required_evidence, "qaScope.required_evidence"),
957
+ required_capabilities: parseOptionalStringArray(qaScope.required_capabilities ?? qaScope.requiredCapabilities, "qaScope.required_capabilities"),
958
+ not_applicable: parseOptionalStringArray(qaScope.not_applicable, "qaScope.not_applicable"),
959
+ browser_required: optionalBoolean(qaScope, "browser_required") ?? optionalBoolean(qaScope, "browserRequired") ?? false,
960
+ api_required: optionalBoolean(qaScope, "api_required") ?? optionalBoolean(qaScope, "apiRequired") ?? false,
961
+ source: optionalString(qaScope, "source"),
962
+ authority: optionalString(qaScope, "authority"),
963
+ confidence: optionalString(qaScope, "confidence"),
964
+ confirmed_by_user: optionalBoolean(qaScope, "confirmed_by_user") ?? optionalBoolean(qaScope, "confirmedByUser"),
965
+ locked: optionalBoolean(qaScope, "locked"),
966
+ derived_from: parseOptionalObject(qaScope.derived_from ?? qaScope.derivedFrom, "qaScope.derived_from"),
967
+ rationale: optionalString(qaScope, "rationale")
968
+ };
969
+ }
200
970
  function parseQaAppLaunchTarget(value) {
201
971
  if (value === undefined || value === null) {
202
972
  return null;
@@ -211,6 +981,7 @@ function parseQaAppLaunchTarget(value) {
211
981
  command: optionalString(launchTarget, "command"),
212
982
  working_directory: optionalString(launchTarget, "working_directory"),
213
983
  ready_url: optionalString(launchTarget, "ready_url"),
984
+ managed_runtime_port: optionalString(launchTarget, "managed_runtime_port"),
214
985
  routes: Array.isArray(routes)
215
986
  ? routes.map((entry, index) => {
216
987
  if (typeof entry !== "string") {
@@ -260,6 +1031,34 @@ function parseArchitectRevision(value) {
260
1031
  feedback: requiredString(revision, "feedback")
261
1032
  };
262
1033
  }
1034
+ function parseArchitectDependencies(value) {
1035
+ if (value === undefined || value === null) {
1036
+ return null;
1037
+ }
1038
+ const context = asObject(value, "architectDependencies");
1039
+ const prerequisites = context.prerequisites;
1040
+ if (!Array.isArray(prerequisites)) {
1041
+ throw new Error("architectDependencies.prerequisites must be an array.");
1042
+ }
1043
+ return {
1044
+ baseBranch: requiredString(context, "baseBranch").trim(),
1045
+ prerequisites: prerequisites.map((entry, index) => {
1046
+ const prerequisite = asObject(entry, `architectDependencies.prerequisites[${index}]`);
1047
+ return {
1048
+ storyId: requiredString(prerequisite, "storyId").trim(),
1049
+ title: requiredString(prerequisite, "title").trim(),
1050
+ scheduleIndex: optionalNonNegativeInteger(prerequisite, "scheduleIndex"),
1051
+ waveIndex: optionalNonNegativeInteger(prerequisite, "waveIndex"),
1052
+ featureScheduleIndex: optionalNonNegativeInteger(prerequisite, "featureScheduleIndex"),
1053
+ expectedOutput: optionalString(prerequisite, "expectedOutput"),
1054
+ handoffSummary: optionalString(prerequisite, "handoffSummary"),
1055
+ dependencyReason: optionalString(prerequisite, "dependencyReason"),
1056
+ pullRequestNumber: optionalPositiveInteger(prerequisite, "pullRequestNumber"),
1057
+ targetBranch: requiredString(prerequisite, "targetBranch").trim()
1058
+ };
1059
+ })
1060
+ };
1061
+ }
263
1062
  function parseStoryDevelop(value) {
264
1063
  if (value === undefined || value === null) {
265
1064
  return null;
@@ -289,6 +1088,7 @@ function parseStoryQa(value) {
289
1088
  }
290
1089
  const storyQa = asObject(value, "storyQa");
291
1090
  return {
1091
+ references: parseStoryQaReferences(storyQa.references),
292
1092
  storyWorkItemId: requiredString(storyQa, "storyWorkItemId"),
293
1093
  planId: requiredString(storyQa, "planId"),
294
1094
  planContentHash: requiredString(storyQa, "planContentHash"),
@@ -301,6 +1101,26 @@ function parseStoryQa(value) {
301
1101
  dependencies: parseStoryQaDependencies(storyQa.dependencies)
302
1102
  };
303
1103
  }
1104
+ function parseStoryQaReferences(value) {
1105
+ if (value === undefined || value === null)
1106
+ return null;
1107
+ const bundle = asObject(value, "storyQa.references");
1108
+ if (!Array.isArray(bundle.files))
1109
+ throw new Error("storyQa.references.files must be an array.");
1110
+ return {
1111
+ schemaVersion: requiredNumber(bundle, "schemaVersion"),
1112
+ cycleRunId: requiredString(bundle, "cycleRunId"),
1113
+ storyWorkItemId: requiredString(bundle, "storyWorkItemId"),
1114
+ projectId: requiredString(bundle, "projectId"), repoId: requiredString(bundle, "repoId"),
1115
+ planId: requiredString(bundle, "planId"), planContentHash: requiredString(bundle, "planContentHash"),
1116
+ effectiveAmendmentHash: requiredString(bundle, "effectiveAmendmentHash"), capturedAt: requiredString(bundle, "capturedAt"),
1117
+ files: bundle.files.map((entry, index) => {
1118
+ const file = asObject(entry, `storyQa.references.files[${index}]`);
1119
+ return { path: requiredString(file, "path"), body: requiredString(file, "body"),
1120
+ contentHash: requiredString(file, "contentHash"), provenance: requiredString(file, "provenance") };
1121
+ })
1122
+ };
1123
+ }
304
1124
  function parseStoryQaChildTasks(value) {
305
1125
  if (!Array.isArray(value)) {
306
1126
  throw new Error("storyQa.childTasks must be an array.");
@@ -365,6 +1185,58 @@ function parseStoryQaDependencies(value) {
365
1185
  };
366
1186
  });
367
1187
  }
1188
+ function parseDesignerRefs(value) {
1189
+ if (value === undefined || value === null) {
1190
+ return null;
1191
+ }
1192
+ const context = asObject(value, "designerRefs");
1193
+ const refs = context.refs;
1194
+ if (!Array.isArray(refs)) {
1195
+ throw new Error("designerRefs.refs must be an array.");
1196
+ }
1197
+ return {
1198
+ schemaVersion: requiredNumber(context, "schemaVersion"),
1199
+ kind: requiredString(context, "kind"),
1200
+ ownerType: requiredString(context, "ownerType"),
1201
+ ownerId: requiredString(context, "ownerId"),
1202
+ projectId: requiredString(context, "projectId"),
1203
+ repoId: requiredString(context, "repoId"),
1204
+ title: requiredString(context, "title"),
1205
+ refs: refs.map((entry, index) => parseDesignerRefAttachment(entry, index))
1206
+ };
1207
+ }
1208
+ function parseDesignerRefAttachment(value, index) {
1209
+ const attachment = asObject(value, `designerRefs.refs[${index}]`);
1210
+ return {
1211
+ ref: parseDesignerRef(attachment.ref, index),
1212
+ reason: optionalString(attachment, "reason") ?? "",
1213
+ required: requiredBoolean(attachment, "required"),
1214
+ source: optionalString(attachment, "source") ?? "",
1215
+ lineageKind: optionalString(attachment, "lineageKind") ?? optionalString(attachment, "lineage_kind") ?? "",
1216
+ inheritedFromItemType: optionalString(attachment, "inheritedFromItemType") ?? optionalString(attachment, "inherited_from_item_type"),
1217
+ inheritedFromItemId: optionalString(attachment, "inheritedFromItemId") ?? optionalString(attachment, "inherited_from_item_id"),
1218
+ inheritedFromDesignerRefId: optionalString(attachment, "inheritedFromDesignerRefId") ?? optionalString(attachment, "inherited_from_designer_ref_id"),
1219
+ addedByLane: optionalString(attachment, "addedByLane") ?? optionalString(attachment, "added_by_lane") ?? "",
1220
+ sortOrder: requiredNumber(attachment, "sortOrder"),
1221
+ createdAt: requiredString(attachment, "createdAt")
1222
+ };
1223
+ }
1224
+ function parseDesignerRef(value, index) {
1225
+ const designerRef = asObject(value, `designerRefs.refs[${index}].ref`);
1226
+ return {
1227
+ id: requiredString(designerRef, "id"),
1228
+ projectId: requiredString(designerRef, "projectId"),
1229
+ repoId: requiredString(designerRef, "repoId"),
1230
+ refKey: requiredString(designerRef, "refKey"),
1231
+ kind: requiredString(designerRef, "kind"),
1232
+ path: requiredString(designerRef, "path"),
1233
+ anchor: optionalString(designerRef, "anchor"),
1234
+ title: optionalString(designerRef, "title") ?? "",
1235
+ contentHash: requiredString(designerRef, "contentHash"),
1236
+ commitSha: requiredString(designerRef, "commitSha"),
1237
+ createdAt: requiredString(designerRef, "createdAt")
1238
+ };
1239
+ }
368
1240
  function parseStringArray(value, label) {
369
1241
  if (!Array.isArray(value)) {
370
1242
  throw new Error(`${label} must be an array.`);
@@ -376,6 +1248,18 @@ function parseStringArray(value, label) {
376
1248
  return entry.trim();
377
1249
  });
378
1250
  }
1251
+ function parseOptionalStringArray(value, label) {
1252
+ if (value === undefined || value === null) {
1253
+ return [];
1254
+ }
1255
+ return parseStringArray(value, label);
1256
+ }
1257
+ function parseOptionalObject(value, label) {
1258
+ if (value === undefined || value === null) {
1259
+ return null;
1260
+ }
1261
+ return asObject(value, label);
1262
+ }
379
1263
  function parseArtifacts(value) {
380
1264
  if (value === undefined || value === null) {
381
1265
  return [];
@@ -397,6 +1281,34 @@ function parseArtifacts(value) {
397
1281
  };
398
1282
  });
399
1283
  }
1284
+ function parsePriorPhaseArtifacts(value) {
1285
+ if (value === undefined || value === null) {
1286
+ return [];
1287
+ }
1288
+ if (!Array.isArray(value)) {
1289
+ throw new Error("priorPhaseArtifacts must be an array when provided.");
1290
+ }
1291
+ return value.map((entry, index) => {
1292
+ const artifact = asObject(entry, `priorPhaseArtifacts[${index}]`);
1293
+ const phase = requiredString(artifact, "phase").trim().toLowerCase().replace(/-/g, "_");
1294
+ if (phase !== "implementation" && phase !== "self_review" && phase !== "validation") {
1295
+ throw new Error(`priorPhaseArtifacts[${index}].phase must be implementation, self_review, or validation.`);
1296
+ }
1297
+ return {
1298
+ workItemId: requiredString(artifact, "workItemId"),
1299
+ artifactId: requiredString(artifact, "artifactId"),
1300
+ cycleRunId: requiredString(artifact, "cycleRunId"),
1301
+ phase,
1302
+ status: requiredString(artifact, "status"),
1303
+ producedAt: requiredString(artifact, "producedAt"),
1304
+ sourcePath: requiredString(artifact, "sourcePath"),
1305
+ verdictJson: requiredString(artifact, "verdictJson"),
1306
+ report: optionalString(artifact, "report") ?? "",
1307
+ verdictContentHash: requiredString(artifact, "verdictContentHash").toLowerCase(),
1308
+ reportContentHash: requiredString(artifact, "reportContentHash").toLowerCase()
1309
+ };
1310
+ });
1311
+ }
400
1312
  function asObject(value, label) {
401
1313
  if (!value || typeof value !== "object" || Array.isArray(value)) {
402
1314
  throw new Error(`${label} must be an object.`);
@@ -447,6 +1359,16 @@ function optionalPositiveInteger(source, key) {
447
1359
  }
448
1360
  return value;
449
1361
  }
1362
+ function optionalNonNegativeInteger(source, key) {
1363
+ const value = source[key];
1364
+ if (value === undefined || value === null) {
1365
+ return null;
1366
+ }
1367
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
1368
+ throw new Error(`${key} must be a non-negative integer when provided.`);
1369
+ }
1370
+ return value;
1371
+ }
450
1372
  function requiredNumber(source, key) {
451
1373
  const value = source[key];
452
1374
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -454,3 +1376,10 @@ function requiredNumber(source, key) {
454
1376
  }
455
1377
  return value;
456
1378
  }
1379
+ function boundedInteger(source, key, minimum, maximum) {
1380
+ const value = requiredNumber(source, key);
1381
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
1382
+ throw new Error(`${key} must be an integer from ${minimum} through ${maximum}.`);
1383
+ }
1384
+ return value;
1385
+ }