@ricsam/r5d-worker 0.0.123 → 0.0.124
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/dist/cjs/main.cjs +488 -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/main.mjs +491 -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/main.d.ts +24 -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
|
@@ -11,6 +11,12 @@ export declare class WorkerRecoveryStore {
|
|
|
11
11
|
readonly ledgerId: string;
|
|
12
12
|
constructor(filename: string);
|
|
13
13
|
close(): void;
|
|
14
|
+
/**
|
|
15
|
+
* Run several store operations as one durable commit (one FULL fsync).
|
|
16
|
+
* Operations that open their own transaction nest as savepoints. A throw
|
|
17
|
+
* rolls the whole group back; callers treat that as a journal failure.
|
|
18
|
+
*/
|
|
19
|
+
batch<T>(operations: () => T): T;
|
|
14
20
|
private row;
|
|
15
21
|
admit(message: Message & {
|
|
16
22
|
requestId: string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File mutations (write, edit, plan updates) run on the asynchronous
|
|
3
|
+
* filesystem API, so a write can still be in flight when a session is
|
|
4
|
+
* stopped. A Stop acknowledges cleanup only after every mutation that had
|
|
5
|
+
* already started for the session has actually settled; mutations that were
|
|
6
|
+
* still queued re-check the session's cancellation immediately before their
|
|
7
|
+
* first write and never start. The tracker records the former; the admission
|
|
8
|
+
* fence inside each mutation's queue callback provides the latter.
|
|
9
|
+
*/
|
|
10
|
+
export declare class SessionFileMutationTracker {
|
|
11
|
+
private readonly inFlight;
|
|
12
|
+
/** Register a started mutation; it is forgotten when it settles, however it settles. */
|
|
13
|
+
track<T>(sessionId: string, mutation: Promise<T>): Promise<T>;
|
|
14
|
+
count(sessionId: string): number;
|
|
15
|
+
/**
|
|
16
|
+
* Resolves once every mutation tracked for the session at the time of the
|
|
17
|
+
* call has settled. Mutations started later are not joined: the session's
|
|
18
|
+
* cancellation marker, recorded before this is awaited, makes their
|
|
19
|
+
* admission recheck refuse them.
|
|
20
|
+
*/
|
|
21
|
+
settled(sessionId: string): Promise<void>;
|
|
22
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
type WorkspaceCommandTarget = {
|
|
1
|
+
export type WorkspaceCommandTarget = {
|
|
2
2
|
type: "project";
|
|
3
|
+
projectId: string;
|
|
4
|
+
branchName: string;
|
|
3
5
|
} | {
|
|
4
6
|
type: "workspace";
|
|
5
7
|
rootProfile: "visible_projects" | "canonical_sync";
|
|
@@ -8,16 +10,29 @@ type WorkspaceCommandMutationCoordinator = {
|
|
|
8
10
|
runMutation<T>(operation: () => Promise<T> | T): Promise<T>;
|
|
9
11
|
acquireMutation(): Promise<() => void>;
|
|
10
12
|
};
|
|
11
|
-
type WorkspaceSyncCompletionBarrier = {
|
|
13
|
+
export type WorkspaceSyncCompletionBarrier = {
|
|
12
14
|
afterCurrent(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Null when no workspace filesystem job holds the target's mount right now;
|
|
17
|
+
* otherwise a promise that settles once the current holders released it
|
|
18
|
+
* (rejecting with WorkspaceMountHoldAbortedError when `signal` aborts
|
|
19
|
+
* first). Workspace targets have no mount and always get null.
|
|
20
|
+
*/
|
|
21
|
+
mountHold(target: WorkspaceCommandTarget, signal?: AbortSignal): Promise<void> | null;
|
|
13
22
|
};
|
|
14
23
|
/**
|
|
15
|
-
* A project command may reserve
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
24
|
+
* A project command may reserve as soon as no workspace filesystem job holds
|
|
25
|
+
* its mount: per-mount hydration and projection observe busy-ness before they
|
|
26
|
+
* dispatch a job and hold the mount until the job ended, so a reservation
|
|
27
|
+
* taken here is never interleaved with a job reading or writing the mount. A
|
|
28
|
+
* visible-projects workspace command spans every project, so it keeps the
|
|
29
|
+
* whole-cycle barrier. Canonical remediation is serialized separately by the
|
|
30
|
+
* mutation gate. Commands without a workspace effect (incident-9 control
|
|
31
|
+
* commands among them) never come here and never wait.
|
|
19
32
|
*/
|
|
20
|
-
export declare function reserveWorkspaceCommandAfterCurrentSync(target: WorkspaceCommandTarget, coordinator: WorkspaceSyncCompletionBarrier, reserve: () => void
|
|
33
|
+
export declare function reserveWorkspaceCommandAfterCurrentSync(target: WorkspaceCommandTarget, coordinator: WorkspaceSyncCompletionBarrier, reserve: () => void, options?: {
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}): Promise<void>;
|
|
21
36
|
/**
|
|
22
37
|
* Commands in visible checkouts behave like independent Git clients and may
|
|
23
38
|
* overlap periodic synchronization. Canonical remediation commands mutate the
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { type WorkspaceFilesystemJobInput, type WorkspaceFilesystemJobKind, type WorkspaceFilesystemJobProgress, type WorkspaceFilesystemJobResult } from "./workspace-filesystem-job-types";
|
|
2
|
+
/**
|
|
3
|
+
* Bounded executor for the worker's workspace filesystem transactions.
|
|
4
|
+
*
|
|
5
|
+
* One thread runs one job at a time; the control thread only ever awaits.
|
|
6
|
+
* The point is the incident this was written for: a single `pread64` or
|
|
7
|
+
* `pwrite64` blocked in the kernel on a stalled network disk used to freeze
|
|
8
|
+
* the worker's only event loop for minutes, so no heartbeat, cancel or Stop
|
|
9
|
+
* could run. On the executor thread that same blocked syscall stalls exactly
|
|
10
|
+
* one job while the control thread keeps serving the socket.
|
|
11
|
+
*
|
|
12
|
+
* A thread rather than a helper process because a thread cannot outlive the
|
|
13
|
+
* process: when the worker dies, its in-flight syscall completes and nothing
|
|
14
|
+
* else is written afterwards, which is the crash model every recovery path
|
|
15
|
+
* already handles. Nothing is ever abandoned: a running job's promise settles
|
|
16
|
+
* only with the thread's reply or the thread's exit, so the gate its caller
|
|
17
|
+
* holds is held for the job's true duration. Only jobs that have not started
|
|
18
|
+
* can fail on the queue-wait bound.
|
|
19
|
+
*
|
|
20
|
+
* A thread that exits while a job is running leaves an unknown partial
|
|
21
|
+
* mutation behind. The executor then fails closed: the job and everything
|
|
22
|
+
* queued fail, no further job is accepted, and `onFailure` lets the worker
|
|
23
|
+
* take its established restart path, whose startup recovery (manifests,
|
|
24
|
+
* receipts, snapshot recovery) runs before any ordinary mutation. No
|
|
25
|
+
* replacement thread is started over that state in this process.
|
|
26
|
+
*/
|
|
27
|
+
export declare const WORKSPACE_FILESYSTEM_EXECUTOR_MAX_QUEUED = 32;
|
|
28
|
+
export declare const WORKSPACE_FILESYSTEM_EXECUTOR_QUEUE_TIMEOUT_MS: number;
|
|
29
|
+
export declare const WORKSPACE_FILESYSTEM_EXECUTOR_STALL_LOG_INTERVAL_MS = 30000;
|
|
30
|
+
export declare class WorkspaceFilesystemExecutorError extends Error {
|
|
31
|
+
readonly name = "WorkspaceFilesystemExecutorError";
|
|
32
|
+
}
|
|
33
|
+
/** The executor thread exited (crashed or was terminated) while a job was running. */
|
|
34
|
+
export declare class WorkspaceFilesystemExecutorThreadExitedError extends WorkspaceFilesystemExecutorError {
|
|
35
|
+
readonly exitCode: number | null;
|
|
36
|
+
readonly description: string;
|
|
37
|
+
constructor(exitCode: number | null, description: string, cause?: unknown);
|
|
38
|
+
}
|
|
39
|
+
export type WorkspaceFilesystemExecutorStatus = {
|
|
40
|
+
running: {
|
|
41
|
+
kind: WorkspaceFilesystemJobKind;
|
|
42
|
+
description: string;
|
|
43
|
+
startedAtMs: number;
|
|
44
|
+
ageMs: number;
|
|
45
|
+
} | null;
|
|
46
|
+
queued: number;
|
|
47
|
+
/** Set once the thread exited under a running job; every job is then rejected until the worker restarts. */
|
|
48
|
+
failed: string | null;
|
|
49
|
+
};
|
|
50
|
+
export type WorkspaceFilesystemExecutorOptions = {
|
|
51
|
+
/** Module the thread executes; must resolve next to the packaged executor module. */
|
|
52
|
+
threadModulePath: string;
|
|
53
|
+
log?: (line: string) => void;
|
|
54
|
+
now?: () => number;
|
|
55
|
+
maxQueued?: number;
|
|
56
|
+
queueTimeoutMs?: number;
|
|
57
|
+
stallLogIntervalMs?: number;
|
|
58
|
+
};
|
|
59
|
+
export declare class WorkspaceFilesystemExecutor {
|
|
60
|
+
private readonly threadModulePath;
|
|
61
|
+
private readonly log;
|
|
62
|
+
private readonly now;
|
|
63
|
+
private readonly maxQueued;
|
|
64
|
+
private readonly queueTimeoutMs;
|
|
65
|
+
private readonly stallLogIntervalMs;
|
|
66
|
+
private worker;
|
|
67
|
+
private readonly queue;
|
|
68
|
+
private running;
|
|
69
|
+
private nextJobId;
|
|
70
|
+
private failed;
|
|
71
|
+
private closed;
|
|
72
|
+
/** Invoked once when the executor fails closed (thread exit under a running job). */
|
|
73
|
+
onFailure: ((error: WorkspaceFilesystemExecutorThreadExitedError) => void) | undefined;
|
|
74
|
+
constructor(options: WorkspaceFilesystemExecutorOptions);
|
|
75
|
+
/**
|
|
76
|
+
* Run one job to completion on the executor thread. Resolves with the
|
|
77
|
+
* job's result; rejects with the job's own error (properties preserved),
|
|
78
|
+
* with a queue-wait or capacity error when the job never started, or with
|
|
79
|
+
* WorkspaceFilesystemExecutorThreadExitedError when the thread died under it.
|
|
80
|
+
*/
|
|
81
|
+
run<K extends WorkspaceFilesystemJobKind>(kind: K, input: WorkspaceFilesystemJobInput<K>, options?: {
|
|
82
|
+
onProgress?: (progress: WorkspaceFilesystemJobProgress) => void;
|
|
83
|
+
}): Promise<WorkspaceFilesystemJobResult<K>>;
|
|
84
|
+
status(): WorkspaceFilesystemExecutorStatus;
|
|
85
|
+
/**
|
|
86
|
+
* Stop accepting jobs, fail everything still queued and ask the thread to
|
|
87
|
+
* stop. A running job settles when the thread actually exits; a thread
|
|
88
|
+
* blocked in the kernel only exits with the process, which is the point.
|
|
89
|
+
*/
|
|
90
|
+
close(): void;
|
|
91
|
+
private describeRunning;
|
|
92
|
+
private expireQueued;
|
|
93
|
+
private dispatch;
|
|
94
|
+
private settleRunning;
|
|
95
|
+
private ensureWorker;
|
|
96
|
+
private lastThreadError;
|
|
97
|
+
private onReply;
|
|
98
|
+
private onExit;
|
|
99
|
+
}
|
|
100
|
+
/** The packaged thread module lives next to this module with the same extension (.ts in source, .cjs/.mjs in the package). */
|
|
101
|
+
export declare function workspaceFilesystemExecutorThreadModulePath(): string;
|
|
102
|
+
/** What the engine modules dispatch through; the thread executor in production. */
|
|
103
|
+
export type WorkspaceFilesystemJobRunner = Pick<WorkspaceFilesystemExecutor, "run">;
|
|
104
|
+
/**
|
|
105
|
+
* The worker process's executor. Like the workspace mutation gate it is
|
|
106
|
+
* process-global: in-process reconnects share it, so a job started by a
|
|
107
|
+
* previous connection generation is still the one running when the next
|
|
108
|
+
* generation's configuration queues behind it.
|
|
109
|
+
*/
|
|
110
|
+
export declare function workspaceFilesystemExecutor(): WorkspaceFilesystemJobRunner;
|
|
111
|
+
/** The worker runtime's fail-closed hook: installed once on the process executor (created if needed). */
|
|
112
|
+
export declare function installWorkspaceFilesystemExecutorFailureHandler(handler: (error: WorkspaceFilesystemExecutorThreadExitedError) => void): void;
|
|
113
|
+
/**
|
|
114
|
+
* Test seam. Engine tests that inject faults or count syscalls by replacing
|
|
115
|
+
* `node:fs` functions in their own thread cannot observe a body running on
|
|
116
|
+
* the executor thread; inside `runJobsInline` the same operation table runs
|
|
117
|
+
* on the calling thread instead. The boundary itself is tested with the real
|
|
118
|
+
* thread in workspace-filesystem-executor.test.ts. Never used in production.
|
|
119
|
+
*/
|
|
120
|
+
export declare const workspaceFilesystemExecutorTestHarness: {
|
|
121
|
+
runJobsInline<T>(body: () => Promise<T>): Promise<T>;
|
|
122
|
+
processExecutor(): WorkspaceFilesystemExecutor | null;
|
|
123
|
+
};
|
|
@@ -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
|
+
}
|