@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.04a1e5b9e484
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -1
- package/dist/bridge/bridge-chat.types.d.ts +111 -0
- package/dist/bridge/bridge-chat.types.js +401 -0
- package/dist/bridge/bridge.base.d.ts +9 -1
- package/dist/bridge/bridge.service.d.ts +129 -3
- package/dist/bridge/bridge.service.js +2427 -70
- package/dist/bridge/bridge.types.d.ts +218 -2
- package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
- package/dist/bridge/dispatch-result-outbox.js +62 -0
- package/dist/bridge/dispatch-result-worker.d.ts +2 -0
- package/dist/bridge/dispatch-result-worker.js +122 -0
- package/dist/bridge/internals.d.ts +2 -0
- package/dist/bridge/internals.js +1 -0
- package/dist/bridge/readiness-result-outbox.d.ts +21 -0
- package/dist/bridge/readiness-result-outbox.js +54 -0
- package/dist/bridge/readiness-result-worker.d.ts +2 -0
- package/dist/bridge/readiness-result-worker.js +121 -0
- package/dist/bridge/runner-host-capabilities.d.ts +15 -0
- package/dist/bridge/runner-host-capabilities.js +93 -0
- package/dist/cleanup/cleanup.service.d.ts +14 -2
- package/dist/cleanup/cleanup.service.js +496 -3
- package/dist/cleanup/cleanup.types.d.ts +10 -0
- package/dist/cleanup/index.d.ts +1 -1
- package/dist/cleanup/index.js +1 -1
- package/dist/cleanup/internals.d.ts +2 -2
- package/dist/cleanup/internals.js +1 -1
- package/dist/cli/cli.help.d.ts +2 -0
- package/dist/cli/cli.help.js +217 -0
- package/dist/cli/cli.service.d.ts +55 -7
- package/dist/cli/cli.service.js +429 -219
- package/dist/cli/local-config.d.ts +7 -0
- package/dist/cli/local-config.js +51 -3
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
- package/dist/runtime/attempt-database-broker.d.ts +89 -0
- package/dist/runtime/attempt-database-broker.js +390 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +18 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
- package/dist/runtime/attempt-path-operation.d.ts +21 -0
- package/dist/runtime/attempt-path-operation.js +94 -0
- package/dist/runtime/attempt-private-directory.d.ts +6 -0
- package/dist/runtime/attempt-private-directory.js +54 -0
- package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
- package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +102 -0
- package/dist/runtime/attempt-process-supervisor.js +421 -0
- package/dist/runtime/canonical-source-file.d.ts +3 -0
- package/dist/runtime/canonical-source-file.js +54 -0
- package/dist/runtime/docker-engine-probe.d.ts +90 -0
- package/dist/runtime/docker-engine-probe.js +333 -0
- package/dist/runtime/generated-output-reset.d.ts +20 -0
- package/dist/runtime/generated-output-reset.js +157 -0
- package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
- package/dist/runtime/host-database-capability-boundary.js +62 -0
- package/dist/runtime/host-diagnostics.d.ts +11 -0
- package/dist/runtime/host-diagnostics.js +192 -0
- package/dist/runtime/host-execution-root.d.ts +4 -0
- package/dist/runtime/host-execution-root.js +39 -0
- package/dist/runtime/host-listener-inspector.d.ts +51 -0
- package/dist/runtime/host-listener-inspector.js +385 -0
- package/dist/runtime/host-port-broker.d.ts +76 -0
- package/dist/runtime/host-port-broker.js +333 -0
- package/dist/runtime/host-runtime-contract.d.ts +58 -0
- package/dist/runtime/host-runtime-contract.js +813 -0
- package/dist/runtime/host-runtime-lease.d.ts +217 -0
- package/dist/runtime/host-runtime-lease.js +2487 -0
- package/dist/runtime/host-runtime-promotion.d.ts +32 -0
- package/dist/runtime/host-runtime-promotion.js +206 -0
- package/dist/runtime/memory-capacity.d.ts +70 -0
- package/dist/runtime/memory-capacity.js +123 -0
- package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
- package/dist/runtime/native-test-postgres-fallback.js +91 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +286 -0
- package/dist/runtime/pinned-directory-process.d.ts +9 -0
- package/dist/runtime/pinned-directory-process.js +61 -0
- package/dist/runtime/pinned-source-directory.d.ts +17 -0
- package/dist/runtime/pinned-source-directory.js +94 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
- package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
- package/dist/runtime/prepared-runtime-identity.js +218 -0
- package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
- package/dist/runtime/prepared-runtime-readiness.js +285 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +161 -0
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/command-runner.d.ts +4 -0
- package/dist/sandbox/command-runner.js +47 -0
- package/dist/sandbox/contract.d.ts +5 -0
- package/dist/sandbox/contract.js +276 -0
- package/dist/sandbox/event-budget.d.ts +11 -0
- package/dist/sandbox/event-budget.js +27 -0
- package/dist/sandbox/image-policy.d.ts +35 -0
- package/dist/sandbox/image-policy.js +247 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/lease-watchdog.d.ts +18 -0
- package/dist/sandbox/lease-watchdog.js +67 -0
- package/dist/sandbox/preflight.d.ts +84 -0
- package/dist/sandbox/preflight.js +521 -0
- package/dist/sandbox/process.d.ts +20 -0
- package/dist/sandbox/process.js +209 -0
- package/dist/sandbox/provider-auth/codex.d.ts +26 -0
- package/dist/sandbox/provider-auth/codex.js +212 -0
- package/dist/sandbox/repository-lock.d.ts +6 -0
- package/dist/sandbox/repository-lock.js +129 -0
- package/dist/sandbox/runtime.d.ts +302 -0
- package/dist/sandbox/runtime.js +2368 -0
- package/dist/sandbox/sandbox.types.d.ts +93 -0
- package/dist/sandbox/sandbox.types.js +1 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.d.ts +13 -0
- package/dist/sandbox/worker-supervisor.js +36 -0
- package/dist/sandbox/worker.d.ts +1 -0
- package/dist/sandbox/worker.js +225 -0
- package/dist/sandbox/workspace.service.d.ts +13 -0
- package/dist/sandbox/workspace.service.js +74 -0
- package/dist/session/managed-attempt-tools.d.ts +28 -0
- package/dist/session/managed-attempt-tools.js +1016 -0
- package/dist/session/managed-build-coordinator.d.ts +5 -0
- package/dist/session/managed-build-coordinator.js +106 -0
- package/dist/session/managed-build-receipt.d.ts +21 -0
- package/dist/session/managed-build-receipt.js +337 -0
- package/dist/session/managed-verification-timeouts.d.ts +8 -0
- package/dist/session/managed-verification-timeouts.js +17 -0
- package/dist/session/phase-completion.artifacts.d.ts +27 -0
- package/dist/session/phase-completion.artifacts.js +829 -0
- package/dist/session/phase-completion.d.ts +53 -42
- package/dist/session/phase-completion.helpers.d.ts +117 -0
- package/dist/session/phase-completion.helpers.js +707 -0
- package/dist/session/phase-completion.js +125 -3007
- package/dist/session/phase-completion.prompts.d.ts +14 -0
- package/dist/session/phase-completion.prompts.js +233 -0
- package/dist/session/phase-completion.runtime.d.ts +49 -0
- package/dist/session/phase-completion.runtime.js +840 -0
- package/dist/session/phase-completion.schemas.d.ts +3 -0
- package/dist/session/phase-completion.schemas.js +859 -0
- package/dist/session/phase-completion.validation.d.ts +38 -0
- package/dist/session/phase-completion.validation.js +918 -0
- package/dist/session/pre-publication-validation.d.ts +75 -0
- package/dist/session/pre-publication-validation.js +661 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
- package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
- package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
- package/dist/session/provider-cli/provider-cli.process.js +874 -0
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +22 -0
- package/dist/session/provider-cli/provider-cli.runtime.js +92 -0
- package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
- package/dist/session/provider-cli/provider-cli.service.js +1258 -2497
- package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
- package/dist/session/provider-cli/provider-output-state.js +817 -0
- package/dist/session/provider-cli/provider-usage.d.ts +7 -0
- package/dist/session/provider-cli/provider-usage.js +28 -5
- package/dist/session/session-redaction.d.ts +3 -0
- package/dist/session/session-redaction.js +448 -0
- package/dist/session/session.base.d.ts +6 -1
- package/dist/session/session.service.d.ts +8 -2
- package/dist/session/session.service.js +586 -59
- package/dist/session/session.types.d.ts +324 -3
- package/dist/session/session.types.js +939 -10
- package/dist/session/unreferenced-exports.d.ts +12 -0
- package/dist/session/unreferenced-exports.js +59 -0
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/git/git.js +2 -1
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +21 -1
- package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
- package/dist/shared/process/dotnet-toolchain.js +307 -0
- package/dist/shared/process/host-process-recovery.d.ts +12 -0
- package/dist/shared/process/host-process-recovery.js +79 -0
- package/dist/shared/process/host-process-supervisor.d.ts +59 -0
- package/dist/shared/process/host-process-supervisor.js +331 -0
- package/dist/shared/process/process.d.ts +3 -0
- package/dist/shared/process/process.js +13 -7
- package/dist/shared/process/windows-launch.d.ts +8 -0
- package/dist/shared/process/windows-launch.js +41 -0
- package/dist/shared/provider-capabilities.d.ts +4 -0
- package/dist/shared/provider-capabilities.js +88 -0
- package/dist/shared/redaction/redaction.d.ts +1 -0
- package/dist/shared/redaction/redaction.js +47 -4
- package/dist/shared/time/monotonic.d.ts +1 -0
- package/dist/shared/time/monotonic.js +8 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +3 -2
- package/dist/worktree/artifact-injector.js +82 -10
- package/dist/worktree/dependency-warmer.d.ts +53 -1
- package/dist/worktree/dependency-warmer.js +619 -11
- package/dist/worktree/index.d.ts +2 -1
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
- package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
- package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
- package/dist/worktree/story-qa-reference-hydrator.js +253 -0
- package/dist/worktree/worktree-paths.d.ts +4 -0
- package/dist/worktree/worktree-paths.js +30 -0
- package/dist/worktree/worktree.base.d.ts +3 -0
- package/dist/worktree/worktree.service.d.ts +16 -3
- package/dist/worktree/worktree.service.js +334 -54
- package/dist/worktree/worktree.types.d.ts +9 -1
- package/package.json +12 -7
- package/runtime/windows-job-supervisor.ps1 +722 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { assertCanonicalDirectory, createPrivateCanonicalDirectory } from "./attempt-private-directory.js";
|
|
6
|
+
import { readCanonicalSourceFile } from "./canonical-source-file.js";
|
|
7
|
+
import { runAttemptPathOperation } from "./attempt-path-operation.js";
|
|
8
|
+
const maximumConfigBytes = 1024 * 1024;
|
|
9
|
+
/**
|
|
10
|
+
* Prepares a finite Playwright test boundary for a managed-host attempt.
|
|
11
|
+
* The generated overlay points tests at Forge's verified listener, disables
|
|
12
|
+
* Playwright-managed web servers, and keeps writable artifacts attempt-local.
|
|
13
|
+
*/
|
|
14
|
+
export class PlaywrightHostRuntimeAdapter {
|
|
15
|
+
async prepare(config, context) {
|
|
16
|
+
const worktreePath = path.resolve(context.worktreePath);
|
|
17
|
+
const sourceConfig = path.resolve(worktreePath, config.config);
|
|
18
|
+
const [realWorktree, realSource] = await Promise.all([
|
|
19
|
+
fs.realpath(worktreePath),
|
|
20
|
+
fs.realpath(sourceConfig),
|
|
21
|
+
]);
|
|
22
|
+
assertContained(realWorktree, realSource, "project Playwright config");
|
|
23
|
+
const sourceBytes = await readCanonicalSourceFile(sourceConfig, maximumConfigBytes);
|
|
24
|
+
if (sourceBytes.length < 1) {
|
|
25
|
+
throw new Error(`playwright_runtime_adapter_invalid: project config must be a regular 1-${maximumConfigBytes} byte file.`);
|
|
26
|
+
}
|
|
27
|
+
const sourceConfigText = sourceBytes.toString("utf8");
|
|
28
|
+
const projectNames = [...sourceConfigText.matchAll(/\bname\s*:\s*["']([^"']+)["']/gu)]
|
|
29
|
+
.map(match => match[1])
|
|
30
|
+
.filter((name, index, names) => names.indexOf(name) === index);
|
|
31
|
+
await ensureAttemptWritableDirectories([
|
|
32
|
+
context.temporaryDirectory,
|
|
33
|
+
context.testOutputDirectory,
|
|
34
|
+
context.browserProfileDirectory,
|
|
35
|
+
], realWorktree);
|
|
36
|
+
const temporaryDirectory = await realContainedDirectory(context.temporaryDirectory, realWorktree, "temporary directory");
|
|
37
|
+
const testOutputDirectory = await realContainedDirectory(context.testOutputDirectory, realWorktree, "test output directory");
|
|
38
|
+
const browserProfileDirectory = await realContainedDirectory(context.browserProfileDirectory, realWorktree, "browser profile directory");
|
|
39
|
+
const attemptRoot = path.dirname(temporaryDirectory);
|
|
40
|
+
if (path.dirname(testOutputDirectory) !== attemptRoot || path.dirname(browserProfileDirectory) !== attemptRoot) {
|
|
41
|
+
throw new Error("playwright_runtime_adapter_invalid: writable directories must belong to the same attempt root.");
|
|
42
|
+
}
|
|
43
|
+
const baseUrl = loopbackHttpUrl(context.baseUrl);
|
|
44
|
+
if (!/^[0-9a-f]{32}$/u.test(context.ownershipToken)) {
|
|
45
|
+
throw new Error("playwright_runtime_adapter_invalid: ownership token must be a normalized dispatch attempt UUID.");
|
|
46
|
+
}
|
|
47
|
+
const overlayPath = path.join(temporaryDirectory, "forge.playwright.config.mjs");
|
|
48
|
+
const browserCache = await resolvePlaywrightBrowserCache(attemptRoot);
|
|
49
|
+
const browserInstallCommand = await resolvePlaywrightBrowserInstallCommand(context.worktreePath, path.dirname(realSource));
|
|
50
|
+
await writeAttemptOverlay(overlayPath, renderOverlay({
|
|
51
|
+
projectConfigUrl: pathToFileURL(realSource).href,
|
|
52
|
+
projectConfigDirectory: path.dirname(realSource),
|
|
53
|
+
testOutputDirectory,
|
|
54
|
+
baseUrl,
|
|
55
|
+
workers: config.workers,
|
|
56
|
+
projectNames,
|
|
57
|
+
}));
|
|
58
|
+
return {
|
|
59
|
+
configPath: overlayPath,
|
|
60
|
+
environment: {
|
|
61
|
+
FORGE_PLAYWRIGHT_CONFIG: overlayPath,
|
|
62
|
+
FORGE_PLAYWRIGHT_BASE_URL: baseUrl,
|
|
63
|
+
FORGE_E2E_WEB_URL: baseUrl,
|
|
64
|
+
FORGE_E2E_OWNERSHIP_MODE: "attempt",
|
|
65
|
+
FORGE_E2E_OWNERSHIP_TOKEN: context.ownershipToken,
|
|
66
|
+
FORGE_PLAYWRIGHT_BROWSER_READY: browserCache.ready ? "true" : "false",
|
|
67
|
+
FORGE_PLAYWRIGHT_BROWSER_INSTALL_COMMAND: browserInstallCommand,
|
|
68
|
+
PLAYWRIGHT_BROWSERS_PATH: browserCache.path,
|
|
69
|
+
PLAYWRIGHT_HTML_OUTPUT_DIR: path.join(testOutputDirectory, "html-report"),
|
|
70
|
+
PLAYWRIGHT_BLOB_OUTPUT_DIR: path.join(testOutputDirectory, "blob-report"),
|
|
71
|
+
PLAYWRIGHT_JUNIT_OUTPUT_NAME: path.join(testOutputDirectory, "results.xml"),
|
|
72
|
+
PLAYWRIGHT_JSON_OUTPUT_NAME: path.join(testOutputDirectory, "results.json"),
|
|
73
|
+
FORGE_BROWSER_PROFILE_DIR: browserProfileDirectory,
|
|
74
|
+
FORGE_TEST_OUTPUT_DIR: testOutputDirectory,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function ensureAttemptWritableDirectories(targets, realWorktree) {
|
|
80
|
+
const resolvedTargets = targets.map(target => path.resolve(target));
|
|
81
|
+
const attemptRoots = new Set(resolvedTargets.map(target => path.dirname(target)));
|
|
82
|
+
if (attemptRoots.size !== 1) {
|
|
83
|
+
throw new Error("playwright_runtime_adapter_invalid: writable directories must belong to the same attempt root.");
|
|
84
|
+
}
|
|
85
|
+
const attemptRoot = [...attemptRoots][0];
|
|
86
|
+
if (!attemptRoot || attemptRoot === realWorktree || isContained(realWorktree, attemptRoot)) {
|
|
87
|
+
throw new Error("playwright_runtime_adapter_invalid: writable directories must be attempt-private and outside the worktree.");
|
|
88
|
+
}
|
|
89
|
+
await assertCanonicalDirectory(attemptRoot, true);
|
|
90
|
+
for (const target of resolvedTargets) {
|
|
91
|
+
if (target === realWorktree || isContained(realWorktree, target)) {
|
|
92
|
+
throw new Error("playwright_runtime_adapter_invalid: writable directories must be attempt-private and outside the worktree.");
|
|
93
|
+
}
|
|
94
|
+
await assertCanonicalDirectory(target, true);
|
|
95
|
+
}
|
|
96
|
+
await createPrivateCanonicalDirectory(attemptRoot);
|
|
97
|
+
for (const target of resolvedTargets)
|
|
98
|
+
await createPrivateCanonicalDirectory(target);
|
|
99
|
+
}
|
|
100
|
+
async function writeAttemptOverlay(target, content) {
|
|
101
|
+
const parent = path.dirname(target);
|
|
102
|
+
const identity = await assertCanonicalDirectory(parent);
|
|
103
|
+
if (!identity)
|
|
104
|
+
throw new Error("playwright_runtime_adapter_invalid: overlay parent is missing.");
|
|
105
|
+
await runAttemptPathOperation(parent, identity, { kind: "overlay", name: path.basename(target), content });
|
|
106
|
+
}
|
|
107
|
+
async function resolvePlaywrightBrowserInstallCommand(worktreePath, configDirectory) {
|
|
108
|
+
const relativeDirectory = path.relative(worktreePath, configDirectory).replaceAll(path.sep, "/") || ".";
|
|
109
|
+
if (await isFile(path.join(configDirectory, "pnpm-lock.yaml"))) {
|
|
110
|
+
return `pnpm --dir ${quoteCommandArgument(relativeDirectory)} exec playwright install chromium`;
|
|
111
|
+
}
|
|
112
|
+
if (await isFile(path.join(configDirectory, "yarn.lock"))) {
|
|
113
|
+
return `yarn --cwd ${quoteCommandArgument(relativeDirectory)} playwright install chromium`;
|
|
114
|
+
}
|
|
115
|
+
return `npm --prefix ${quoteCommandArgument(relativeDirectory)} exec playwright install chromium`;
|
|
116
|
+
}
|
|
117
|
+
async function isFile(candidate) {
|
|
118
|
+
try {
|
|
119
|
+
return (await fs.stat(candidate)).isFile();
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function quoteCommandArgument(value) {
|
|
126
|
+
return /[\s"']/u.test(value) ? JSON.stringify(value) : value;
|
|
127
|
+
}
|
|
128
|
+
async function resolvePlaywrightBrowserCache(attemptRoot) {
|
|
129
|
+
const candidates = [
|
|
130
|
+
process.env.PLAYWRIGHT_BROWSERS_PATH,
|
|
131
|
+
process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, "ms-playwright") : null,
|
|
132
|
+
path.join(os.homedir(), ".cache", "ms-playwright"),
|
|
133
|
+
].filter((candidate) => Boolean(candidate?.trim()));
|
|
134
|
+
for (const candidate of [...new Set(candidates.map(candidate => path.resolve(candidate)))]) {
|
|
135
|
+
if (await hasChromiumExecutable(candidate)) {
|
|
136
|
+
return { path: candidate, ready: true };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const attemptCache = path.join(attemptRoot, "cache", "ms-playwright");
|
|
140
|
+
await createPrivateCanonicalDirectory(attemptCache);
|
|
141
|
+
return { path: attemptCache, ready: false };
|
|
142
|
+
}
|
|
143
|
+
async function hasChromiumExecutable(cachePath) {
|
|
144
|
+
let entries;
|
|
145
|
+
try {
|
|
146
|
+
entries = await fs.readdir(cachePath, { withFileTypes: true });
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const executableSuffixes = process.platform === "win32"
|
|
152
|
+
? [path.join("chrome-win64", "chrome.exe"), path.join("chrome-win", "chrome.exe")]
|
|
153
|
+
: process.platform === "darwin"
|
|
154
|
+
? [path.join("chrome-mac", "Chromium.app", "Contents", "MacOS", "Chromium")]
|
|
155
|
+
: [path.join("chrome-linux", "chrome"), path.join("chrome-linux64", "chrome")];
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (!entry.isDirectory() || !entry.name.startsWith("chromium-"))
|
|
158
|
+
continue;
|
|
159
|
+
for (const suffix of executableSuffixes) {
|
|
160
|
+
try {
|
|
161
|
+
const stat = await fs.stat(path.join(cachePath, entry.name, suffix));
|
|
162
|
+
if (stat.isFile())
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Continue through known Playwright layouts.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
function renderOverlay(values) {
|
|
173
|
+
return [
|
|
174
|
+
"// Generated by forge-bridge for one fenced host attempt.",
|
|
175
|
+
`// forge-playwright-projects: ${JSON.stringify(values.projectNames)}`,
|
|
176
|
+
"import path from \"node:path\";",
|
|
177
|
+
`import projectConfig from ${JSON.stringify(values.projectConfigUrl)};`,
|
|
178
|
+
`const projectConfigDirectory = ${JSON.stringify(values.projectConfigDirectory)};`,
|
|
179
|
+
"const resolveConfigPath = value => typeof value === \"string\" && !path.isAbsolute(value)",
|
|
180
|
+
" ? path.resolve(projectConfigDirectory, value)",
|
|
181
|
+
" : value;",
|
|
182
|
+
"const resolveModulePath = value => typeof value === \"string\" && value.startsWith(\".\")",
|
|
183
|
+
" ? path.resolve(projectConfigDirectory, value)",
|
|
184
|
+
" : value;",
|
|
185
|
+
"const resolveUse = use => ({",
|
|
186
|
+
" ...(use ?? {}),",
|
|
187
|
+
" storageState: typeof use?.storageState === \"string\" ? resolveConfigPath(use.storageState) : use?.storageState,",
|
|
188
|
+
" baseURL: " + JSON.stringify(values.baseUrl) + ",",
|
|
189
|
+
"});",
|
|
190
|
+
"const forgeUse = resolveUse(projectConfig.use);",
|
|
191
|
+
"const forgeProjects = Array.isArray(projectConfig.projects)",
|
|
192
|
+
" ? projectConfig.projects.map(project => ({",
|
|
193
|
+
" ...project,",
|
|
194
|
+
" testDir: resolveConfigPath(project.testDir),",
|
|
195
|
+
" snapshotDir: resolveConfigPath(project.snapshotDir),",
|
|
196
|
+
" use: resolveUse(project.use),",
|
|
197
|
+
" }))",
|
|
198
|
+
" : projectConfig.projects;",
|
|
199
|
+
"const forgeReporter = Array.isArray(projectConfig.reporter)",
|
|
200
|
+
" ? projectConfig.reporter.map(reporter => Array.isArray(reporter)",
|
|
201
|
+
" ? [resolveModulePath(reporter[0]), ...reporter.slice(1)]",
|
|
202
|
+
" : resolveModulePath(reporter))",
|
|
203
|
+
" : resolveModulePath(projectConfig.reporter);",
|
|
204
|
+
"const forgeProviderOnlySpecs = (() => {",
|
|
205
|
+
" try {",
|
|
206
|
+
" const parsed = JSON.parse(process.env.FORGE_CI_REPAIR_PROVIDER_ONLY_SPECS ?? '[]');",
|
|
207
|
+
" return Array.isArray(parsed) ? parsed.filter(value => typeof value === 'string' && value.length > 0) : [];",
|
|
208
|
+
" } catch {",
|
|
209
|
+
" return [];",
|
|
210
|
+
" }",
|
|
211
|
+
"})();",
|
|
212
|
+
"const forgeTestIgnore = forgeProviderOnlySpecs.length === 0",
|
|
213
|
+
" ? projectConfig.testIgnore",
|
|
214
|
+
" : [...(Array.isArray(projectConfig.testIgnore) ? projectConfig.testIgnore : projectConfig.testIgnore ? [projectConfig.testIgnore] : []), ...forgeProviderOnlySpecs];",
|
|
215
|
+
"export default {",
|
|
216
|
+
" ...projectConfig,",
|
|
217
|
+
" testDir: resolveConfigPath(projectConfig.testDir),",
|
|
218
|
+
" snapshotDir: resolveConfigPath(projectConfig.snapshotDir),",
|
|
219
|
+
" globalSetup: resolveModulePath(projectConfig.globalSetup),",
|
|
220
|
+
" globalTeardown: resolveModulePath(projectConfig.globalTeardown),",
|
|
221
|
+
" reporter: forgeReporter,",
|
|
222
|
+
" testIgnore: forgeTestIgnore,",
|
|
223
|
+
` outputDir: ${JSON.stringify(values.testOutputDirectory)},`,
|
|
224
|
+
` workers: ${values.workers},`,
|
|
225
|
+
" webServer: undefined,",
|
|
226
|
+
" use: forgeUse,",
|
|
227
|
+
" projects: forgeProjects,",
|
|
228
|
+
"};",
|
|
229
|
+
"",
|
|
230
|
+
].join("\n");
|
|
231
|
+
}
|
|
232
|
+
async function realContainedDirectory(target, realWorktree, label) {
|
|
233
|
+
await assertCanonicalDirectory(target);
|
|
234
|
+
const realTarget = await fs.realpath(path.resolve(target));
|
|
235
|
+
if (realTarget === realWorktree || isContained(realWorktree, realTarget)) {
|
|
236
|
+
throw new Error(`playwright_runtime_adapter_invalid: ${label} must be attempt-private and outside the worktree.`);
|
|
237
|
+
}
|
|
238
|
+
const stat = await fs.stat(realTarget);
|
|
239
|
+
if (!stat.isDirectory())
|
|
240
|
+
throw new Error(`playwright_runtime_adapter_invalid: ${label} must be a directory.`);
|
|
241
|
+
return realTarget;
|
|
242
|
+
}
|
|
243
|
+
function assertContained(parent, child, label) {
|
|
244
|
+
if (!isContained(parent, child)) {
|
|
245
|
+
throw new Error(`playwright_runtime_adapter_invalid: ${label} must stay inside the prepared worktree.`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function isContained(parent, child) {
|
|
249
|
+
const relative = path.relative(parent, child);
|
|
250
|
+
return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
251
|
+
}
|
|
252
|
+
function loopbackHttpUrl(value) {
|
|
253
|
+
let url;
|
|
254
|
+
try {
|
|
255
|
+
url = new URL(value);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
throw new Error("playwright_runtime_adapter_invalid: base URL must be an HTTP loopback URL.");
|
|
259
|
+
}
|
|
260
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:")
|
|
261
|
+
|| url.hostname !== "127.0.0.1"
|
|
262
|
+
|| !url.port
|
|
263
|
+
|| url.username
|
|
264
|
+
|| url.password) {
|
|
265
|
+
throw new Error("playwright_runtime_adapter_invalid: base URL must be an HTTP loopback URL.");
|
|
266
|
+
}
|
|
267
|
+
return url.href.replace(/\/$/u, "");
|
|
268
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type HostParallelProofIdentity } from "./host-runtime-promotion.js";
|
|
2
|
+
export interface PreparedRuntimeIdentityDiagnostics {
|
|
3
|
+
readonly entries: Map<string, string>;
|
|
4
|
+
}
|
|
5
|
+
/** Bind evidence to the prepared source, including unstaged replacements, not just HEAD. */
|
|
6
|
+
export declare function preparedRuntimeIdentity(worktreePath: string, signal?: AbortSignal, diagnostics?: PreparedRuntimeIdentityDiagnostics): Promise<HostParallelProofIdentity>;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { GitClient } from "../shared/git/git.js";
|
|
5
|
+
import { hostRuntimeContractPath, loadHostRuntimeContract } from "./host-runtime-contract.js";
|
|
6
|
+
import { hostRuntimeContractFingerprint } from "./host-runtime-promotion.js";
|
|
7
|
+
import { readCanonicalSourceFile } from "./canonical-source-file.js";
|
|
8
|
+
import { assertCanonicalDirectory } from "./attempt-private-directory.js";
|
|
9
|
+
import { generatedOutputRootForPath } from "./generated-output-reset.js";
|
|
10
|
+
import { readPinnedSourceDirectory } from "./pinned-source-directory.js";
|
|
11
|
+
/** Bind evidence to the prepared source, including unstaged replacements, not just HEAD. */
|
|
12
|
+
export async function preparedRuntimeIdentity(worktreePath, signal, diagnostics) {
|
|
13
|
+
diagnostics?.entries.clear();
|
|
14
|
+
const root = await fs.realpath(worktreePath);
|
|
15
|
+
const rootIdentity = (await assertCanonicalDirectory(root));
|
|
16
|
+
const sourceKey = (target) => process.platform === "win32" ? target.toLowerCase() : target;
|
|
17
|
+
const expectedEntries = new Map();
|
|
18
|
+
const directories = new Map();
|
|
19
|
+
const containedRelative = (target) => {
|
|
20
|
+
const relative = path.relative(root, path.resolve(target));
|
|
21
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
22
|
+
throw new Error("host_runtime_source_invalid: source path escapes the worktree.");
|
|
23
|
+
}
|
|
24
|
+
return relative.replaceAll("\\", "/");
|
|
25
|
+
};
|
|
26
|
+
const directorySnapshot = async (target) => {
|
|
27
|
+
const resolved = path.resolve(target);
|
|
28
|
+
containedRelative(resolved);
|
|
29
|
+
const key = sourceKey(resolved);
|
|
30
|
+
let captured = directories.get(key);
|
|
31
|
+
if (!captured) {
|
|
32
|
+
captured = (async () => {
|
|
33
|
+
let identity;
|
|
34
|
+
if (key === sourceKey(root))
|
|
35
|
+
identity = rootIdentity;
|
|
36
|
+
else {
|
|
37
|
+
const parent = await expectedEntry(resolved);
|
|
38
|
+
if (!parent || parent.kind !== "directory") {
|
|
39
|
+
throw new Error("host_runtime_source_changed: discovery directory identity changed.");
|
|
40
|
+
}
|
|
41
|
+
identity = parent.identity;
|
|
42
|
+
}
|
|
43
|
+
const entries = await readPinnedSourceDirectory(resolved, identity);
|
|
44
|
+
for (const entry of entries)
|
|
45
|
+
expectedEntries.set(sourceKey(path.join(resolved, entry.name)), entry);
|
|
46
|
+
return { path: resolved, identity, entries };
|
|
47
|
+
})();
|
|
48
|
+
directories.set(key, captured);
|
|
49
|
+
}
|
|
50
|
+
return (await captured).entries.map(entry => ({
|
|
51
|
+
name: entry.name,
|
|
52
|
+
isDirectory: entry.kind === "directory",
|
|
53
|
+
isFile: entry.kind === "file",
|
|
54
|
+
}));
|
|
55
|
+
};
|
|
56
|
+
const expectedEntry = async (target) => {
|
|
57
|
+
const resolved = path.resolve(target);
|
|
58
|
+
containedRelative(resolved);
|
|
59
|
+
if (sourceKey(resolved) === sourceKey(root)) {
|
|
60
|
+
return { name: path.basename(root), kind: "directory", identity: rootIdentity, size: 0n };
|
|
61
|
+
}
|
|
62
|
+
const parentPath = path.dirname(resolved);
|
|
63
|
+
if (sourceKey(parentPath) !== sourceKey(root)) {
|
|
64
|
+
const parent = await expectedEntry(parentPath);
|
|
65
|
+
if (!parent)
|
|
66
|
+
return undefined;
|
|
67
|
+
if (parent.kind !== "directory") {
|
|
68
|
+
throw new Error("host_runtime_source_changed: discovery ancestor is not a directory.");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await directorySnapshot(parentPath);
|
|
72
|
+
return expectedEntries.get(sourceKey(resolved));
|
|
73
|
+
};
|
|
74
|
+
// Pin the worktree before Git or discovery uses it. Every child directory is
|
|
75
|
+
// subsequently reached through an identity captured from its pinned parent.
|
|
76
|
+
await directorySnapshot(root);
|
|
77
|
+
const git = new GitClient();
|
|
78
|
+
const head = (await git.run(root, ["rev-parse", "HEAD"])).trim();
|
|
79
|
+
const inventory = async () => {
|
|
80
|
+
const included = (name) => name && (!name.startsWith(".forge/") || name === ".forge/forge.runtime.yml");
|
|
81
|
+
// ls-files --deleted loses a deletion once staged. HEAD-relative tombstones
|
|
82
|
+
// keep the same prepared source identity before and after git add -u.
|
|
83
|
+
const deleted = [...new Set((await git.run(root, ["ls-files", "-z", "--deleted"]))
|
|
84
|
+
.split("\0").concat((await git.run(root, ["diff", "--name-only", "--diff-filter=D", "-z", "HEAD", "--"]))
|
|
85
|
+
.split("\0")).filter(included))].sort();
|
|
86
|
+
const tracked = (await git.run(root, ["ls-files", "-z", "--cached"]))
|
|
87
|
+
.split("\0").filter(included);
|
|
88
|
+
const untracked = (await git.run(root, ["ls-files", "-z", "--others", "--exclude-standard"]))
|
|
89
|
+
.split("\0").filter(included);
|
|
90
|
+
const generatedRoots = [...new Set(untracked
|
|
91
|
+
.map(generatedOutputRootForPath)
|
|
92
|
+
.filter((candidate) => candidate !== null))];
|
|
93
|
+
const comparisonKey = (value) => process.platform === "win32" ? value.toLowerCase() : value;
|
|
94
|
+
const containedBy = (name, candidate) => {
|
|
95
|
+
const key = comparisonKey(name);
|
|
96
|
+
const rootKey = comparisonKey(candidate);
|
|
97
|
+
return key === rootKey || key.startsWith(`${rootKey}/`);
|
|
98
|
+
};
|
|
99
|
+
// resetGeneratedOutputs only quarantines a generated directory when Git
|
|
100
|
+
// confirms it contains no tracked files. Mirror that boundary here so the
|
|
101
|
+
// cleanup performed by runtime.start cannot invalidate its own foreground
|
|
102
|
+
// proof, while tracked or mixed-content directories remain source-bound.
|
|
103
|
+
const resettableRoots = generatedRoots.filter(candidate => !tracked.some(name => containedBy(name, candidate)));
|
|
104
|
+
const names = [...new Set(tracked.concat(untracked.filter(name => !resettableRoots.some(candidate => containedBy(name, candidate))), deleted, hostRuntimeContractPath).filter(included))].sort();
|
|
105
|
+
return { names, deleted };
|
|
106
|
+
};
|
|
107
|
+
const source = await inventory();
|
|
108
|
+
const { names } = source;
|
|
109
|
+
if (names.length > 100_000)
|
|
110
|
+
throw new Error("host_runtime_source_invalid: source inventory exceeds the file limit.");
|
|
111
|
+
const deleted = new Set(source.deleted);
|
|
112
|
+
const hash = createHash("sha256");
|
|
113
|
+
const snapshot = new Map();
|
|
114
|
+
const additional = new Map();
|
|
115
|
+
let discoveryCapture = Promise.resolve();
|
|
116
|
+
let bytes = 0;
|
|
117
|
+
for (const name of names) {
|
|
118
|
+
signal?.throwIfAborted();
|
|
119
|
+
const target = path.resolve(root, name);
|
|
120
|
+
const relative = path.relative(root, target);
|
|
121
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
122
|
+
throw new Error("host_runtime_source_invalid: source path escapes the worktree.");
|
|
123
|
+
}
|
|
124
|
+
hash.update(JSON.stringify(name));
|
|
125
|
+
try {
|
|
126
|
+
const expected = await expectedEntry(target);
|
|
127
|
+
if (!expected || expected.kind !== "file") {
|
|
128
|
+
throw Object.assign(new Error("host_runtime_source_changed: source inventory no longer names an ordinary file."), { code: "ENOENT" });
|
|
129
|
+
}
|
|
130
|
+
const sourceBytes = await readCanonicalSourceFile(target, 256 * 1024 * 1024 - bytes, expected.identity);
|
|
131
|
+
bytes += sourceBytes.length;
|
|
132
|
+
const digest = createHash("sha256").update(sourceBytes).digest();
|
|
133
|
+
hash.update(digest);
|
|
134
|
+
diagnostics?.entries.set(name, digest.toString("hex"));
|
|
135
|
+
snapshot.set(sourceKey(target), Promise.resolve(sourceBytes));
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
if (error.code !== "ENOENT" || (!deleted.has(name) && name !== hostRuntimeContractPath))
|
|
139
|
+
throw error;
|
|
140
|
+
const disposition = deleted.has(name) ? "deleted" : "absent";
|
|
141
|
+
hash.update(disposition);
|
|
142
|
+
diagnostics?.entries.set(name, disposition);
|
|
143
|
+
snapshot.set(sourceKey(target), Promise.resolve(null));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const contract = await loadHostRuntimeContract(root, target => {
|
|
147
|
+
const key = sourceKey(path.resolve(target));
|
|
148
|
+
const captured = snapshot.get(key);
|
|
149
|
+
if (captured)
|
|
150
|
+
return captured;
|
|
151
|
+
// Discovery also consumes ignored lockfiles and launch metadata. Capture
|
|
152
|
+
// those once, too; omission from Git's inventory must not change discovery.
|
|
153
|
+
const capture = discoveryCapture.then(async () => {
|
|
154
|
+
signal?.throwIfAborted();
|
|
155
|
+
const relative = path.relative(root, target).replaceAll("\\", "/");
|
|
156
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
157
|
+
throw new Error("host_runtime_source_invalid: discovery input escapes the worktree.");
|
|
158
|
+
}
|
|
159
|
+
let sourceBytes;
|
|
160
|
+
try {
|
|
161
|
+
const expected = await expectedEntry(target);
|
|
162
|
+
if (!expected)
|
|
163
|
+
sourceBytes = null;
|
|
164
|
+
else if (expected.kind !== "file") {
|
|
165
|
+
throw new Error("host_runtime_source_changed: discovery input is not an ordinary file.");
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
sourceBytes = await readCanonicalSourceFile(target, 256 * 1024 * 1024 - bytes, expected.identity);
|
|
169
|
+
}
|
|
170
|
+
if (sourceBytes !== null) {
|
|
171
|
+
bytes += sourceBytes.length;
|
|
172
|
+
if (bytes > 256 * 1024 * 1024)
|
|
173
|
+
throw new Error("host_runtime_source_invalid: discovery exceeds the byte limit.");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (error.code !== "ENOENT")
|
|
178
|
+
throw error;
|
|
179
|
+
sourceBytes = null;
|
|
180
|
+
}
|
|
181
|
+
additional.set(relative, sourceBytes);
|
|
182
|
+
return sourceBytes;
|
|
183
|
+
});
|
|
184
|
+
// Discovery walks in parallel, but reads share one total byte budget.
|
|
185
|
+
discoveryCapture = capture.then(() => undefined, () => undefined);
|
|
186
|
+
snapshot.set(key, capture);
|
|
187
|
+
return capture;
|
|
188
|
+
}, directorySnapshot);
|
|
189
|
+
for (const [name, sourceBytes] of [...additional].sort(([left], [right]) => left.localeCompare(right))) {
|
|
190
|
+
hash.update(JSON.stringify(["runtime-discovery", name]));
|
|
191
|
+
const digest = sourceBytes === null
|
|
192
|
+
? null
|
|
193
|
+
: createHash("sha256").update(sourceBytes).digest();
|
|
194
|
+
hash.update(digest ?? "absent");
|
|
195
|
+
diagnostics?.entries.set(`runtime-discovery:${name}`, digest?.toString("hex") ?? "absent");
|
|
196
|
+
}
|
|
197
|
+
if (head !== (await git.run(root, ["rev-parse", "HEAD"])).trim()
|
|
198
|
+
|| JSON.stringify(source) !== JSON.stringify(await inventory())) {
|
|
199
|
+
throw new Error("host_runtime_source_changed: prepared source changed during verification.");
|
|
200
|
+
}
|
|
201
|
+
for (const captured of await Promise.all([...directories.values()])) {
|
|
202
|
+
const current = await readPinnedSourceDirectory(captured.path, captured.identity);
|
|
203
|
+
if (directoryInventory(current) !== directoryInventory(captured.entries)) {
|
|
204
|
+
throw new Error("host_runtime_source_changed: runtime discovery inventory changed during verification.");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
inputCommitSha: head,
|
|
209
|
+
runtimeContractFingerprint: hostRuntimeContractFingerprint(contract),
|
|
210
|
+
sourceFingerprint: `sha256:${hash.digest("hex")}`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function directoryInventory(entries) {
|
|
214
|
+
return JSON.stringify(entries.map(entry => ({
|
|
215
|
+
name: entry.name,
|
|
216
|
+
kind: entry.kind,
|
|
217
|
+
})));
|
|
218
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { RunOnceDispatchPacket } from "../session/session.types.js";
|
|
2
|
+
import type { HostRuntimeAdmissionCheckpointHandler, HostRuntimeLeaseRuntime } from "./host-runtime-lease.js";
|
|
3
|
+
import { HostParallelPromotionStore, type HostParallelProofIdentity } from "./host-runtime-promotion.js";
|
|
4
|
+
export interface PreparedRuntimePendingSnapshot {
|
|
5
|
+
readonly repoId: string;
|
|
6
|
+
readonly dispatchAttemptId: string;
|
|
7
|
+
readonly fencingToken: string;
|
|
8
|
+
readonly probeDispatchAttemptId?: string;
|
|
9
|
+
readonly probeFencingToken?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Foreground barrier: prepare a wave, verify its exact runtimes, then release agents. */
|
|
12
|
+
export declare class PreparedRuntimeReadiness {
|
|
13
|
+
private readonly runtime;
|
|
14
|
+
private readonly store;
|
|
15
|
+
private readonly releasePreparation;
|
|
16
|
+
private readonly admissionExpired;
|
|
17
|
+
private readonly preparations;
|
|
18
|
+
private readonly draining;
|
|
19
|
+
constructor(runtime: HostRuntimeLeaseRuntime, store: HostParallelPromotionStore, releasePreparation: (id: string, fence: string) => Promise<unknown>, admissionExpired: () => Error);
|
|
20
|
+
acquire(dispatch: RunOnceDispatchPacket, signal?: AbortSignal): Promise<readonly string[]>;
|
|
21
|
+
has(id: string): boolean;
|
|
22
|
+
hasPending(): boolean;
|
|
23
|
+
pendingSnapshots(): readonly PreparedRuntimePendingSnapshot[];
|
|
24
|
+
verify(dispatch: RunOnceDispatchPacket, worktree: string, checkpoint?: HostRuntimeAdmissionCheckpointHandler): Promise<HostParallelProofIdentity | undefined>;
|
|
25
|
+
renew(id: string, fence: string, expiresAt: string): Promise<void>;
|
|
26
|
+
cancel(id: string, fence: string, reason?: Error): void;
|
|
27
|
+
private drain;
|
|
28
|
+
private scheduleDeadline;
|
|
29
|
+
private check;
|
|
30
|
+
}
|