@ricsam/r5d-worker 0.0.100 → 0.0.103
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 +16 -3
- package/dist/cjs/main.cjs +390 -322
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-mirror-fetch-policy.cjs +48 -7
- package/dist/cjs/project-mirror-refs-token.cjs +36 -2
- package/dist/cjs/project-worktrees.cjs +49 -32
- package/dist/cjs/working-tree-mirror.cjs +347 -99
- package/dist/cjs/workspace-git-sync.cjs +189 -28
- package/dist/cjs/workspace-merge-projection.cjs +5 -19
- package/dist/cjs/workspace-mutation-gate.cjs +35 -0
- package/dist/cjs/workspace-projection-ledger.cjs +81 -0
- package/dist/cjs/workspace-sync-coalescer.cjs +58 -0
- package/dist/mjs/main.mjs +395 -324
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-mirror-fetch-policy.mjs +46 -6
- package/dist/mjs/project-mirror-refs-token.mjs +30 -1
- package/dist/mjs/project-worktrees.mjs +52 -33
- package/dist/mjs/working-tree-mirror.mjs +347 -99
- package/dist/mjs/workspace-git-sync.mjs +189 -28
- package/dist/mjs/workspace-merge-projection.mjs +5 -19
- package/dist/mjs/workspace-mutation-gate.mjs +35 -0
- package/dist/mjs/workspace-projection-ledger.mjs +57 -0
- package/dist/mjs/workspace-sync-coalescer.mjs +34 -0
- package/dist/types/main.d.ts +29 -17
- package/dist/types/project-mirror-fetch-policy.d.ts +70 -15
- package/dist/types/project-mirror-refs-token.d.ts +34 -0
- package/dist/types/project-worktrees.d.ts +26 -8
- package/dist/types/working-tree-mirror.d.ts +56 -11
- package/dist/types/workspace-git-sync.d.ts +41 -4
- package/dist/types/workspace-mutation-gate.d.ts +24 -3
- package/dist/types/workspace-projection-ledger.d.ts +59 -0
- package/dist/types/workspace-sync-coalescer.d.ts +28 -0
- package/package.json +1 -1
package/dist/types/main.d.ts
CHANGED
|
@@ -185,6 +185,11 @@ type WorkerClientMessage = {
|
|
|
185
185
|
requestId: string;
|
|
186
186
|
result?: WorkerOperationResult;
|
|
187
187
|
error?: string;
|
|
188
|
+
} | {
|
|
189
|
+
/** Immediate receipt for a branch create/delete; `queuePosition` is 0 when it runs at once. */
|
|
190
|
+
type: "operation_queued";
|
|
191
|
+
requestId: string;
|
|
192
|
+
queuePosition: number;
|
|
188
193
|
} | {
|
|
189
194
|
type: "exec_result";
|
|
190
195
|
requestId: string;
|
|
@@ -290,13 +295,18 @@ type WorkerServerMessage = {
|
|
|
290
295
|
requiredAncestorHeads?: string[];
|
|
291
296
|
} | {
|
|
292
297
|
/**
|
|
293
|
-
* Content-derived tokens naming
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
298
|
+
* Content-derived tokens naming ref state the server read from disk:
|
|
299
|
+
* `tokens` per canonical project repository, `mountTokens` per
|
|
300
|
+
* checked-out project branch (keyed by project id then branch name),
|
|
301
|
+
* and the current head of the hidden workspace repository. Replace-all:
|
|
302
|
+
* anything absent from a message has no advertised token and the
|
|
303
|
+
* corresponding fetch runs. Older servers send `tokens` only. See
|
|
304
|
+
* `project-mirror-fetch-policy.ts`.
|
|
297
305
|
*/
|
|
298
306
|
type: "project_mirror_refs_tokens";
|
|
299
307
|
tokens: Record<string, string>;
|
|
308
|
+
mountTokens?: Record<string, Record<string, string>>;
|
|
309
|
+
workspaceHead?: string;
|
|
300
310
|
} | {
|
|
301
311
|
type: "create_project_branch";
|
|
302
312
|
requestId: string;
|
|
@@ -304,6 +314,8 @@ type WorkerServerMessage = {
|
|
|
304
314
|
projectId: string;
|
|
305
315
|
sourceBranch: string;
|
|
306
316
|
targetBranch: string;
|
|
317
|
+
/** `carry` copies the source checkout's uncommitted changes; `clean` checks out its commit only. */
|
|
318
|
+
workingTree: "carry" | "clean";
|
|
307
319
|
} | {
|
|
308
320
|
type: "delete_project_branch";
|
|
309
321
|
requestId: string;
|
|
@@ -618,14 +630,17 @@ export declare const workerPtyTestHarness: {
|
|
|
618
630
|
removeEnvFiles: typeof removePtyEnvFiles;
|
|
619
631
|
resolveEnvFileReferences: typeof resolvePtyEnvFileReferences;
|
|
620
632
|
commandHasWorkspaceEffect: typeof workerCommandHasWorkspaceEffect;
|
|
633
|
+
targetProcessEnv: typeof targetProcessEnv;
|
|
621
634
|
canonicalSyncTerminalHead: typeof canonicalSyncTerminalHead;
|
|
622
635
|
ptyIsWorkspaceBusy: typeof workerPtyIsWorkspaceBusy;
|
|
623
636
|
parseLinuxForegroundBusy: typeof parseLinuxPtyForegroundBusy;
|
|
624
637
|
};
|
|
625
638
|
export type WorkerBuiltInToolPaths = {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
639
|
+
rootDir?: string;
|
|
640
|
+
sessionId?: string;
|
|
641
|
+
projectId?: string;
|
|
642
|
+
branchName?: string;
|
|
643
|
+
activePlanId?: string;
|
|
629
644
|
};
|
|
630
645
|
export declare function isArtifactEnvPath(filePath: string): boolean;
|
|
631
646
|
export declare function syncSessionArtifacts(input: {
|
|
@@ -638,11 +653,11 @@ export declare function prepareArtifactEnvForShell(input: {
|
|
|
638
653
|
baseUrl: string;
|
|
639
654
|
token: string;
|
|
640
655
|
sessionId?: string;
|
|
641
|
-
|
|
656
|
+
rootDir: string;
|
|
642
657
|
}): Promise<Record<string, string>>;
|
|
643
658
|
export declare function preparePlanEnvForShell(input: {
|
|
644
659
|
target: WorkerSessionTarget;
|
|
645
|
-
|
|
660
|
+
rootDir: string;
|
|
646
661
|
activePlanId?: string;
|
|
647
662
|
}): Record<string, string>;
|
|
648
663
|
type ResolvedWorkerFilePath = {
|
|
@@ -943,8 +958,7 @@ export declare function prepareBuiltInToolPathsForTarget(input: {
|
|
|
943
958
|
token: string;
|
|
944
959
|
sessionId?: string;
|
|
945
960
|
activePlanId?: string;
|
|
946
|
-
|
|
947
|
-
planRoot: string;
|
|
961
|
+
rootDir: string;
|
|
948
962
|
access: "read" | "write";
|
|
949
963
|
}): Promise<WorkerBuiltInToolPaths | undefined>;
|
|
950
964
|
export declare function readWorkerTextFile(branchPath: string, filePath: string, offset?: number, limit?: number, builtInPaths?: WorkerBuiltInToolPaths): WorkerReadFileResult;
|
|
@@ -960,8 +974,7 @@ declare function executeWriteFileOperation(input: {
|
|
|
960
974
|
resolvedTarget: ResolvedWorkerSessionTarget;
|
|
961
975
|
baseUrl: string;
|
|
962
976
|
token: string;
|
|
963
|
-
|
|
964
|
-
planRoot: string;
|
|
977
|
+
rootDir: string;
|
|
965
978
|
assertAdmission: () => void;
|
|
966
979
|
}): Promise<WorkerWriteFileResult>;
|
|
967
980
|
declare function executeEditFileOperation(input: {
|
|
@@ -971,8 +984,7 @@ declare function executeEditFileOperation(input: {
|
|
|
971
984
|
resolvedTarget: ResolvedWorkerSessionTarget;
|
|
972
985
|
baseUrl: string;
|
|
973
986
|
token: string;
|
|
974
|
-
|
|
975
|
-
planRoot: string;
|
|
987
|
+
rootDir: string;
|
|
976
988
|
assertAdmission: () => void;
|
|
977
989
|
}): Promise<WorkerEditFileResult>;
|
|
978
990
|
export declare function grepWorkerFiles(branchPath: string, input: {
|
|
@@ -992,14 +1004,14 @@ export declare function listWorkerDirectory(branchPath: string, inputPath?: stri
|
|
|
992
1004
|
export declare function listWorkerCodeDirectory(branchPath: string, inputPath: string): WorkerCodeListResult;
|
|
993
1005
|
export declare function readWorkerCodeFile(branchPath: string, inputPath: string): WorkerCodeReadResult;
|
|
994
1006
|
export declare function readWorkerImageFile(branchPath: string, filePath: string, builtInPaths?: WorkerBuiltInToolPaths): WorkerViewFileBytesResult;
|
|
1007
|
+
declare function targetProcessEnv(rootDir: string, target: WorkerSessionTarget): Record<string, string>;
|
|
995
1008
|
export declare function prepareShellEnvForTarget(input: {
|
|
996
1009
|
target: WorkerSessionTarget;
|
|
997
1010
|
baseUrl: string;
|
|
998
1011
|
token: string;
|
|
999
1012
|
sessionId?: string;
|
|
1000
1013
|
activePlanId?: string;
|
|
1001
|
-
|
|
1002
|
-
planRoot: string;
|
|
1014
|
+
rootDir: string;
|
|
1003
1015
|
}): Promise<Record<string, string>>;
|
|
1004
1016
|
declare function createNodePtyBridge(options: {
|
|
1005
1017
|
file: string;
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Decides
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* observation fetch would transfer); when the advertised token equals the
|
|
6
|
-
* token the local refs already cover, the fetch would be a no-op.
|
|
2
|
+
* Decides which part of a project's hidden mirror a periodic observation must
|
|
3
|
+
* fetch, from the tokens the server advertises and the tokens the worker's
|
|
4
|
+
* own mirror refs cover (both computed with `project-mirror-refs-token.ts`).
|
|
7
5
|
*
|
|
8
|
-
* Skipping is fail-open: any missing or mismatched information fetches.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
6
|
+
* Skipping is fail-open: any missing or mismatched information fetches.
|
|
7
|
+
*
|
|
8
|
+
* There is deliberately no timed backstop refetch for matching tokens. The
|
|
9
|
+
* server recomputes every advertised token from the canonical repository on
|
|
10
|
+
* disk on each heartbeat sweep, so a matching token is a fresh statement that
|
|
11
|
+
* the fetch would transfer nothing, whoever moved the refs. The worker
|
|
12
|
+
* records the tokens it covers from its own refs after every fetch and lease
|
|
13
|
+
* push, with the same shared formula, so a skip can only be stale if the
|
|
14
|
+
* canonical repository moved after the sweep that produced the advertisement
|
|
15
|
+
* — which the next sweep corrects, exactly like a tip that lands mid-cycle —
|
|
16
|
+
* or if the formula itself were wrong, which the shared module and its locked
|
|
17
|
+
* vectors guard and a periodic blind fetch would only hide. A project or
|
|
18
|
+
* mount the server advertises no token for fetches every cycle, as before
|
|
19
|
+
* tokens existed.
|
|
13
20
|
*/
|
|
14
|
-
|
|
21
|
+
/** Why a fetch runs. `fail_closed` is never decided here: reset and remediation observation bypasses the gate. */
|
|
22
|
+
export type ProjectMirrorFetchReason = "skipping_disabled" | "no_advertised_token" | "never_fetched" | "token_changed" | "fail_closed";
|
|
15
23
|
export type ProjectMirrorFetchDecision = {
|
|
16
24
|
fetch: true;
|
|
17
|
-
reason:
|
|
25
|
+
reason: ProjectMirrorFetchReason;
|
|
18
26
|
} | {
|
|
19
27
|
fetch: false;
|
|
20
28
|
reason: "token_match";
|
|
@@ -22,8 +30,55 @@ export type ProjectMirrorFetchDecision = {
|
|
|
22
30
|
export declare function projectMirrorFetchDecision(input: {
|
|
23
31
|
advertisedToken: string | undefined;
|
|
24
32
|
fetchedToken: string | undefined;
|
|
25
|
-
lastFetchAtMs: number | undefined;
|
|
26
|
-
nowMs: number;
|
|
27
33
|
skippingEnabled: boolean;
|
|
28
|
-
backstopMs?: number;
|
|
29
34
|
}): ProjectMirrorFetchDecision;
|
|
35
|
+
export type ProjectMirrorObservationFetchPlan =
|
|
36
|
+
/** Fetch every canonical ref of the project, as observation always did before per-mount tokens. */
|
|
37
|
+
{
|
|
38
|
+
scope: "project";
|
|
39
|
+
reason: ProjectMirrorFetchReason;
|
|
40
|
+
}
|
|
41
|
+
/** Fetch only the named mounts' branch heads and handoff refs. */
|
|
42
|
+
| {
|
|
43
|
+
scope: "mounts";
|
|
44
|
+
mounts: Array<{
|
|
45
|
+
branchName: string;
|
|
46
|
+
reason: ProjectMirrorFetchReason;
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
/** Every token the observation depends on matches; reuse the local refs. */
|
|
50
|
+
| {
|
|
51
|
+
scope: "none";
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Plan one project's observation fetch. With per-mount tokens advertised for
|
|
55
|
+
* the project, each mount decides for itself and only changed mounts fetch;
|
|
56
|
+
* without them (an older server) the project token decides for the whole
|
|
57
|
+
* mirror.
|
|
58
|
+
*/
|
|
59
|
+
export declare function planProjectMirrorObservationFetch(input: {
|
|
60
|
+
branchNames: readonly string[];
|
|
61
|
+
advertisedProjectToken: string | undefined;
|
|
62
|
+
/** Per-mount tokens for this project, keyed by branch name; undefined when the server advertised none. */
|
|
63
|
+
advertisedMountTokens: ReadonlyMap<string, string> | undefined;
|
|
64
|
+
fetchedProjectToken: string | undefined;
|
|
65
|
+
fetchedMountTokens: ReadonlyMap<string, string>;
|
|
66
|
+
skippingEnabled: boolean;
|
|
67
|
+
}): ProjectMirrorObservationFetchPlan;
|
|
68
|
+
export type ProjectMirrorRefsTokensAdvertisement = {
|
|
69
|
+
/** One token per project id. */
|
|
70
|
+
projectTokens: Map<string, string>;
|
|
71
|
+
/** Per-mount tokens keyed by project id then branch name; a project is present only when the server advertised mount tokens for it. */
|
|
72
|
+
mountTokens: Map<string, Map<string, string>>;
|
|
73
|
+
/** Current head of the user's hidden workspace repository, when advertised. */
|
|
74
|
+
workspaceHead: string | undefined;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Read a `project_mirror_refs_tokens` message. Older servers send only
|
|
78
|
+
* `tokens`; malformed entries are dropped, which fails open into fetching.
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseProjectMirrorRefsTokensMessage(message: {
|
|
81
|
+
tokens?: unknown;
|
|
82
|
+
mountTokens?: unknown;
|
|
83
|
+
workspaceHead?: unknown;
|
|
84
|
+
}): ProjectMirrorRefsTokensAdvertisement;
|
|
@@ -1,15 +1,40 @@
|
|
|
1
|
+
/** Canonical namespaces the token hashes; also the observation fetch's sources. */
|
|
2
|
+
export declare const PROJECT_MIRROR_REFS_TOKEN_NAMESPACES: readonly ["refs/heads/", "refs/r5d/server-synced/"];
|
|
3
|
+
/** Worker-local copy of a canonical `refs/heads/*` ref. */
|
|
1
4
|
export declare const PROJECT_MIRROR_REF_PREFIX = "refs/r5d/mirror/";
|
|
2
5
|
export declare const PROJECT_SERVER_SYNCED_REF_PREFIX = "refs/r5d/server-synced/";
|
|
6
|
+
/** `git for-each-ref` format both sides list refs with; see parseProjectMirrorRefListing. */
|
|
7
|
+
export declare const PROJECT_MIRROR_REF_LISTING_FORMAT = "--format=%(objectname) %(refname)";
|
|
3
8
|
export type ProjectMirrorRef = {
|
|
4
9
|
refname: string;
|
|
5
10
|
objectName: string;
|
|
6
11
|
};
|
|
12
|
+
/** One checked-out project branch, identified by its durable incarnation id. */
|
|
13
|
+
export type ProjectMirrorMount = {
|
|
14
|
+
branchName: string;
|
|
15
|
+
branchId: string;
|
|
16
|
+
};
|
|
17
|
+
export type ProjectMirrorRefsTokens = {
|
|
18
|
+
/** Hash over every ref in the observed namespaces. */
|
|
19
|
+
project: string;
|
|
20
|
+
/** Hash over one mount's branch head plus its handoff refs, keyed by branch name. */
|
|
21
|
+
mounts: Map<string, string>;
|
|
22
|
+
};
|
|
7
23
|
/**
|
|
8
24
|
* Hash a ref listing into a mirror-refs token. Both sides sort here rather
|
|
9
25
|
* than trusting git's listing order: renaming `refs/r5d/mirror/*` back to
|
|
10
26
|
* `refs/heads/*` changes sort neighborhoods.
|
|
11
27
|
*/
|
|
12
28
|
export declare function hashProjectMirrorRefs(refs: readonly ProjectMirrorRef[]): string;
|
|
29
|
+
/**
|
|
30
|
+
* Parse `git for-each-ref` output produced with
|
|
31
|
+
* PROJECT_MIRROR_REF_LISTING_FORMAT. Malformed rows throw rather than being
|
|
32
|
+
* dropped: a caller that cannot trust its listing must not produce a token.
|
|
33
|
+
* With `namespaces`, rows outside them throw too.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseProjectMirrorRefListing(output: string, options?: {
|
|
36
|
+
namespaces?: readonly string[];
|
|
37
|
+
}): ProjectMirrorRef[];
|
|
13
38
|
/**
|
|
14
39
|
* Rename locally fetched mirror refs into the namespaces the server hashes.
|
|
15
40
|
* Rows outside the two observed namespaces are dropped; the caller lists only
|
|
@@ -17,3 +42,12 @@ export declare function hashProjectMirrorRefs(refs: readonly ProjectMirrorRef[])
|
|
|
17
42
|
* it can only make tokens differ — which fails open into a fetch.
|
|
18
43
|
*/
|
|
19
44
|
export declare function canonicalizeLocalProjectMirrorRefs(refs: readonly ProjectMirrorRef[]): ProjectMirrorRef[];
|
|
45
|
+
/** The canonical refs one mount's token covers: its branch head and its server-synced handoff refs. */
|
|
46
|
+
export declare function projectMirrorMountRefs(refs: readonly ProjectMirrorRef[], mount: ProjectMirrorMount): ProjectMirrorRef[];
|
|
47
|
+
/**
|
|
48
|
+
* Compute the project token and one token per mount from one canonical ref
|
|
49
|
+
* listing. A mount's token is the project formula applied to the mount's own
|
|
50
|
+
* refs, so a mount whose branch is the only ref of its project has the same
|
|
51
|
+
* token as the project.
|
|
52
|
+
*/
|
|
53
|
+
export declare function projectMirrorRefsTokens(refs: readonly ProjectMirrorRef[], mounts: readonly ProjectMirrorMount[]): ProjectMirrorRefsTokens;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ProjectMirrorMount, type ProjectMirrorRefsTokens } from "./project-mirror-refs-token";
|
|
1
2
|
export declare const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
|
|
2
3
|
declare const PROJECT_WORKTREE_SNAPSHOT_MANIFEST_VERSION: 1;
|
|
3
4
|
type ProjectWorktreeSnapshotManifest = {
|
|
@@ -124,10 +125,17 @@ export declare function ensureProjectWorktrees(input: {
|
|
|
124
125
|
onSnapshotProgress?: (progress: ProjectWorktreeSnapshotProgress) => void;
|
|
125
126
|
}): Promise<ProjectWorktreeState[]>;
|
|
126
127
|
export declare function projectWorktreeOperationInProgress(checkoutPath: string): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* How a new linked worktree's working tree starts. `carry` materializes the
|
|
130
|
+
* source checkout's Git-visible working state, uncommitted changes included;
|
|
131
|
+
* `clean` checks out the source branch's current commit and nothing else.
|
|
132
|
+
*/
|
|
133
|
+
export type ProjectWorktreeWorkingTreeMode = "carry" | "clean";
|
|
127
134
|
export declare function createLinkedProjectBranch(input: {
|
|
128
135
|
projectRoot: string;
|
|
129
136
|
sourceBranchName: string;
|
|
130
137
|
branchName: string;
|
|
138
|
+
workingTree: ProjectWorktreeWorkingTreeMode;
|
|
131
139
|
}): {
|
|
132
140
|
branchPath: string;
|
|
133
141
|
baseCommitHash: string;
|
|
@@ -143,6 +151,7 @@ export declare function createOrRetryLinkedProjectBranch(input: {
|
|
|
143
151
|
primaryBranchName: string;
|
|
144
152
|
sourceBranchName: string;
|
|
145
153
|
branchName: string;
|
|
154
|
+
workingTree: ProjectWorktreeWorkingTreeMode;
|
|
146
155
|
pendingRetry: boolean;
|
|
147
156
|
}): {
|
|
148
157
|
branchPath: string;
|
|
@@ -225,19 +234,28 @@ export declare function observeProjectMirrorHeads(input: Omit<ProjectMirrorHeadU
|
|
|
225
234
|
allowNonFastForward: boolean;
|
|
226
235
|
branchIds?: ReadonlyMap<string, string>;
|
|
227
236
|
refreshRemote?: boolean;
|
|
237
|
+
/**
|
|
238
|
+
* Fetch only these mounts' branch heads and handoff refs, because
|
|
239
|
+
* per-mount token gating found every other mount current. Undefined
|
|
240
|
+
* fetches the whole mirror; an empty set fetches nothing, exactly like
|
|
241
|
+
* `refreshRemote: false`.
|
|
242
|
+
*/
|
|
243
|
+
fetchOnlyBranches?: ReadonlySet<string>;
|
|
228
244
|
}): Promise<ProjectMirrorHeadObservation[]>;
|
|
229
245
|
/**
|
|
230
|
-
* Compute the
|
|
231
|
-
* token
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
246
|
+
* Compute the tokens the locally cached mirror state covers — the project
|
|
247
|
+
* token and one token per mount — from one listing of `refs/r5d/mirror/*` and
|
|
248
|
+
* `refs/r5d/server-synced/*`, with the formula shared with the server. After a
|
|
249
|
+
* fetch these name exactly the canonical state the fetch transferred; after a
|
|
250
|
+
* successful lease push (which updates `refs/r5d/mirror/*` above) they predict
|
|
251
|
+
* the server's next advertisement unless something else also moved the
|
|
252
|
+
* canonical repository, in which case the mismatch fails open into a fetch.
|
|
236
253
|
*/
|
|
237
|
-
export declare function
|
|
254
|
+
export declare function computeLocalProjectMirrorRefsTokens(input: {
|
|
238
255
|
projectRoot: string;
|
|
239
256
|
primaryBranchName: string;
|
|
240
|
-
|
|
257
|
+
mounts: readonly ProjectMirrorMount[];
|
|
258
|
+
}): Promise<ProjectMirrorRefsTokens>;
|
|
241
259
|
/** Apply only previously observed object ids; this function never refetches. */
|
|
242
260
|
export declare function applyObservedProjectMirrorHeads(input: {
|
|
243
261
|
projectRoot: string;
|
|
@@ -12,6 +12,44 @@ export type TreeEntry = {
|
|
|
12
12
|
kind: "symlink";
|
|
13
13
|
mode: number;
|
|
14
14
|
target: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A submodule (D4): the enclosing repository records a commit at this path
|
|
18
|
+
* and never its contents. A leaf for every operation here: nothing beneath
|
|
19
|
+
* it is inspected, copied, captured, removed, or restored.
|
|
20
|
+
*/
|
|
21
|
+
| {
|
|
22
|
+
kind: "gitlink";
|
|
23
|
+
objectId: string;
|
|
24
|
+
};
|
|
25
|
+
export type WorkingTreeGitlink = {
|
|
26
|
+
relativePath: string;
|
|
27
|
+
objectId: string;
|
|
28
|
+
};
|
|
29
|
+
/** One index record of a Git checkout, in the form `git update-index --index-info` accepts back. */
|
|
30
|
+
export type WorkingTreeIndexRecord = {
|
|
31
|
+
mode: string;
|
|
32
|
+
objectId: string;
|
|
33
|
+
stage: number;
|
|
34
|
+
relativePath: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Index entries a Git-target mirror changes: the records it removes or
|
|
38
|
+
* overwrites (a restore puts them back) and the gitlinks it writes (a restore
|
|
39
|
+
* drops them first).
|
|
40
|
+
*/
|
|
41
|
+
export type WorkingTreeIndexChanges = {
|
|
42
|
+
removed: WorkingTreeIndexRecord[];
|
|
43
|
+
written: WorkingTreeGitlink[];
|
|
44
|
+
};
|
|
45
|
+
export type WorkingTreeInspectionOptions = {
|
|
46
|
+
/**
|
|
47
|
+
* Gitlinks the enclosing repository records under an all-mode root, keyed
|
|
48
|
+
* by relative path. Git checks a submodule out as an empty directory, so
|
|
49
|
+
* the filesystem alone cannot show one; the entry replaces whatever is on
|
|
50
|
+
* disk at that path. Ignored in Git mode, where the index is read directly.
|
|
51
|
+
*/
|
|
52
|
+
gitlinks?: ReadonlyMap<string, string>;
|
|
15
53
|
};
|
|
16
54
|
/**
|
|
17
55
|
* Destructive native-tree moves cannot silently apply the Git-target filter:
|
|
@@ -20,19 +58,14 @@ export type TreeEntry = {
|
|
|
20
58
|
* every portable alias must be remediated before the old tree is retired.
|
|
21
59
|
*/
|
|
22
60
|
export declare function assertWorkingTreeHasNoPortableGitMetadataAliases(root: string): void;
|
|
23
|
-
export declare function inspectWorkingTree(root: string, mode: WorkingTreeSourceMode, options?:
|
|
24
|
-
/**
|
|
25
|
-
* Leave an uninitialized submodule (index gitlink whose directory is
|
|
26
|
-
* empty) out of a Git-mode inspection. Its empty directory is what keeps
|
|
27
|
-
* `git status` clean, so a deletion set must never remove it.
|
|
28
|
-
*/
|
|
29
|
-
omitUnpopulatedGitlinks?: boolean;
|
|
30
|
-
}): Map<string, TreeEntry>;
|
|
61
|
+
export declare function inspectWorkingTree(root: string, mode: WorkingTreeSourceMode, options?: WorkingTreeInspectionOptions): Map<string, TreeEntry>;
|
|
31
62
|
export declare function mirrorWorkingTree(input: {
|
|
32
63
|
sourceRoot: string;
|
|
33
64
|
targetRoot: string;
|
|
34
65
|
sourceMode: WorkingTreeSourceMode;
|
|
35
66
|
deletionMode: WorkingTreeDeletionMode;
|
|
67
|
+
/** Gitlinks the enclosing repository records for an all-mode source (see `inspectWorkingTree`). */
|
|
68
|
+
sourceGitlinks?: ReadonlyMap<string, string>;
|
|
36
69
|
/**
|
|
37
70
|
* Defer destination fsync only while building an unpublished private tree.
|
|
38
71
|
* The caller must durably fsync that complete tree before publishing it or
|
|
@@ -41,20 +74,28 @@ export declare function mirrorWorkingTree(input: {
|
|
|
41
74
|
durability?: WorkingTreeMirrorDurability;
|
|
42
75
|
}): {
|
|
43
76
|
paths: string[];
|
|
77
|
+
gitlinks: WorkingTreeGitlink[];
|
|
44
78
|
};
|
|
45
79
|
/** Canonical relative form used by mirror plans and scopes, or null when the value is unsafe. */
|
|
46
80
|
export declare function normalizeWorkingTreeRelativePath(value: string): string | null;
|
|
47
81
|
export type WorkingTreeMirrorPlan = {
|
|
48
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Every target path the mirror creates, rewrites, or verifies, ancestor
|
|
84
|
+
* directories included. A gitlink is verified as a directory; paths a
|
|
85
|
+
* submodule directory on the target shields are left out.
|
|
86
|
+
*/
|
|
49
87
|
desired: Map<string, TreeEntry>;
|
|
50
88
|
/**
|
|
51
89
|
* Target entries the mirror removes or overwrites, as they are before it
|
|
52
90
|
* runs. A directory entry means the whole subtree goes, ignored files
|
|
53
|
-
* included; entries beneath it are folded into it.
|
|
91
|
+
* included; entries beneath it are folded into it. Never a gitlink: a
|
|
92
|
+
* submodule directory stays, and only its index entry can change.
|
|
54
93
|
*/
|
|
55
94
|
replaced: Map<string, TreeEntry>;
|
|
56
95
|
/** Desired paths that have no target entry yet. */
|
|
57
96
|
absent: string[];
|
|
97
|
+
/** Index entries the mirror changes on a Git target; empty for any other target. */
|
|
98
|
+
index: WorkingTreeIndexChanges;
|
|
58
99
|
};
|
|
59
100
|
/**
|
|
60
101
|
* Compute exactly which target entries `mirrorWorkingTree` would remove,
|
|
@@ -63,19 +104,23 @@ export type WorkingTreeMirrorPlan = {
|
|
|
63
104
|
* A desired path replaces its target entry when the kinds differ, a symlink
|
|
64
105
|
* points elsewhere, or a file's bytes or mode differ; an equal entry is left
|
|
65
106
|
* alone, so the plan covers only what the mirror actually touches. A Git
|
|
66
|
-
* target's ignored trees appear only where a desired path lands on them
|
|
107
|
+
* target's ignored trees appear only where a desired path lands on them, and
|
|
108
|
+
* its submodule directories only as index changes.
|
|
67
109
|
*/
|
|
68
110
|
export declare function planWorkingTreeMirror(input: {
|
|
69
111
|
sourceRoot: string;
|
|
70
112
|
targetRoot: string;
|
|
71
113
|
sourceMode: WorkingTreeSourceMode;
|
|
72
114
|
deletionMode: WorkingTreeDeletionMode;
|
|
115
|
+
sourceGitlinks?: ReadonlyMap<string, string>;
|
|
73
116
|
}): WorkingTreeMirrorPlan;
|
|
74
117
|
export type WorkingTreeMirrorScope = {
|
|
75
118
|
/** Desired paths that did not exist before the mirror ran; a restore removes them. */
|
|
76
119
|
absent: string[];
|
|
77
120
|
/** Directories captured whole; a restore makes them identical to the snapshot again. */
|
|
78
121
|
trees: string[];
|
|
122
|
+
/** Index entries the mirror changed on a Git target; a restore drops the written gitlinks and puts the removed records back. */
|
|
123
|
+
index?: WorkingTreeIndexChanges;
|
|
79
124
|
};
|
|
80
125
|
/**
|
|
81
126
|
* Copy the entries a mirror plan replaces into `snapshotRoot` at their
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WorkingTreeSourceMode } from "./working-tree-mirror";
|
|
1
|
+
import { type WorkingTreeGitlink, type WorkingTreeSourceMode } from "./working-tree-mirror";
|
|
2
2
|
export declare const WORKSPACE_GIT_BRANCH = "main";
|
|
3
3
|
export declare const MAX_WORKSPACE_GIT_DIFF_BYTES: number;
|
|
4
4
|
export declare const WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION = "r5d-confirm-large-diff-v1";
|
|
@@ -38,6 +38,14 @@ export type WorkspaceGitMount = {
|
|
|
38
38
|
busy?: () => boolean;
|
|
39
39
|
/** Monotonic/opaque visible-mutation token used to catch short writes that begin and end between busy checks. */
|
|
40
40
|
mutationToken?: () => string;
|
|
41
|
+
/**
|
|
42
|
+
* The token a completed cycle observed when it last projected this mount.
|
|
43
|
+
* While the current token still equals it and the mount's hydration basis
|
|
44
|
+
* is the outer HEAD being projected, the outer subtree already carries the
|
|
45
|
+
* visible bytes and the mirror copy is skipped. This is a cross-cycle skip
|
|
46
|
+
* only; the per-cycle busy and token re-checks are unchanged.
|
|
47
|
+
*/
|
|
48
|
+
lastProjectedMutationToken?: string;
|
|
41
49
|
/** Activity-only fence used before checkout readiness is established on reconnect. */
|
|
42
50
|
busyForRecovery?: () => boolean;
|
|
43
51
|
};
|
|
@@ -86,8 +94,12 @@ export type WorkspaceGitSyncResult = {
|
|
|
86
94
|
affectedPaths: string[];
|
|
87
95
|
activeMountIds: string[];
|
|
88
96
|
skippedMountIds: string[];
|
|
97
|
+
/** Token each active mount carried when this cycle projected it; recorded by the caller to skip unchanged mounts later. */
|
|
98
|
+
projectedMutationTokens?: Record<string, string>;
|
|
89
99
|
/** Creator-local mounts whose first outer publication was measured against their source subtree. */
|
|
90
100
|
initialPublicationMountIds?: string[];
|
|
101
|
+
/** Network fetches of the outer workspace head this cycle ran; zero when the advertised head proved them unnecessary. */
|
|
102
|
+
remoteHeadFetches?: number;
|
|
91
103
|
conflictPaths?: string[];
|
|
92
104
|
conflictSnapshotRefs?: {
|
|
93
105
|
local: string;
|
|
@@ -150,11 +162,23 @@ export declare function ensureWorkspaceGitClone(input: {
|
|
|
150
162
|
email: string;
|
|
151
163
|
};
|
|
152
164
|
preserveResolutionInProgress?: boolean;
|
|
165
|
+
/** Server-advertised head of the workspace remote; see resolveWorkspaceRemoteHead. */
|
|
166
|
+
advertisedRemoteHead?: string | null;
|
|
153
167
|
}): Promise<{
|
|
154
168
|
localHead: string | null;
|
|
155
169
|
remoteHead: string | null;
|
|
170
|
+
remoteHeadFetched: boolean;
|
|
156
171
|
}>;
|
|
157
|
-
|
|
172
|
+
type ProjectedMountGitlinks = {
|
|
173
|
+
mount: WorkspaceGitMount;
|
|
174
|
+
gitlinks: WorkingTreeGitlink[];
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Mirror each mount's visible tree into the outer worktree. A submodule
|
|
178
|
+
* arrives as an empty directory plus a gitlink the caller must stage with
|
|
179
|
+
* `stageWorkspaceGitlinks` before `git add -A`, which can never create one.
|
|
180
|
+
*/
|
|
181
|
+
declare function mirrorMountsToWorkspace(workspacePath: string, mounts: readonly WorkspaceGitMount[]): ProjectedMountGitlinks[];
|
|
158
182
|
export declare function hydrateWorkspaceGitMounts(workspacePath: string, mounts: readonly WorkspaceGitMount[], options?: {
|
|
159
183
|
ignoreBusy?: boolean;
|
|
160
184
|
hydrationHooks?: WorkspaceHydrationTransactionHooks;
|
|
@@ -188,8 +212,14 @@ export declare function resetWorkspaceGit(input: {
|
|
|
188
212
|
activeMountIds: string[];
|
|
189
213
|
skippedMountIds: string[];
|
|
190
214
|
}>;
|
|
215
|
+
/**
|
|
216
|
+
* Bytes of the raw `git diff --binary` between two revisions, capped at
|
|
217
|
+
* `limit + 1`. `pathspecs` narrows what is measured; callers pass
|
|
218
|
+
* `:(exclude,literal)<path>` for paths whose text is not publication content,
|
|
219
|
+
* such as a subtree a gitlink replaces (see `pathsReplacedByGitlinks`).
|
|
220
|
+
*/
|
|
191
221
|
declare function diffSizeBytes(workspacePath: string, baseRevision: string | null, headRevision: string, limit: number, pathspecs?: readonly string[]): Promise<number>;
|
|
192
|
-
export
|
|
222
|
+
export type SynchronizeWorkspaceGitInput = {
|
|
193
223
|
attemptId?: string;
|
|
194
224
|
workerLabel: string;
|
|
195
225
|
workspacePath: string;
|
|
@@ -209,6 +239,12 @@ export declare function synchronizeWorkspaceGit(input: {
|
|
|
209
239
|
skipMountMirror?: boolean;
|
|
210
240
|
/** Positive ancestry proof required before a remediation may integrate, publish, or hydrate. */
|
|
211
241
|
requiredAncestorHeads?: readonly string[];
|
|
242
|
+
/**
|
|
243
|
+
* Head of the workspace remote as the server last read it from disk. Equal
|
|
244
|
+
* to the local remote-tracking ref, it lets the cycle skip its workspace
|
|
245
|
+
* head fetches; see resolveWorkspaceRemoteHead. Omit to always fetch.
|
|
246
|
+
*/
|
|
247
|
+
advertisedRemoteHead?: string | null;
|
|
212
248
|
/** Revalidate the worker/config/incident generation after every async boundary. */
|
|
213
249
|
assertStillAdmitted?: () => void;
|
|
214
250
|
/** Test seam for exercising an incident transition during asynchronous diff measurement. */
|
|
@@ -219,7 +255,8 @@ export declare function synchronizeWorkspaceGit(input: {
|
|
|
219
255
|
}) => void | Promise<void>;
|
|
220
256
|
/** Announces each synchronous hydration transaction of this cycle; see WorkspaceHydrationTransactionHooks. */
|
|
221
257
|
hydrationHooks?: WorkspaceHydrationTransactionHooks;
|
|
222
|
-
}
|
|
258
|
+
};
|
|
259
|
+
export declare function synchronizeWorkspaceGit(input: SynchronizeWorkspaceGitInput): Promise<WorkspaceGitSyncResult>;
|
|
223
260
|
export declare const workspaceGitSyncTestHarness: {
|
|
224
261
|
commandArgs: typeof gitCommandArgs;
|
|
225
262
|
workspaceCloneCommandArgs: typeof workspaceCloneCommandArgs;
|
|
@@ -3,9 +3,20 @@ export type WorkspaceMutationLease = () => void;
|
|
|
3
3
|
* Coordinates project worktrees with the outer workspace Git clone.
|
|
4
4
|
*
|
|
5
5
|
* Ordinary worktree activity may overlap other ordinary activity, but a sync
|
|
6
|
-
* gets exclusive access. Once a sync is queued, later mutations wait
|
|
7
|
-
* so a queued sync cannot be starved by later short mutations.
|
|
8
|
-
*
|
|
6
|
+
* gets exclusive access. Once a sync is queued, later ordinary mutations wait
|
|
7
|
+
* behind it so a queued sync cannot be starved by later short mutations.
|
|
8
|
+
*
|
|
9
|
+
* Branch creation and deletion use their own lane: a branch mutation never
|
|
10
|
+
* waits behind a queued sync, only behind a sync that is already running.
|
|
11
|
+
* Every sync still gets exclusive access, so hydration cannot overlap a
|
|
12
|
+
* worktree being created or removed; the lane only decides ordering between
|
|
13
|
+
* cycles. Branch operations are short and rare, so the queued sync they
|
|
14
|
+
* step in front of waits at most their own duration. Without the lane a
|
|
15
|
+
* branch operation queued behind a backlog of syncs waited for every one of
|
|
16
|
+
* them, outlived the server's operation timeout, and then ran anyway against
|
|
17
|
+
* a request the server had already rolled back.
|
|
18
|
+
*
|
|
19
|
+
* Long-running project commands do not enter this gate; outer-workspace
|
|
9
20
|
* remediation commands do because they operate on the sync clone itself.
|
|
10
21
|
*/
|
|
11
22
|
export declare class WorkspaceMutationGate {
|
|
@@ -15,6 +26,16 @@ export declare class WorkspaceMutationGate {
|
|
|
15
26
|
acquireMutation(): Promise<WorkspaceMutationLease>;
|
|
16
27
|
runMutation<T>(operation: () => Promise<T> | T): Promise<T>;
|
|
17
28
|
runSync<T>(operation: () => Promise<T> | T): Promise<T>;
|
|
29
|
+
/** A worktree create/delete lease; see the branch lane in the class comment. */
|
|
30
|
+
acquireBranchMutation(): Promise<WorkspaceMutationLease>;
|
|
31
|
+
runBranchMutation<T>(operation: () => Promise<T> | T): Promise<T>;
|
|
32
|
+
/**
|
|
33
|
+
* Operations a branch mutation requested now would wait for: the running
|
|
34
|
+
* sync, if any, plus branch mutations queued behind it. Zero means it runs
|
|
35
|
+
* at once. Ordinary waiters are not counted because the branch lane admits
|
|
36
|
+
* ahead of them.
|
|
37
|
+
*/
|
|
38
|
+
branchMutationQueuePosition(): number;
|
|
18
39
|
private drain;
|
|
19
40
|
private releaseMutation;
|
|
20
41
|
private releaseSync;
|