@ricsam/r5d-worker 0.0.131 → 0.0.133
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/atomic-rename.cjs +303 -0
- package/dist/cjs/git-blob-hash.cjs +41 -0
- package/dist/cjs/main.cjs +187 -38
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/three-way-merge.cjs +346 -0
- package/dist/cjs/working-tree-mirror.cjs +1049 -64
- package/dist/cjs/workspace-command-sync-policy.cjs +8 -4
- package/dist/cjs/workspace-command-targets.cjs +63 -0
- package/dist/cjs/workspace-filesystem-job-types.cjs +11 -1
- package/dist/cjs/workspace-filesystem-jobs.cjs +2 -0
- package/dist/cjs/workspace-git-sync.cjs +846 -61
- package/dist/cjs/workspace-hydration-ledger.cjs +66 -0
- package/dist/cjs/workspace-hydration-merge.cjs +433 -0
- package/dist/cjs/workspace-hydration-recovery-state.cjs +53 -0
- package/dist/cjs/workspace-merge-projection.cjs +81 -10
- package/dist/cjs/workspace-project-config-policy.cjs +19 -12
- package/dist/mjs/atomic-rename.mjs +261 -0
- package/dist/mjs/git-blob-hash.mjs +16 -0
- package/dist/mjs/main.mjs +196 -39
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/three-way-merge.mjs +318 -0
- package/dist/mjs/working-tree-mirror.mjs +1035 -64
- package/dist/mjs/workspace-command-sync-policy.mjs +8 -4
- package/dist/mjs/workspace-command-targets.mjs +37 -0
- package/dist/mjs/workspace-filesystem-job-types.mjs +11 -1
- package/dist/mjs/workspace-filesystem-jobs.mjs +4 -0
- package/dist/mjs/workspace-git-sync.mjs +854 -62
- package/dist/mjs/workspace-hydration-ledger.mjs +42 -0
- package/dist/mjs/workspace-hydration-merge.mjs +399 -0
- package/dist/mjs/workspace-hydration-recovery-state.mjs +29 -0
- package/dist/mjs/workspace-merge-projection.mjs +85 -11
- package/dist/mjs/workspace-project-config-policy.mjs +16 -10
- package/dist/types/atomic-rename.d.ts +78 -0
- package/dist/types/git-blob-hash.d.ts +10 -0
- package/dist/types/main.d.ts +21 -2
- package/dist/types/three-way-merge.d.ts +77 -0
- package/dist/types/working-tree-mirror.d.ts +270 -7
- package/dist/types/workspace-command-sync-policy.d.ts +12 -6
- package/dist/types/workspace-command-targets.d.ts +37 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +46 -4
- package/dist/types/workspace-git-sync.d.ts +125 -3
- package/dist/types/workspace-hydration-ledger.d.ts +43 -0
- package/dist/types/workspace-hydration-merge.d.ts +95 -0
- package/dist/types/workspace-hydration-recovery-state.d.ts +10 -0
- package/dist/types/workspace-merge-projection.d.ts +19 -1
- package/dist/types/workspace-project-config-policy.d.ts +17 -3
- package/package.json +2 -2
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declared additional targets of a command, PTY or one-shot exec.
|
|
3
|
+
*
|
|
4
|
+
* A server that forwards a session's live `checkout` claims attaches the
|
|
5
|
+
* claimed sibling checkouts as `additionalTargets`. The worker treats every
|
|
6
|
+
* listed project branch exactly like the command's own target for admission
|
|
7
|
+
* (every mount is reserved and waits on the hold fence), busy detection and
|
|
8
|
+
* mutation tokens, for the whole lifetime of the process: the list is fixed
|
|
9
|
+
* when the command is admitted and only the process exit removes it. The
|
|
10
|
+
* primary target alone keeps environment attribution, the plans-mount
|
|
11
|
+
* predicate and credential-bearing process-group ownership.
|
|
12
|
+
*/
|
|
13
|
+
export type WorkspaceCommandProjectTarget = {
|
|
14
|
+
type: "project";
|
|
15
|
+
projectId: string;
|
|
16
|
+
branchName: string;
|
|
17
|
+
};
|
|
18
|
+
export type WorkspaceCommandAnyTarget = WorkspaceCommandProjectTarget | {
|
|
19
|
+
type: "workspace";
|
|
20
|
+
ownerUserId: string;
|
|
21
|
+
rootProfile: "visible_projects" | "canonical_sync";
|
|
22
|
+
};
|
|
23
|
+
export type WorkspaceCommandTargetValidators = {
|
|
24
|
+
validateProjectId: (projectId: string) => void;
|
|
25
|
+
validateBranchName: (branchName: string) => void;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The additional targets a message declares, checked and deduplicated: only
|
|
29
|
+
* project branches, each structurally valid, none equal to the primary
|
|
30
|
+
* target. A malformed entry is a protocol violation and fails the command
|
|
31
|
+
* before admission rather than silently narrowing its fence.
|
|
32
|
+
*/
|
|
33
|
+
export declare function normalizeCommandAdditionalTargets(primary: WorkspaceCommandAnyTarget, additional: unknown, validators: WorkspaceCommandTargetValidators): WorkspaceCommandProjectTarget[];
|
|
34
|
+
/** Every target a command fences, primary first. */
|
|
35
|
+
export declare function commandTargets<T extends WorkspaceCommandAnyTarget>(primary: T, additional: readonly WorkspaceCommandProjectTarget[]): Array<T | WorkspaceCommandProjectTarget>;
|
|
36
|
+
/** Log suffix naming the additional checkouts a command fences; empty when there are none. */
|
|
37
|
+
export declare function describeCommandAdditionalTargets(additional: readonly WorkspaceCommandProjectTarget[]): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
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";
|
|
2
|
+
import type { ProjectedWorkingTreeEntry, WorkingTreeGitlink } from "./working-tree-mirror";
|
|
3
|
+
import type { WorkspaceGitMountData, WorkspaceHydrationReceipt, WorkspaceHydrationRecoveryRequired, WorkspaceHydrationPreservationResult, WorkspaceHydrationMountSkip } from "./workspace-git-sync";
|
|
4
|
+
import type { WorkspaceHydrationMergeBasis } from "./workspace-hydration-merge";
|
|
4
5
|
import type { WorkspaceMergeProjectionMount, WorkspaceMergeProjectionResult } from "./workspace-merge-projection";
|
|
5
6
|
import type { ProjectCheckoutPathMoveBranch } from "./workspace-path-move";
|
|
6
7
|
/**
|
|
@@ -28,16 +29,43 @@ export type WorkspaceFilesystemJobTable = {
|
|
|
28
29
|
};
|
|
29
30
|
result: null;
|
|
30
31
|
};
|
|
31
|
-
/**
|
|
32
|
+
/** Read-only pending guarded transaction evidence for configuration admission. */
|
|
33
|
+
hydration_recovery_inspect: {
|
|
34
|
+
input: {
|
|
35
|
+
workspacePath: string;
|
|
36
|
+
};
|
|
37
|
+
result: WorkspaceHydrationRecoveryRequired | null;
|
|
38
|
+
};
|
|
39
|
+
/** Explicit exact-identity recovery: preserve visible data and archive all interrupted guarded transaction evidence. */
|
|
40
|
+
hydration_preserve_interrupted: {
|
|
41
|
+
input: {
|
|
42
|
+
workspacePath: string;
|
|
43
|
+
expectedTransactionId: string;
|
|
44
|
+
};
|
|
45
|
+
result: WorkspaceHydrationPreservationResult;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The complete hydration transaction: snapshot, hydrate, fsync, commit
|
|
49
|
+
* marker, receipt; restore on failure. A mount listed in `mergeBases` is
|
|
50
|
+
* merge-hydrated (docs/merge-hydration-design.md): files changed in the
|
|
51
|
+
* checkout since the projection read are three-way merged instead of
|
|
52
|
+
* overwritten, and a conflict or a write racing the hydration pins the
|
|
53
|
+
* mount at its previous receipt entry (`skippedMounts`) instead of
|
|
54
|
+
* failing the transaction.
|
|
55
|
+
*/
|
|
32
56
|
hydration_transaction: {
|
|
33
57
|
input: {
|
|
34
58
|
workspacePath: string;
|
|
35
59
|
hydrationMounts: WorkspaceGitMountData[];
|
|
36
60
|
advancedDurabilityMounts: WorkspaceGitMountData[];
|
|
37
61
|
targetReceipt: WorkspaceHydrationReceipt | null;
|
|
62
|
+
mergeBases?: Record<string, WorkspaceHydrationMergeBasis>;
|
|
38
63
|
};
|
|
39
64
|
result: {
|
|
40
65
|
hydratedMountIds: string[];
|
|
66
|
+
skippedMounts: WorkspaceHydrationMountSkip[];
|
|
67
|
+
/** Per merge-hydrated mount, the blob each replaced path held before hydration (the stale-rewrite detector's input). */
|
|
68
|
+
hydratedPreBlobs: Record<string, Record<string, string>>;
|
|
41
69
|
};
|
|
42
70
|
};
|
|
43
71
|
/** Pre-clone hydration of visible mounts without a transaction (no outer Git directory yet). */
|
|
@@ -79,17 +107,30 @@ export type WorkspaceFilesystemJobTable = {
|
|
|
79
107
|
basisHead: string;
|
|
80
108
|
currentHead: string;
|
|
81
109
|
attemptId: string;
|
|
110
|
+
/** Blob ids the last merge hydration replaced, by mount-relative path; a changed file hashing to one is a stale rewrite. */
|
|
111
|
+
staleRewriteBlobs?: Record<string, string>;
|
|
82
112
|
};
|
|
83
113
|
result: WorkspaceMergeProjectionResult;
|
|
84
114
|
};
|
|
85
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Byte mirror of one visible mount into the outer checkout. Returns what it
|
|
117
|
+
* read for every file (merge hydration's stat basis), or the paths that
|
|
118
|
+
* hold a stale rewrite, in which case nothing was mirrored.
|
|
119
|
+
*/
|
|
86
120
|
projection_mirror: {
|
|
87
121
|
input: {
|
|
88
122
|
workspacePath: string;
|
|
89
123
|
mount: WorkspaceGitMountData;
|
|
124
|
+
staleRewriteBlobs?: Record<string, string>;
|
|
90
125
|
};
|
|
91
126
|
result: {
|
|
92
127
|
gitlinks: WorkingTreeGitlink[];
|
|
128
|
+
projectedFiles: Record<string, ProjectedWorkingTreeEntry>;
|
|
129
|
+
/** Wall-clock time the mirror finished reading; projected stats younger than this minus a window are not trusted. */
|
|
130
|
+
readAtMs: number;
|
|
131
|
+
staleRewritePaths: string[];
|
|
132
|
+
/** Ledger paths whose checkout content moved on from both the replaced and the hydrated bytes. */
|
|
133
|
+
staleRewriteCleared: string[];
|
|
93
134
|
};
|
|
94
135
|
};
|
|
95
136
|
/** Outer-tree edits after the mirrors: tombstone removal, merged subtree materialization, deferred restores. */
|
|
@@ -216,6 +257,7 @@ export type WorkspaceFilesystemSerializedError = {
|
|
|
216
257
|
code?: string;
|
|
217
258
|
branchMayExist?: true;
|
|
218
259
|
cleanupFailures?: string[];
|
|
260
|
+
hydrationRecovery?: WorkspaceHydrationRecoveryRequired;
|
|
219
261
|
errors?: WorkspaceFilesystemSerializedError[];
|
|
220
262
|
cause?: WorkspaceFilesystemSerializedError;
|
|
221
263
|
};
|
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
import { type WorkingTreeGitlink, type WorkingTreeSourceMode } from "./working-tree-mirror";
|
|
1
|
+
import { type PreparedWorkingTreeMirror, type ProjectedWorkingTreeEntry, type WorkingTreeGitlink, type WorkingTreeMirrorPlan, type WorkingTreeMirrorScope, type WorkingTreeSourceMode } from "./working-tree-mirror";
|
|
2
|
+
import { type WorkspaceHydrationMergeBasis, type WorkspaceHydrationMergeDecision, type WorkspaceHydrationMountSkip as WorkspaceHydrationMountSkipBase } from "./workspace-hydration-merge";
|
|
3
|
+
import { WorkspaceHydrationPreBlobLedger } from "./workspace-hydration-ledger";
|
|
2
4
|
export declare const WORKSPACE_GIT_BRANCH = "main";
|
|
3
5
|
export declare const MAX_WORKSPACE_GIT_DIFF_BYTES: number;
|
|
4
6
|
export declare const WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION = "r5d-confirm-large-diff-v1";
|
|
5
7
|
export declare const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
|
|
6
8
|
export declare const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
|
|
9
|
+
export declare const WORKSPACE_GIT_HYDRATION_RECOVERIES = "r5d/workspace-hydration-recoveries";
|
|
7
10
|
export declare const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
|
|
11
|
+
/** `R5D_MERGE_HYDRATION=0` restores the plain byte mirror for post-publication hydration (docs/merge-hydration-design.md §4). */
|
|
12
|
+
export declare const WORKSPACE_MERGE_HYDRATION_SWITCH = "R5D_MERGE_HYDRATION";
|
|
13
|
+
declare function hydrationPreBlobLedger(workspacePath: string): WorkspaceHydrationPreBlobLedger;
|
|
14
|
+
export type WorkspaceHydrationMountSkip = WorkspaceHydrationMountSkipBase & {
|
|
15
|
+
retainedPaths?: string[];
|
|
16
|
+
};
|
|
17
|
+
export type WorkspaceMountSkipReason = WorkspaceHydrationMountSkip["reason"] | "stale_rewrite_detected";
|
|
18
|
+
/** Why a mount was left at its recorded basis this cycle, with the paths involved; a diagnostic beside `skippedMountIds`. */
|
|
19
|
+
export type WorkspaceMountSkipReasons = Record<string, {
|
|
20
|
+
reason: WorkspaceMountSkipReason;
|
|
21
|
+
paths: string[];
|
|
22
|
+
retainedPaths?: string[];
|
|
23
|
+
}>;
|
|
8
24
|
type WorkspaceHydrationMountBasis = {
|
|
9
25
|
id: string;
|
|
10
26
|
incarnationKey: string;
|
|
@@ -26,6 +42,57 @@ export type WorkspaceHydrationReceipt = {
|
|
|
26
42
|
/** Non-enumerable read-compat marker used only to finish a v1/v2 crash transition. */
|
|
27
43
|
legacyHead?: string;
|
|
28
44
|
};
|
|
45
|
+
type WorkspaceHydrationTransactionManifest = {
|
|
46
|
+
/** Version 5 makes guarded recovery explicit before any visible mutation. */
|
|
47
|
+
version: 3 | 4 | 5;
|
|
48
|
+
transactionId?: string;
|
|
49
|
+
targetReceipt: WorkspaceHydrationReceipt | null;
|
|
50
|
+
receiptBefore: WorkspaceHydrationReceipt | null;
|
|
51
|
+
mounts: Array<{
|
|
52
|
+
id: string;
|
|
53
|
+
incarnationKey: string;
|
|
54
|
+
sourcePath: string;
|
|
55
|
+
durabilityRootPath: string;
|
|
56
|
+
workspaceRelativePath: string;
|
|
57
|
+
existed: boolean;
|
|
58
|
+
snapshotName: string;
|
|
59
|
+
/**
|
|
60
|
+
* Present when the snapshot holds only the entries hydration replaces or
|
|
61
|
+
* removes, with the paths it creates listed. Without it the snapshot is a
|
|
62
|
+
* complete copy of the mount.
|
|
63
|
+
*/
|
|
64
|
+
scope?: WorkingTreeMirrorScope;
|
|
65
|
+
/** Required in v5: guarded mounts never enter unguarded snapshot restore. */
|
|
66
|
+
recovery?: "snapshot" | "guarded";
|
|
67
|
+
}>;
|
|
68
|
+
/** Settled rollback scopes remain evidence; they are never restored again. */
|
|
69
|
+
preservedMounts?: WorkspaceHydrationTransactionManifest["mounts"];
|
|
70
|
+
};
|
|
71
|
+
export type WorkspaceHydrationRecoveryRequired = {
|
|
72
|
+
transactionId: string;
|
|
73
|
+
transactionPath: string;
|
|
74
|
+
mounts: Array<Pick<WorkspaceHydrationTransactionManifest["mounts"][number], "id" | "incarnationKey" | "sourcePath" | "durabilityRootPath" | "workspaceRelativePath"> & {
|
|
75
|
+
retainedPaths?: string[];
|
|
76
|
+
}>;
|
|
77
|
+
receiptBefore: WorkspaceHydrationReceipt | null;
|
|
78
|
+
};
|
|
79
|
+
export declare class WorkspaceHydrationRecoveryRequiredError extends Error {
|
|
80
|
+
readonly hydrationRecovery: WorkspaceHydrationRecoveryRequired;
|
|
81
|
+
readonly code = "hydration_recovery_required";
|
|
82
|
+
constructor(hydrationRecovery: WorkspaceHydrationRecoveryRequired, options?: ErrorOptions);
|
|
83
|
+
}
|
|
84
|
+
export type WorkspaceHydrationPreservationResult = {
|
|
85
|
+
transactionId: string;
|
|
86
|
+
archivePath: string;
|
|
87
|
+
receiptBefore: WorkspaceHydrationReceipt | null;
|
|
88
|
+
preservedMountIds: string[];
|
|
89
|
+
alreadyPreserved: boolean;
|
|
90
|
+
retainedEntries?: Array<{
|
|
91
|
+
mountId: string;
|
|
92
|
+
sourcePath: string;
|
|
93
|
+
archivePath: string;
|
|
94
|
+
}>;
|
|
95
|
+
};
|
|
29
96
|
export type WorkspaceGitMount = {
|
|
30
97
|
id: string;
|
|
31
98
|
/** Durable logical incarnation (for example the project-branch row id). */
|
|
@@ -57,6 +124,8 @@ export type WorkspaceGitMount = {
|
|
|
57
124
|
/** Remove the outer-workspace subtree when the mounted source was intentionally deleted. */
|
|
58
125
|
deleteWhenSourceMissing?: boolean;
|
|
59
126
|
busy?: () => boolean;
|
|
127
|
+
/** Control-thread diagnostics only; never sent to a filesystem job. */
|
|
128
|
+
describeHolders?: () => string;
|
|
60
129
|
/** Monotonic/opaque visible-mutation token used to catch short writes that begin and end between busy checks. */
|
|
61
130
|
mutationToken?: () => string;
|
|
62
131
|
/**
|
|
@@ -130,6 +199,14 @@ export type WorkspaceGitSyncResult = {
|
|
|
130
199
|
affectedPaths: string[];
|
|
131
200
|
activeMountIds: string[];
|
|
132
201
|
skippedMountIds: string[];
|
|
202
|
+
/**
|
|
203
|
+
* Mounts merge hydration pinned this cycle (a conflict between a write
|
|
204
|
+
* that landed after the projection read and the inbound change, or a write
|
|
205
|
+
* racing the hydration) and mounts the stale-rewrite detector refused to
|
|
206
|
+
* project, each with the paths involved. Worker-internal until the server
|
|
207
|
+
* contract carries it.
|
|
208
|
+
*/
|
|
209
|
+
mountSkipReasons?: WorkspaceMountSkipReasons;
|
|
133
210
|
/** Token each active mount carried when this cycle projected it; recorded by the caller to skip unchanged mounts later. */
|
|
134
211
|
projectedMutationTokens?: Record<string, string>;
|
|
135
212
|
/** Creator-local mounts whose first outer publication was measured against their source subtree. */
|
|
@@ -179,6 +256,40 @@ export declare function runCheckoutDurabilityRecoveryJob(input: {
|
|
|
179
256
|
export declare function runOuterCheckoutFsyncJob(input: {
|
|
180
257
|
workspacePath: string;
|
|
181
258
|
}): null;
|
|
259
|
+
/** Read-only admission inspection, including before incident configuration fast paths. */
|
|
260
|
+
export declare function runHydrationRecoveryInspectJob(input: {
|
|
261
|
+
workspacePath: string;
|
|
262
|
+
}): WorkspaceHydrationRecoveryRequired | null;
|
|
263
|
+
/**
|
|
264
|
+
* Explicitly preserve an interrupted guarded transaction. The caller owns
|
|
265
|
+
* workspace admission/exclusion. No visible file is restored or deleted and
|
|
266
|
+
* no mount is receipted at the partially applied target head. The entire
|
|
267
|
+
* transaction directory is archived atomically, including snapshots and any
|
|
268
|
+
* interrupted manifest writes, before ordinary merge projection may resume.
|
|
269
|
+
*/
|
|
270
|
+
export declare function runHydrationPreserveInterruptedJob(input: {
|
|
271
|
+
workspacePath: string;
|
|
272
|
+
expectedTransactionId: string;
|
|
273
|
+
}): WorkspaceHydrationPreservationResult;
|
|
274
|
+
/** Everything the apply phase needs for a merge-hydrated mount, decided before the manifest was written. */
|
|
275
|
+
type WorkspaceHydrationMergePlan = {
|
|
276
|
+
prepared: PreparedWorkingTreeMirror;
|
|
277
|
+
decision: Extract<WorkspaceHydrationMergeDecision, {
|
|
278
|
+
kind: "apply";
|
|
279
|
+
}>;
|
|
280
|
+
plan: WorkingTreeMirrorPlan;
|
|
281
|
+
/** The mount's snapshot directory once the transaction directory is published. */
|
|
282
|
+
snapshotPath: string;
|
|
283
|
+
basis: WorkspaceHydrationMergeBasis;
|
|
284
|
+
};
|
|
285
|
+
declare function beginHydrationTransaction(workspacePath: string, mounts: readonly WorkspaceGitMount[], targetReceipt: WorkspaceHydrationReceipt | null, mergeBases?: Readonly<Record<string, WorkspaceHydrationMergeBasis>>): {
|
|
286
|
+
manifest: WorkspaceHydrationTransactionManifest;
|
|
287
|
+
durabilityScopes: Map<string, readonly string[]>;
|
|
288
|
+
mergePlans: Map<string, WorkspaceHydrationMergePlan>;
|
|
289
|
+
/** Mounts merge hydration decided not to touch; they have no snapshot, no manifest entry and keep their receipt entry. */
|
|
290
|
+
skippedMounts: WorkspaceHydrationMountSkip[];
|
|
291
|
+
targetReceipt: WorkspaceHydrationReceipt | null;
|
|
292
|
+
};
|
|
182
293
|
declare function configureWorkspaceRepository(input: {
|
|
183
294
|
workspacePath: string;
|
|
184
295
|
remoteUrl: string;
|
|
@@ -231,7 +342,7 @@ type ProjectedMountGitlinks = {
|
|
|
231
342
|
* arrives as an empty directory plus a gitlink the caller must stage with
|
|
232
343
|
* `stageWorkspaceGitlinks` before `git add -A`, which can never create one.
|
|
233
344
|
*/
|
|
234
|
-
declare function mirrorMountsToWorkspace(workspacePath: string, mounts: readonly WorkspaceGitMount[]): ProjectedMountGitlinks[];
|
|
345
|
+
declare function mirrorMountsToWorkspace(workspacePath: string, mounts: readonly WorkspaceGitMount[], projectedFiles?: Map<string, ProjectedWorkingTreeEntry>): ProjectedMountGitlinks[];
|
|
235
346
|
/**
|
|
236
347
|
* Executor job body: the complete hydration transaction. Snapshot exactly
|
|
237
348
|
* what hydration replaces, hydrate, cross the durability barrier, write the
|
|
@@ -244,8 +355,11 @@ export declare function runHydrationTransactionJob(input: {
|
|
|
244
355
|
hydrationMounts: readonly WorkspaceGitMountData[];
|
|
245
356
|
advancedDurabilityMounts: readonly WorkspaceGitMountData[];
|
|
246
357
|
targetReceipt: WorkspaceHydrationReceipt | null;
|
|
358
|
+
mergeBases?: Readonly<Record<string, WorkspaceHydrationMergeBasis>>;
|
|
247
359
|
}): {
|
|
248
360
|
hydratedMountIds: string[];
|
|
361
|
+
skippedMounts: WorkspaceHydrationMountSkip[];
|
|
362
|
+
hydratedPreBlobs: Record<string, Record<string, string>>;
|
|
249
363
|
};
|
|
250
364
|
/** Executor job body: hydrate visible mounts before an outer clone exists (no transaction to record). */
|
|
251
365
|
export declare function runHydrationRawJob(input: {
|
|
@@ -340,12 +454,17 @@ export type SynchronizeWorkspaceGitInput = {
|
|
|
340
454
|
hydrationHooks?: WorkspaceHydrationTransactionHooks;
|
|
341
455
|
};
|
|
342
456
|
export declare function synchronizeWorkspaceGit(input: SynchronizeWorkspaceGitInput): Promise<WorkspaceGitSyncResult>;
|
|
343
|
-
/** Executor job body: byte-mirror one visible mount into the outer checkout. */
|
|
457
|
+
/** Executor job body: byte-mirror one visible mount into the outer checkout, recording what it read. */
|
|
344
458
|
export declare function runProjectionMirrorJob(input: {
|
|
345
459
|
workspacePath: string;
|
|
346
460
|
mount: WorkspaceGitMountData;
|
|
461
|
+
staleRewriteBlobs?: Record<string, string>;
|
|
347
462
|
}): {
|
|
348
463
|
gitlinks: WorkingTreeGitlink[];
|
|
464
|
+
projectedFiles: Record<string, ProjectedWorkingTreeEntry>;
|
|
465
|
+
readAtMs: number;
|
|
466
|
+
staleRewritePaths: string[];
|
|
467
|
+
staleRewriteCleared: string[];
|
|
349
468
|
};
|
|
350
469
|
/** Executor job body: outer-tree edits that follow the mirrors, in the cycle's order. */
|
|
351
470
|
export declare function runProjectionOuterApplyJob(input: {
|
|
@@ -364,10 +483,13 @@ export declare function runProjectionReceiptJob(input: {
|
|
|
364
483
|
receipt: WorkspaceHydrationReceipt;
|
|
365
484
|
}): null;
|
|
366
485
|
export declare const workspaceGitSyncTestHarness: {
|
|
486
|
+
beginHydrationTransaction: typeof beginHydrationTransaction;
|
|
367
487
|
commandArgs: typeof gitCommandArgs;
|
|
368
488
|
workspaceCloneCommandArgs: typeof workspaceCloneCommandArgs;
|
|
369
489
|
configureWorkspaceRepository: typeof configureWorkspaceRepository;
|
|
370
490
|
mirrorMountsToWorkspace: typeof mirrorMountsToWorkspace;
|
|
371
491
|
hydrateMountsFromWorkspace: typeof hydrateWorkspaceGitMounts;
|
|
492
|
+
hydrationPreBlobLedger: typeof hydrationPreBlobLedger;
|
|
493
|
+
resetHydrationPreBlobLedgers(): void;
|
|
372
494
|
};
|
|
373
495
|
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blob ids of the bytes merge hydration replaced in a checkout, kept in
|
|
3
|
+
* memory per mount incarnation until a later hydration of the same path
|
|
4
|
+
* replaces them or the mount is hydrated outside a cycle (recovery, reset,
|
|
5
|
+
* configuration), which rewrites the checkout without recording anything.
|
|
6
|
+
*
|
|
7
|
+
* The projection reads it as the stale-rewrite detector
|
|
8
|
+
* (docs/merge-hydration-design.md §3.3): a process that read a file before a
|
|
9
|
+
* hydration and re-emits the same bytes afterwards would otherwise publish a
|
|
10
|
+
* revert of the inbound change as an ordinary local edit. A changed file whose
|
|
11
|
+
* content hashes to the blob hydration replaced is exactly that, so the mount
|
|
12
|
+
* is pinned instead of published, loudly.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately not persisted: a restart loses the entries and the detector is
|
|
15
|
+
* best effort. Phase 2 of the design turns this into a durable ledger if
|
|
16
|
+
* telemetry warrants it.
|
|
17
|
+
*/
|
|
18
|
+
export declare class WorkspaceHydrationPreBlobLedger {
|
|
19
|
+
private readonly mounts;
|
|
20
|
+
private static key;
|
|
21
|
+
/** Blob ids by mount-relative path for the mount, or an empty map. */
|
|
22
|
+
preBlobs(mount: {
|
|
23
|
+
id: string;
|
|
24
|
+
hydrationIncarnationKey: string;
|
|
25
|
+
}): ReadonlyMap<string, string>;
|
|
26
|
+
/** Record the pre-hydration blob of every path a merge hydration replaced; earlier entries for other paths are kept. */
|
|
27
|
+
record(mount: {
|
|
28
|
+
id: string;
|
|
29
|
+
hydrationIncarnationKey: string;
|
|
30
|
+
}, preBlobs: ReadonlyMap<string, string> | Readonly<Record<string, string>>): void;
|
|
31
|
+
/** Forget the mount: its checkout was rewritten by something this ledger did not observe. */
|
|
32
|
+
clearMount(mount: {
|
|
33
|
+
id: string;
|
|
34
|
+
hydrationIncarnationKey: string;
|
|
35
|
+
}): void;
|
|
36
|
+
/** Forget paths whose checkout content moved on from the hydrated bytes: a later return to the old bytes is a deliberate edit. */
|
|
37
|
+
forget(mount: {
|
|
38
|
+
id: string;
|
|
39
|
+
hydrationIncarnationKey: string;
|
|
40
|
+
}, paths: readonly string[]): void;
|
|
41
|
+
clear(): void;
|
|
42
|
+
get size(): number;
|
|
43
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { type PreparedWorkingTreeMirror, type ProjectedWorkingTreeEntry, type WorkingTreeMirrorOverride, type WorkingTreeMirrorSelection } from "./working-tree-mirror";
|
|
3
|
+
/**
|
|
4
|
+
* Where a mount's bytes were read this cycle (docs/merge-hydration-design.md
|
|
5
|
+
* §2.1). `projectedCommit` is the commit whose subtree at the mount path is
|
|
6
|
+
* exactly what projection read: the projected head for a mirrored mount, the
|
|
7
|
+
* synthesized ours commit for a merge-projected one. `projectedFiles` is
|
|
8
|
+
* what the mirror or synthesis saw for each entry, or null when the mount was
|
|
9
|
+
* not read this cycle (its token was unchanged), in which case a changed
|
|
10
|
+
* file is recognized by hashing it against the projected blob.
|
|
11
|
+
*/
|
|
12
|
+
export type WorkspaceHydrationMergeBasis = {
|
|
13
|
+
projectedCommit: string;
|
|
14
|
+
projectedFiles: Record<string, ProjectedWorkingTreeEntry> | null;
|
|
15
|
+
/**
|
|
16
|
+
* Wall-clock time the projection finished reading. A projected stat whose
|
|
17
|
+
* timestamps fall within `RACY_PROJECTION_WINDOW_MS` of it is not trusted
|
|
18
|
+
* to prove the file unchanged (git's racy-clean rule: a rewrite of the
|
|
19
|
+
* same size in the same coarse timestamp tick leaves the stat intact), so
|
|
20
|
+
* such a file is hashed against the projected blob instead.
|
|
21
|
+
*/
|
|
22
|
+
readAtMs?: number;
|
|
23
|
+
};
|
|
24
|
+
export declare const RACY_PROJECTION_WINDOW_MS = 2000;
|
|
25
|
+
export type WorkspaceHydrationSkipReason = "hydration_merge_conflict" | "hydration_target_changed";
|
|
26
|
+
/** A mount merge hydration left at its previous receipt entry, and why. */
|
|
27
|
+
export type WorkspaceHydrationMountSkip = {
|
|
28
|
+
mountId: string;
|
|
29
|
+
reason: WorkspaceHydrationSkipReason;
|
|
30
|
+
paths: string[];
|
|
31
|
+
};
|
|
32
|
+
/** One path whose blob differs between the projected subtree (base) and the hydration head's subtree (theirs). */
|
|
33
|
+
export type WorkspaceSubtreeChange = {
|
|
34
|
+
base: {
|
|
35
|
+
mode: string;
|
|
36
|
+
objectId: string;
|
|
37
|
+
} | null;
|
|
38
|
+
theirs: {
|
|
39
|
+
mode: string;
|
|
40
|
+
objectId: string;
|
|
41
|
+
} | null;
|
|
42
|
+
};
|
|
43
|
+
export type WorkspaceHydrationMergeDecision = {
|
|
44
|
+
kind: "conflict";
|
|
45
|
+
paths: string[];
|
|
46
|
+
} | {
|
|
47
|
+
kind: "target_changed";
|
|
48
|
+
paths: string[];
|
|
49
|
+
} | {
|
|
50
|
+
kind: "apply";
|
|
51
|
+
selection: WorkingTreeMirrorSelection;
|
|
52
|
+
overrides: Map<string, WorkingTreeMirrorOverride>;
|
|
53
|
+
/** The target `lstat` (or absence) every mutated path must still show when it is mutated. */
|
|
54
|
+
expectations: Map<string, fs.Stats | null>;
|
|
55
|
+
/** Blob of the bytes each written or removed path held before hydration; the stale-rewrite detector's input. */
|
|
56
|
+
preBlobs: Map<string, string>;
|
|
57
|
+
/** Paths whose content was three-way merged. */
|
|
58
|
+
mergedPaths: string[];
|
|
59
|
+
/** Paths the checkout changed since projection that hydration leaves as they are. */
|
|
60
|
+
keptPaths: string[];
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Decide, per path the hydration head changed relative to what projection
|
|
64
|
+
* read, whether hydration writes theirs (the checkout is unchanged there),
|
|
65
|
+
* leaves the checkout alone (theirs did not change there), writes a
|
|
66
|
+
* three-way merge, or cannot proceed (docs/merge-hydration-design.md §2.2,
|
|
67
|
+
* §2.6). Every path the checkout changed since projection that the head did
|
|
68
|
+
* not change survives untouched, including files created since. Nothing is
|
|
69
|
+
* written here; the result drives `applyWorkingTreeMirror` under guards that
|
|
70
|
+
* re-check each path's `lstat` immediately before mutating it.
|
|
71
|
+
*/
|
|
72
|
+
export declare function decideWorkspaceHydrationMerge(input: {
|
|
73
|
+
prepared: PreparedWorkingTreeMirror;
|
|
74
|
+
changes: ReadonlyMap<string, WorkspaceSubtreeChange>;
|
|
75
|
+
projectedFiles: Readonly<Record<string, ProjectedWorkingTreeEntry>> | null;
|
|
76
|
+
readAtMs?: number;
|
|
77
|
+
/** Blob contents by object id, in one batch. */
|
|
78
|
+
readBaseBlobs: (objectIds: readonly string[]) => Map<string, Buffer>;
|
|
79
|
+
/** Paths marked -diff on either side must never enter the text merger. */
|
|
80
|
+
readBinaryPaths?: (relativePaths: readonly string[]) => ReadonlySet<string>;
|
|
81
|
+
}): WorkspaceHydrationMergeDecision;
|
|
82
|
+
/** Batch attribute reads, with NUL delimiters so arbitrary Git filenames stay intact. */
|
|
83
|
+
export declare function readHydrationBinaryPaths(input: {
|
|
84
|
+
checkoutRoot: string;
|
|
85
|
+
outerRoot: string;
|
|
86
|
+
relativePaths: readonly string[];
|
|
87
|
+
}): Set<string>;
|
|
88
|
+
/**
|
|
89
|
+
* Parse `git diff-tree -r -z --no-renames --raw <base tree> <theirs tree>`
|
|
90
|
+
* output into per-path changes. Records come in pairs: the status line
|
|
91
|
+
* (`:<base mode> <theirs mode> <base id> <theirs id> <status>`) and the path.
|
|
92
|
+
*/
|
|
93
|
+
export declare function parseWorkspaceSubtreeChanges(raw: Buffer): Map<string, WorkspaceSubtreeChange>;
|
|
94
|
+
/** The mount-relative path of a checkout entry as its absolute path, for diagnostics. */
|
|
95
|
+
export declare function describeHydrationSkipPaths(mountSourcePath: string, paths: readonly string[]): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Wire evidence owned by the standalone worker package; no API runtime dependency. */
|
|
2
|
+
export type WorkerHydrationRecovery = {
|
|
3
|
+
transactionId: string;
|
|
4
|
+
transactionPath: string;
|
|
5
|
+
mountIds: string[];
|
|
6
|
+
previousHeads: Record<string, string>;
|
|
7
|
+
retainedPaths?: string[];
|
|
8
|
+
};
|
|
9
|
+
/** Extract durable recovery evidence even through executor/AggregateError wrapping. */
|
|
10
|
+
export declare function findHydrationRecovery(error: unknown): WorkerHydrationRecovery | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WorkingTreeSourceMode } from "./working-tree-mirror";
|
|
1
|
+
import { type ProjectedWorkingTreeEntry, type WorkingTreeSourceMode } from "./working-tree-mirror";
|
|
2
2
|
export type WorkspaceMergeProjectionSupport = {
|
|
3
3
|
supported: true;
|
|
4
4
|
} | {
|
|
@@ -11,15 +11,32 @@ export type WorkspaceMergeProjectionMount = {
|
|
|
11
11
|
workspaceRelativePath: string;
|
|
12
12
|
sourceMode: WorkingTreeSourceMode;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* `projectedFiles` records what the synthesis read for every visible entry
|
|
16
|
+
* (merge hydration's basis for "changed since projection"). A `stale_rewrite`
|
|
17
|
+
* result means the checkout holds, at the listed paths, exactly the bytes the
|
|
18
|
+
* last hydration replaced; nothing was merged and the mount must stay pinned.
|
|
19
|
+
*/
|
|
14
20
|
export type WorkspaceMergeProjectionResult = {
|
|
15
21
|
kind: "clean";
|
|
16
22
|
resultTree: string;
|
|
17
23
|
oursCommit: string;
|
|
24
|
+
projectedFiles: Record<string, ProjectedWorkingTreeEntry>;
|
|
25
|
+
readAtMs: number;
|
|
26
|
+
/** Ledger paths whose checkout content moved on from both the replaced and the basis bytes (or that are gone). */
|
|
27
|
+
staleRewriteCleared: string[];
|
|
18
28
|
} | {
|
|
19
29
|
kind: "conflict";
|
|
20
30
|
oursCommit: string;
|
|
21
31
|
conflictPaths: string[];
|
|
22
32
|
error: string;
|
|
33
|
+
projectedFiles: Record<string, ProjectedWorkingTreeEntry>;
|
|
34
|
+
readAtMs: number;
|
|
35
|
+
staleRewriteCleared: string[];
|
|
36
|
+
} | {
|
|
37
|
+
kind: "stale_rewrite";
|
|
38
|
+
paths: string[];
|
|
39
|
+
staleRewriteCleared: string[];
|
|
23
40
|
};
|
|
24
41
|
declare function gitVersionIsSupported(version: string): boolean;
|
|
25
42
|
export declare function workspaceMergeProjectionSupport(): WorkspaceMergeProjectionSupport;
|
|
@@ -29,6 +46,7 @@ export declare function mergeWorkspaceProjectionMount(input: {
|
|
|
29
46
|
basisHead: string;
|
|
30
47
|
currentHead: string;
|
|
31
48
|
attemptId: string;
|
|
49
|
+
staleRewriteBlobs?: Readonly<Record<string, string>>;
|
|
32
50
|
}): WorkspaceMergeProjectionResult;
|
|
33
51
|
export declare function materializeWorkspaceProjectionTree(input: {
|
|
34
52
|
workspacePath: string;
|
|
@@ -10,25 +10,39 @@ type ProjectConfiguration = {
|
|
|
10
10
|
projectId: string;
|
|
11
11
|
};
|
|
12
12
|
type ProjectConfigurationWithBranches = ProjectConfiguration & {
|
|
13
|
+
executionDisabled?: boolean;
|
|
13
14
|
branches: readonly {
|
|
14
15
|
branchName: string;
|
|
15
16
|
}[];
|
|
16
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Every executable checkout of a configuration whose reconciliation did not
|
|
20
|
+
* run. Frozen projects expose neither ready nor pending execution targets, so
|
|
21
|
+
* the report stays inside the server's executable checkout ledger.
|
|
22
|
+
*/
|
|
17
23
|
export declare function deferredProjectConfigurationPendingBranches(projects: readonly ProjectConfigurationWithBranches[]): Array<{
|
|
18
24
|
projectId: string;
|
|
19
25
|
branchName: string;
|
|
20
26
|
}>;
|
|
27
|
+
export type BusyProjectConfigurationChangeReason = "added" | "removed" | "initializing" | "changed";
|
|
28
|
+
export type BusyProjectConfigurationChange = {
|
|
29
|
+
projectId: string;
|
|
30
|
+
reason: BusyProjectConfigurationChangeReason;
|
|
31
|
+
};
|
|
21
32
|
/**
|
|
22
33
|
* Identify configuration changes that would touch a project while visible
|
|
23
34
|
* checkout activity is active or reserved. A visible-projects target covers
|
|
24
|
-
* every project; canonical-sync activity is isolated in the outer clone.
|
|
35
|
+
* every project; canonical-sync activity is isolated in the outer clone. The
|
|
36
|
+
* reason names why the busy project cannot take the configuration now, so a
|
|
37
|
+
* deferral can be diagnosed without reconstructing both generations.
|
|
25
38
|
*/
|
|
26
|
-
export declare function
|
|
39
|
+
export declare function busyProjectConfigurationChanges<TProject extends ProjectConfiguration>(input: {
|
|
27
40
|
currentProjects: readonly TProject[];
|
|
28
41
|
incomingProjects: readonly TProject[];
|
|
29
42
|
activeTargets: Iterable<ProjectTarget | WorkspaceTarget>;
|
|
30
43
|
currentFingerprint: (project: TProject) => string;
|
|
31
44
|
incomingFingerprint: (project: TProject) => string;
|
|
32
45
|
currentProjectReady: (project: TProject) => boolean;
|
|
33
|
-
}):
|
|
46
|
+
}): BusyProjectConfigurationChange[];
|
|
47
|
+
export declare function describeBusyProjectConfigurationChanges(changes: readonly BusyProjectConfigurationChange[]): string;
|
|
34
48
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5d-worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.133",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/cjs/main.cjs",
|
|
6
6
|
"module": "./dist/mjs/main.mjs",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"r5d-worker": "dist/cjs/main.cjs"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@ricsam/r5d-api": "^0.0.
|
|
24
|
+
"@ricsam/r5d-api": "^0.0.133",
|
|
25
25
|
"node-pty": "^1.1.0",
|
|
26
26
|
"picomatch": "^4.0.3"
|
|
27
27
|
},
|