@ricsam/r5d-worker 0.0.78 → 0.0.80
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 +6 -0
- package/dist/cjs/main.cjs +1550 -335
- package/dist/cjs/managed-paths.cjs +101 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-workspace-state.cjs +184 -5
- package/dist/cjs/project-worktrees.cjs +676 -58
- package/dist/cjs/registry-auth.cjs +310 -0
- package/dist/cjs/repository-transition-policy.cjs +49 -0
- package/dist/cjs/supervisor.cjs +62 -11
- package/dist/cjs/working-tree-mirror.cjs +196 -36
- package/dist/cjs/workspace-automatic-sync-policy.cjs +69 -0
- package/dist/cjs/workspace-branch-incarnation-policy.cjs +37 -0
- package/dist/cjs/workspace-command-sync-policy.cjs +18 -0
- package/dist/cjs/workspace-git-sync.cjs +1037 -54
- package/dist/cjs/workspace-mount-boundary.cjs +71 -0
- package/dist/cjs/workspace-path-move.cjs +195 -0
- package/dist/cjs/workspace-preserve-only-policy.cjs +36 -0
- package/dist/cjs/workspace-project-config-policy.cjs +46 -0
- package/dist/cjs/workspace-publication-evidence.cjs +46 -0
- package/dist/mjs/main.mjs +1584 -341
- package/dist/mjs/managed-paths.mjs +100 -1
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-workspace-state.mjs +181 -5
- package/dist/mjs/project-worktrees.mjs +667 -57
- package/dist/mjs/registry-auth.mjs +269 -0
- package/dist/mjs/repository-transition-policy.mjs +22 -0
- package/dist/mjs/supervisor.mjs +61 -11
- package/dist/mjs/working-tree-mirror.mjs +195 -36
- package/dist/mjs/workspace-automatic-sync-policy.mjs +40 -0
- package/dist/mjs/workspace-branch-incarnation-policy.mjs +13 -0
- package/dist/mjs/workspace-command-sync-policy.mjs +17 -0
- package/dist/mjs/workspace-git-sync.mjs +1032 -54
- package/dist/mjs/workspace-mount-boundary.mjs +37 -0
- package/dist/mjs/workspace-path-move.mjs +160 -0
- package/dist/mjs/workspace-preserve-only-policy.mjs +11 -0
- package/dist/mjs/workspace-project-config-policy.mjs +21 -0
- package/dist/mjs/workspace-publication-evidence.mjs +21 -0
- package/dist/types/credential-authority-lock-fixture.d.ts +1 -0
- package/dist/types/main.d.ts +175 -1
- package/dist/types/managed-paths.d.ts +11 -0
- package/dist/types/project-workspace-state.d.ts +45 -1
- package/dist/types/project-worktrees.d.ts +119 -2
- package/dist/types/registry-auth.d.ts +47 -0
- package/dist/types/repository-transition-policy.d.ts +26 -0
- package/dist/types/supervisor-daemonized-fixture.d.ts +1 -0
- package/dist/types/supervisor-signal-fixture.d.ts +1 -0
- package/dist/types/supervisor.d.ts +6 -4
- package/dist/types/working-tree-mirror.d.ts +14 -0
- package/dist/types/workspace-automatic-sync-policy.d.ts +37 -0
- package/dist/types/workspace-branch-incarnation-policy.d.ts +14 -0
- package/dist/types/workspace-command-sync-policy.d.ts +9 -0
- package/dist/types/workspace-git-sync.d.ts +33 -3
- package/dist/types/workspace-mount-boundary.d.ts +10 -0
- package/dist/types/workspace-path-move.d.ts +21 -0
- package/dist/types/workspace-preserve-only-policy.d.ts +15 -0
- package/dist/types/workspace-project-config-policy.d.ts +34 -0
- package/dist/types/workspace-publication-evidence.d.ts +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function containedRelative(root, candidate) {
|
|
4
|
+
const relative = path.relative(root, candidate);
|
|
5
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
6
|
+
throw new Error(`Managed directory path escapes its trusted root: ${candidate}`);
|
|
7
|
+
}
|
|
8
|
+
return relative;
|
|
9
|
+
}
|
|
10
|
+
function assertManagedDirectoryPath(input) {
|
|
11
|
+
const trustedRoot = path.resolve(input.trustedRoot);
|
|
12
|
+
const candidate = path.resolve(input.candidate);
|
|
13
|
+
const relative = containedRelative(trustedRoot, candidate);
|
|
14
|
+
const rootStatus = fs.lstatSync(trustedRoot);
|
|
15
|
+
if (!rootStatus.isDirectory() || rootStatus.isSymbolicLink()) {
|
|
16
|
+
throw new Error(`${input.label} trusted root is not a regular directory: ${trustedRoot}`);
|
|
17
|
+
}
|
|
18
|
+
const realRoot = fs.realpathSync(trustedRoot);
|
|
19
|
+
let current = trustedRoot;
|
|
20
|
+
for (const segment of relative.split(path.sep)) {
|
|
21
|
+
current = path.join(current, segment);
|
|
22
|
+
let status;
|
|
23
|
+
try {
|
|
24
|
+
status = fs.lstatSync(current);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.code === "ENOENT") return;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
30
|
+
throw new Error(`${input.label} contains a symlink or non-directory component: ${current}`);
|
|
31
|
+
}
|
|
32
|
+
containedRelative(realRoot, fs.realpathSync(current));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
assertManagedDirectoryPath
|
|
37
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { projectWorktreeOperationInProgress } from "./project-worktrees.mjs";
|
|
5
|
+
import { validateManagedBranchName } from "./managed-paths.mjs";
|
|
6
|
+
import { assertWorkingTreeHasNoPortableGitMetadataAliases, mirrorWorkingTree } from "./working-tree-mirror.mjs";
|
|
7
|
+
import { assertManagedDirectoryPath } from "./workspace-mount-boundary.mjs";
|
|
8
|
+
function checkoutPathMovePublicationComplete(requiredMountIds, activeMountIds) {
|
|
9
|
+
return requiredMountIds.length > 0 && requiredMountIds.every((mountId) => activeMountIds.has(mountId));
|
|
10
|
+
}
|
|
11
|
+
function pathIsInside(root, candidate) {
|
|
12
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
13
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
14
|
+
}
|
|
15
|
+
function fsyncTree(targetPath) {
|
|
16
|
+
const stat = fs.lstatSync(targetPath);
|
|
17
|
+
if (stat.isSymbolicLink()) return;
|
|
18
|
+
if (stat.isDirectory()) {
|
|
19
|
+
for (const entry of fs.readdirSync(targetPath).sort()) fsyncTree(path.join(targetPath, entry));
|
|
20
|
+
}
|
|
21
|
+
if (!stat.isDirectory() && !stat.isFile()) return;
|
|
22
|
+
const descriptor = fs.openSync(targetPath, "r");
|
|
23
|
+
try {
|
|
24
|
+
fs.fsyncSync(descriptor);
|
|
25
|
+
} finally {
|
|
26
|
+
fs.closeSync(descriptor);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function fsyncDirectory(directoryPath) {
|
|
30
|
+
const descriptor = fs.openSync(directoryPath, "r");
|
|
31
|
+
try {
|
|
32
|
+
fs.fsyncSync(descriptor);
|
|
33
|
+
} finally {
|
|
34
|
+
fs.closeSync(descriptor);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function fsyncAncestorsToRoot(targetPath, durabilityRoot) {
|
|
38
|
+
const root = path.resolve(durabilityRoot);
|
|
39
|
+
let current = path.dirname(path.resolve(targetPath));
|
|
40
|
+
while (true) {
|
|
41
|
+
if (fs.existsSync(current)) {
|
|
42
|
+
const status = fs.lstatSync(current);
|
|
43
|
+
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
44
|
+
throw new Error(`Project checkout path move durability boundary is not a regular directory: ${current}`);
|
|
45
|
+
}
|
|
46
|
+
fsyncDirectory(current);
|
|
47
|
+
}
|
|
48
|
+
if (current === root) return;
|
|
49
|
+
const parent = path.dirname(current);
|
|
50
|
+
if (parent === current) throw new Error(`Project checkout path move escapes durability root ${root}`);
|
|
51
|
+
current = parent;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function stableTree(targetPath, durabilityRoot) {
|
|
55
|
+
fsyncTree(targetPath);
|
|
56
|
+
fsyncAncestorsToRoot(targetPath, durabilityRoot);
|
|
57
|
+
}
|
|
58
|
+
function directoryEntryExists(targetPath) {
|
|
59
|
+
try {
|
|
60
|
+
fs.lstatSync(targetPath);
|
|
61
|
+
return true;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error.code === "ENOENT") return false;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function retiredCheckoutPath(projectsDurabilityRoot, oldProjectRoot) {
|
|
68
|
+
const storageRoot = path.join(projectsDurabilityRoot, ".r5d-retired-checkouts");
|
|
69
|
+
const digest = createHash("sha256").update(oldProjectRoot).digest("hex");
|
|
70
|
+
return { storageRoot, retiredRoot: path.join(storageRoot, digest) };
|
|
71
|
+
}
|
|
72
|
+
function removeRetiredCheckout(retiredRoot, storageRoot) {
|
|
73
|
+
if (!directoryEntryExists(retiredRoot)) return;
|
|
74
|
+
fs.rmSync(retiredRoot, { recursive: true, force: true });
|
|
75
|
+
fsyncDirectory(storageRoot);
|
|
76
|
+
}
|
|
77
|
+
function preserveProjectCheckoutPathMove(input) {
|
|
78
|
+
const oldProjectRoot = path.resolve(input.oldProjectRoot);
|
|
79
|
+
const newProjectRoot = path.resolve(input.newProjectRoot);
|
|
80
|
+
const projectsDurabilityRoot = path.resolve(input.projectsDurabilityRoot);
|
|
81
|
+
const outerWorkspaceRoot = path.resolve(input.outerWorkspaceRoot);
|
|
82
|
+
if (oldProjectRoot === newProjectRoot || pathIsInside(oldProjectRoot, newProjectRoot) || pathIsInside(newProjectRoot, oldProjectRoot)) {
|
|
83
|
+
throw new Error("Project checkout path move roots overlap");
|
|
84
|
+
}
|
|
85
|
+
const branchNames = input.branches.map(({ branchName }) => branchName);
|
|
86
|
+
if (branchNames.length === 0 || new Set(branchNames).size !== branchNames.length) {
|
|
87
|
+
throw new Error("Project checkout path move requires unique branches");
|
|
88
|
+
}
|
|
89
|
+
assertManagedDirectoryPath({
|
|
90
|
+
trustedRoot: projectsDurabilityRoot,
|
|
91
|
+
candidate: oldProjectRoot,
|
|
92
|
+
label: "Old project checkout root"
|
|
93
|
+
});
|
|
94
|
+
assertManagedDirectoryPath({
|
|
95
|
+
trustedRoot: projectsDurabilityRoot,
|
|
96
|
+
candidate: newProjectRoot,
|
|
97
|
+
label: "New project checkout root"
|
|
98
|
+
});
|
|
99
|
+
for (const branchName of branchNames) validateManagedBranchName(branchName);
|
|
100
|
+
if (directoryEntryExists(oldProjectRoot)) {
|
|
101
|
+
assertWorkingTreeHasNoPortableGitMetadataAliases(oldProjectRoot);
|
|
102
|
+
}
|
|
103
|
+
const { storageRoot: retirementStorageRoot, retiredRoot } = retiredCheckoutPath(projectsDurabilityRoot, oldProjectRoot);
|
|
104
|
+
assertManagedDirectoryPath({
|
|
105
|
+
trustedRoot: projectsDurabilityRoot,
|
|
106
|
+
candidate: retirementStorageRoot,
|
|
107
|
+
label: "Retired project checkout storage"
|
|
108
|
+
});
|
|
109
|
+
fs.mkdirSync(retirementStorageRoot, { recursive: true, mode: 448 });
|
|
110
|
+
assertManagedDirectoryPath({
|
|
111
|
+
trustedRoot: projectsDurabilityRoot,
|
|
112
|
+
candidate: retirementStorageRoot,
|
|
113
|
+
label: "Retired project checkout storage"
|
|
114
|
+
});
|
|
115
|
+
fsyncDirectory(retirementStorageRoot);
|
|
116
|
+
fsyncDirectory(projectsDurabilityRoot);
|
|
117
|
+
assertManagedDirectoryPath({
|
|
118
|
+
trustedRoot: projectsDurabilityRoot,
|
|
119
|
+
candidate: retiredRoot,
|
|
120
|
+
label: "Retired project checkout"
|
|
121
|
+
});
|
|
122
|
+
removeRetiredCheckout(retiredRoot, retirementStorageRoot);
|
|
123
|
+
if (!directoryEntryExists(oldProjectRoot)) return { preservedBranchNames: [] };
|
|
124
|
+
const preservedBranchNames = [];
|
|
125
|
+
for (const branch of input.branches) {
|
|
126
|
+
validateManagedBranchName(branch.branchName);
|
|
127
|
+
const oldBranchPath = path.join(oldProjectRoot, ...branch.branchName.split("/"));
|
|
128
|
+
if (!fs.existsSync(oldBranchPath)) continue;
|
|
129
|
+
const newBranchPath = path.join(newProjectRoot, ...branch.branchName.split("/"));
|
|
130
|
+
const outerBranchPath = path.resolve(outerWorkspaceRoot, ...branch.outerWorkspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
131
|
+
if (!pathIsInside(outerWorkspaceRoot, outerBranchPath)) {
|
|
132
|
+
throw new Error(`Project checkout path move escapes the outer workspace: ${branch.outerWorkspaceRelativePath}`);
|
|
133
|
+
}
|
|
134
|
+
assertManagedDirectoryPath({ trustedRoot: projectsDurabilityRoot, candidate: oldBranchPath, label: "Old project checkout branch" });
|
|
135
|
+
assertManagedDirectoryPath({ trustedRoot: projectsDurabilityRoot, candidate: newBranchPath, label: "New project checkout branch" });
|
|
136
|
+
assertManagedDirectoryPath({ trustedRoot: outerWorkspaceRoot, candidate: outerBranchPath, label: "Outer workspace branch" });
|
|
137
|
+
mirrorWorkingTree({ sourceRoot: oldBranchPath, targetRoot: outerBranchPath, sourceMode: "git", deletionMode: "all" });
|
|
138
|
+
mirrorWorkingTree({ sourceRoot: oldBranchPath, targetRoot: newBranchPath, sourceMode: "all", deletionMode: "all" });
|
|
139
|
+
assertManagedDirectoryPath({ trustedRoot: projectsDurabilityRoot, candidate: newBranchPath, label: "New project checkout branch" });
|
|
140
|
+
assertManagedDirectoryPath({ trustedRoot: outerWorkspaceRoot, candidate: outerBranchPath, label: "Outer workspace branch" });
|
|
141
|
+
stableTree(outerBranchPath, outerWorkspaceRoot);
|
|
142
|
+
stableTree(newBranchPath, projectsDurabilityRoot);
|
|
143
|
+
preservedBranchNames.push(branch.branchName);
|
|
144
|
+
}
|
|
145
|
+
for (const branchName of branchNames) {
|
|
146
|
+
const checkoutPath = path.join(oldProjectRoot, ...branchName.split("/"));
|
|
147
|
+
if (fs.existsSync(checkoutPath) && projectWorktreeOperationInProgress(checkoutPath)) {
|
|
148
|
+
throw new Error(`Project branch ${branchName} has an in-progress Git operation`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
fs.renameSync(oldProjectRoot, retiredRoot);
|
|
152
|
+
fsyncAncestorsToRoot(oldProjectRoot, projectsDurabilityRoot);
|
|
153
|
+
fsyncDirectory(retirementStorageRoot);
|
|
154
|
+
removeRetiredCheckout(retiredRoot, retirementStorageRoot);
|
|
155
|
+
return { preservedBranchNames: preservedBranchNames.sort() };
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
checkoutPathMovePublicationComplete,
|
|
159
|
+
preserveProjectCheckoutPathMove
|
|
160
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function creatorLocalProjectBranchIsAuthorized(input) {
|
|
2
|
+
return Boolean(input.pendingBranchId && (!input.preserveOnlyBranchId || input.pendingBranchId === input.preserveOnlyBranchId));
|
|
3
|
+
}
|
|
4
|
+
function workerProjectBranchDisposition(input) {
|
|
5
|
+
if (input.activeConfigured) return "active";
|
|
6
|
+
return input.locallyPendingCreated ? "creator_local" : "preserve_only";
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
creatorLocalProjectBranchIsAuthorized,
|
|
10
|
+
workerProjectBranchDisposition
|
|
11
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function deferredProjectConfigurationPendingBranches(projects) {
|
|
2
|
+
return projects.flatMap((project) => project.branches.map(({ branchName }) => ({ projectId: project.projectId, branchName }))).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName));
|
|
3
|
+
}
|
|
4
|
+
function busyProjectConfigurationChangeIds(input) {
|
|
5
|
+
const currentById = new Map(input.currentProjects.map((project) => [project.projectId, project]));
|
|
6
|
+
const incomingById = new Map(input.incomingProjects.map((project) => [project.projectId, project]));
|
|
7
|
+
const targets = [...input.activeTargets];
|
|
8
|
+
const busyProjectIds = targets.some((target) => target.type === "workspace" && target.rootProfile === "visible_projects") ? /* @__PURE__ */ new Set([...currentById.keys(), ...incomingById.keys()]) : new Set(targets.flatMap((target) => target.type === "project" ? [target.projectId] : []));
|
|
9
|
+
return [...busyProjectIds].filter((projectId) => {
|
|
10
|
+
const current = currentById.get(projectId);
|
|
11
|
+
const incoming = incomingById.get(projectId);
|
|
12
|
+
if (!current && !incoming) return false;
|
|
13
|
+
if (!current || !incoming) return true;
|
|
14
|
+
if (!input.currentProjectReady(current)) return true;
|
|
15
|
+
return input.currentFingerprint(current) !== input.incomingFingerprint(incoming);
|
|
16
|
+
}).sort();
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
busyProjectConfigurationChangeIds,
|
|
20
|
+
deferredProjectConfigurationPendingBranches
|
|
21
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function projectBranchMountId(projectId, branchName) {
|
|
2
|
+
return `project:${projectId}:${encodeURIComponent(branchName)}`;
|
|
3
|
+
}
|
|
4
|
+
function activeProjectBranchNames(projectId, branchNames, activeMountIds) {
|
|
5
|
+
const active = new Set(activeMountIds);
|
|
6
|
+
return [...branchNames].filter((branchName) => active.has(projectBranchMountId(projectId, branchName))).sort((left, right) => left.localeCompare(right));
|
|
7
|
+
}
|
|
8
|
+
function activeProjectBranchPublicationEvidence(projects, activeMountIds) {
|
|
9
|
+
const active = new Set(activeMountIds);
|
|
10
|
+
return projects.flatMap(
|
|
11
|
+
(project) => project.executionDisabled || project.mirrorWritesDisabled ? [] : project.branches.flatMap(
|
|
12
|
+
(branch) => active.has(projectBranchMountId(project.projectId, branch.branchName)) ? [{ branchId: branch.branchId, projectId: project.projectId, branchName: branch.branchName }] : []
|
|
13
|
+
)
|
|
14
|
+
).sort(
|
|
15
|
+
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName) || left.branchId.localeCompare(right.branchId)
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
activeProjectBranchNames,
|
|
20
|
+
activeProjectBranchPublicationEvidence
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/main.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import { Database } from "bun:sqlite";
|
|
3
|
+
import { configureGitHubRegistryAuthFiles, type PreparedPrivateAuthFileGeneration } from "./registry-auth";
|
|
2
4
|
type WorkerProjectConfig = {
|
|
3
5
|
projectId: string;
|
|
4
6
|
checkoutPathSegments: [namespace: string, project: string];
|
|
@@ -6,7 +8,15 @@ type WorkerProjectConfig = {
|
|
|
6
8
|
repoHttpUrl: string | null;
|
|
7
9
|
repoAuthHeader: string | null;
|
|
8
10
|
defaultBranch: string;
|
|
11
|
+
repositoryTransitionId: string | null;
|
|
12
|
+
executionDisabled: boolean;
|
|
13
|
+
mirrorWritesDisabled: boolean;
|
|
14
|
+
preserveOnlyBranches: Array<{
|
|
15
|
+
branchId: string;
|
|
16
|
+
branchName: string;
|
|
17
|
+
}>;
|
|
9
18
|
branches: Array<{
|
|
19
|
+
branchId: string;
|
|
10
20
|
branchName: string;
|
|
11
21
|
sourceBranchName: string | null;
|
|
12
22
|
baseCommitHash: string;
|
|
@@ -33,6 +43,13 @@ type WorkerReadFileResult = {
|
|
|
33
43
|
truncated: boolean;
|
|
34
44
|
continuation?: string;
|
|
35
45
|
};
|
|
46
|
+
type WorkerGitHubCredential = {
|
|
47
|
+
host: "github.com";
|
|
48
|
+
registry: "ghcr.io";
|
|
49
|
+
username: string;
|
|
50
|
+
token: string;
|
|
51
|
+
missingScopes: Array<"repo" | "read:org" | "gist" | "workflow" | "read:packages" | "write:packages">;
|
|
52
|
+
};
|
|
36
53
|
type WorkerWriteFileResult = {
|
|
37
54
|
type: "write";
|
|
38
55
|
file: string;
|
|
@@ -89,6 +106,14 @@ type WorkerCodeReadResult = {
|
|
|
89
106
|
mtime: string;
|
|
90
107
|
base64: string;
|
|
91
108
|
};
|
|
109
|
+
declare function assertWorkerChildAdmission(input: {
|
|
110
|
+
capturedGeneration: number;
|
|
111
|
+
currentGeneration: number;
|
|
112
|
+
sourceSocketIsCurrent: boolean;
|
|
113
|
+
sourceSocketOpen: boolean;
|
|
114
|
+
workspaceConfigured: boolean;
|
|
115
|
+
runtimeHealthy: boolean;
|
|
116
|
+
}): void;
|
|
92
117
|
export type WorkerBuiltInToolPaths = {
|
|
93
118
|
artifactsDir?: string;
|
|
94
119
|
plansDir?: string;
|
|
@@ -131,17 +156,118 @@ type ResolvedWorkerFilePath = {
|
|
|
131
156
|
};
|
|
132
157
|
export declare function resolveWorkerFilePath(branchPath: string, inputPath: string, builtInPaths?: WorkerBuiltInToolPaths): ResolvedWorkerFilePath;
|
|
133
158
|
declare function readOnlyCredentialStoreHelper(storePath: string): string;
|
|
159
|
+
declare function credentialGenerationReceiptPath(directoryPath?: string): string;
|
|
160
|
+
declare function pendingCredentialGenerationPath(directoryPath?: string): string;
|
|
161
|
+
declare function bootstrapCredentialGenerationPath(directoryPath?: string): string;
|
|
162
|
+
type PendingCredentialGenerationBoundary = {
|
|
163
|
+
kind: "systemd";
|
|
164
|
+
machineId: string | null;
|
|
165
|
+
invocationId: string;
|
|
166
|
+
} | {
|
|
167
|
+
kind: "boot";
|
|
168
|
+
machineId: string | null;
|
|
169
|
+
bootSessionId: string | null;
|
|
170
|
+
};
|
|
171
|
+
type PendingCredentialGenerationIntent = {
|
|
172
|
+
fingerprint: string;
|
|
173
|
+
boundary: PendingCredentialGenerationBoundary;
|
|
174
|
+
};
|
|
175
|
+
declare function pendingCredentialGenerationIntentContent(intent: PendingCredentialGenerationIntent): string;
|
|
176
|
+
declare function readPendingCredentialGenerationIntent(intentPath?: string): PendingCredentialGenerationIntent | null;
|
|
177
|
+
type BootSessionIdentifierProbe = {
|
|
178
|
+
platform: NodeJS.Platform;
|
|
179
|
+
kernelValue: string;
|
|
180
|
+
};
|
|
181
|
+
declare function readBootSessionIdentifier(probe?: BootSessionIdentifierProbe): string | null;
|
|
182
|
+
type MachineIdentifierProbe = {
|
|
183
|
+
platform: NodeJS.Platform;
|
|
184
|
+
kernelValue: string;
|
|
185
|
+
};
|
|
186
|
+
declare function readMachineIdentifier(probe?: MachineIdentifierProbe): string | null;
|
|
187
|
+
type CredentialAuthorityLockHandle = {
|
|
188
|
+
lockPath: string;
|
|
189
|
+
database: Database;
|
|
190
|
+
};
|
|
191
|
+
declare function credentialAuthorityLockPath(rootPath?: string): string;
|
|
192
|
+
declare function acquireCredentialAuthorityLock(input: {
|
|
193
|
+
rootPath?: string;
|
|
194
|
+
machineId: string | null;
|
|
195
|
+
}): CredentialAuthorityLockHandle;
|
|
196
|
+
declare function releaseCredentialAuthorityLockHandle(handle: CredentialAuthorityLockHandle): void;
|
|
134
197
|
declare function legacySharedCredentialStorePath(): string;
|
|
135
198
|
declare function credentialStorePathForProject(projectId: string): string;
|
|
136
199
|
declare function credentialStorePathForWorkspace(remoteUrl: string): string;
|
|
137
200
|
declare function removeLegacySharedCredentialStore(storePath?: string): void;
|
|
138
201
|
declare function resetCredentialStoreDirectory(directoryPath?: string): void;
|
|
202
|
+
declare function readCredentialGenerationReceipt(receiptPath?: string): string | null;
|
|
139
203
|
declare function pruneCredentialStoreFiles(requiredStorePaths: ReadonlySet<string>, directoryPath?: string): string[];
|
|
140
204
|
declare function replaceCredentialStoreFile(storePath: string, content: string): void;
|
|
141
205
|
declare function credentialHelperForRemotes(remotes: readonly {
|
|
142
206
|
remoteUrl: string;
|
|
143
207
|
authHeader: string | null | undefined;
|
|
144
|
-
}[], storePath: string): string | null;
|
|
208
|
+
}[], storePath: string, desiredFiles?: Map<string, string | null>): string | null;
|
|
209
|
+
type CredentialGenerationConfiguration = {
|
|
210
|
+
workerBaseUrl: string;
|
|
211
|
+
workerAuthHeader: string;
|
|
212
|
+
workspaceRemoteUrl: string;
|
|
213
|
+
githubCredential: WorkerGitHubCredential | null;
|
|
214
|
+
projects: readonly Pick<WorkerProjectConfig, "projectId" | "repoHttpUrl" | "repoAuthHeader">[];
|
|
215
|
+
};
|
|
216
|
+
declare function credentialGenerationFingerprint(config: CredentialGenerationConfiguration): string;
|
|
217
|
+
type CredentialGenerationTransitionPhase = "current" | "stage_pending_restart" | "publish_from_clean_restart";
|
|
218
|
+
declare function credentialGenerationTransitionPhase(input: {
|
|
219
|
+
committedFingerprint: string | null;
|
|
220
|
+
pendingFingerprintAtProcessStart: string | null;
|
|
221
|
+
incomingFingerprint: string;
|
|
222
|
+
}): CredentialGenerationTransitionPhase;
|
|
223
|
+
declare function initialCredentialBootstrapIsSafe(input: {
|
|
224
|
+
transitionPhase: CredentialGenerationTransitionPhase;
|
|
225
|
+
committedFingerprint: string | null;
|
|
226
|
+
pendingFingerprintAtProcessStart: string | null;
|
|
227
|
+
bootstrapFingerprintAtProcessStart: string | null;
|
|
228
|
+
incomingFingerprint: string;
|
|
229
|
+
credentialArtifactsProvenAbsentAtProcessStart: boolean;
|
|
230
|
+
trackedChildTerminationProven: boolean;
|
|
231
|
+
activeProcessCount: number;
|
|
232
|
+
credentialProcessGroupCount: number;
|
|
233
|
+
activePtyCount: number;
|
|
234
|
+
}): boolean;
|
|
235
|
+
declare function fenceCredentialGenerationAtConfigureEntry(input: {
|
|
236
|
+
currentFingerprint: string | null;
|
|
237
|
+
incomingFingerprint: string;
|
|
238
|
+
revokeInMemory: () => void;
|
|
239
|
+
terminatePreviousGeneration: () => Promise<void>;
|
|
240
|
+
}): Promise<boolean>;
|
|
241
|
+
declare function requireCredentialGenerationRestart(required: boolean, fatalExit?: (exitCode: number) => void, exitCode?: number): void;
|
|
242
|
+
declare function pendingCredentialGenerationBoundary(input: {
|
|
243
|
+
systemdContract: boolean;
|
|
244
|
+
systemdInvocationId: string | null;
|
|
245
|
+
bootSessionId: string | null;
|
|
246
|
+
machineId: string | null;
|
|
247
|
+
}): PendingCredentialGenerationBoundary;
|
|
248
|
+
declare function credentialGenerationBoundaryIsComplete(boundary: PendingCredentialGenerationBoundary): boolean;
|
|
249
|
+
declare function credentialGenerationRestartIsContained(input: {
|
|
250
|
+
systemdContract: boolean;
|
|
251
|
+
pendingBoundary: PendingCredentialGenerationBoundary | null;
|
|
252
|
+
currentSystemdInvocationId: string | null;
|
|
253
|
+
currentBootSessionId: string | null;
|
|
254
|
+
currentMachineId: string | null;
|
|
255
|
+
}): boolean;
|
|
256
|
+
type CredentialReapContractProbe = {
|
|
257
|
+
platform: NodeJS.Platform;
|
|
258
|
+
invocationId: string | undefined;
|
|
259
|
+
cgroupText: string;
|
|
260
|
+
unitProperties: string;
|
|
261
|
+
};
|
|
262
|
+
type CredentialReapContractStatus = "direct" | "systemd_unverified" | "verified_systemd";
|
|
263
|
+
declare function credentialReapContractStatus(probe?: CredentialReapContractProbe): CredentialReapContractStatus;
|
|
264
|
+
declare function verifiedCredentialReapContract(probe?: CredentialReapContractProbe): boolean;
|
|
265
|
+
declare function fenceUnsafeWorkspaceSyncFailure(input: {
|
|
266
|
+
hydrationCurrent: boolean;
|
|
267
|
+
invalidateExecution: () => void;
|
|
268
|
+
fatalExit?: (exitCode: number) => void;
|
|
269
|
+
exitCode?: number;
|
|
270
|
+
}): boolean;
|
|
145
271
|
export declare const workerGitSecurityTestHarness: {
|
|
146
272
|
commandArgs: (args: string[]) => string[];
|
|
147
273
|
credentialHelperForRemotes: typeof credentialHelperForRemotes;
|
|
@@ -153,8 +279,47 @@ export declare const workerGitSecurityTestHarness: {
|
|
|
153
279
|
readOnlyCredentialStoreHelper: typeof readOnlyCredentialStoreHelper;
|
|
154
280
|
resetCredentialStoreDirectory: typeof resetCredentialStoreDirectory;
|
|
155
281
|
replaceCredentialStoreFile: typeof replaceCredentialStoreFile;
|
|
282
|
+
configureGitHubAuth(credential: WorkerGitHubCredential | null, filePaths: readonly string[], configureFiles?: typeof configureGitHubRegistryAuthFiles): void;
|
|
283
|
+
githubCredential: () => WorkerGitHubCredential | null;
|
|
284
|
+
credentialGenerationFingerprint: typeof credentialGenerationFingerprint;
|
|
285
|
+
credentialGenerationTransitionPhase: typeof credentialGenerationTransitionPhase;
|
|
286
|
+
initialCredentialBootstrapIsSafe: typeof initialCredentialBootstrapIsSafe;
|
|
287
|
+
fenceCredentialGenerationAtConfigureEntry: typeof fenceCredentialGenerationAtConfigureEntry;
|
|
288
|
+
requireCredentialGenerationRestart: typeof requireCredentialGenerationRestart;
|
|
289
|
+
pendingCredentialGenerationBoundary: typeof pendingCredentialGenerationBoundary;
|
|
290
|
+
credentialGenerationBoundaryIsComplete: typeof credentialGenerationBoundaryIsComplete;
|
|
291
|
+
credentialGenerationRestartIsContained: typeof credentialGenerationRestartIsContained;
|
|
292
|
+
credentialReapContractStatus: typeof credentialReapContractStatus;
|
|
293
|
+
verifiedCredentialReapContract: typeof verifiedCredentialReapContract;
|
|
294
|
+
fenceUnsafeWorkspaceSyncFailure: typeof fenceUnsafeWorkspaceSyncFailure;
|
|
295
|
+
assertWorkerChildAdmission: typeof assertWorkerChildAdmission;
|
|
296
|
+
terminateCredentialBearingChildren: typeof terminateCredentialBearingChildren;
|
|
297
|
+
terminateCredentialBearingChildrenWithRetention: typeof terminateCredentialBearingChildrenWithRetention;
|
|
298
|
+
commitCredentialGeneration(prepared: PreparedPrivateAuthFileGeneration, credential: WorkerGitHubCredential | null, children: readonly {
|
|
299
|
+
label: string;
|
|
300
|
+
terminate: () => void | Promise<void>;
|
|
301
|
+
}[], beforeMutation?: (filePath: string, index: number) => void, previousGenerationFenced?: boolean): Promise<boolean>;
|
|
302
|
+
credentialGenerationReceiptPath: typeof credentialGenerationReceiptPath;
|
|
303
|
+
credentialAuthorityLockPath: typeof credentialAuthorityLockPath;
|
|
304
|
+
acquireCredentialAuthorityLock: typeof acquireCredentialAuthorityLock;
|
|
305
|
+
releaseCredentialAuthorityLockHandle: typeof releaseCredentialAuthorityLockHandle;
|
|
306
|
+
pendingCredentialGenerationPath: typeof pendingCredentialGenerationPath;
|
|
307
|
+
pendingCredentialGenerationIntentContent: typeof pendingCredentialGenerationIntentContent;
|
|
308
|
+
readPendingCredentialGenerationIntent: typeof readPendingCredentialGenerationIntent;
|
|
309
|
+
readBootSessionIdentifier: typeof readBootSessionIdentifier;
|
|
310
|
+
readMachineIdentifier: typeof readMachineIdentifier;
|
|
311
|
+
bootstrapCredentialGenerationPath: typeof bootstrapCredentialGenerationPath;
|
|
312
|
+
readCredentialGenerationReceipt: typeof readCredentialGenerationReceipt;
|
|
156
313
|
};
|
|
157
314
|
export declare function githubCliEnv(token: string | null | undefined): Record<string, string>;
|
|
315
|
+
/**
|
|
316
|
+
* Build an agent-command environment without inheriting credentials that were
|
|
317
|
+
* supplied to bootstrap or authenticate the worker itself. Later layers are
|
|
318
|
+
* explicit server-issued per-run credentials or the currently committed GitHub
|
|
319
|
+
* generation and are intentionally allowed to use the same public variable
|
|
320
|
+
* names.
|
|
321
|
+
*/
|
|
322
|
+
export declare function workerChildProcessEnvironment(layers: readonly NodeJS.ProcessEnv[], inherited?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): NodeJS.ProcessEnv;
|
|
158
323
|
type ResolvedWorkerSessionTarget = {
|
|
159
324
|
target: WorkerSessionTarget;
|
|
160
325
|
rootPath: string;
|
|
@@ -215,4 +380,13 @@ export declare function resolveHostShell(command?: string, platform?: NodeJS.Pla
|
|
|
215
380
|
args: string[];
|
|
216
381
|
env?: Record<string, string>;
|
|
217
382
|
};
|
|
383
|
+
declare function terminateCredentialBearingChildren(children: readonly {
|
|
384
|
+
label: string;
|
|
385
|
+
terminate: () => void | Promise<void>;
|
|
386
|
+
}[]): Promise<void>;
|
|
387
|
+
declare function terminateCredentialBearingChildrenWithRetention(children: readonly {
|
|
388
|
+
label: string;
|
|
389
|
+
terminate: () => void | Promise<void>;
|
|
390
|
+
onTerminated: () => void;
|
|
391
|
+
}[]): Promise<void>;
|
|
218
392
|
export {};
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
export declare const BRANCH_NAME_MAX_LENGTH = 45;
|
|
2
2
|
export declare const BRANCH_NAME_PATTERN: RegExp;
|
|
3
3
|
export type CheckoutPathSegments = [namespace: string, project: string];
|
|
4
|
+
export type ManagedRoot = {
|
|
5
|
+
label: string;
|
|
6
|
+
rootPath: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Destructive worker roots must never alias or contain one another. Resolve
|
|
10
|
+
* and materialize every root first so symlinks, bind mounts, casing aliases,
|
|
11
|
+
* and Unicode-normalization aliases cannot disguise an overlap. This runs
|
|
12
|
+
* before any snapshot recovery or other destructive filesystem operation.
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertDisjointManagedRoots(roots: readonly ManagedRoot[]): void;
|
|
4
15
|
export declare function validateManagedBranchName(branchName: string): void;
|
|
5
16
|
export declare function validateCheckoutPathSegments(value: unknown): CheckoutPathSegments;
|
|
6
17
|
export declare function assertPathInside(root: string, candidate: string, label: string): void;
|
|
@@ -17,9 +17,20 @@ export type ProjectWorkspaceDesiredProject = {
|
|
|
17
17
|
}>;
|
|
18
18
|
};
|
|
19
19
|
export type LocallyPendingCreatedProjectBranch = {
|
|
20
|
+
/** Durable project_branches row incarnation; absent only in legacy state. */
|
|
21
|
+
branchId?: string;
|
|
20
22
|
projectId: string;
|
|
21
23
|
branchName: string;
|
|
22
24
|
};
|
|
25
|
+
export type PreserveOnlyProjectBranch = {
|
|
26
|
+
branchId: string;
|
|
27
|
+
projectId: string;
|
|
28
|
+
branchName: string;
|
|
29
|
+
};
|
|
30
|
+
export type EnabledRepositoryTransition = {
|
|
31
|
+
projectId: string;
|
|
32
|
+
transitionId: string | null;
|
|
33
|
+
};
|
|
23
34
|
type ProjectWorkspaceDeletionBase = {
|
|
24
35
|
id: string;
|
|
25
36
|
projectId: string;
|
|
@@ -66,10 +77,33 @@ export type ProjectWorkspaceStateView = {
|
|
|
66
77
|
desiredProjects: ProjectWorkspaceDesiredProject[];
|
|
67
78
|
effectiveDesiredProjects: ProjectWorkspaceDesiredProject[];
|
|
68
79
|
locallyPendingCreatedBranches: LocallyPendingCreatedProjectBranch[];
|
|
80
|
+
enabledRepositoryTransitions: EnabledRepositoryTransition[];
|
|
69
81
|
tombstones: ProjectWorkspaceDeletionTombstone[];
|
|
70
82
|
pendingTreeDeletions: PendingProjectWorkspaceTreeDeletion[];
|
|
71
83
|
pendingMirrorRefDeletions: PendingProjectMirrorRefDeletion[];
|
|
72
84
|
};
|
|
85
|
+
export declare class ProjectWorkspacePendingBranchPathChangeError extends Error {
|
|
86
|
+
readonly projectId: string;
|
|
87
|
+
readonly branchName: string;
|
|
88
|
+
readonly currentCheckoutPathSegments: CheckoutPathSegments;
|
|
89
|
+
readonly incomingCheckoutPathSegments: CheckoutPathSegments;
|
|
90
|
+
constructor(input: {
|
|
91
|
+
projectId: string;
|
|
92
|
+
branchName: string;
|
|
93
|
+
currentCheckoutPathSegments: CheckoutPathSegments;
|
|
94
|
+
incomingCheckoutPathSegments: CheckoutPathSegments;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export declare class ProjectWorkspaceCheckoutPathCollisionError extends Error {
|
|
98
|
+
readonly projectId: string;
|
|
99
|
+
readonly occupyingProjectId: string;
|
|
100
|
+
readonly checkoutPathSegments: CheckoutPathSegments;
|
|
101
|
+
constructor(input: {
|
|
102
|
+
projectId: string;
|
|
103
|
+
occupyingProjectId: string;
|
|
104
|
+
checkoutPathSegments: CheckoutPathSegments;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
73
107
|
export type StaleOuterProjectWorkspaceBranch = {
|
|
74
108
|
kind: "branch";
|
|
75
109
|
projectId: string;
|
|
@@ -125,9 +159,16 @@ export declare class ProjectWorkspaceStateStore {
|
|
|
125
159
|
reconcile(input: {
|
|
126
160
|
desiredProjects: readonly ProjectWorkspaceDesiredProjectInput[];
|
|
127
161
|
locallyPendingCreatedBranches?: readonly LocallyPendingCreatedProjectBranch[];
|
|
162
|
+
preserveOnlyBranches?: readonly PreserveOnlyProjectBranch[];
|
|
128
163
|
}): ProjectWorkspaceStateView;
|
|
164
|
+
/** Record a successful enabled reseed without letting disabled config consume it. */
|
|
165
|
+
recordEnabledRepositoryTransition(input: EnabledRepositoryTransition): ProjectWorkspaceStateView;
|
|
129
166
|
/** Persist the create intent before invoking Git so a crash cannot orphan an untracked worktree. */
|
|
130
|
-
recordPendingCreatedBranch(input: LocallyPendingCreatedProjectBranch
|
|
167
|
+
recordPendingCreatedBranch(input: LocallyPendingCreatedProjectBranch & {
|
|
168
|
+
branchId: string;
|
|
169
|
+
}): ProjectWorkspaceStateView;
|
|
170
|
+
/** Roll back an intent only when Git failed before creating its branch. */
|
|
171
|
+
rollbackPendingCreatedBranch(input: LocallyPendingCreatedProjectBranch): ProjectWorkspaceStateView;
|
|
131
172
|
/** Clear a create intent after an authoritative desired config contains it. */
|
|
132
173
|
clearPendingCreatedBranch(input: LocallyPendingCreatedProjectBranch): ProjectWorkspaceStateView;
|
|
133
174
|
/** Persist a branch tombstone before mutating its linked worktree or local ref. */
|
|
@@ -141,4 +182,7 @@ export declare class ProjectWorkspaceStateStore {
|
|
|
141
182
|
branchName: string;
|
|
142
183
|
}): ProjectWorkspaceStateView;
|
|
143
184
|
}
|
|
185
|
+
export declare const projectWorkspaceStateTestHarness: {
|
|
186
|
+
writeEmptyStateAtDurableRootBoundary(stateRoot: string, afterStateRootFsync: () => void): void;
|
|
187
|
+
};
|
|
144
188
|
export {};
|