@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 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 (221) 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 +97 -0
  46. package/dist/runtime/attempt-process-supervisor.js +397 -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 +191 -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 +285 -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 +128 -0
  109. package/dist/sandbox/runtime.d.ts +302 -0
  110. package/dist/sandbox/runtime.js +2367 -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 +1015 -0
  123. package/dist/session/managed-build-coordinator.d.ts +5 -0
  124. package/dist/session/managed-build-coordinator.js +105 -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 +16 -0
  165. package/dist/session/provider-cli/provider-cli.runtime.js +81 -0
  166. package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
  167. package/dist/session/provider-cli/provider-cli.service.js +1257 -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 +78 -0
  191. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  192. package/dist/shared/process/host-process-supervisor.js +330 -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/version.d.ts +1 -1
  202. package/dist/version.js +1 -1
  203. package/dist/worktree/artifact-injector.d.ts +3 -2
  204. package/dist/worktree/artifact-injector.js +82 -10
  205. package/dist/worktree/dependency-warmer.d.ts +53 -1
  206. package/dist/worktree/dependency-warmer.js +619 -11
  207. package/dist/worktree/index.d.ts +2 -1
  208. package/dist/worktree/index.js +1 -0
  209. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  210. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  211. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  212. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  213. package/dist/worktree/worktree-paths.d.ts +4 -0
  214. package/dist/worktree/worktree-paths.js +30 -0
  215. package/dist/worktree/worktree.base.d.ts +3 -0
  216. package/dist/worktree/worktree.service.d.ts +16 -3
  217. package/dist/worktree/worktree.service.js +334 -54
  218. package/dist/worktree/worktree.types.d.ts +9 -1
  219. package/package.json +12 -7
  220. package/runtime/windows-job-supervisor.ps1 +722 -0
  221. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -0,0 +1,54 @@
1
+ import { constants } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ /** Keep the validated source inode open: reopening a checked pathname can follow a replacement. */
5
+ export async function readCanonicalSourceFile(target, maximumBytes, expectedIdentity) {
6
+ const resolved = path.resolve(target);
7
+ const pathBefore = await canonicalStat(resolved);
8
+ if (expectedIdentity && !sameFile(pathBefore, expectedIdentity)) {
9
+ throw new Error("host_runtime_source_changed: source identity changed before reading.");
10
+ }
11
+ const handle = await fs.open(resolved, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)
12
+ | (constants.O_NONBLOCK ?? 0));
13
+ try {
14
+ const before = await handle.stat({ bigint: true });
15
+ if (!before.isFile() || before.nlink !== 1n || !sameFile(pathBefore, before) || before.size > BigInt(maximumBytes)) {
16
+ throw new Error("host_runtime_source_invalid: source changed or exceeds the byte bound.");
17
+ }
18
+ if (!sameFile(before, await canonicalStat(resolved))) {
19
+ throw new Error("host_runtime_source_changed: source changed before reading.");
20
+ }
21
+ const bytes = Buffer.alloc(Number(before.size));
22
+ let offset = 0;
23
+ while (offset < bytes.length) {
24
+ const read = await handle.read(bytes, offset, bytes.length - offset, offset);
25
+ if (!read.bytesRead)
26
+ throw new Error("host_runtime_source_changed: incomplete source read.");
27
+ offset += read.bytesRead;
28
+ }
29
+ const after = await handle.stat({ bigint: true });
30
+ const pathAfter = await canonicalStat(resolved);
31
+ if (after.nlink !== 1n || !sameFile(before, after) || !sameFile(after, pathAfter) || before.size !== after.size
32
+ || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs) {
33
+ throw new Error("host_runtime_source_changed: source changed while reading.");
34
+ }
35
+ return bytes;
36
+ }
37
+ finally {
38
+ await handle.close();
39
+ }
40
+ }
41
+ async function canonicalStat(target) {
42
+ const stat = await fs.lstat(target, { bigint: true });
43
+ const real = await fs.realpath(target);
44
+ const realStat = await fs.lstat(real, { bigint: true });
45
+ const samePath = process.platform === "win32" ? real.toLowerCase() === target.toLowerCase() : real === target;
46
+ if (!samePath || !stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n || !realStat.isFile()
47
+ || realStat.isSymbolicLink() || realStat.nlink !== 1n || !sameFile(stat, realStat)) {
48
+ throw new Error("host_runtime_source_invalid: source must be a canonical regular file.");
49
+ }
50
+ return realStat;
51
+ }
52
+ function sameFile(left, right) {
53
+ return left.dev === right.dev && left.ino === right.ino;
54
+ }
@@ -0,0 +1,90 @@
1
+ import type { CommandRunner } from "../sandbox/sandbox.types.js";
2
+ export interface DockerEngineCapabilitySnapshot {
3
+ readonly available: boolean;
4
+ readonly sampledAt: string;
5
+ readonly runtimeVersion: string | null;
6
+ readonly operatingSystem: string | null;
7
+ readonly context: string;
8
+ readonly unavailableCode: string | null;
9
+ readonly unavailableReason: string | null;
10
+ }
11
+ export interface DockerEngineProbeOptions {
12
+ readonly dockerExecutable?: string;
13
+ readonly dockerContext?: string;
14
+ readonly recovery?: DockerEngineRecoveryPolicy;
15
+ readonly recoveryExecutor?: DockerEngineRecoveryExecutor;
16
+ readonly platform?: NodeJS.Platform;
17
+ readonly dockerHost?: string;
18
+ readonly now?: () => number;
19
+ readonly wait?: (delayMs: number) => Promise<void>;
20
+ readonly onStatus?: (message: string) => void;
21
+ }
22
+ export interface DockerEngineRecoveryPolicy {
23
+ readonly enabled: boolean;
24
+ readonly failureThreshold?: number;
25
+ readonly confirmationDelayMs?: number;
26
+ readonly cooldownMs?: number;
27
+ readonly startupTimeoutMs?: number;
28
+ readonly pollIntervalMs?: number;
29
+ readonly canRecover?: () => boolean;
30
+ }
31
+ export interface DockerEngineRecoveryResult {
32
+ readonly started: boolean;
33
+ readonly reason?: string;
34
+ }
35
+ export interface DockerEngineRecoveryExecutor {
36
+ restart(): Promise<DockerEngineRecoveryResult>;
37
+ }
38
+ /**
39
+ * Probes ordinary Docker-daemon access for host-run Testcontainers and project
40
+ * services. This is deliberately independent from the stricter Forge sandbox
41
+ * preflight: a usable Docker Desktop engine may satisfy docker_engine even
42
+ * when it is not approved to host an agent sandbox.
43
+ */
44
+ export declare class DockerEngineProbe {
45
+ private readonly commands;
46
+ private readonly cacheMs;
47
+ private cached;
48
+ private sampledAtMs;
49
+ private inFlight;
50
+ private requiredWorkRecoveryInFlight;
51
+ private consecutiveFailures;
52
+ private lastRecoveryAttemptAtMs;
53
+ private readonly docker;
54
+ private readonly context;
55
+ private readonly platform;
56
+ private readonly dockerHost;
57
+ private readonly now;
58
+ private readonly wait;
59
+ private readonly recovery;
60
+ private readonly recoveryExecutor;
61
+ private readonly onStatus;
62
+ constructor(options?: DockerEngineProbeOptions, commands?: CommandRunner, cacheMs?: number);
63
+ current(): DockerEngineCapabilitySnapshot | null;
64
+ refresh(force?: boolean): Promise<DockerEngineCapabilitySnapshot>;
65
+ /**
66
+ * Establishes a fresh daemon fence for work that declares Docker as a hard
67
+ * prerequisite. This remains bounded, cooldown protected and single-flight;
68
+ * it never contacts Docker unless a caller explicitly invokes this method.
69
+ */
70
+ ensureAvailableForRequiredWork(): Promise<DockerEngineCapabilitySnapshot>;
71
+ private inspectSafely;
72
+ private recoverIfEligible;
73
+ private isLocalDockerDesktopContext;
74
+ private inspect;
75
+ }
76
+ /**
77
+ * Restarts only the local Docker Desktop installation. It never targets a
78
+ * remote daemon and is used only behind the explicit bridge recovery policy.
79
+ */
80
+ export declare class WindowsDockerDesktopRecoveryExecutor implements DockerEngineRecoveryExecutor {
81
+ private readonly platform;
82
+ private readonly commands;
83
+ private readonly programFiles;
84
+ private readonly wait;
85
+ constructor(platform?: NodeJS.Platform, commands?: CommandRunner, programFiles?: string, wait?: (delayMs: number) => Promise<void>);
86
+ restart(): Promise<DockerEngineRecoveryResult>;
87
+ private readProcessState;
88
+ private cleanupOrphanedCompanions;
89
+ private startDesktop;
90
+ }
@@ -0,0 +1,333 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { BoundedCommandRunner } from "../sandbox/command-runner.js";
4
+ /**
5
+ * Probes ordinary Docker-daemon access for host-run Testcontainers and project
6
+ * services. This is deliberately independent from the stricter Forge sandbox
7
+ * preflight: a usable Docker Desktop engine may satisfy docker_engine even
8
+ * when it is not approved to host an agent sandbox.
9
+ */
10
+ export class DockerEngineProbe {
11
+ commands;
12
+ cacheMs;
13
+ cached = null;
14
+ sampledAtMs = 0;
15
+ inFlight = null;
16
+ requiredWorkRecoveryInFlight = null;
17
+ consecutiveFailures = 0;
18
+ lastRecoveryAttemptAtMs = null;
19
+ docker;
20
+ context;
21
+ platform;
22
+ dockerHost;
23
+ now;
24
+ wait;
25
+ recovery;
26
+ recoveryExecutor;
27
+ onStatus;
28
+ constructor(options = {}, commands = new BoundedCommandRunner(), cacheMs = 15_000) {
29
+ this.commands = commands;
30
+ this.cacheMs = cacheMs;
31
+ this.docker = options.dockerExecutable ?? process.env.FORGE_DOCKER_EXECUTABLE ?? "docker";
32
+ this.context = options.dockerContext?.trim() || process.env.DOCKER_CONTEXT?.trim() || "default";
33
+ this.platform = options.platform ?? process.platform;
34
+ this.dockerHost = options.dockerHost ?? process.env.DOCKER_HOST?.trim() ?? "";
35
+ this.now = options.now ?? Date.now;
36
+ this.wait = options.wait ?? (delayMs => new Promise(resolve => setTimeout(resolve, delayMs)));
37
+ this.recovery = {
38
+ enabled: options.recovery?.enabled ?? false,
39
+ failureThreshold: positiveInteger(options.recovery?.failureThreshold, 3),
40
+ confirmationDelayMs: positiveInteger(options.recovery?.confirmationDelayMs, 30_000),
41
+ cooldownMs: positiveInteger(options.recovery?.cooldownMs, 10 * 60_000),
42
+ startupTimeoutMs: positiveInteger(options.recovery?.startupTimeoutMs, 2 * 60_000),
43
+ pollIntervalMs: positiveInteger(options.recovery?.pollIntervalMs, 2_000),
44
+ canRecover: options.recovery?.canRecover,
45
+ };
46
+ this.recoveryExecutor = options.recoveryExecutor ?? new WindowsDockerDesktopRecoveryExecutor(this.platform, commands);
47
+ this.onStatus = options.onStatus ?? (() => undefined);
48
+ }
49
+ current() {
50
+ return this.cached;
51
+ }
52
+ async refresh(force = false) {
53
+ if (!force && this.cached && this.now() - this.sampledAtMs < this.cacheMs) {
54
+ return this.cached;
55
+ }
56
+ if (this.inFlight)
57
+ return await this.inFlight;
58
+ const operation = this.inspectSafely()
59
+ .then(result => this.recoverIfEligible(result))
60
+ .then((result) => {
61
+ this.cached = result;
62
+ this.sampledAtMs = this.now();
63
+ return result;
64
+ })
65
+ .finally(() => {
66
+ if (this.inFlight === operation)
67
+ this.inFlight = null;
68
+ });
69
+ this.inFlight = operation;
70
+ return await operation;
71
+ }
72
+ /**
73
+ * Establishes a fresh daemon fence for work that declares Docker as a hard
74
+ * prerequisite. This remains bounded, cooldown protected and single-flight;
75
+ * it never contacts Docker unless a caller explicitly invokes this method.
76
+ */
77
+ async ensureAvailableForRequiredWork() {
78
+ const fresh = await this.refresh(true);
79
+ if (fresh.available || !this.recovery.enabled || !this.isLocalDockerDesktopContext()) {
80
+ return fresh;
81
+ }
82
+ if (this.requiredWorkRecoveryInFlight)
83
+ return await this.requiredWorkRecoveryInFlight;
84
+ const operation = this.recoverIfEligible(fresh, true)
85
+ .then((result) => {
86
+ this.cached = result;
87
+ this.sampledAtMs = this.now();
88
+ return result;
89
+ })
90
+ .finally(() => {
91
+ if (this.requiredWorkRecoveryInFlight === operation)
92
+ this.requiredWorkRecoveryInFlight = null;
93
+ });
94
+ this.requiredWorkRecoveryInFlight = operation;
95
+ return await operation;
96
+ }
97
+ async inspectSafely() {
98
+ return await this.inspect().catch((error) => unavailable(this.context, "docker_engine_unreachable", error instanceof Error ? error.message : String(error)));
99
+ }
100
+ async recoverIfEligible(snapshot, requiredWork = false) {
101
+ if (snapshot.available) {
102
+ this.consecutiveFailures = 0;
103
+ return snapshot;
104
+ }
105
+ this.consecutiveFailures += 1;
106
+ if (!this.recovery.enabled
107
+ || !this.isLocalDockerDesktopContext()
108
+ || (!requiredWork && this.consecutiveFailures < this.recovery.failureThreshold)
109
+ || (!requiredWork && this.recovery.canRecover?.() === false)) {
110
+ return snapshot;
111
+ }
112
+ const now = this.now();
113
+ if (this.lastRecoveryAttemptAtMs !== null
114
+ && now - this.lastRecoveryAttemptAtMs < this.recovery.cooldownMs) {
115
+ return snapshot;
116
+ }
117
+ this.onStatus(`Docker engine remained unavailable for ${this.consecutiveFailures} consecutive probes; waiting for one fresh confirmation before recovery.`);
118
+ await this.wait(this.recovery.confirmationDelayMs);
119
+ const confirmed = await this.inspectSafely();
120
+ if (confirmed.available) {
121
+ this.consecutiveFailures = 0;
122
+ this.onStatus("Docker engine recovered during the confirmation window; skipped Docker Desktop recovery.");
123
+ return confirmed;
124
+ }
125
+ if (!requiredWork && this.recovery.canRecover?.() === false) {
126
+ this.onStatus("Docker Desktop recovery was deferred because runner work became active during confirmation.");
127
+ return confirmed;
128
+ }
129
+ this.consecutiveFailures += 1;
130
+ this.lastRecoveryAttemptAtMs = this.now();
131
+ this.onStatus(`Docker engine remained unavailable after confirmation; starting one bounded Docker Desktop recovery.`);
132
+ const recovery = await this.recoveryExecutor.restart().catch((error) => ({
133
+ started: false,
134
+ reason: error instanceof Error ? error.message : String(error),
135
+ }));
136
+ if (!recovery.started) {
137
+ const reason = recovery.reason?.trim() || "Docker Desktop recovery could not be started.";
138
+ this.onStatus(`Docker Desktop recovery failed to start: ${singleLine(reason)}`);
139
+ return unavailable(this.context, "docker_engine_recovery_failed", reason);
140
+ }
141
+ const deadline = this.now() + this.recovery.startupTimeoutMs;
142
+ while (this.now() < deadline) {
143
+ await this.wait(Math.min(this.recovery.pollIntervalMs, Math.max(1, deadline - this.now())));
144
+ const verified = await this.inspectSafely();
145
+ if (verified.available) {
146
+ this.consecutiveFailures = 0;
147
+ this.onStatus("Docker Desktop recovery completed and the engine passed a fresh daemon probe.");
148
+ return verified;
149
+ }
150
+ }
151
+ this.onStatus("Docker Desktop recovery timed out before the engine became reachable; cooldown is active.");
152
+ return unavailable(this.context, "docker_engine_recovery_timeout", "Docker Desktop restart was requested, but the engine did not become reachable before the recovery timeout.");
153
+ }
154
+ isLocalDockerDesktopContext() {
155
+ return this.platform === "win32"
156
+ && !this.dockerHost
157
+ && (this.context === "default" || this.context === "desktop-linux");
158
+ }
159
+ async inspect() {
160
+ const dockerArgs = this.context === "default" ? [] : ["--context", this.context];
161
+ const result = await this.commands.run(this.docker, [...dockerArgs, "info", "--format", "{{json .}}"], { timeoutMs: 10_000, maxOutputBytes: 512 * 1024 });
162
+ if (result.exitCode !== 0) {
163
+ return unavailable(this.context, "docker_engine_unreachable", result.stderr || result.stdout || "Docker daemon is unreachable.");
164
+ }
165
+ let info;
166
+ try {
167
+ const parsed = JSON.parse(result.stdout.trim());
168
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
169
+ throw new Error("not an object");
170
+ info = parsed;
171
+ }
172
+ catch {
173
+ return unavailable(this.context, "docker_engine_invalid_response", "Docker returned an invalid info document.");
174
+ }
175
+ const version = clean(info.ServerVersion);
176
+ const operatingSystem = clean(info.OSType)?.toLowerCase() ?? null;
177
+ const serverErrors = Array.isArray(info.ServerErrors)
178
+ ? info.ServerErrors.map(clean).filter((value) => Boolean(value))
179
+ : [];
180
+ if (serverErrors.length > 0 || !version || !operatingSystem) {
181
+ return unavailable(this.context, "docker_engine_unavailable_response", serverErrors.join(" ") || "Docker returned an incomplete daemon identity.");
182
+ }
183
+ return {
184
+ available: true,
185
+ sampledAt: new Date().toISOString(),
186
+ runtimeVersion: `docker/${version}`,
187
+ operatingSystem,
188
+ context: this.context,
189
+ unavailableCode: null,
190
+ unavailableReason: null,
191
+ };
192
+ }
193
+ }
194
+ /**
195
+ * Restarts only the local Docker Desktop installation. It never targets a
196
+ * remote daemon and is used only behind the explicit bridge recovery policy.
197
+ */
198
+ export class WindowsDockerDesktopRecoveryExecutor {
199
+ platform;
200
+ commands;
201
+ programFiles;
202
+ wait;
203
+ constructor(platform = process.platform, commands = new BoundedCommandRunner(), programFiles = process.env.ProgramFiles ?? "C:\\Program Files", wait = delayMs => new Promise(resolve => setTimeout(resolve, delayMs))) {
204
+ this.platform = platform;
205
+ this.commands = commands;
206
+ this.programFiles = programFiles;
207
+ this.wait = wait;
208
+ }
209
+ async restart() {
210
+ if (this.platform !== "win32") {
211
+ return { started: false, reason: "Automatic Docker Desktop recovery is supported only on Windows." };
212
+ }
213
+ const installRoot = path.join(this.programFiles, "Docker", "Docker");
214
+ const desktopExecutable = path.join(installRoot, "Docker Desktop.exe");
215
+ if (!fs.existsSync(desktopExecutable)) {
216
+ return { started: false, reason: "Docker Desktop executable was not found in the standard installation directory." };
217
+ }
218
+ const processState = await this.readProcessState();
219
+ if (processState === "unknown") {
220
+ return { started: false, reason: "Docker Desktop process state could not be determined safely." };
221
+ }
222
+ if (processState === "stopped") {
223
+ if (!await this.cleanupOrphanedCompanions()) {
224
+ return { started: false, reason: "Orphaned Docker Desktop companion processes could not be verified and cleaned safely." };
225
+ }
226
+ await this.wait(1_000);
227
+ return await this.startDesktop(desktopExecutable);
228
+ }
229
+ const dockerCli = path.join(installRoot, "DockerCli.exe");
230
+ if (fs.existsSync(dockerCli)) {
231
+ await this.commands.run(dockerCli, ["-Shutdown"], {
232
+ timeoutMs: 15_000,
233
+ maxOutputBytes: 64 * 1024,
234
+ }).catch(() => undefined);
235
+ }
236
+ const stopScript = [
237
+ "$ErrorActionPreference = 'SilentlyContinue'",
238
+ "Get-Process | Where-Object { $_.ProcessName -in @('Docker Desktop', 'com.docker.backend') } | Stop-Process -Force",
239
+ ].join("; ");
240
+ await this.commands.run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", stopScript], {
241
+ timeoutMs: 15_000,
242
+ maxOutputBytes: 64 * 1024,
243
+ }).catch(() => undefined);
244
+ await this.commands.run("wsl.exe", ["--terminate", "docker-desktop"], {
245
+ timeoutMs: 15_000,
246
+ maxOutputBytes: 64 * 1024,
247
+ }).catch(() => undefined);
248
+ await this.wait(5_000);
249
+ const stoppedState = await this.readProcessState();
250
+ if (stoppedState !== "stopped") {
251
+ return {
252
+ started: false,
253
+ reason: stoppedState === "running"
254
+ ? "Docker Desktop processes did not stop before relaunch."
255
+ : "Docker Desktop shutdown state could not be verified safely.",
256
+ };
257
+ }
258
+ if (!await this.cleanupOrphanedCompanions()) {
259
+ return { started: false, reason: "Orphaned Docker Desktop companion processes could not be verified and cleaned safely." };
260
+ }
261
+ await this.wait(1_000);
262
+ return await this.startDesktop(desktopExecutable);
263
+ }
264
+ async readProcessState() {
265
+ const script = [
266
+ "$ErrorActionPreference = 'Stop'",
267
+ "$processes = @(Get-Process -Name 'Docker Desktop','com.docker.backend' -ErrorAction SilentlyContinue)",
268
+ "if ($processes.Count -gt 0) { Write-Output 'running' } else { Write-Output 'stopped' }",
269
+ ].join("; ");
270
+ const result = await this.commands.run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => null);
271
+ if (!result || result.exitCode !== 0)
272
+ return "unknown";
273
+ const state = result.stdout.trim().toLowerCase();
274
+ return state === "running" || state === "stopped" ? state : "unknown";
275
+ }
276
+ async cleanupOrphanedCompanions() {
277
+ const dockerRoot = path.join(this.programFiles, "Docker").replaceAll("'", "''");
278
+ const script = [
279
+ "$ErrorActionPreference = 'Stop'",
280
+ `$dockerRoot = [System.IO.Path]::GetFullPath('${dockerRoot}').TrimEnd('\\') + '\\'`,
281
+ "$names = @('com.docker.build.exe','docker-sandbox.exe','docker-agent.exe','local-sandboxesd.exe','docker-sandboxd.exe')",
282
+ "$candidates = @(Get-CimInstance Win32_Process | Where-Object { $_.Name -in $names -and $_.ExecutablePath -and [System.IO.Path]::GetFullPath($_.ExecutablePath).StartsWith($dockerRoot, [System.StringComparison]::OrdinalIgnoreCase) })",
283
+ "foreach ($candidate in $candidates) { $parent = Get-CimInstance Win32_Process -Filter ('ProcessId=' + $candidate.ParentProcessId) -ErrorAction SilentlyContinue; $parentIsDockerBackend = $null -ne $parent -and $parent.Name -eq 'com.docker.backend.exe' -and $parent.ExecutablePath -and [System.IO.Path]::GetFullPath($parent.ExecutablePath).StartsWith($dockerRoot, [System.StringComparison]::OrdinalIgnoreCase); if (-not $parentIsDockerBackend) { Stop-Process -Id $candidate.ProcessId -Force -ErrorAction Stop } }",
284
+ ].join("; ");
285
+ const result = await this.commands.run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch(() => null);
286
+ return result?.exitCode === 0;
287
+ }
288
+ async startDesktop(desktopExecutable) {
289
+ const quotedDesktop = desktopExecutable.replaceAll("'", "''");
290
+ let lastReason = "Docker Desktop failed to start.";
291
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
292
+ const startScript = [
293
+ "$ErrorActionPreference = 'Stop'",
294
+ `$desktop = Start-Process -FilePath '${quotedDesktop}' -WindowStyle Hidden -PassThru`,
295
+ "Start-Sleep -Seconds 3",
296
+ "if ($desktop.HasExited) { Write-Error ('Docker Desktop exited during startup with code ' + $desktop.ExitCode); exit 1 }",
297
+ ].join("; ");
298
+ const start = await this.commands.run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", startScript], { timeoutMs: 15_000, maxOutputBytes: 64 * 1024 }).catch((error) => ({
299
+ exitCode: 1,
300
+ stdout: "",
301
+ stderr: error instanceof Error ? error.message : String(error),
302
+ }));
303
+ if (start.exitCode === 0)
304
+ return { started: true };
305
+ lastReason = start.stderr || start.stdout || lastReason;
306
+ if (attempt < 3)
307
+ await this.wait(5_000);
308
+ }
309
+ return { started: false, reason: singleLine(lastReason) };
310
+ }
311
+ }
312
+ function unavailable(context, code, reason) {
313
+ return {
314
+ available: false,
315
+ sampledAt: new Date().toISOString(),
316
+ runtimeVersion: null,
317
+ operatingSystem: null,
318
+ context,
319
+ unavailableCode: code,
320
+ unavailableReason: singleLine(reason),
321
+ };
322
+ }
323
+ function clean(value) {
324
+ return typeof value === "string" && value.trim() ? value.trim() : null;
325
+ }
326
+ function singleLine(value) {
327
+ return value.replace(/[\r\n\t]+/gu, " ").trim().slice(0, 1000);
328
+ }
329
+ function positiveInteger(value, fallback) {
330
+ if (value === undefined || !Number.isFinite(value) || value <= 0)
331
+ return fallback;
332
+ return Math.floor(value);
333
+ }
@@ -0,0 +1,20 @@
1
+ export declare function isGeneratedOutputDirectoryName(name: string): boolean;
2
+ /**
3
+ * Returns the generated-output directory that contains a Git-relative path,
4
+ * provided the reset walker would reach it. Paths below discovery boundaries
5
+ * such as node_modules or .forge are intentionally not reset candidates.
6
+ */
7
+ export declare function generatedOutputRootForPath(relativePath: string): string | null;
8
+ export interface GeneratedOutputResetResult {
9
+ readonly quarantineDirectory: string;
10
+ readonly quarantinedPaths: readonly string[];
11
+ readonly retainedPaths: readonly string[];
12
+ }
13
+ /**
14
+ * Moves only known, untracked test-output directories out of a worktree before
15
+ * a managed attempt starts. The quarantine lives in the attempt-owned output
16
+ * directory, so cleanup is recoverable and cannot cross the attempt boundary.
17
+ * A directory with tracked files is committed source, such as recorded test
18
+ * evidence, so it is left untouched and reported instead of being moved.
19
+ */
20
+ export declare function resetGeneratedOutputs(worktreePath: string, testOutputDirectory: string): Promise<GeneratedOutputResetResult>;
@@ -0,0 +1,157 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ const execFileAsync = promisify(execFile);
7
+ const generatedDirectoryNames = new Set(["TestResults", "test-results", "playwright-report"]);
8
+ const skippedDirectoryNames = new Set([
9
+ ".git",
10
+ ".forge",
11
+ ".idea",
12
+ ".next",
13
+ ".pnpm",
14
+ ".vs",
15
+ ".vscode",
16
+ "bin",
17
+ "build",
18
+ "coverage",
19
+ "dist",
20
+ "node_modules",
21
+ "obj",
22
+ "target",
23
+ "vendor",
24
+ ]);
25
+ export function isGeneratedOutputDirectoryName(name) {
26
+ return generatedDirectoryNames.has(name);
27
+ }
28
+ /**
29
+ * Returns the generated-output directory that contains a Git-relative path,
30
+ * provided the reset walker would reach it. Paths below discovery boundaries
31
+ * such as node_modules or .forge are intentionally not reset candidates.
32
+ */
33
+ export function generatedOutputRootForPath(relativePath) {
34
+ const segments = relativePath.replaceAll("\\", "/").split("/").filter(Boolean);
35
+ for (let index = 0; index < segments.length; index += 1) {
36
+ const segment = segments[index];
37
+ if (skippedDirectoryNames.has(segment))
38
+ return null;
39
+ if (isGeneratedOutputDirectoryName(segment))
40
+ return segments.slice(0, index + 1).join("/");
41
+ }
42
+ return null;
43
+ }
44
+ /**
45
+ * Moves only known, untracked test-output directories out of a worktree before
46
+ * a managed attempt starts. The quarantine lives in the attempt-owned output
47
+ * directory, so cleanup is recoverable and cannot cross the attempt boundary.
48
+ * A directory with tracked files is committed source, such as recorded test
49
+ * evidence, so it is left untouched and reported instead of being moved.
50
+ */
51
+ export async function resetGeneratedOutputs(worktreePath, testOutputDirectory) {
52
+ const worktreeRoot = await fs.realpath(path.resolve(worktreePath));
53
+ const outputRoot = await fs.realpath(path.resolve(testOutputDirectory));
54
+ if (isContainedPath(worktreeRoot, outputRoot)) {
55
+ throw new Error("generated_output_reset_invalid: attempt output must be outside the source worktree.");
56
+ }
57
+ const candidates = await findGeneratedDirectories(worktreeRoot);
58
+ const quarantineDirectory = path.join(outputRoot, "preflight-reset");
59
+ const quarantinedPaths = [];
60
+ const retainedPaths = [];
61
+ for (const candidate of candidates) {
62
+ const relativePath = path.relative(worktreeRoot, candidate);
63
+ if (!relativePath || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) {
64
+ throw new Error("generated_output_reset_invalid: generated output escaped the source worktree.");
65
+ }
66
+ if (await containsTrackedFiles(worktreeRoot, relativePath)) {
67
+ retainedPaths.push(relativePath.split(path.sep).join("/"));
68
+ continue;
69
+ }
70
+ await fs.mkdir(quarantineDirectory, { recursive: true, mode: 0o700 });
71
+ const digest = createHash("sha256").update(relativePath).digest("hex").slice(0, 12);
72
+ const destination = path.join(quarantineDirectory, `${digest}-${randomUUID()}-${path.basename(candidate)}`);
73
+ try {
74
+ await quarantineGeneratedDirectory(candidate, destination);
75
+ }
76
+ catch (error) {
77
+ const code = error.code ?? "unknown";
78
+ throw new Error(`generated_output_reset_failed: could not quarantine ${relativePath} (${code}).`, { cause: error });
79
+ }
80
+ quarantinedPaths.push(relativePath.split(path.sep).join("/"));
81
+ }
82
+ if (quarantinedPaths.length > 0) {
83
+ await fs.writeFile(path.join(quarantineDirectory, "manifest.json"), `${JSON.stringify({ schemaVersion: 1, worktreePath: worktreeRoot, quarantinedPaths }, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
84
+ }
85
+ return { quarantineDirectory, quarantinedPaths, retainedPaths };
86
+ }
87
+ async function quarantineGeneratedDirectory(source, destination) {
88
+ try {
89
+ await fs.rename(source, destination);
90
+ return;
91
+ }
92
+ catch (error) {
93
+ if (error.code !== "EXDEV")
94
+ throw error;
95
+ }
96
+ // Windows installations commonly keep repositories on a data volume while
97
+ // the short per-user attempt root remains on the system volume. rename(2)
98
+ // cannot cross that boundary, so stage a complete copy on the destination
99
+ // volume, publish it there atomically, and only then remove the untracked
100
+ // source directory. A failed copy leaves the source untouched.
101
+ const staging = `${destination}.copying`;
102
+ try {
103
+ await fs.cp(source, staging, {
104
+ recursive: true,
105
+ force: false,
106
+ errorOnExist: true,
107
+ dereference: false,
108
+ verbatimSymlinks: true,
109
+ });
110
+ await fs.rename(staging, destination);
111
+ await fs.rm(source, { recursive: true, force: false });
112
+ }
113
+ catch (error) {
114
+ await fs.rm(staging, { recursive: true, force: true }).catch(() => undefined);
115
+ throw error;
116
+ }
117
+ }
118
+ async function findGeneratedDirectories(root) {
119
+ const candidates = [];
120
+ const pending = [root];
121
+ while (pending.length > 0) {
122
+ const current = pending.pop();
123
+ const entries = await fs.readdir(current, { withFileTypes: true });
124
+ for (const entry of entries) {
125
+ if (!entry.isDirectory() || entry.isSymbolicLink())
126
+ continue;
127
+ if (skippedDirectoryNames.has(entry.name))
128
+ continue;
129
+ const candidate = path.join(current, entry.name);
130
+ if (isGeneratedOutputDirectoryName(entry.name)) {
131
+ const realCandidate = await fs.realpath(candidate);
132
+ if (!isContainedPath(root, realCandidate)) {
133
+ throw new Error("generated_output_reset_invalid: generated output resolves outside the source worktree.");
134
+ }
135
+ candidates.push(realCandidate);
136
+ continue;
137
+ }
138
+ pending.push(candidate);
139
+ }
140
+ }
141
+ return candidates.sort((left, right) => left.localeCompare(right));
142
+ }
143
+ async function containsTrackedFiles(worktreeRoot, relativePath) {
144
+ const pathspec = relativePath.split(path.sep).join("/");
145
+ let stdout;
146
+ try {
147
+ ({ stdout } = await execFileAsync("git", ["-C", worktreeRoot, "ls-files", "-z", "--", pathspec], { encoding: "utf8", windowsHide: true, maxBuffer: 4 * 1024 * 1024 }));
148
+ }
149
+ catch (error) {
150
+ throw new Error(`generated_output_reset_unverified: git could not verify ${pathspec}.`, { cause: error });
151
+ }
152
+ return stdout.length > 0;
153
+ }
154
+ function isContainedPath(root, candidate) {
155
+ const relative = path.relative(root, candidate);
156
+ return relative.length > 0 && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
157
+ }
@@ -0,0 +1,16 @@
1
+ export declare const hostDatabaseCapabilityEnvironmentNames: readonly ["FORGE_TEST_POSTGRES_ADMIN_CONNECTION_STRING", "FORGE_TEST_POSTGRES_CONNECTION_STRING"];
2
+ export interface HostDatabaseCapabilityBoundaryOptions {
3
+ readonly platform?: NodeJS.Platform;
4
+ readonly processEnvironment?: NodeJS.ProcessEnv;
5
+ readonly inspectWindowsUserEnvironment?: boolean;
6
+ readonly windowsUserCapabilityPresent?: () => boolean;
7
+ }
8
+ /**
9
+ * Fails before untrusted host work can start when a PostgreSQL administrator
10
+ * capability is reachable through either the process or Windows User scope.
11
+ * The default Windows probe returns only presence; it never reads the value
12
+ * into JavaScript or emits it in diagnostics.
13
+ */
14
+ export declare function assertHostDatabaseCapabilityBoundary(options?: HostDatabaseCapabilityBoundaryOptions): void;
15
+ /** Establishes the production Bridge boundary and enables per-launch rechecks. */
16
+ export declare function establishHostDatabaseCapabilityBoundary(): void;