@ricsam/r5d-worker 0.0.123 → 0.0.125
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 +1 -1
- package/dist/cjs/command-launcher.cjs +6 -2
- package/dist/cjs/main.cjs +496 -238
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-checkout-garbage.cjs +32 -3
- package/dist/cjs/project-workspace-state.cjs +51 -35
- package/dist/cjs/project-worktrees.cjs +79 -34
- package/dist/cjs/recovery-journal-protocol.cjs +56 -0
- package/dist/cjs/recovery-journal-runtime.cjs +133 -0
- package/dist/cjs/recovery-journal-thread.cjs +9 -0
- package/dist/cjs/recovery-journal.cjs +735 -0
- package/dist/cjs/recovery-store.cjs +8 -0
- package/dist/cjs/session-file-mutations.cjs +61 -0
- package/dist/cjs/working-tree-mirror.cjs +1 -0
- package/dist/cjs/workspace-command-sync-policy.cjs +37 -8
- package/dist/cjs/workspace-filesystem-executor-thread.cjs +36 -0
- package/dist/cjs/workspace-filesystem-executor.cjs +327 -0
- package/dist/cjs/workspace-filesystem-job-types.cjs +134 -0
- package/dist/cjs/workspace-filesystem-jobs.cjs +57 -0
- package/dist/cjs/workspace-git-sync.cjs +275 -201
- package/dist/cjs/workspace-mount-hold-fence.cjs +120 -0
- package/dist/mjs/command-launcher.mjs +6 -2
- package/dist/mjs/main.mjs +499 -244
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-checkout-garbage.mjs +30 -2
- package/dist/mjs/project-workspace-state.mjs +51 -35
- package/dist/mjs/project-worktrees.mjs +74 -34
- package/dist/mjs/recovery-journal-protocol.mjs +30 -0
- package/dist/mjs/recovery-journal-runtime.mjs +112 -0
- package/dist/mjs/recovery-journal-thread.mjs +8 -0
- package/dist/mjs/recovery-journal.mjs +687 -0
- package/dist/mjs/recovery-store.mjs +8 -0
- package/dist/mjs/session-file-mutations.mjs +37 -0
- package/dist/mjs/working-tree-mirror.mjs +1 -0
- package/dist/mjs/workspace-command-sync-policy.mjs +37 -8
- package/dist/mjs/workspace-filesystem-executor-thread.mjs +38 -0
- package/dist/mjs/workspace-filesystem-executor.mjs +287 -0
- package/dist/mjs/workspace-filesystem-job-types.mjs +106 -0
- package/dist/mjs/workspace-filesystem-jobs.mjs +51 -0
- package/dist/mjs/workspace-git-sync.mjs +264 -202
- package/dist/mjs/workspace-mount-hold-fence.mjs +95 -0
- package/dist/types/command-launcher.d.ts +2 -1
- package/dist/types/main.d.ts +30 -19
- package/dist/types/project-checkout-garbage.d.ts +19 -2
- package/dist/types/project-workspace-state.d.ts +9 -9
- package/dist/types/project-worktrees.d.ts +49 -5
- package/dist/types/recovery-journal-protocol.d.ts +35 -0
- package/dist/types/recovery-journal-runtime.d.ts +12 -0
- package/dist/types/recovery-journal-stall-fixture.d.ts +1 -0
- package/dist/types/recovery-journal-thread.d.ts +1 -0
- package/dist/types/recovery-journal.d.ts +246 -0
- package/dist/types/recovery-store.d.ts +6 -0
- package/dist/types/session-file-mutations.d.ts +22 -0
- package/dist/types/workspace-command-sync-policy.d.ts +22 -7
- package/dist/types/workspace-filesystem-executor-thread.d.ts +1 -0
- package/dist/types/workspace-filesystem-executor.d.ts +123 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +246 -0
- package/dist/types/workspace-filesystem-jobs.d.ts +7 -0
- package/dist/types/workspace-git-sync.d.ts +113 -7
- package/dist/types/workspace-mount-hold-fence.d.ts +42 -0
- package/package.json +1 -1
- package/dist/cjs/project-snapshot-recovery-runner.cjs +0 -171
- package/dist/mjs/project-snapshot-recovery-runner.mjs +0 -135
- package/dist/types/project-snapshot-recovery-runner.d.ts +0 -10
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { ProjectWorktreeBranch, ProjectWorktreeSnapshot, ProjectWorktreeSnapshotProgress, ProjectWorktreeSnapshotScope, ProjectWorktreeWorkingTreeMode } from "./project-worktrees";
|
|
2
|
+
import type { WorkingTreeGitlink } from "./working-tree-mirror";
|
|
3
|
+
import type { WorkspaceGitMountData, WorkspaceHydrationReceipt } from "./workspace-git-sync";
|
|
4
|
+
import type { WorkspaceMergeProjectionMount, WorkspaceMergeProjectionResult } from "./workspace-merge-projection";
|
|
5
|
+
import type { ProjectCheckoutPathMoveBranch } from "./workspace-path-move";
|
|
6
|
+
/**
|
|
7
|
+
* The closed set of filesystem transactions the worker runs on its executor
|
|
8
|
+
* thread. Every input is plain data (paths, mount descriptors, receipts); the
|
|
9
|
+
* busy predicates, mutation tokens, hooks and gates that decide *whether* a
|
|
10
|
+
* job may run stay on the control thread. Every body is the synchronous code
|
|
11
|
+
* that used to run on the control thread, unchanged in its write and fsync
|
|
12
|
+
* ordering. A job kind outside this table is rejected by both threads.
|
|
13
|
+
*/
|
|
14
|
+
export type WorkspaceFilesystemJobTable = {
|
|
15
|
+
/** `recoverWorkspaceCheckoutDurability`: rebuild an interrupted outer checkout boundary. */
|
|
16
|
+
checkout_durability_recovery: {
|
|
17
|
+
input: {
|
|
18
|
+
workspacePath: string;
|
|
19
|
+
preserveResolutionInProgress: boolean;
|
|
20
|
+
};
|
|
21
|
+
result: null;
|
|
22
|
+
};
|
|
23
|
+
/** Checkout durability recovery, pending hydration transaction restore/completion, basis-ref repair. */
|
|
24
|
+
hydration_recovery: {
|
|
25
|
+
input: {
|
|
26
|
+
workspacePath: string;
|
|
27
|
+
preserveResolutionInProgress: boolean;
|
|
28
|
+
};
|
|
29
|
+
result: null;
|
|
30
|
+
};
|
|
31
|
+
/** The complete hydration transaction: snapshot, hydrate, fsync, commit marker, receipt; restore on failure. */
|
|
32
|
+
hydration_transaction: {
|
|
33
|
+
input: {
|
|
34
|
+
workspacePath: string;
|
|
35
|
+
hydrationMounts: WorkspaceGitMountData[];
|
|
36
|
+
advancedDurabilityMounts: WorkspaceGitMountData[];
|
|
37
|
+
targetReceipt: WorkspaceHydrationReceipt | null;
|
|
38
|
+
};
|
|
39
|
+
result: {
|
|
40
|
+
hydratedMountIds: string[];
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/** Pre-clone hydration of visible mounts without a transaction (no outer Git directory yet). */
|
|
44
|
+
hydration_raw: {
|
|
45
|
+
input: {
|
|
46
|
+
workspacePath: string;
|
|
47
|
+
mounts: WorkspaceGitMountData[];
|
|
48
|
+
};
|
|
49
|
+
result: {
|
|
50
|
+
hydratedMountIds: string[];
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/** Recover the durable checkout boundary and record the transition that follows. */
|
|
54
|
+
checkout_transition_begin: {
|
|
55
|
+
input: {
|
|
56
|
+
workspacePath: string;
|
|
57
|
+
};
|
|
58
|
+
result: null;
|
|
59
|
+
};
|
|
60
|
+
/** Make the outer checkout durable and record its head as the durable boundary. */
|
|
61
|
+
checkout_transition_complete: {
|
|
62
|
+
input: {
|
|
63
|
+
workspacePath: string;
|
|
64
|
+
};
|
|
65
|
+
result: null;
|
|
66
|
+
};
|
|
67
|
+
/** Durability barrier over the outer checkout tree without changing the record. */
|
|
68
|
+
outer_checkout_fsync: {
|
|
69
|
+
input: {
|
|
70
|
+
workspacePath: string;
|
|
71
|
+
};
|
|
72
|
+
result: null;
|
|
73
|
+
};
|
|
74
|
+
/** Three-way merge projection of one stale mount (ours snapshot + merge-tree). */
|
|
75
|
+
projection_merge: {
|
|
76
|
+
input: {
|
|
77
|
+
workspacePath: string;
|
|
78
|
+
mount: WorkspaceMergeProjectionMount;
|
|
79
|
+
basisHead: string;
|
|
80
|
+
currentHead: string;
|
|
81
|
+
attemptId: string;
|
|
82
|
+
};
|
|
83
|
+
result: WorkspaceMergeProjectionResult;
|
|
84
|
+
};
|
|
85
|
+
/** Byte mirror of one visible mount into the outer checkout. */
|
|
86
|
+
projection_mirror: {
|
|
87
|
+
input: {
|
|
88
|
+
workspacePath: string;
|
|
89
|
+
mount: WorkspaceGitMountData;
|
|
90
|
+
};
|
|
91
|
+
result: {
|
|
92
|
+
gitlinks: WorkingTreeGitlink[];
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
/** Outer-tree edits after the mirrors: tombstone removal, merged subtree materialization, deferred restores. */
|
|
96
|
+
projection_outer_apply: {
|
|
97
|
+
input: {
|
|
98
|
+
workspacePath: string;
|
|
99
|
+
removeMounts: WorkspaceGitMountData[];
|
|
100
|
+
materialize: Array<{
|
|
101
|
+
workspaceRelativePath: string;
|
|
102
|
+
resultTree: string;
|
|
103
|
+
}>;
|
|
104
|
+
restoreFromHead: WorkspaceGitMountData[];
|
|
105
|
+
};
|
|
106
|
+
result: null;
|
|
107
|
+
};
|
|
108
|
+
/** Persist projected visible mounts and publish the projection receipt. */
|
|
109
|
+
projection_receipt: {
|
|
110
|
+
input: {
|
|
111
|
+
workspacePath: string;
|
|
112
|
+
fsyncMounts: WorkspaceGitMountData[];
|
|
113
|
+
receipt: WorkspaceHydrationReceipt;
|
|
114
|
+
};
|
|
115
|
+
result: null;
|
|
116
|
+
};
|
|
117
|
+
/** `snapshotBranchTrees`: copy every checkout a reconciliation may rewrite. */
|
|
118
|
+
project_snapshot_create: {
|
|
119
|
+
input: {
|
|
120
|
+
projectRoot: string;
|
|
121
|
+
branches: ProjectWorktreeBranch[];
|
|
122
|
+
temporaryRoot: string;
|
|
123
|
+
scope: ProjectWorktreeSnapshotScope;
|
|
124
|
+
ownerSessionId: string;
|
|
125
|
+
};
|
|
126
|
+
result: ProjectWorktreeSnapshot;
|
|
127
|
+
};
|
|
128
|
+
/** Mirror captured checkouts back over the reconciled worktrees. */
|
|
129
|
+
project_snapshot_apply: {
|
|
130
|
+
input: {
|
|
131
|
+
mirrors: Array<{
|
|
132
|
+
snapshotPath: string;
|
|
133
|
+
targetPath: string;
|
|
134
|
+
}>;
|
|
135
|
+
};
|
|
136
|
+
result: null;
|
|
137
|
+
};
|
|
138
|
+
/** `persistReconciledProjectAndConsumeSnapshot`. */
|
|
139
|
+
project_snapshot_persist: {
|
|
140
|
+
input: {
|
|
141
|
+
snapshot: ProjectWorktreeSnapshot;
|
|
142
|
+
durabilityPaths: string[] | null;
|
|
143
|
+
};
|
|
144
|
+
result: null;
|
|
145
|
+
};
|
|
146
|
+
/** `rollbackProjectWorktreeSnapshot`. */
|
|
147
|
+
project_snapshot_rollback: {
|
|
148
|
+
input: {
|
|
149
|
+
snapshot: ProjectWorktreeSnapshot;
|
|
150
|
+
};
|
|
151
|
+
result: null;
|
|
152
|
+
};
|
|
153
|
+
/** `recoverStaleProjectWorktreeSnapshots` at startup. */
|
|
154
|
+
project_snapshots_recover: {
|
|
155
|
+
input: {
|
|
156
|
+
projectsRoot: string;
|
|
157
|
+
temporaryRoot: string;
|
|
158
|
+
currentProcessId: number;
|
|
159
|
+
currentOwnerSessionId: string;
|
|
160
|
+
};
|
|
161
|
+
result: {
|
|
162
|
+
restored: string[];
|
|
163
|
+
removed: string[];
|
|
164
|
+
failed: Array<{
|
|
165
|
+
path: string;
|
|
166
|
+
error: string;
|
|
167
|
+
}>;
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
/** `createLinkedProjectBranch`: branch ref, linked worktree, index seed and working-tree copy. */
|
|
171
|
+
project_branch_create: {
|
|
172
|
+
input: {
|
|
173
|
+
projectRoot: string;
|
|
174
|
+
sourceBranchName: string;
|
|
175
|
+
branchName: string;
|
|
176
|
+
workingTree: ProjectWorktreeWorkingTreeMode;
|
|
177
|
+
};
|
|
178
|
+
result: {
|
|
179
|
+
branchPath: string;
|
|
180
|
+
baseCommitHash: string;
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
/** `preserveProjectCheckoutPathMove`. */
|
|
184
|
+
project_checkout_path_move: {
|
|
185
|
+
input: {
|
|
186
|
+
oldProjectRoot: string;
|
|
187
|
+
newProjectRoot: string;
|
|
188
|
+
projectsDurabilityRoot: string;
|
|
189
|
+
outerWorkspaceRoot: string;
|
|
190
|
+
branches: ProjectCheckoutPathMoveBranch[];
|
|
191
|
+
};
|
|
192
|
+
result: {
|
|
193
|
+
preservedBranchNames: string[];
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
export type WorkspaceFilesystemJobKind = keyof WorkspaceFilesystemJobTable;
|
|
198
|
+
export type WorkspaceFilesystemJobInput<K extends WorkspaceFilesystemJobKind> = WorkspaceFilesystemJobTable[K]["input"];
|
|
199
|
+
export type WorkspaceFilesystemJobResult<K extends WorkspaceFilesystemJobKind> = WorkspaceFilesystemJobTable[K]["result"];
|
|
200
|
+
export type WorkspaceFilesystemJobProgress = ProjectWorktreeSnapshotProgress;
|
|
201
|
+
export type WorkspaceFilesystemJobContext = {
|
|
202
|
+
progress: (progress: WorkspaceFilesystemJobProgress) => void;
|
|
203
|
+
};
|
|
204
|
+
export type WorkspaceFilesystemJobOperations = {
|
|
205
|
+
[K in WorkspaceFilesystemJobKind]: (input: WorkspaceFilesystemJobInput<K>, context: WorkspaceFilesystemJobContext) => WorkspaceFilesystemJobResult<K>;
|
|
206
|
+
};
|
|
207
|
+
export declare const WORKSPACE_FILESYSTEM_JOB_KINDS: readonly WorkspaceFilesystemJobKind[];
|
|
208
|
+
export declare function isWorkspaceFilesystemJobKind(value: unknown): value is WorkspaceFilesystemJobKind;
|
|
209
|
+
/** Short, path-bearing description used in stall diagnostics and errors. */
|
|
210
|
+
export declare function describeWorkspaceFilesystemJob<K extends WorkspaceFilesystemJobKind>(kind: K, input: WorkspaceFilesystemJobInput<K>): string;
|
|
211
|
+
/** Errors cross the thread boundary as data; the properties callers inspect are preserved. */
|
|
212
|
+
export type WorkspaceFilesystemSerializedError = {
|
|
213
|
+
name: string;
|
|
214
|
+
message: string;
|
|
215
|
+
stack?: string;
|
|
216
|
+
code?: string;
|
|
217
|
+
branchMayExist?: true;
|
|
218
|
+
cleanupFailures?: string[];
|
|
219
|
+
errors?: WorkspaceFilesystemSerializedError[];
|
|
220
|
+
cause?: WorkspaceFilesystemSerializedError;
|
|
221
|
+
};
|
|
222
|
+
export declare function serializeWorkspaceFilesystemError(error: unknown, depth?: number): WorkspaceFilesystemSerializedError;
|
|
223
|
+
export declare function restoreWorkspaceFilesystemError(serialized: WorkspaceFilesystemSerializedError): Error;
|
|
224
|
+
/** Control thread → executor thread. */
|
|
225
|
+
export type WorkspaceFilesystemThreadRequest = {
|
|
226
|
+
type: "run";
|
|
227
|
+
id: number;
|
|
228
|
+
kind: WorkspaceFilesystemJobKind;
|
|
229
|
+
input: unknown;
|
|
230
|
+
/** The control thread's environment at dispatch; the thread runs the job under exactly this. */
|
|
231
|
+
environment: Record<string, string>;
|
|
232
|
+
};
|
|
233
|
+
/** Executor thread → control thread. */
|
|
234
|
+
export type WorkspaceFilesystemThreadReply = {
|
|
235
|
+
type: "progress";
|
|
236
|
+
id: number;
|
|
237
|
+
progress: WorkspaceFilesystemJobProgress;
|
|
238
|
+
} | {
|
|
239
|
+
type: "result";
|
|
240
|
+
id: number;
|
|
241
|
+
result: unknown;
|
|
242
|
+
} | {
|
|
243
|
+
type: "failure";
|
|
244
|
+
id: number;
|
|
245
|
+
error: WorkspaceFilesystemSerializedError;
|
|
246
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorkspaceFilesystemJobOperations } from "./workspace-filesystem-job-types";
|
|
2
|
+
/**
|
|
3
|
+
* The operation table of the workspace filesystem executor thread. It is the
|
|
4
|
+
* only thing the thread can run; every entry is an existing synchronous
|
|
5
|
+
* transaction body. Imported by the thread entry module only.
|
|
6
|
+
*/
|
|
7
|
+
export declare const workspaceFilesystemJobOperations: WorkspaceFilesystemJobOperations;
|
|
@@ -5,6 +5,27 @@ export declare const WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION = "r5d-confi
|
|
|
5
5
|
export declare const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
|
|
6
6
|
export declare const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
|
|
7
7
|
export declare const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
|
|
8
|
+
type WorkspaceHydrationMountBasis = {
|
|
9
|
+
id: string;
|
|
10
|
+
incarnationKey: string;
|
|
11
|
+
sourcePath: string;
|
|
12
|
+
durabilityRootPath: string;
|
|
13
|
+
workspaceRelativePath: string;
|
|
14
|
+
sourceMode: WorkingTreeSourceMode;
|
|
15
|
+
hydrateDeletionMode: "all" | "git";
|
|
16
|
+
preserveLocalOnInitialOuterAbsence: boolean;
|
|
17
|
+
preserveLocalOnHydrationBasisChange: boolean;
|
|
18
|
+
deleteWhenSourceMissing: boolean;
|
|
19
|
+
};
|
|
20
|
+
type WorkspaceHydrationMountReceipt = WorkspaceHydrationMountBasis & {
|
|
21
|
+
head: string;
|
|
22
|
+
};
|
|
23
|
+
export type WorkspaceHydrationReceipt = {
|
|
24
|
+
version: 3;
|
|
25
|
+
mounts: WorkspaceHydrationMountReceipt[];
|
|
26
|
+
/** Non-enumerable read-compat marker used only to finish a v1/v2 crash transition. */
|
|
27
|
+
legacyHead?: string;
|
|
28
|
+
};
|
|
8
29
|
export type WorkspaceGitMount = {
|
|
9
30
|
id: string;
|
|
10
31
|
/** Durable logical incarnation (for example the project-branch row id). */
|
|
@@ -49,6 +70,15 @@ export type WorkspaceGitMount = {
|
|
|
49
70
|
/** Activity-only fence used before checkout readiness is established on reconnect. */
|
|
50
71
|
busyForRecovery?: () => boolean;
|
|
51
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* The data of a mount, without its control-thread predicates. This is what a
|
|
75
|
+
* workspace filesystem job receives: a job never evaluates `busy()` or
|
|
76
|
+
* `mutationToken()`; the control thread evaluated them before dispatch and
|
|
77
|
+
* holds the mount (see WorkspaceHydrationTransactionHooks.holdMounts) until
|
|
78
|
+
* the job ends.
|
|
79
|
+
*/
|
|
80
|
+
export type WorkspaceGitMountData = Pick<WorkspaceGitMount, "id" | "hydrationIncarnationKey" | "sourcePath" | "durabilityRootPath" | "workspaceRelativePath" | "sourceMode" | "hydrateDeletionMode" | "preserveLocalOnInitialOuterAbsence" | "preserveLocalOnHydrationBasisChange" | "initialPublicationSourceRelativePath" | "deleteWhenSourceMissing" | "lastProjectedMutationToken">;
|
|
81
|
+
export declare function workspaceGitMountData(mount: WorkspaceGitMount): WorkspaceGitMountData;
|
|
52
82
|
/**
|
|
53
83
|
* Incident-bound configuration keeps project checkouts unready while the
|
|
54
84
|
* canonical remediation owns the workspace. The guarded remediation sync is
|
|
@@ -65,17 +95,23 @@ export type WorkspaceHydrationTransactionContext = {
|
|
|
65
95
|
mountIds: string[];
|
|
66
96
|
};
|
|
67
97
|
/**
|
|
68
|
-
* Observes
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
98
|
+
* Observes a hydration transaction (snapshot, hydration pass, durability
|
|
99
|
+
* barrier, receipt) and fences the mounts any workspace filesystem job reads
|
|
100
|
+
* or writes. The body runs on the executor thread and on a stalled disk it
|
|
101
|
+
* can run for a long time. `beforeTransaction` is awaited after the last
|
|
102
|
+
* asynchronous precheck and before the final busy observation, so it may
|
|
103
|
+
* announce the operation and yield once; `afterTransaction` runs after the
|
|
104
|
+
* body committed or rolled back. `holdMounts` is called synchronously right
|
|
105
|
+
* after the busy observation that selected the mounts, before the job is
|
|
106
|
+
* dispatched, and its release is called when the job's result or failure
|
|
107
|
+
* arrived: while held, command admission for those mounts waits (see
|
|
108
|
+
* WorkspaceMountHoldFence), which replaces the guarantee the un-yielding body
|
|
109
|
+
* used to give.
|
|
75
110
|
*/
|
|
76
111
|
export type WorkspaceHydrationTransactionHooks = {
|
|
77
112
|
beforeTransaction: (context: WorkspaceHydrationTransactionContext) => Promise<void> | void;
|
|
78
113
|
afterTransaction: (context: WorkspaceHydrationTransactionContext) => void;
|
|
114
|
+
holdMounts: (mountIds: readonly string[], description: string) => () => void;
|
|
79
115
|
};
|
|
80
116
|
export type WorkspaceGitSyncOutcome = "no_change" | "updated" | "pushed" | "large_diff_blocked" | "conflict_blocked";
|
|
81
117
|
export type WorkspaceGitSyncResult = {
|
|
@@ -126,6 +162,23 @@ export declare function workspaceGitHydrationIsCurrent(workspacePath: string, mo
|
|
|
126
162
|
* Configuration-time recovery keeps the strict predicate.
|
|
127
163
|
*/
|
|
128
164
|
export declare function workspaceGitHydrationIsRecoverable(workspacePath: string, mounts?: readonly WorkspaceGitMount[]): boolean;
|
|
165
|
+
/** Executor job body: recover the durable boundary, then record the transition that follows. */
|
|
166
|
+
export declare function runCheckoutTransitionBeginJob(input: {
|
|
167
|
+
workspacePath: string;
|
|
168
|
+
}): null;
|
|
169
|
+
/** Executor job body: make the outer checkout durable and record its head as the durable boundary. */
|
|
170
|
+
export declare function runCheckoutTransitionCompleteJob(input: {
|
|
171
|
+
workspacePath: string;
|
|
172
|
+
}): null;
|
|
173
|
+
/** Executor job body: `recoverWorkspaceCheckoutDurability`. */
|
|
174
|
+
export declare function runCheckoutDurabilityRecoveryJob(input: {
|
|
175
|
+
workspacePath: string;
|
|
176
|
+
preserveResolutionInProgress: boolean;
|
|
177
|
+
}): null;
|
|
178
|
+
/** Executor job body: the outer-tree durability barrier alone. */
|
|
179
|
+
export declare function runOuterCheckoutFsyncJob(input: {
|
|
180
|
+
workspacePath: string;
|
|
181
|
+
}): null;
|
|
129
182
|
declare function configureWorkspaceRepository(input: {
|
|
130
183
|
workspacePath: string;
|
|
131
184
|
remoteUrl: string;
|
|
@@ -179,6 +232,36 @@ type ProjectedMountGitlinks = {
|
|
|
179
232
|
* `stageWorkspaceGitlinks` before `git add -A`, which can never create one.
|
|
180
233
|
*/
|
|
181
234
|
declare function mirrorMountsToWorkspace(workspacePath: string, mounts: readonly WorkspaceGitMount[]): ProjectedMountGitlinks[];
|
|
235
|
+
/**
|
|
236
|
+
* Executor job body: the complete hydration transaction. Snapshot exactly
|
|
237
|
+
* what hydration replaces, hydrate, cross the durability barrier, write the
|
|
238
|
+
* commit marker, publish the receipt, remove the transaction; on failure
|
|
239
|
+
* restore the snapshot unless the visible tree is already durable. The write
|
|
240
|
+
* and fsync ordering is the one the control thread used to run inline.
|
|
241
|
+
*/
|
|
242
|
+
export declare function runHydrationTransactionJob(input: {
|
|
243
|
+
workspacePath: string;
|
|
244
|
+
hydrationMounts: readonly WorkspaceGitMountData[];
|
|
245
|
+
advancedDurabilityMounts: readonly WorkspaceGitMountData[];
|
|
246
|
+
targetReceipt: WorkspaceHydrationReceipt | null;
|
|
247
|
+
}): {
|
|
248
|
+
hydratedMountIds: string[];
|
|
249
|
+
};
|
|
250
|
+
/** Executor job body: hydrate visible mounts before an outer clone exists (no transaction to record). */
|
|
251
|
+
export declare function runHydrationRawJob(input: {
|
|
252
|
+
workspacePath: string;
|
|
253
|
+
mounts: readonly WorkspaceGitMountData[];
|
|
254
|
+
}): {
|
|
255
|
+
hydratedMountIds: string[];
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Executor job body: checkout durability recovery, pending hydration
|
|
259
|
+
* transaction restore or completion, and basis-ref repair, in that order.
|
|
260
|
+
*/
|
|
261
|
+
export declare function runHydrationRecoveryJob(input: {
|
|
262
|
+
workspacePath: string;
|
|
263
|
+
preserveResolutionInProgress: boolean;
|
|
264
|
+
}): null;
|
|
182
265
|
export declare function hydrateWorkspaceGitMounts(workspacePath: string, mounts: readonly WorkspaceGitMount[], options?: {
|
|
183
266
|
ignoreBusy?: boolean;
|
|
184
267
|
hydrationHooks?: WorkspaceHydrationTransactionHooks;
|
|
@@ -257,6 +340,29 @@ export type SynchronizeWorkspaceGitInput = {
|
|
|
257
340
|
hydrationHooks?: WorkspaceHydrationTransactionHooks;
|
|
258
341
|
};
|
|
259
342
|
export declare function synchronizeWorkspaceGit(input: SynchronizeWorkspaceGitInput): Promise<WorkspaceGitSyncResult>;
|
|
343
|
+
/** Executor job body: byte-mirror one visible mount into the outer checkout. */
|
|
344
|
+
export declare function runProjectionMirrorJob(input: {
|
|
345
|
+
workspacePath: string;
|
|
346
|
+
mount: WorkspaceGitMountData;
|
|
347
|
+
}): {
|
|
348
|
+
gitlinks: WorkingTreeGitlink[];
|
|
349
|
+
};
|
|
350
|
+
/** Executor job body: outer-tree edits that follow the mirrors, in the cycle's order. */
|
|
351
|
+
export declare function runProjectionOuterApplyJob(input: {
|
|
352
|
+
workspacePath: string;
|
|
353
|
+
removeMounts: readonly WorkspaceGitMountData[];
|
|
354
|
+
materialize: ReadonlyArray<{
|
|
355
|
+
workspaceRelativePath: string;
|
|
356
|
+
resultTree: string;
|
|
357
|
+
}>;
|
|
358
|
+
restoreFromHead: readonly WorkspaceGitMountData[];
|
|
359
|
+
}): null;
|
|
360
|
+
/** Executor job body: persist projected visible mounts, then publish the projection receipt. */
|
|
361
|
+
export declare function runProjectionReceiptJob(input: {
|
|
362
|
+
workspacePath: string;
|
|
363
|
+
fsyncMounts: readonly WorkspaceGitMountData[];
|
|
364
|
+
receipt: WorkspaceHydrationReceipt;
|
|
365
|
+
}): null;
|
|
260
366
|
export declare const workspaceGitSyncTestHarness: {
|
|
261
367
|
commandArgs: typeof gitCommandArgs;
|
|
262
368
|
workspaceCloneCommandArgs: typeof workspaceCloneCommandArgs;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks the mounts whose bytes a workspace filesystem job is reading or
|
|
3
|
+
* writing on the executor thread. The sync engine takes a hold immediately
|
|
4
|
+
* after the busy observation that selected a mount, in the same synchronous
|
|
5
|
+
* stretch, and releases it when the job's result or failure arrives. Command
|
|
6
|
+
* admission waits on the fence before reserving a mount, so no tracked
|
|
7
|
+
* process, PTY or file operation can start in a mount while a job owns it,
|
|
8
|
+
* and no job can start on a mount a command already reserved (that mount was
|
|
9
|
+
* busy at the observation). Holds are counted per mount; overlapping holds
|
|
10
|
+
* (a recovery pass and a projection of the same mount cannot overlap under the
|
|
11
|
+
* mutation gate, but counting keeps the fence correct regardless) release in
|
|
12
|
+
* any order.
|
|
13
|
+
*/
|
|
14
|
+
export type WorkspaceMountHoldRelease = () => void;
|
|
15
|
+
export declare class WorkspaceMountHoldAbortedError extends Error {
|
|
16
|
+
constructor(reason: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class WorkspaceMountHoldFence {
|
|
19
|
+
private readonly holds;
|
|
20
|
+
private readonly waiters;
|
|
21
|
+
private readonly now;
|
|
22
|
+
constructor(options?: {
|
|
23
|
+
now?: () => number;
|
|
24
|
+
});
|
|
25
|
+
/** Take one hold on every listed mount; the returned release is idempotent. */
|
|
26
|
+
hold(mountIds: readonly string[], description: string): WorkspaceMountHoldRelease;
|
|
27
|
+
isHeld(mountIds: readonly string[]): boolean;
|
|
28
|
+
/** Human-readable holder of the first held mount among `mountIds`, for diagnostics. */
|
|
29
|
+
describeHold(mountIds: readonly string[]): string | null;
|
|
30
|
+
/** Every held mount id, sorted, for status reporting. */
|
|
31
|
+
heldMountIds(): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Resolves in the same turn as the release that leaves none of `mountIds`
|
|
34
|
+
* held, so the caller can re-check and reserve without an interleaving
|
|
35
|
+
* hold. Resolves immediately when nothing is held. Rejects with
|
|
36
|
+
* WorkspaceMountHoldAbortedError when `signal` aborts first.
|
|
37
|
+
*/
|
|
38
|
+
waitForRelease(mountIds: readonly string[], options?: {
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
private wake;
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var project_snapshot_recovery_runner_exports = {};
|
|
30
|
-
__export(project_snapshot_recovery_runner_exports, {
|
|
31
|
-
PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND: () => PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND,
|
|
32
|
-
recoverProjectSnapshotsInChild: () => recoverProjectSnapshotsInChild,
|
|
33
|
-
runProjectSnapshotRecoveryHelper: () => runProjectSnapshotRecoveryHelper
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(project_snapshot_recovery_runner_exports);
|
|
36
|
-
var import_node_path = __toESM(require("node:path"), 1);
|
|
37
|
-
var import_node_child_process = require("node:child_process");
|
|
38
|
-
var import_project_worktrees = require("./project-worktrees.cjs");
|
|
39
|
-
const PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND = "__recover-project-snapshots";
|
|
40
|
-
function requireAbsolutePath(value, label) {
|
|
41
|
-
if (!value) throw new Error(`Missing ${label}`);
|
|
42
|
-
const resolved = import_node_path.default.resolve(value);
|
|
43
|
-
if (resolved !== value) throw new Error(`${label} must be an absolute path`);
|
|
44
|
-
return resolved;
|
|
45
|
-
}
|
|
46
|
-
function parseHelperArguments(args) {
|
|
47
|
-
let projectsRoot;
|
|
48
|
-
let snapshotRoot;
|
|
49
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
50
|
-
const argument = args[index];
|
|
51
|
-
if (argument === "--projects-root") {
|
|
52
|
-
projectsRoot = args[index + 1];
|
|
53
|
-
index += 1;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (argument === "--snapshot-root") {
|
|
57
|
-
snapshotRoot = args[index + 1];
|
|
58
|
-
index += 1;
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
throw new Error(`Unknown project snapshot recovery helper argument: ${argument ?? "(missing)"}`);
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
projectsRoot: requireAbsolutePath(projectsRoot, "projects root"),
|
|
65
|
-
snapshotRoot: requireAbsolutePath(snapshotRoot, "snapshot root")
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function writeHelperMessage(message) {
|
|
69
|
-
process.stdout.write(`${JSON.stringify(message)}
|
|
70
|
-
`);
|
|
71
|
-
}
|
|
72
|
-
function runProjectSnapshotRecoveryHelper(args) {
|
|
73
|
-
const { projectsRoot, snapshotRoot } = parseHelperArguments(args);
|
|
74
|
-
const result = (0, import_project_worktrees.recoverStaleProjectWorktreeSnapshots)({
|
|
75
|
-
projectsRoot,
|
|
76
|
-
temporaryRoot: snapshotRoot,
|
|
77
|
-
onProgress: (progress) => writeHelperMessage({ type: "progress", progress })
|
|
78
|
-
});
|
|
79
|
-
writeHelperMessage({ type: "result", result });
|
|
80
|
-
return result.failed.length === 0 ? 0 : 1;
|
|
81
|
-
}
|
|
82
|
-
function isRecoveryProgress(value) {
|
|
83
|
-
if (!value || typeof value !== "object") return false;
|
|
84
|
-
const progress = value;
|
|
85
|
-
return progress.operation === "restore" && (progress.phase === "scanning" || progress.phase === "copying" || progress.phase === "moving" || progress.phase === "syncing" || progress.phase === "cleaning") && typeof progress.projectRoot === "string" && Number.isSafeInteger(progress.completedBranches) && Number.isSafeInteger(progress.totalBranches) && typeof progress.completedBytes === "number" && Number.isFinite(progress.completedBytes) && (progress.totalBytes === void 0 || typeof progress.totalBytes === "number" && Number.isFinite(progress.totalBytes));
|
|
86
|
-
}
|
|
87
|
-
function isRecoveryResult(value) {
|
|
88
|
-
if (!value || typeof value !== "object") return false;
|
|
89
|
-
const result = value;
|
|
90
|
-
return Array.isArray(result.restored) && Array.isArray(result.removed) && Array.isArray(result.failed);
|
|
91
|
-
}
|
|
92
|
-
function recoveryChildEnvironment() {
|
|
93
|
-
const environment = {};
|
|
94
|
-
for (const name of ["PATH", "TMPDIR", "TMP", "TEMP", "SystemRoot", "WINDIR"]) {
|
|
95
|
-
const value = process.env[name];
|
|
96
|
-
if (value !== void 0) environment[name] = value;
|
|
97
|
-
}
|
|
98
|
-
return environment;
|
|
99
|
-
}
|
|
100
|
-
async function recoverProjectSnapshotsInChild(input) {
|
|
101
|
-
const entrypoint = process.argv[1];
|
|
102
|
-
if (!entrypoint) throw new Error("Cannot locate the r5d-worker runtime entrypoint for snapshot recovery");
|
|
103
|
-
const child = (0, import_node_child_process.spawn)(
|
|
104
|
-
process.execPath,
|
|
105
|
-
[
|
|
106
|
-
entrypoint,
|
|
107
|
-
PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND,
|
|
108
|
-
"--projects-root",
|
|
109
|
-
import_node_path.default.resolve(input.projectsRoot),
|
|
110
|
-
"--snapshot-root",
|
|
111
|
-
import_node_path.default.resolve(input.snapshotRoot)
|
|
112
|
-
],
|
|
113
|
-
{
|
|
114
|
-
env: recoveryChildEnvironment(),
|
|
115
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
116
|
-
}
|
|
117
|
-
);
|
|
118
|
-
const terminateOnParentExit = () => {
|
|
119
|
-
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
|
|
120
|
-
};
|
|
121
|
-
const parentProcessEvents = process;
|
|
122
|
-
parentProcessEvents.once("exit", terminateOnParentExit);
|
|
123
|
-
let stdout = "";
|
|
124
|
-
let stderr = "";
|
|
125
|
-
let result;
|
|
126
|
-
child.stdout.setEncoding("utf8");
|
|
127
|
-
child.stdout.on("data", (chunk) => {
|
|
128
|
-
stdout += chunk;
|
|
129
|
-
while (true) {
|
|
130
|
-
const newline = stdout.indexOf("\n");
|
|
131
|
-
if (newline < 0) break;
|
|
132
|
-
const line = stdout.slice(0, newline);
|
|
133
|
-
stdout = stdout.slice(newline + 1);
|
|
134
|
-
if (!line.trim()) continue;
|
|
135
|
-
let message;
|
|
136
|
-
try {
|
|
137
|
-
message = JSON.parse(line);
|
|
138
|
-
} catch {
|
|
139
|
-
stderr += `Invalid recovery helper output: ${line}
|
|
140
|
-
`;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
if (!message || typeof message !== "object") continue;
|
|
144
|
-
const record = message;
|
|
145
|
-
if (record.type === "progress" && isRecoveryProgress(record.progress)) input.onProgress?.(record.progress);
|
|
146
|
-
if (record.type === "result" && isRecoveryResult(record.result)) result = record.result;
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
child.stderr.setEncoding("utf8");
|
|
150
|
-
child.stderr.on("data", (chunk) => {
|
|
151
|
-
stderr = `${stderr}${chunk}`.slice(-32768);
|
|
152
|
-
});
|
|
153
|
-
const exitCode = await new Promise((resolve, reject) => {
|
|
154
|
-
child.once("error", reject);
|
|
155
|
-
child.once("close", (code, signal) => {
|
|
156
|
-
if (signal) reject(new Error(`Project snapshot recovery helper exited on signal ${signal}`));
|
|
157
|
-
else resolve(code ?? 1);
|
|
158
|
-
});
|
|
159
|
-
}).finally(() => parentProcessEvents.off("exit", terminateOnParentExit));
|
|
160
|
-
if (exitCode !== 0 || !result) {
|
|
161
|
-
const detail = stderr.trim();
|
|
162
|
-
throw new Error(detail || `Project snapshot recovery helper exited with status ${exitCode}`);
|
|
163
|
-
}
|
|
164
|
-
return result;
|
|
165
|
-
}
|
|
166
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
167
|
-
0 && (module.exports = {
|
|
168
|
-
PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND,
|
|
169
|
-
recoverProjectSnapshotsInChild,
|
|
170
|
-
runProjectSnapshotRecoveryHelper
|
|
171
|
-
});
|