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

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 (219) 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 +2426 -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-node-runtime-launcher.d.ts +18 -0
  36. package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
  37. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  38. package/dist/runtime/attempt-path-operation.js +94 -0
  39. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  40. package/dist/runtime/attempt-private-directory.js +54 -0
  41. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  42. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  43. package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
  44. package/dist/runtime/attempt-process-supervisor.js +397 -0
  45. package/dist/runtime/canonical-source-file.d.ts +3 -0
  46. package/dist/runtime/canonical-source-file.js +54 -0
  47. package/dist/runtime/docker-engine-probe.d.ts +90 -0
  48. package/dist/runtime/docker-engine-probe.js +333 -0
  49. package/dist/runtime/generated-output-reset.d.ts +20 -0
  50. package/dist/runtime/generated-output-reset.js +157 -0
  51. package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
  52. package/dist/runtime/host-database-capability-boundary.js +62 -0
  53. package/dist/runtime/host-diagnostics.d.ts +11 -0
  54. package/dist/runtime/host-diagnostics.js +191 -0
  55. package/dist/runtime/host-execution-root.d.ts +4 -0
  56. package/dist/runtime/host-execution-root.js +39 -0
  57. package/dist/runtime/host-listener-inspector.d.ts +51 -0
  58. package/dist/runtime/host-listener-inspector.js +385 -0
  59. package/dist/runtime/host-port-broker.d.ts +76 -0
  60. package/dist/runtime/host-port-broker.js +333 -0
  61. package/dist/runtime/host-runtime-contract.d.ts +58 -0
  62. package/dist/runtime/host-runtime-contract.js +754 -0
  63. package/dist/runtime/host-runtime-lease.d.ts +215 -0
  64. package/dist/runtime/host-runtime-lease.js +2449 -0
  65. package/dist/runtime/host-runtime-promotion.d.ts +32 -0
  66. package/dist/runtime/host-runtime-promotion.js +206 -0
  67. package/dist/runtime/memory-capacity.d.ts +70 -0
  68. package/dist/runtime/memory-capacity.js +123 -0
  69. package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
  70. package/dist/runtime/native-test-postgres-fallback.js +91 -0
  71. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  72. package/dist/runtime/node-host-runtime-adapter.js +285 -0
  73. package/dist/runtime/pinned-directory-process.d.ts +9 -0
  74. package/dist/runtime/pinned-directory-process.js +61 -0
  75. package/dist/runtime/pinned-source-directory.d.ts +17 -0
  76. package/dist/runtime/pinned-source-directory.js +94 -0
  77. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  78. package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
  79. package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
  80. package/dist/runtime/prepared-runtime-identity.js +218 -0
  81. package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
  82. package/dist/runtime/prepared-runtime-readiness.js +285 -0
  83. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  84. package/dist/runtime/runtime-isolation-lease.js +161 -0
  85. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  86. package/dist/runtime/workspace-resource-estimator.js +174 -0
  87. package/dist/sandbox/command-runner.d.ts +4 -0
  88. package/dist/sandbox/command-runner.js +47 -0
  89. package/dist/sandbox/contract.d.ts +5 -0
  90. package/dist/sandbox/contract.js +276 -0
  91. package/dist/sandbox/event-budget.d.ts +11 -0
  92. package/dist/sandbox/event-budget.js +27 -0
  93. package/dist/sandbox/image-policy.d.ts +35 -0
  94. package/dist/sandbox/image-policy.js +247 -0
  95. package/dist/sandbox/index.d.ts +5 -0
  96. package/dist/sandbox/index.js +5 -0
  97. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  98. package/dist/sandbox/lease-watchdog.js +67 -0
  99. package/dist/sandbox/preflight.d.ts +84 -0
  100. package/dist/sandbox/preflight.js +521 -0
  101. package/dist/sandbox/process.d.ts +20 -0
  102. package/dist/sandbox/process.js +209 -0
  103. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  104. package/dist/sandbox/provider-auth/codex.js +212 -0
  105. package/dist/sandbox/repository-lock.d.ts +6 -0
  106. package/dist/sandbox/repository-lock.js +128 -0
  107. package/dist/sandbox/runtime.d.ts +302 -0
  108. package/dist/sandbox/runtime.js +2367 -0
  109. package/dist/sandbox/sandbox.types.d.ts +93 -0
  110. package/dist/sandbox/sandbox.types.js +1 -0
  111. package/dist/sandbox/smoke.d.ts +76 -0
  112. package/dist/sandbox/smoke.js +380 -0
  113. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  114. package/dist/sandbox/worker-supervisor.js +36 -0
  115. package/dist/sandbox/worker.d.ts +1 -0
  116. package/dist/sandbox/worker.js +225 -0
  117. package/dist/sandbox/workspace.service.d.ts +13 -0
  118. package/dist/sandbox/workspace.service.js +74 -0
  119. package/dist/session/managed-attempt-tools.d.ts +28 -0
  120. package/dist/session/managed-attempt-tools.js +1011 -0
  121. package/dist/session/managed-build-coordinator.d.ts +5 -0
  122. package/dist/session/managed-build-coordinator.js +105 -0
  123. package/dist/session/managed-build-receipt.d.ts +21 -0
  124. package/dist/session/managed-build-receipt.js +337 -0
  125. package/dist/session/managed-verification-timeouts.d.ts +8 -0
  126. package/dist/session/managed-verification-timeouts.js +17 -0
  127. package/dist/session/phase-completion.artifacts.d.ts +27 -0
  128. package/dist/session/phase-completion.artifacts.js +829 -0
  129. package/dist/session/phase-completion.d.ts +53 -42
  130. package/dist/session/phase-completion.helpers.d.ts +117 -0
  131. package/dist/session/phase-completion.helpers.js +707 -0
  132. package/dist/session/phase-completion.js +125 -3007
  133. package/dist/session/phase-completion.prompts.d.ts +14 -0
  134. package/dist/session/phase-completion.prompts.js +233 -0
  135. package/dist/session/phase-completion.runtime.d.ts +49 -0
  136. package/dist/session/phase-completion.runtime.js +833 -0
  137. package/dist/session/phase-completion.schemas.d.ts +3 -0
  138. package/dist/session/phase-completion.schemas.js +859 -0
  139. package/dist/session/phase-completion.validation.d.ts +38 -0
  140. package/dist/session/phase-completion.validation.js +918 -0
  141. package/dist/session/pre-publication-validation.d.ts +75 -0
  142. package/dist/session/pre-publication-validation.js +657 -0
  143. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
  144. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
  145. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  146. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  147. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
  148. package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
  149. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  150. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  151. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  152. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  153. package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
  154. package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
  155. package/dist/session/provider-cli/provider-cli.process.js +874 -0
  156. package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
  157. package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
  158. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  159. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
  160. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  161. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  162. package/dist/session/provider-cli/provider-cli.runtime.d.ts +16 -0
  163. package/dist/session/provider-cli/provider-cli.runtime.js +78 -0
  164. package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
  165. package/dist/session/provider-cli/provider-cli.service.js +1257 -2497
  166. package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
  167. package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
  168. package/dist/session/provider-cli/provider-output-state.js +817 -0
  169. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  170. package/dist/session/provider-cli/provider-usage.js +28 -5
  171. package/dist/session/session-redaction.d.ts +3 -0
  172. package/dist/session/session-redaction.js +448 -0
  173. package/dist/session/session.base.d.ts +6 -1
  174. package/dist/session/session.service.d.ts +8 -2
  175. package/dist/session/session.service.js +586 -59
  176. package/dist/session/session.types.d.ts +324 -3
  177. package/dist/session/session.types.js +939 -10
  178. package/dist/session/unreferenced-exports.d.ts +12 -0
  179. package/dist/session/unreferenced-exports.js +59 -0
  180. package/dist/shared/format/format-bytes.d.ts +1 -0
  181. package/dist/shared/format/format-bytes.js +9 -0
  182. package/dist/shared/git/git.js +2 -1
  183. package/dist/shared/path/path.d.ts +1 -0
  184. package/dist/shared/path/path.js +21 -1
  185. package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
  186. package/dist/shared/process/dotnet-toolchain.js +307 -0
  187. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  188. package/dist/shared/process/host-process-recovery.js +78 -0
  189. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  190. package/dist/shared/process/host-process-supervisor.js +330 -0
  191. package/dist/shared/process/process.d.ts +3 -0
  192. package/dist/shared/process/process.js +13 -7
  193. package/dist/shared/process/windows-launch.d.ts +8 -0
  194. package/dist/shared/process/windows-launch.js +41 -0
  195. package/dist/shared/provider-capabilities.d.ts +4 -0
  196. package/dist/shared/provider-capabilities.js +88 -0
  197. package/dist/shared/redaction/redaction.d.ts +1 -0
  198. package/dist/shared/redaction/redaction.js +47 -4
  199. package/dist/version.d.ts +1 -1
  200. package/dist/version.js +1 -1
  201. package/dist/worktree/artifact-injector.d.ts +3 -2
  202. package/dist/worktree/artifact-injector.js +82 -10
  203. package/dist/worktree/dependency-warmer.d.ts +53 -1
  204. package/dist/worktree/dependency-warmer.js +619 -11
  205. package/dist/worktree/index.d.ts +2 -1
  206. package/dist/worktree/index.js +1 -0
  207. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  208. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  209. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  210. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  211. package/dist/worktree/worktree-paths.d.ts +4 -0
  212. package/dist/worktree/worktree-paths.js +30 -0
  213. package/dist/worktree/worktree.base.d.ts +3 -0
  214. package/dist/worktree/worktree.service.d.ts +16 -3
  215. package/dist/worktree/worktree.service.js +334 -54
  216. package/dist/worktree/worktree.types.d.ts +9 -1
  217. package/package.json +12 -7
  218. package/runtime/windows-job-supervisor.ps1 +722 -0
  219. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -0,0 +1,32 @@
1
+ import type { LoadedHostRuntimeContract } from "./host-runtime-contract.js";
2
+ export interface HostParallelPromotionRecord {
3
+ readonly schemaVersion: 1;
4
+ readonly repoId: string;
5
+ readonly runtimeContractFingerprint: string;
6
+ readonly inputCommitSha?: string | null;
7
+ readonly sourceFingerprint?: string;
8
+ readonly runtimeContractSource?: "project" | "derived";
9
+ readonly observedAt: string;
10
+ readonly observedDispatchAttemptId: string;
11
+ readonly verifiedPortNames: readonly string[];
12
+ }
13
+ export interface HostParallelProofIdentity {
14
+ readonly runtimeContractFingerprint: string;
15
+ readonly inputCommitSha: string;
16
+ readonly sourceFingerprint?: string;
17
+ }
18
+ /** Persists only observed, contract-bound host parallel-safety evidence. */
19
+ export declare class HostParallelPromotionStore {
20
+ private readonly stateRoot;
21
+ constructor(stateRoot: string);
22
+ load(repoId: string, identity?: HostParallelProofIdentity): Promise<HostParallelPromotionRecord | null>;
23
+ list(): Promise<readonly HostParallelPromotionRecord[]>;
24
+ observe(repoId: string, runtimeContract: LoadedHostRuntimeContract, dispatchAttemptId: string, verifiedPortNames: readonly string[], inputCommitSha?: string, sourceFingerprint?: string): Promise<HostParallelPromotionRecord>;
25
+ private writeRecord;
26
+ private pruneVersions;
27
+ invalidate(repoId: string, scope?: "all" | "discovery"): Promise<void>;
28
+ private recordPath;
29
+ }
30
+ export declare function hostRuntimeContractFingerprint(runtimeContract: LoadedHostRuntimeContract): string;
31
+ export declare function hostRuntimeParallelEligible(runtimeContract: LoadedHostRuntimeContract): boolean;
32
+ export declare function configuredLaunchPortNames(runtimeContract: LoadedHostRuntimeContract): readonly string[];
@@ -0,0 +1,206 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ /** Persists only observed, contract-bound host parallel-safety evidence. */
5
+ export class HostParallelPromotionStore {
6
+ stateRoot;
7
+ constructor(stateRoot) {
8
+ this.stateRoot = stateRoot;
9
+ }
10
+ async load(repoId, identity) {
11
+ const normalizedRepoId = normalizeRepoId(repoId);
12
+ try {
13
+ const parsed = JSON.parse(await fs.readFile(this.recordPath(normalizedRepoId, identity), "utf8"));
14
+ return validRecord(parsed, normalizedRepoId)
15
+ && (!identity || matchesIdentity(parsed, identity)) ? parsed : null;
16
+ }
17
+ catch (error) {
18
+ if (error.code === "ENOENT" || error instanceof SyntaxError)
19
+ return null;
20
+ throw error;
21
+ }
22
+ }
23
+ async list() {
24
+ const root = path.join(this.stateRoot, "runtime-promotion", "host");
25
+ let entries;
26
+ try {
27
+ entries = await fs.readdir(root);
28
+ }
29
+ catch (error) {
30
+ if (error.code === "ENOENT")
31
+ return [];
32
+ throw error;
33
+ }
34
+ const records = [];
35
+ for (const entry of entries.filter(value => /^[0-9a-f]{64}\.json$/u.test(value)).slice(0, 4096)) {
36
+ try {
37
+ const parsed = JSON.parse(await fs.readFile(path.join(root, entry), "utf8"));
38
+ const repoId = typeof parsed.repoId === "string" ? normalizeRepoId(parsed.repoId) : "";
39
+ if (repoId && validRecord(parsed, repoId))
40
+ records.push(parsed);
41
+ }
42
+ catch (error) {
43
+ if (!(error instanceof SyntaxError))
44
+ throw error;
45
+ }
46
+ }
47
+ return records.sort((left, right) => left.repoId.localeCompare(right.repoId));
48
+ }
49
+ async observe(repoId, runtimeContract, dispatchAttemptId, verifiedPortNames, inputCommitSha, sourceFingerprint) {
50
+ if (!hostRuntimeParallelEligible(runtimeContract)) {
51
+ throw new Error("host_parallel_promotion_ineligible: the runtime contract is not eligible for parallel host execution.");
52
+ }
53
+ const requiredPorts = configuredLaunchPortNames(runtimeContract);
54
+ const observedPorts = [...new Set(verifiedPortNames)].sort((left, right) => left.localeCompare(right));
55
+ if (requiredPorts.length !== observedPorts.length
56
+ || requiredPorts.some((name, index) => name !== observedPorts[index])) {
57
+ throw new Error("host_parallel_promotion_unverified: every configured runtime listener must pass its assigned-port probe.");
58
+ }
59
+ const normalizedRepoId = normalizeRepoId(repoId);
60
+ const record = {
61
+ schemaVersion: 1,
62
+ repoId: normalizedRepoId,
63
+ runtimeContractFingerprint: hostRuntimeContractFingerprint(runtimeContract),
64
+ inputCommitSha: inputCommitSha ?? null,
65
+ ...(sourceFingerprint ? { sourceFingerprint } : {}),
66
+ runtimeContractSource: runtimeContract.source === "project" ? "project" : "derived",
67
+ observedAt: new Date().toISOString(),
68
+ observedDispatchAttemptId: normalizedUuid(dispatchAttemptId, "dispatch attempt"),
69
+ verifiedPortNames: observedPorts,
70
+ };
71
+ if (inputCommitSha) {
72
+ await this.writeRecord(this.recordPath(normalizedRepoId, {
73
+ runtimeContractFingerprint: record.runtimeContractFingerprint, inputCommitSha, sourceFingerprint,
74
+ }), record);
75
+ }
76
+ await this.writeRecord(this.recordPath(normalizedRepoId), record);
77
+ await this.pruneVersions(normalizedRepoId);
78
+ return record;
79
+ }
80
+ async writeRecord(target, record) {
81
+ const root = path.dirname(target);
82
+ await ensurePrivateDirectory(root);
83
+ const temporary = `${target}.${randomUUID()}.tmp`;
84
+ try {
85
+ const handle = await fs.open(temporary, "wx", 0o600);
86
+ try {
87
+ await handle.writeFile(`${JSON.stringify(record)}\n`, "utf8");
88
+ await handle.sync();
89
+ }
90
+ finally {
91
+ await handle.close();
92
+ }
93
+ await fs.rename(temporary, target);
94
+ }
95
+ catch (error) {
96
+ await fs.rm(temporary, { force: true }).catch(() => undefined);
97
+ throw error;
98
+ }
99
+ }
100
+ async pruneVersions(repoId) {
101
+ const target = this.recordPath(repoId);
102
+ const root = path.dirname(target);
103
+ const prefix = path.basename(target, ".json") + ".";
104
+ const versions = (await fs.readdir(root)).filter(name => name.startsWith(prefix)
105
+ && /^[0-9a-f]{64}\.[0-9a-f]{64}\.json$/u.test(name));
106
+ if (versions.length <= 128)
107
+ return;
108
+ const dated = await Promise.all(versions.map(async (name) => ({ name,
109
+ at: await fs.stat(path.join(root, name)).then(stat => stat.mtimeMs, () => Number.MAX_SAFE_INTEGER) })));
110
+ for (const entry of dated.sort((a, b) => b.at - a.at).slice(128)) {
111
+ await fs.rm(path.join(root, entry.name), { force: true });
112
+ }
113
+ }
114
+ async invalidate(repoId, scope = "all") {
115
+ const target = this.recordPath(normalizeRepoId(repoId));
116
+ await fs.rm(target, { force: true });
117
+ // Background/default-branch discovery must not erase proofs bound to other
118
+ // prepared sources. Actual containment/cleanup degradation invalidates all.
119
+ if (scope === "discovery")
120
+ return;
121
+ const prefix = path.basename(target, ".json") + ".";
122
+ const entries = await fs.readdir(path.dirname(target)).catch((error) => {
123
+ if (error.code === "ENOENT")
124
+ return [];
125
+ throw error;
126
+ });
127
+ for (const entry of entries.filter(name => name.startsWith(prefix) && /^[0-9a-f]{64}\.[0-9a-f]{64}\.json$/u.test(name))) {
128
+ await fs.rm(path.join(path.dirname(target), entry), { force: true });
129
+ }
130
+ }
131
+ recordPath(normalizedRepoId, identity) {
132
+ const key = createHash("sha256").update(normalizedRepoId).digest("hex");
133
+ const version = identity ? `.${createHash("sha256").update(JSON.stringify([
134
+ identity.runtimeContractFingerprint, identity.inputCommitSha, identity.sourceFingerprint ?? null,
135
+ ])).digest("hex")}` : "";
136
+ return path.join(this.stateRoot, "runtime-promotion", "host", `${key}${version}.json`);
137
+ }
138
+ }
139
+ function matchesIdentity(record, identity) {
140
+ return record.runtimeContractFingerprint === identity.runtimeContractFingerprint
141
+ && record.inputCommitSha === identity.inputCommitSha
142
+ && record.sourceFingerprint === identity.sourceFingerprint;
143
+ }
144
+ export function hostRuntimeContractFingerprint(runtimeContract) {
145
+ const canonical = JSON.stringify({
146
+ schemaVersion: runtimeContract.contract.schemaVersion,
147
+ runtime: runtimeContract.contract.runtime,
148
+ derivedCandidateFingerprint: runtimeContract.source === "derived"
149
+ ? runtimeContract.discovery?.candidateFingerprint ?? null
150
+ : null,
151
+ });
152
+ return `sha256:${createHash("sha256").update(canonical).digest("hex")}`;
153
+ }
154
+ export function hostRuntimeParallelEligible(runtimeContract) {
155
+ const runtime = runtimeContract.contract.runtime;
156
+ const launchedPorts = runtime.ports.filter(port => port.launch !== null);
157
+ return (runtimeContract.source === "project" || runtimeContract.source === "derived")
158
+ && runtime.hostConcurrency === "parallel"
159
+ && runtime.browserProfile === "isolated"
160
+ && runtime.singletonResources.length === 0
161
+ && launchedPorts.length > 0
162
+ && launchedPorts.length === runtime.ports.length
163
+ && launchedPorts.every(port => port.adapter !== null);
164
+ }
165
+ export function configuredLaunchPortNames(runtimeContract) {
166
+ return runtimeContract.contract.runtime.ports
167
+ .filter(port => port.launch !== null)
168
+ .map(port => port.name)
169
+ .sort((left, right) => left.localeCompare(right));
170
+ }
171
+ function validRecord(value, repoId) {
172
+ if (!value || typeof value !== "object" || Array.isArray(value))
173
+ return false;
174
+ const record = value;
175
+ return record.schemaVersion === 1
176
+ && record.repoId === repoId
177
+ && /^sha256:[0-9a-f]{64}$/u.test(record.runtimeContractFingerprint ?? "")
178
+ && Number.isFinite(Date.parse(record.observedAt ?? ""))
179
+ && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(record.observedDispatchAttemptId ?? "")
180
+ && Array.isArray(record.verifiedPortNames)
181
+ && record.verifiedPortNames.length > 0
182
+ && record.verifiedPortNames.length <= 16
183
+ && record.verifiedPortNames.every(name => typeof name === "string" && /^[a-z][a-z0-9_-]{0,63}$/u.test(name));
184
+ }
185
+ function normalizeRepoId(repoId) {
186
+ const normalized = repoId.trim().toLowerCase();
187
+ if (!normalized || normalized.length > 512 || Array.from(normalized).some(character => {
188
+ const codePoint = character.codePointAt(0) ?? 0;
189
+ return codePoint < 32 || codePoint === 127;
190
+ })) {
191
+ throw new Error("host_parallel_promotion_invalid: repository identity is invalid.");
192
+ }
193
+ return normalized;
194
+ }
195
+ function normalizedUuid(value, label) {
196
+ const normalized = value.trim().toLowerCase();
197
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(normalized)) {
198
+ throw new Error(`host_parallel_promotion_invalid: ${label} must be a full UUID.`);
199
+ }
200
+ return normalized;
201
+ }
202
+ async function ensurePrivateDirectory(target) {
203
+ await fs.mkdir(target, { recursive: true, mode: 0o700 });
204
+ if (process.platform !== "win32")
205
+ await fs.chmod(target, 0o700);
206
+ }
@@ -0,0 +1,70 @@
1
+ export declare const memoryAdmissionPolicyVersion = "memory-admission-v2";
2
+ export declare const memoryAdmissionEquation = "max(0, min(stabilized_available - reserve - pending_commitment, total - reserve - committed))";
3
+ export interface MemoryAdmissionDiagnostics {
4
+ readonly policyVersion: string;
5
+ readonly sampledAt: string;
6
+ readonly totalMemoryBytes: number;
7
+ readonly rawAvailableMemoryBytes: number;
8
+ readonly stabilizedAvailableMemoryBytes: number;
9
+ readonly reclaimableMemoryBytes: number | null;
10
+ readonly reclaimableMemorySource: "not_sampled_separately";
11
+ readonly controlPlaneMemoryBytes: number;
12
+ readonly reserveMemoryBytes: number;
13
+ readonly committedMemoryBytes: number;
14
+ readonly pendingCommitmentMemoryBytes: number;
15
+ readonly availableForAdmissionBytes: number;
16
+ readonly requestedMemoryBytes: number | null;
17
+ readonly shortfallMemoryBytes: number | null;
18
+ readonly decision: "snapshot" | "admitted" | "denied";
19
+ readonly equation: string;
20
+ }
21
+ export interface MemoryAdmissionInputs {
22
+ readonly sampledAt: string;
23
+ readonly totalMemoryBytes: number;
24
+ readonly rawAvailableMemoryBytes: number;
25
+ readonly stabilizedAvailableMemoryBytes: number;
26
+ readonly controlPlaneMemoryBytes: number;
27
+ readonly reserveMemoryBytes: number;
28
+ /** Every committed Forge request, including pending requests. */
29
+ readonly committedMemoryBytes: number;
30
+ /** Commitments not yet reflected in the OS available-memory sample. */
31
+ readonly pendingCommitmentMemoryBytes: number;
32
+ readonly requestedMemoryBytes?: number | null;
33
+ }
34
+ /**
35
+ * One memory admission equation for Docker and managed-host capacity. The raw
36
+ * OS/daemon sample already reflects resident active children, so only pending
37
+ * commitments are charged against sampled headroom. Ambient host use must not
38
+ * also be folded into committedMemoryBytes: it is already reflected in the OS
39
+ * sample, and doing both permanently double-charges long-lived host processes.
40
+ * The total-memory ceiling independently caps Forge's own reservations.
41
+ */
42
+ export declare function buildMemoryAdmissionDiagnostics(inputs: MemoryAdmissionInputs): MemoryAdmissionDiagnostics;
43
+ export interface AdaptiveMemoryReserveOptions {
44
+ readonly explicitBytes?: number | null;
45
+ readonly floorBytes?: number | null;
46
+ readonly ceilingBytes?: number | null;
47
+ readonly percent?: number | null;
48
+ }
49
+ /**
50
+ * Derives a host safety reserve from the capacity domain it protects. An
51
+ * explicit operator value remains authoritative; otherwise the reserve scales
52
+ * with the sampled total inside conservative floors and ceilings.
53
+ */
54
+ export declare function resolveAdaptiveMemoryReserveBytes(totalMemoryBytes: number, options?: AdaptiveMemoryReserveOptions): number;
55
+ /**
56
+ * Applies pressure immediately while requiring durable recovery samples before
57
+ * increasing advertised capacity. This prevents one GC/cache sample from
58
+ * waking queued work and then immediately withdrawing the same capacity.
59
+ */
60
+ export declare class RecoveryStabilizedMemory {
61
+ private readonly requiredRecoverySamples;
62
+ private readonly minimumRecoverySampleIntervalMs;
63
+ private stableBytes;
64
+ private recoveryFloorBytes;
65
+ private recoverySamples;
66
+ private lastRecoverySampleAt;
67
+ constructor(requiredRecoverySamples?: number, minimumRecoverySampleIntervalMs?: number);
68
+ observe(rawAvailableBytes: number, sampledAt?: number): number;
69
+ private resetRecovery;
70
+ }
@@ -0,0 +1,123 @@
1
+ const GiB = 1024 ** 3;
2
+ export const memoryAdmissionPolicyVersion = "memory-admission-v2";
3
+ export const memoryAdmissionEquation = "max(0, min(stabilized_available - reserve - pending_commitment, total - reserve - committed))";
4
+ /**
5
+ * One memory admission equation for Docker and managed-host capacity. The raw
6
+ * OS/daemon sample already reflects resident active children, so only pending
7
+ * commitments are charged against sampled headroom. Ambient host use must not
8
+ * also be folded into committedMemoryBytes: it is already reflected in the OS
9
+ * sample, and doing both permanently double-charges long-lived host processes.
10
+ * The total-memory ceiling independently caps Forge's own reservations.
11
+ */
12
+ export function buildMemoryAdmissionDiagnostics(inputs) {
13
+ const totalMemoryBytes = nonNegativeInteger(inputs.totalMemoryBytes);
14
+ const rawAvailableMemoryBytes = nonNegativeInteger(inputs.rawAvailableMemoryBytes);
15
+ const stabilizedAvailableMemoryBytes = nonNegativeInteger(inputs.stabilizedAvailableMemoryBytes);
16
+ const reserveMemoryBytes = nonNegativeInteger(inputs.reserveMemoryBytes);
17
+ const committedMemoryBytes = nonNegativeInteger(inputs.committedMemoryBytes);
18
+ const pendingCommitmentMemoryBytes = Math.min(committedMemoryBytes, nonNegativeInteger(inputs.pendingCommitmentMemoryBytes));
19
+ const requestedMemoryBytes = inputs.requestedMemoryBytes == null
20
+ ? null
21
+ : nonNegativeInteger(inputs.requestedMemoryBytes);
22
+ const availableForAdmissionBytes = Math.max(0, Math.min(stabilizedAvailableMemoryBytes - reserveMemoryBytes - pendingCommitmentMemoryBytes, totalMemoryBytes - reserveMemoryBytes - committedMemoryBytes));
23
+ const shortfallMemoryBytes = requestedMemoryBytes === null
24
+ ? null
25
+ : Math.max(0, requestedMemoryBytes - availableForAdmissionBytes);
26
+ return {
27
+ policyVersion: memoryAdmissionPolicyVersion,
28
+ sampledAt: inputs.sampledAt,
29
+ totalMemoryBytes,
30
+ rawAvailableMemoryBytes,
31
+ stabilizedAvailableMemoryBytes,
32
+ reclaimableMemoryBytes: null,
33
+ reclaimableMemorySource: "not_sampled_separately",
34
+ controlPlaneMemoryBytes: nonNegativeInteger(inputs.controlPlaneMemoryBytes),
35
+ reserveMemoryBytes,
36
+ committedMemoryBytes,
37
+ pendingCommitmentMemoryBytes,
38
+ availableForAdmissionBytes,
39
+ requestedMemoryBytes,
40
+ shortfallMemoryBytes,
41
+ decision: requestedMemoryBytes === null
42
+ ? "snapshot"
43
+ : shortfallMemoryBytes === 0 ? "admitted" : "denied",
44
+ equation: memoryAdmissionEquation,
45
+ };
46
+ }
47
+ /**
48
+ * Derives a host safety reserve from the capacity domain it protects. An
49
+ * explicit operator value remains authoritative; otherwise the reserve scales
50
+ * with the sampled total inside conservative floors and ceilings.
51
+ */
52
+ export function resolveAdaptiveMemoryReserveBytes(totalMemoryBytes, options = {}) {
53
+ const explicit = positiveInteger(options.explicitBytes);
54
+ if (explicit !== null)
55
+ return explicit;
56
+ const total = Math.max(0, finiteInteger(totalMemoryBytes));
57
+ const floor = positiveInteger(options.floorBytes) ?? GiB;
58
+ const ceiling = Math.max(floor, positiveInteger(options.ceilingBytes) ?? 4 * GiB);
59
+ const percent = positiveNumber(options.percent) ?? 12.5;
60
+ const proportional = Math.floor(total * Math.min(50, percent) / 100);
61
+ return Math.max(floor, Math.min(ceiling, proportional));
62
+ }
63
+ /**
64
+ * Applies pressure immediately while requiring durable recovery samples before
65
+ * increasing advertised capacity. This prevents one GC/cache sample from
66
+ * waking queued work and then immediately withdrawing the same capacity.
67
+ */
68
+ export class RecoveryStabilizedMemory {
69
+ requiredRecoverySamples;
70
+ minimumRecoverySampleIntervalMs;
71
+ stableBytes = null;
72
+ recoveryFloorBytes = null;
73
+ recoverySamples = 0;
74
+ lastRecoverySampleAt = null;
75
+ constructor(requiredRecoverySamples = 2, minimumRecoverySampleIntervalMs = 1_000) {
76
+ this.requiredRecoverySamples = requiredRecoverySamples;
77
+ this.minimumRecoverySampleIntervalMs = minimumRecoverySampleIntervalMs;
78
+ }
79
+ observe(rawAvailableBytes, sampledAt = Date.now()) {
80
+ const observed = Math.max(0, finiteInteger(rawAvailableBytes));
81
+ if (this.stableBytes === null) {
82
+ this.stableBytes = observed;
83
+ return observed;
84
+ }
85
+ if (observed <= this.stableBytes) {
86
+ this.stableBytes = observed;
87
+ this.resetRecovery();
88
+ return observed;
89
+ }
90
+ this.recoveryFloorBytes = this.recoveryFloorBytes === null
91
+ ? observed
92
+ : Math.min(this.recoveryFloorBytes, observed);
93
+ if (this.lastRecoverySampleAt !== null
94
+ && sampledAt - this.lastRecoverySampleAt < Math.max(0, this.minimumRecoverySampleIntervalMs)) {
95
+ return this.stableBytes;
96
+ }
97
+ this.recoverySamples += 1;
98
+ this.lastRecoverySampleAt = sampledAt;
99
+ if (this.recoverySamples < Math.max(1, this.requiredRecoverySamples)) {
100
+ return this.stableBytes;
101
+ }
102
+ this.stableBytes = this.recoveryFloorBytes ?? this.stableBytes;
103
+ this.resetRecovery();
104
+ return this.stableBytes;
105
+ }
106
+ resetRecovery() {
107
+ this.recoveryFloorBytes = null;
108
+ this.recoverySamples = 0;
109
+ this.lastRecoverySampleAt = null;
110
+ }
111
+ }
112
+ function finiteInteger(value) {
113
+ return Number.isFinite(value) ? Math.floor(value) : 0;
114
+ }
115
+ function positiveInteger(value) {
116
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : null;
117
+ }
118
+ function positiveNumber(value) {
119
+ return Number.isFinite(value) && value > 0 ? value : null;
120
+ }
121
+ function nonNegativeInteger(value) {
122
+ return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
123
+ }
@@ -0,0 +1,25 @@
1
+ export declare const nativeTestPostgresFallbackConfigEnvironment = "FORGE_NATIVE_TEST_POSTGRES_CONFIG";
2
+ export declare const nativeTestPostgresFallbackFileName = "native-test-postgres.json";
3
+ export declare const nativeTestPostgresFallbackDatabaseMode = "native_fallback";
4
+ /**
5
+ * A dedicated, operator-provisioned PostgreSQL cluster that runner-authored .NET
6
+ * test tiers may use only while the Docker engine is unavailable. The cluster
7
+ * must hold nothing but disposable test databases, so its capability carries no
8
+ * more authority than the attempt-private container it replaces.
9
+ */
10
+ export interface NativeTestPostgresFallback {
11
+ readonly connectionString: string;
12
+ readonly endpoint: string;
13
+ }
14
+ export declare class NativeTestPostgresFallbackError extends Error {
15
+ readonly code = "native_test_postgres_fallback_invalid";
16
+ constructor(detail: string);
17
+ }
18
+ export declare function resolveNativeTestPostgresFallbackPath(): string;
19
+ /** The operator's fallback file when one has been provisioned on this host. */
20
+ export declare function configuredNativeTestPostgresFallbackPath(): string | null;
21
+ /** Returns null when no fallback is configured; throws when one is configured unsafely. */
22
+ export declare function readNativeTestPostgresFallback(configPath?: string): NativeTestPostgresFallback | null;
23
+ export declare function parseNativeTestPostgresFallback(connectionString: string): NativeTestPostgresFallback;
24
+ /** Environment for one runner-authored .NET test process using the fallback. */
25
+ export declare function nativeTestPostgresFallbackEnvironment(fallback: NativeTestPostgresFallback): Readonly<Record<string, string>>;
@@ -0,0 +1,91 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveLocalConfigPath } from "../cli/local-config.js";
4
+ import { hostRuntimeLegacyTestPostgresEnvironment, hostRuntimeTestPostgresAdminEnvironment, } from "./host-runtime-contract.js";
5
+ export const nativeTestPostgresFallbackConfigEnvironment = "FORGE_NATIVE_TEST_POSTGRES_CONFIG";
6
+ export const nativeTestPostgresFallbackFileName = "native-test-postgres.json";
7
+ export const nativeTestPostgresFallbackDatabaseMode = "native_fallback";
8
+ const defaultPostgresPort = 5432;
9
+ const loopbackHosts = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
10
+ export class NativeTestPostgresFallbackError extends Error {
11
+ code = "native_test_postgres_fallback_invalid";
12
+ constructor(detail) {
13
+ super(`native_test_postgres_fallback_invalid: ${detail}`);
14
+ this.name = "NativeTestPostgresFallbackError";
15
+ }
16
+ }
17
+ export function resolveNativeTestPostgresFallbackPath() {
18
+ const configured = process.env[nativeTestPostgresFallbackConfigEnvironment]?.trim();
19
+ return configured
20
+ ? path.resolve(configured)
21
+ : path.join(path.dirname(resolveLocalConfigPath()), nativeTestPostgresFallbackFileName);
22
+ }
23
+ /** The operator's fallback file when one has been provisioned on this host. */
24
+ export function configuredNativeTestPostgresFallbackPath() {
25
+ const configPath = resolveNativeTestPostgresFallbackPath();
26
+ return fs.existsSync(configPath) ? configPath : null;
27
+ }
28
+ /** Returns null when no fallback is configured; throws when one is configured unsafely. */
29
+ export function readNativeTestPostgresFallback(configPath = resolveNativeTestPostgresFallbackPath()) {
30
+ let raw;
31
+ try {
32
+ raw = fs.readFileSync(configPath, "utf8");
33
+ }
34
+ catch (error) {
35
+ if (error.code === "ENOENT")
36
+ return null;
37
+ throw error;
38
+ }
39
+ let document;
40
+ try {
41
+ document = JSON.parse(raw);
42
+ }
43
+ catch {
44
+ throw new NativeTestPostgresFallbackError(`${configPath} is not valid JSON.`);
45
+ }
46
+ if (document.schemaVersion !== 1 || typeof document.connectionString !== "string") {
47
+ throw new NativeTestPostgresFallbackError(`${configPath} must contain schemaVersion 1 and a connectionString.`);
48
+ }
49
+ return parseNativeTestPostgresFallback(document.connectionString);
50
+ }
51
+ export function parseNativeTestPostgresFallback(connectionString) {
52
+ const fields = new Map();
53
+ for (const segment of connectionString.split(";")) {
54
+ const separator = segment.indexOf("=");
55
+ if (separator <= 0)
56
+ continue;
57
+ fields.set(segment.slice(0, separator).trim().toLowerCase().replaceAll(" ", ""), segment.slice(separator + 1).trim());
58
+ }
59
+ const host = (fields.get("host") ?? fields.get("server") ?? "").toLowerCase();
60
+ if (!loopbackHosts.has(host)) {
61
+ throw new NativeTestPostgresFallbackError("the fallback must use a single loopback host.");
62
+ }
63
+ const database = (fields.get("database") ?? fields.get("db") ?? fields.get("initialcatalog") ?? "").toLowerCase();
64
+ if (database !== "postgres") {
65
+ throw new NativeTestPostgresFallbackError("the fallback must connect to the 'postgres' administrative database.");
66
+ }
67
+ if (fields.get("searchpath")) {
68
+ throw new NativeTestPostgresFallbackError("the fallback must not set a search path.");
69
+ }
70
+ const port = Number.parseInt(fields.get("port") ?? "", 10);
71
+ if (!Number.isInteger(port) || port <= 0 || port > 65_535) {
72
+ throw new NativeTestPostgresFallbackError("the fallback must name an explicit port.");
73
+ }
74
+ if (port === defaultPostgresPort) {
75
+ // The default port is where developer databases live; a dedicated test
76
+ // cluster must be provisioned on its own port.
77
+ throw new NativeTestPostgresFallbackError(`the fallback must not use the default PostgreSQL port ${defaultPostgresPort}; provision a dedicated test cluster.`);
78
+ }
79
+ if (!(fields.get("username") ?? fields.get("userid") ?? fields.get("user"))) {
80
+ throw new NativeTestPostgresFallbackError("the fallback must name a dedicated test role.");
81
+ }
82
+ return { connectionString: connectionString.trim(), endpoint: `${host}:${port}` };
83
+ }
84
+ /** Environment for one runner-authored .NET test process using the fallback. */
85
+ export function nativeTestPostgresFallbackEnvironment(fallback) {
86
+ return {
87
+ [hostRuntimeTestPostgresAdminEnvironment]: fallback.connectionString,
88
+ [hostRuntimeLegacyTestPostgresEnvironment]: fallback.connectionString,
89
+ FORGE_MANAGED_VERIFICATION_DATABASE_MODE: nativeTestPostgresFallbackDatabaseMode,
90
+ };
91
+ }
@@ -0,0 +1,54 @@
1
+ import { HostPortBroker, type HostPortLaunch, type HostPortLeaseOwner, type HostPortReservation } from "./host-port-broker.js";
2
+ import { type HostRuntimePortAdapter, type HostRuntimePortConfig } from "./host-runtime-contract.js";
3
+ export type HostListenerOwnership = "unbound" | "owned" | "foreign";
4
+ export interface NodeRuntimeCommand {
5
+ readonly command: string;
6
+ readonly args: readonly string[];
7
+ readonly cwd: string;
8
+ readonly environment?: Readonly<Record<string, string>>;
9
+ }
10
+ export interface NodeRuntimeLaunchRequest extends NodeRuntimeCommand {
11
+ readonly framework: HostRuntimePortAdapter;
12
+ readonly port: HostPortReservation;
13
+ readonly baseUrl: string;
14
+ }
15
+ export interface NodeRuntimeCommandLauncher<T> {
16
+ readonly launch: (request: NodeRuntimeLaunchRequest, signal?: AbortSignal) => Promise<T>;
17
+ readonly inspectListener: (value: T, request: NodeRuntimeLaunchRequest, signal?: AbortSignal) => Promise<HostListenerOwnership>;
18
+ readonly readinessDiagnostics?: (value: T) => Promise<NodeRuntimeReadinessDiagnostics>;
19
+ readonly stop: (value: T) => Promise<void>;
20
+ }
21
+ export interface NodeRuntimeReadinessDiagnostics {
22
+ readonly exited: boolean;
23
+ readonly exitCode?: number;
24
+ readonly stdoutTail?: string;
25
+ readonly stderrTail?: string;
26
+ readonly outputRevision?: number;
27
+ }
28
+ export interface NodeHostRuntimeAdapterOptions {
29
+ readonly readinessTimeoutMs?: number;
30
+ readonly hardReadinessTimeoutMs?: number;
31
+ readonly readinessPollIntervalMs?: number;
32
+ readonly httpProbeTimeoutMs?: number;
33
+ readonly probeHttp?: (url: string, timeoutMs: number, signal?: AbortSignal) => Promise<boolean>;
34
+ }
35
+ export interface NodeRuntimeAdapterLaunch<T> extends HostPortLaunch<T> {
36
+ readonly request: NodeRuntimeLaunchRequest;
37
+ }
38
+ /**
39
+ * Launches an explicitly configured HTTP project command beneath the caller's
40
+ * attempt supervisor. It never shells a command and never promotes a project
41
+ * by framework detection alone.
42
+ */
43
+ export declare class NodeHostRuntimeAdapter {
44
+ private readonly broker;
45
+ private readonly readinessTimeoutMs;
46
+ private readonly hardReadinessTimeoutMs;
47
+ private readonly readinessPollIntervalMs;
48
+ private readonly httpProbeTimeoutMs;
49
+ private readonly probeHttp;
50
+ constructor(broker: HostPortBroker, options?: NodeHostRuntimeAdapterOptions);
51
+ launch<T>(owner: HostPortLeaseOwner, portConfig: HostRuntimePortConfig, command: NodeRuntimeCommand, launcher: NodeRuntimeCommandLauncher<T>, signal?: AbortSignal): Promise<NodeRuntimeAdapterLaunch<T>>;
52
+ private waitUntilReady;
53
+ }
54
+ export declare function buildNodeRuntimeLaunchRequest(framework: HostRuntimePortAdapter, portConfig: HostRuntimePortConfig, command: NodeRuntimeCommand, port: HostPortReservation): NodeRuntimeLaunchRequest;