@loucompanion/forge-bridge 0.1.3-dev.fbe15023cdfe → 0.1.4-dev.1ff37038366e

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 (162) hide show
  1. package/README.md +88 -2
  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 +4 -1
  5. package/dist/bridge/bridge.service.d.ts +72 -3
  6. package/dist/bridge/bridge.service.js +1563 -152
  7. package/dist/bridge/bridge.types.d.ts +46 -1
  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 +1 -0
  21. package/dist/cleanup/cleanup.service.js +70 -9
  22. package/dist/cli/cli.help.js +2 -2
  23. package/dist/cli/cli.service.d.ts +14 -6
  24. package/dist/cli/cli.service.js +75 -16
  25. package/dist/cli/local-config.d.ts +1 -0
  26. package/dist/cli/local-config.js +4 -0
  27. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  28. package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
  29. package/dist/runtime/attempt-database-broker.d.ts +89 -0
  30. package/dist/runtime/attempt-database-broker.js +390 -0
  31. package/dist/runtime/attempt-node-runtime-launcher.d.ts +2 -1
  32. package/dist/runtime/attempt-node-runtime-launcher.js +57 -18
  33. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  34. package/dist/runtime/attempt-path-operation.js +94 -0
  35. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  36. package/dist/runtime/attempt-private-directory.js +54 -0
  37. package/dist/runtime/attempt-process-supervisor.d.ts +11 -0
  38. package/dist/runtime/attempt-process-supervisor.js +67 -20
  39. package/dist/runtime/canonical-source-file.d.ts +3 -0
  40. package/dist/runtime/canonical-source-file.js +54 -0
  41. package/dist/runtime/docker-engine-probe.d.ts +90 -0
  42. package/dist/runtime/docker-engine-probe.js +333 -0
  43. package/dist/runtime/generated-output-reset.d.ts +10 -0
  44. package/dist/runtime/generated-output-reset.js +62 -8
  45. package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
  46. package/dist/runtime/host-database-capability-boundary.js +62 -0
  47. package/dist/runtime/host-diagnostics.d.ts +11 -0
  48. package/dist/runtime/host-diagnostics.js +191 -0
  49. package/dist/runtime/host-execution-root.d.ts +4 -0
  50. package/dist/runtime/host-execution-root.js +39 -0
  51. package/dist/runtime/host-listener-inspector.d.ts +8 -1
  52. package/dist/runtime/host-listener-inspector.js +121 -60
  53. package/dist/runtime/host-runtime-contract.d.ts +12 -1
  54. package/dist/runtime/host-runtime-contract.js +168 -55
  55. package/dist/runtime/host-runtime-lease.d.ts +92 -8
  56. package/dist/runtime/host-runtime-lease.js +1141 -114
  57. package/dist/runtime/host-runtime-promotion.d.ts +12 -3
  58. package/dist/runtime/host-runtime-promotion.js +59 -12
  59. package/dist/runtime/memory-capacity.d.ts +70 -0
  60. package/dist/runtime/memory-capacity.js +123 -0
  61. package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
  62. package/dist/runtime/native-test-postgres-fallback.js +91 -0
  63. package/dist/runtime/node-host-runtime-adapter.d.ts +3 -0
  64. package/dist/runtime/node-host-runtime-adapter.js +28 -11
  65. package/dist/runtime/pinned-directory-process.d.ts +9 -0
  66. package/dist/runtime/pinned-directory-process.js +61 -0
  67. package/dist/runtime/pinned-source-directory.d.ts +17 -0
  68. package/dist/runtime/pinned-source-directory.js +94 -0
  69. package/dist/runtime/playwright-host-runtime-adapter.d.ts +1 -0
  70. package/dist/runtime/playwright-host-runtime-adapter.js +53 -5
  71. package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
  72. package/dist/runtime/prepared-runtime-identity.js +218 -0
  73. package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
  74. package/dist/runtime/prepared-runtime-readiness.js +285 -0
  75. package/dist/runtime/runtime-isolation-lease.js +4 -1
  76. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  77. package/dist/runtime/workspace-resource-estimator.js +174 -0
  78. package/dist/sandbox/preflight.d.ts +31 -1
  79. package/dist/sandbox/preflight.js +237 -51
  80. package/dist/sandbox/repository-lock.d.ts +2 -1
  81. package/dist/sandbox/repository-lock.js +17 -8
  82. package/dist/sandbox/runtime.d.ts +6 -1
  83. package/dist/sandbox/runtime.js +132 -103
  84. package/dist/sandbox/sandbox.types.d.ts +7 -0
  85. package/dist/sandbox/workspace.service.js +2 -0
  86. package/dist/session/managed-attempt-tools.d.ts +17 -1
  87. package/dist/session/managed-attempt-tools.js +485 -31
  88. package/dist/session/managed-build-coordinator.d.ts +5 -0
  89. package/dist/session/managed-build-coordinator.js +105 -0
  90. package/dist/session/managed-build-receipt.d.ts +21 -0
  91. package/dist/session/managed-build-receipt.js +337 -0
  92. package/dist/session/managed-verification-timeouts.d.ts +8 -0
  93. package/dist/session/managed-verification-timeouts.js +17 -0
  94. package/dist/session/phase-completion.artifacts.d.ts +17 -0
  95. package/dist/session/phase-completion.artifacts.js +224 -22
  96. package/dist/session/phase-completion.d.ts +11 -1
  97. package/dist/session/phase-completion.helpers.d.ts +5 -0
  98. package/dist/session/phase-completion.helpers.js +30 -7
  99. package/dist/session/phase-completion.js +14 -3
  100. package/dist/session/phase-completion.prompts.js +17 -18
  101. package/dist/session/phase-completion.runtime.d.ts +3 -1
  102. package/dist/session/phase-completion.runtime.js +126 -17
  103. package/dist/session/phase-completion.schemas.js +49 -2
  104. package/dist/session/phase-completion.validation.d.ts +1 -0
  105. package/dist/session/phase-completion.validation.js +111 -21
  106. package/dist/session/pre-publication-validation.d.ts +75 -0
  107. package/dist/session/pre-publication-validation.js +661 -0
  108. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
  109. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
  110. package/dist/session/provider-cli/ci-repair-readiness.d.ts +8 -0
  111. package/dist/session/provider-cli/ci-repair-readiness.js +23 -3
  112. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +4 -2
  113. package/dist/session/provider-cli/provider-cli.artifacts.js +47 -13
  114. package/dist/session/provider-cli/provider-cli.deterministic.js +34 -2
  115. package/dist/session/provider-cli/provider-cli.process.d.ts +51 -0
  116. package/dist/session/provider-cli/provider-cli.process.js +422 -22
  117. package/dist/session/provider-cli/provider-cli.prompts.d.ts +5 -0
  118. package/dist/session/provider-cli/provider-cli.prompts.js +136 -16
  119. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  120. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  121. package/dist/session/provider-cli/provider-cli.runtime.d.ts +8 -1
  122. package/dist/session/provider-cli/provider-cli.runtime.js +43 -3
  123. package/dist/session/provider-cli/provider-cli.service.d.ts +30 -5
  124. package/dist/session/provider-cli/provider-cli.service.js +771 -68
  125. package/dist/session/provider-cli/provider-cli.types.d.ts +2 -0
  126. package/dist/session/provider-cli/provider-output-state.d.ts +10 -0
  127. package/dist/session/provider-cli/provider-output-state.js +361 -17
  128. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  129. package/dist/session/provider-cli/provider-usage.js +28 -5
  130. package/dist/session/session-redaction.js +434 -9
  131. package/dist/session/session.service.js +145 -23
  132. package/dist/session/session.types.d.ts +147 -2
  133. package/dist/session/session.types.js +587 -7
  134. package/dist/session/unreferenced-exports.d.ts +12 -0
  135. package/dist/session/unreferenced-exports.js +59 -0
  136. package/dist/shared/format/format-bytes.d.ts +1 -0
  137. package/dist/shared/format/format-bytes.js +9 -0
  138. package/dist/shared/git/git.js +2 -1
  139. package/dist/shared/path/path.d.ts +1 -0
  140. package/dist/shared/path/path.js +18 -0
  141. package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
  142. package/dist/shared/process/dotnet-toolchain.js +307 -0
  143. package/dist/shared/process/host-process-supervisor.d.ts +2 -0
  144. package/dist/shared/process/host-process-supervisor.js +57 -9
  145. package/dist/shared/process/process.d.ts +2 -0
  146. package/dist/shared/process/process.js +12 -7
  147. package/dist/shared/provider-capabilities.d.ts +3 -0
  148. package/dist/shared/provider-capabilities.js +85 -0
  149. package/dist/shared/redaction/redaction.d.ts +1 -0
  150. package/dist/shared/redaction/redaction.js +39 -3
  151. package/dist/version.d.ts +1 -1
  152. package/dist/version.js +1 -1
  153. package/dist/worktree/dependency-warmer.d.ts +41 -9
  154. package/dist/worktree/dependency-warmer.js +497 -22
  155. package/dist/worktree/index.d.ts +1 -1
  156. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  157. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  158. package/dist/worktree/worktree.service.d.ts +7 -0
  159. package/dist/worktree/worktree.service.js +253 -21
  160. package/dist/worktree/worktree.types.d.ts +7 -1
  161. package/package.json +7 -5
  162. package/runtime/windows-job-supervisor.ps1 +380 -14
@@ -1,4 +1,15 @@
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");
@@ -34,6 +45,134 @@ export function parseRunOnceDispatchPacket(value) {
34
45
  draft: parseDraftPrompt(packet.draft)
35
46
  };
36
47
  }
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
+ }
37
176
  function parseRuntimeIntent(value) {
38
177
  if (value === undefined || value === null) {
39
178
  return { executionClass: "managed_runtime", applicationRuntimeRequired: true };
@@ -171,17 +310,391 @@ function parseSandboxNetworkPolicy(value) {
171
310
  }
172
311
  return { mode, allowedHosts };
173
312
  }
174
- export function failureResult(summary, events = []) {
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")) {
175
376
  return {
176
- schemaVersion: 1,
377
+ schemaVersion: 2,
177
378
  exitCode: 1,
178
- summary,
179
- 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),
425
+ };
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,
180
439
  };
181
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
+ }
182
694
  function parseWorkItem(value) {
183
695
  const item = asObject(value, "workItem");
184
696
  return {
697
+ projectId: optionalString(item, "projectId"),
185
698
  id: requiredString(item, "id"),
186
699
  kind: requiredString(item, "kind"),
187
700
  title: requiredString(item, "title"),
@@ -205,6 +718,7 @@ function parseRepo(value) {
205
718
  function parseCycleRun(value) {
206
719
  const cycle = asObject(value, "cycleRun");
207
720
  return {
721
+ effectivePlanAmendmentHash: optionalString(cycle, "effectivePlanAmendmentHash"),
208
722
  id: requiredString(cycle, "id"),
209
723
  ownerType: optionalString(cycle, "ownerType"),
210
724
  ownerId: optionalString(cycle, "ownerId"),
@@ -214,6 +728,7 @@ function parseCycleRun(value) {
214
728
  requestedBase: optionalString(cycle, "requestedBase") ?? "develop",
215
729
  baseBranch: optionalString(cycle, "baseBranch") ?? "develop",
216
730
  expectedCurrentBranch: optionalString(cycle, "expectedCurrentBranch"),
731
+ repairHeadSha: optionalString(cycle, "repairHeadSha"),
217
732
  attempt: requiredNumber(cycle, "attempt"),
218
733
  model: requiredString(cycle, "model")
219
734
  };
@@ -223,7 +738,9 @@ function parseSession(value) {
223
738
  return {
224
739
  id: requiredString(session, "id"),
225
740
  model: requiredString(session, "model"),
226
- lane: requiredString(session, "lane")
741
+ lane: requiredString(session, "lane"),
742
+ resumedFromSessionId: optionalString(session, "resumedFromSessionId"),
743
+ resumeProviderThreadId: optionalString(session, "resumeProviderThreadId")
227
744
  };
228
745
  }
229
746
  function parseLease(value) {
@@ -251,7 +768,8 @@ function parseExecution(value) {
251
768
  role: requiredString(execution, "role"),
252
769
  lane: requiredString(execution, "lane"),
253
770
  requiresApiKeySecretGrant: requiredBoolean(execution, "requiresApiKeySecretGrant"),
254
- runnerRepoLocalPath: optionalString(execution, "runnerRepoLocalPath")
771
+ runnerRepoLocalPath: optionalString(execution, "runnerRepoLocalPath"),
772
+ effort: optionalString(execution, "effort")
255
773
  };
256
774
  }
257
775
  function parseDraftPrompt(value) {
@@ -349,9 +867,49 @@ function parsePullRequest(value) {
349
867
  requiredChecksTotal: requiredNumber(pullRequest, "requiredChecksTotal"),
350
868
  unresolvedThreads: requiredNumber(pullRequest, "unresolvedThreads"),
351
869
  humanApproved: requiredBoolean(pullRequest, "humanApproved"),
352
- 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)
353
875
  };
354
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
+ }
355
913
  function parseWarmPathOverrides(value) {
356
914
  if (value === undefined || value === null) {
357
915
  return [];
@@ -396,6 +954,7 @@ function parseQaScope(value) {
396
954
  schema_version: requiredNumber(qaScope, "schema_version"),
397
955
  surface: parseOptionalStringArray(qaScope.surface, "qaScope.surface"),
398
956
  required_evidence: parseOptionalStringArray(qaScope.required_evidence, "qaScope.required_evidence"),
957
+ required_capabilities: parseOptionalStringArray(qaScope.required_capabilities ?? qaScope.requiredCapabilities, "qaScope.required_capabilities"),
399
958
  not_applicable: parseOptionalStringArray(qaScope.not_applicable, "qaScope.not_applicable"),
400
959
  browser_required: optionalBoolean(qaScope, "browser_required") ?? optionalBoolean(qaScope, "browserRequired") ?? false,
401
960
  api_required: optionalBoolean(qaScope, "api_required") ?? optionalBoolean(qaScope, "apiRequired") ?? false,
@@ -529,6 +1088,7 @@ function parseStoryQa(value) {
529
1088
  }
530
1089
  const storyQa = asObject(value, "storyQa");
531
1090
  return {
1091
+ references: parseStoryQaReferences(storyQa.references),
532
1092
  storyWorkItemId: requiredString(storyQa, "storyWorkItemId"),
533
1093
  planId: requiredString(storyQa, "planId"),
534
1094
  planContentHash: requiredString(storyQa, "planContentHash"),
@@ -541,6 +1101,26 @@ function parseStoryQa(value) {
541
1101
  dependencies: parseStoryQaDependencies(storyQa.dependencies)
542
1102
  };
543
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
+ }
544
1124
  function parseStoryQaChildTasks(value) {
545
1125
  if (!Array.isArray(value)) {
546
1126
  throw new Error("storyQa.childTasks must be an array.");