@ricsam/r5d-worker 0.0.101 → 0.0.104

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.
@@ -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 behind it
7
- * so a queued sync cannot be starved by later short mutations. Long-running
8
- * long-running project commands do not enter this gate; outer-workspace
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;
@@ -0,0 +1,59 @@
1
+ export type WorkspaceProjectionLedgerMount = {
2
+ id: string;
3
+ /** Durable logical incarnation; a re-created branch never matches its predecessor's record. */
4
+ hydrationIncarnationKey: string;
5
+ mutationToken?: () => string;
6
+ };
7
+ /**
8
+ * Remembers, per mount incarnation, the visible-mutation token observed when
9
+ * the mount was last projected into the outer clone by a completed cycle,
10
+ * together with that cycle's result.
11
+ *
12
+ * Two decisions read it. A cycle skips mirroring a mount whose token is
13
+ * unchanged, because its outer subtree already carries those bytes. A
14
+ * request that targets a project checkout is answered with the previous
15
+ * cycle's result, without running a cycle, when no mount's token moved since
16
+ * that cycle. Both are cross-cycle skips only; the within-cycle busy and
17
+ * token re-checks in the sync engine stay as they are.
18
+ *
19
+ * The ledger is reset whenever something other than a recorded visible
20
+ * mutation can change checkout bytes: workspace configuration, canonical
21
+ * reset, outer remediation, branch creation or deletion, and any cycle that
22
+ * failed or ended in a conflict.
23
+ */
24
+ export declare class WorkspaceProjectionLedger<Result extends {
25
+ outcome: string;
26
+ }> {
27
+ private readonly projectedTokens;
28
+ private lastCycle;
29
+ private static recordKey;
30
+ get lastCycleResult(): Result | null;
31
+ lastProjectedToken(mount: {
32
+ id: string;
33
+ hydrationIncarnationKey: string;
34
+ }): string | undefined;
35
+ /**
36
+ * Record a completed cycle. `projectedMutationTokens` names the token each
37
+ * active mount carried when the cycle projected it; a mount absent from it
38
+ * keeps its earlier record. Any outcome that leaves the outer clone without
39
+ * a trustworthy projection clears the ledger.
40
+ */
41
+ recordCycle(input: {
42
+ result: Result;
43
+ mounts: readonly WorkspaceProjectionLedgerMount[];
44
+ projectedMutationTokens: Readonly<Record<string, string>> | undefined;
45
+ }): void;
46
+ reset(): void;
47
+ /**
48
+ * The previous cycle's result when every request targets a configured
49
+ * mount and no mount's token moved since its recorded projection. A request
50
+ * without a target (a workspace-rooted command may touch any checkout), a
51
+ * mount without a token (a pending deletion), or pending deletion work all
52
+ * require a real cycle.
53
+ */
54
+ answerFromLastCycle(input: {
55
+ targetMountIds: ReadonlyArray<string | null>;
56
+ mounts: readonly WorkspaceProjectionLedgerMount[];
57
+ pendingDeletions: boolean;
58
+ }): Result | null;
59
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Batches ordinary workspace synchronization requests into one exclusive
3
+ * cycle each. A request that arrives while a batch is queued but not yet
4
+ * running joins that batch; a request that arrives while a batch is running
5
+ * opens the next one, because the running cycle may have selected its mounts
6
+ * before the request's command finished. Every request in a batch receives
7
+ * the same settlement, and the caller fans that out per attempt.
8
+ *
9
+ * Before this, every finished command queued its own full cycle behind every
10
+ * earlier one, so queue time grew without bound under a steady arrival rate
11
+ * even though each cycle finished in seconds.
12
+ */
13
+ export declare class WorkspaceSyncCoalescer<Request, Settlement> {
14
+ private readonly options;
15
+ private pendingBatch;
16
+ private batchesStarted;
17
+ constructor(options: {
18
+ /** The exclusive sync lease; the batch body runs inside it. */
19
+ runExclusive: <T>(operation: () => Promise<T>) => Promise<T>;
20
+ /** Runs (or answers without running) one cycle for the whole batch. */
21
+ runBatch: (requests: readonly Request[]) => Promise<Settlement>;
22
+ });
23
+ /** Requests attached to the batch that has not started yet. */
24
+ get queuedRequestCount(): number;
25
+ /** Batches whose body has been entered, whether or not they ran a cycle. */
26
+ get startedBatchCount(): number;
27
+ request(request: Request): Promise<Settlement>;
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.101",
3
+ "version": "0.0.104",
4
4
  "type": "module",
5
5
  "main": "./dist/cjs/main.cjs",
6
6
  "module": "./dist/mjs/main.mjs",