@indigoai-us/hq-cloud 6.16.58-alpha.3 → 6.16.59

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.
Files changed (154) hide show
  1. package/dist/bin/sync-runner-company.d.ts +0 -14
  2. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  3. package/dist/bin/sync-runner-company.js +89 -57
  4. package/dist/bin/sync-runner-company.js.map +1 -1
  5. package/dist/bin/sync-runner-company.test.js +313 -56
  6. package/dist/bin/sync-runner-company.test.js.map +1 -1
  7. package/dist/bin/sync-runner-planning.d.ts +3 -12
  8. package/dist/bin/sync-runner-planning.d.ts.map +1 -1
  9. package/dist/bin/sync-runner-planning.js +3 -65
  10. package/dist/bin/sync-runner-planning.js.map +1 -1
  11. package/dist/bin/sync-runner-planning.test.js +1 -172
  12. package/dist/bin/sync-runner-planning.test.js.map +1 -1
  13. package/dist/bin/sync-runner-watch-loop.d.ts +2 -51
  14. package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
  15. package/dist/bin/sync-runner-watch-loop.js +165 -409
  16. package/dist/bin/sync-runner-watch-loop.js.map +1 -1
  17. package/dist/bin/sync-runner-watch-routes.d.ts +2 -19
  18. package/dist/bin/sync-runner-watch-routes.d.ts.map +1 -1
  19. package/dist/bin/sync-runner-watch-routes.js +5 -40
  20. package/dist/bin/sync-runner-watch-routes.js.map +1 -1
  21. package/dist/bin/sync-runner-watch-routes.test.js +0 -47
  22. package/dist/bin/sync-runner-watch-routes.test.js.map +1 -1
  23. package/dist/bin/sync-runner.d.ts +13 -59
  24. package/dist/bin/sync-runner.d.ts.map +1 -1
  25. package/dist/bin/sync-runner.js +4 -48
  26. package/dist/bin/sync-runner.js.map +1 -1
  27. package/dist/bin/sync-runner.test.js +229 -1499
  28. package/dist/bin/sync-runner.test.js.map +1 -1
  29. package/dist/cli/share.d.ts +0 -19
  30. package/dist/cli/share.d.ts.map +1 -1
  31. package/dist/cli/share.js +0 -55
  32. package/dist/cli/share.js.map +1 -1
  33. package/dist/cli/share.test.js +1 -87
  34. package/dist/cli/share.test.js.map +1 -1
  35. package/dist/cli/sync-scope.test.js +0 -8
  36. package/dist/cli/sync-scope.test.js.map +1 -1
  37. package/dist/cli/sync.d.ts +8 -12
  38. package/dist/cli/sync.d.ts.map +1 -1
  39. package/dist/cli/sync.js +152 -302
  40. package/dist/cli/sync.js.map +1 -1
  41. package/dist/cli/sync.test.js +126 -256
  42. package/dist/cli/sync.test.js.map +1 -1
  43. package/dist/cognito-auth.d.ts +34 -1
  44. package/dist/cognito-auth.d.ts.map +1 -1
  45. package/dist/cognito-auth.js +92 -21
  46. package/dist/cognito-auth.js.map +1 -1
  47. package/dist/context.d.ts +5 -0
  48. package/dist/context.d.ts.map +1 -1
  49. package/dist/context.js +92 -12
  50. package/dist/context.js.map +1 -1
  51. package/dist/context.test.js +77 -2
  52. package/dist/context.test.js.map +1 -1
  53. package/dist/ignore.d.ts +10 -0
  54. package/dist/ignore.d.ts.map +1 -1
  55. package/dist/ignore.js +47 -5
  56. package/dist/ignore.js.map +1 -1
  57. package/dist/ignore.test.js +30 -3
  58. package/dist/ignore.test.js.map +1 -1
  59. package/dist/journal-row-store.d.ts +5 -40
  60. package/dist/journal-row-store.d.ts.map +1 -1
  61. package/dist/journal-row-store.js +21 -240
  62. package/dist/journal-row-store.js.map +1 -1
  63. package/dist/journal-row-store.test.js +0 -54
  64. package/dist/journal-row-store.test.js.map +1 -1
  65. package/dist/journal-snapshot-v4.d.ts.map +1 -1
  66. package/dist/journal-snapshot-v4.js +6 -11
  67. package/dist/journal-snapshot-v4.js.map +1 -1
  68. package/dist/journal-store.test.js +7 -324
  69. package/dist/journal-store.test.js.map +1 -1
  70. package/dist/journal.d.ts +16 -51
  71. package/dist/journal.d.ts.map +1 -1
  72. package/dist/journal.js +79 -303
  73. package/dist/journal.js.map +1 -1
  74. package/dist/lib/exit-codes.d.ts +0 -15
  75. package/dist/lib/exit-codes.d.ts.map +1 -1
  76. package/dist/lib/exit-codes.js +0 -15
  77. package/dist/lib/exit-codes.js.map +1 -1
  78. package/dist/lib/net-errors.d.ts +12 -0
  79. package/dist/lib/net-errors.d.ts.map +1 -1
  80. package/dist/lib/net-errors.js +35 -0
  81. package/dist/lib/net-errors.js.map +1 -1
  82. package/dist/lib/net-errors.test.js +19 -1
  83. package/dist/lib/net-errors.test.js.map +1 -1
  84. package/dist/machine-auth.test.js +6 -4
  85. package/dist/machine-auth.test.js.map +1 -1
  86. package/dist/machine-token-cache-isolation.test.d.ts +24 -0
  87. package/dist/machine-token-cache-isolation.test.d.ts.map +1 -0
  88. package/dist/machine-token-cache-isolation.test.js +241 -0
  89. package/dist/machine-token-cache-isolation.test.js.map +1 -0
  90. package/dist/manifest/build-manifest-resident-parity.test.js +3 -3
  91. package/dist/manifest/build-manifest-resident-parity.test.js.map +1 -1
  92. package/dist/personal-vault.d.ts.map +1 -1
  93. package/dist/personal-vault.js +3 -10
  94. package/dist/personal-vault.js.map +1 -1
  95. package/dist/personal-vault.test.js +0 -35
  96. package/dist/personal-vault.test.js.map +1 -1
  97. package/dist/remote-pull.d.ts.map +1 -1
  98. package/dist/remote-pull.js +2 -9
  99. package/dist/remote-pull.js.map +1 -1
  100. package/dist/remote-pull.test.js +2 -59
  101. package/dist/remote-pull.test.js.map +1 -1
  102. package/dist/s3.d.ts +0 -1
  103. package/dist/s3.d.ts.map +1 -1
  104. package/dist/s3.js +2 -20
  105. package/dist/s3.js.map +1 -1
  106. package/dist/sync/area-ledger.d.ts +3 -27
  107. package/dist/sync/area-ledger.d.ts.map +1 -1
  108. package/dist/sync/area-ledger.js +39 -149
  109. package/dist/sync/area-ledger.js.map +1 -1
  110. package/dist/sync/area-ledger.test.js +5 -192
  111. package/dist/sync/area-ledger.test.js.map +1 -1
  112. package/dist/sync/parallel-sync-coordinator.test.js +0 -29
  113. package/dist/sync/parallel-sync-coordinator.test.js.map +1 -1
  114. package/dist/sync/realtime-telemetry.d.ts +1 -1
  115. package/dist/sync/realtime-telemetry.d.ts.map +1 -1
  116. package/dist/sync/realtime-telemetry.js +6 -14
  117. package/dist/sync/realtime-telemetry.js.map +1 -1
  118. package/dist/sync/realtime-telemetry.test.js +16 -136
  119. package/dist/sync/realtime-telemetry.test.js.map +1 -1
  120. package/dist/sync/state-store.d.ts +0 -6
  121. package/dist/sync/state-store.d.ts.map +1 -1
  122. package/dist/sync/state-store.js +2 -14
  123. package/dist/sync/state-store.js.map +1 -1
  124. package/dist/watcher.d.ts +0 -12
  125. package/dist/watcher.d.ts.map +1 -1
  126. package/dist/watcher.js +0 -131
  127. package/dist/watcher.js.map +1 -1
  128. package/dist/watcher.test.js +0 -63
  129. package/dist/watcher.test.js.map +1 -1
  130. package/package.json +1 -2
  131. package/dist/cli/onepass-pull-planner-regressions.test.d.ts +0 -2
  132. package/dist/cli/onepass-pull-planner-regressions.test.d.ts.map +0 -1
  133. package/dist/cli/onepass-pull-planner-regressions.test.js +0 -300
  134. package/dist/cli/onepass-pull-planner-regressions.test.js.map +0 -1
  135. package/dist/cli/onepass-pull-planner.test.d.ts +0 -2
  136. package/dist/cli/onepass-pull-planner.test.d.ts.map +0 -1
  137. package/dist/cli/onepass-pull-planner.test.js +0 -266
  138. package/dist/cli/onepass-pull-planner.test.js.map +0 -1
  139. package/dist/cli/streaming-pull-planner.bench.test.d.ts +0 -15
  140. package/dist/cli/streaming-pull-planner.bench.test.d.ts.map +0 -1
  141. package/dist/cli/streaming-pull-planner.bench.test.js +0 -273
  142. package/dist/cli/streaming-pull-planner.bench.test.js.map +0 -1
  143. package/dist/s3.list-cost.test.d.ts +0 -2
  144. package/dist/s3.list-cost.test.d.ts.map +0 -1
  145. package/dist/s3.list-cost.test.js +0 -51
  146. package/dist/s3.list-cost.test.js.map +0 -1
  147. package/dist/sync/remote-file-spool.d.ts +0 -16
  148. package/dist/sync/remote-file-spool.d.ts.map +0 -1
  149. package/dist/sync/remote-file-spool.js +0 -91
  150. package/dist/sync/remote-file-spool.js.map +0 -1
  151. package/dist/sync/remote-file-spool.test.d.ts +0 -2
  152. package/dist/sync/remote-file-spool.test.d.ts.map +0 -1
  153. package/dist/sync/remote-file-spool.test.js +0 -77
  154. package/dist/sync/remote-file-spool.test.js.map +0 -1
@@ -10,11 +10,11 @@ import { pickCanonicalPersonEntity } from "../vault-client.js";
10
10
  import { acquireOperationLock, withOperationLock, withOperationLockSync, OperationLockedError, OPERATION_LOCKED_EXIT, } from "../operation-lock.js";
11
11
  import { describeError } from "../lib/describe-error.js";
12
12
  import { TRANSIENT_NETWORK_EXIT } from "../lib/net-errors.js";
13
- import { PARTIAL_SYNC_EXIT, UNRESOLVED_COMPANY_EXIT, } from "../lib/exit-codes.js";
13
+ import { PARTIAL_SYNC_EXIT } from "../lib/exit-codes.js";
14
14
  import { getOrCreateMachineId } from "../lib/machine-id.js";
15
15
  import { localPathForVaultKey } from "../local-path-codec.js";
16
16
  import { TreeWatcher, resolveEventDebounceConfig, systemClock, } from "../watcher.js";
17
- import { clearLocalDeleteIntent, getStateDir, markLocalDeleteIntent, PERSONAL_VAULT_JOURNAL_SLUG, openJournalStoreSession, openJournalStoreSessionAsync, listJournalSlugs, listJournals, maintainAreaJournalSlice, disposeScopedJournalStores, releaseScopedJournalStore, releaseCachedAreaJournalMigrations, journalCacheCensusSizes, journalRows, readJournalScoped, writeJournal, } from "../journal.js";
17
+ import { clearLocalDeleteIntent, getStateDir, markLocalDeleteIntent, PERSONAL_VAULT_JOURNAL_SLUG, openJournalStoreSession, openJournalStoreSessionAsync, listJournalSlugs, listJournals, maintainAreaJournalSlice, readJournal, releaseCachedAreaJournalMigrations, journalCacheCensusSizes, journalRows, readJournalScoped, writeJournal, } from "../journal.js";
18
18
  import { isPersonalVaultDeletionExcluded } from "../personal-vault.js";
19
19
  import { NoopPushReceiver, } from "../sync/push-receiver.js";
20
20
  import { resolveEventSync, startEventSync as defaultStartEventSync, } from "../sync/event-sync.js";
@@ -30,219 +30,6 @@ import { InlineSyncWorkerAdapter, } from "../sync/sync-work.js";
30
30
  import { buildRoutePullArgv, buildScopedPushArgv, buildScopedDrainPullArgv, buildTargetedPullArgv, isWatchRouteSelected, routeChangeToTarget, routeKey, } from "./sync-runner-watch-routes.js";
31
31
  import { resolveSkipCompanies } from "./sync-runner-planning.js";
32
32
  const FULL_RECONCILE_EVERY_TICKS = 10;
33
- /** A deferred full chore must get the next admission after this long. */
34
- export const CHORE_STARVATION_CAP_MS = 15 * 60 * 1000;
35
- /**
36
- * One durable-state owner at a time for a full chore. Realtime work never runs
37
- * beside that chore: it explicitly awaits `checkpoint()` after a completed
38
- * durable unit, then resumes once the hand-off is finished. A separately
39
- * tracked, explicitly opted-in watcher fast pass may use the pre-existing
40
- * scoped fast-over-slow exception, but never while a chore owns durable state.
41
- */
42
- export class CooperativePassScheduler {
43
- now;
44
- starvationCapMs;
45
- pending = [];
46
- active = null;
47
- // Scoped fast realtime work has the established in-process lock exception
48
- // while a scoped slow realtime upload is in flight. Keep that slot separate
49
- // from `active`: a chore still has exactly one durable-state owner.
50
- activeFastRealtime = null;
51
- stopped = false;
52
- _handoffActive = false;
53
- constructor(now, starvationCapMs = CHORE_STARVATION_CAP_MS) {
54
- this.now = now;
55
- this.starvationCapMs = starvationCapMs;
56
- }
57
- get handoffActive() {
58
- return this._handoffActive;
59
- }
60
- get hasActiveChore() {
61
- return this.active?.workClass === "chore";
62
- }
63
- get hasActiveSlowRealtime() {
64
- return this.active?.workClass === "realtime" && this.active.lane === "slow";
65
- }
66
- pendingLaneCount(lane) {
67
- return this.pending.filter((entry) => entry.lane === lane).length;
68
- }
69
- submit(task, workClass, lane = "slow", allowFastRealtimeOvertake = false) {
70
- if (this.stopped)
71
- return Promise.resolve(0);
72
- const directEntry = {
73
- task,
74
- workClass,
75
- lane,
76
- allowFastRealtimeOvertake,
77
- // An immediately admitted task cannot starve, so avoid sampling the
78
- // cadence clock merely to stamp it. This preserves the loop's deadline
79
- // accounting and keeps the timestamp meaningful only for real queues.
80
- enqueuedAt: 0,
81
- resolve: () => undefined,
82
- reject: () => undefined,
83
- };
84
- if (this.canStartFastRealtime(directEntry)) {
85
- return this.startFastRealtime(directEntry);
86
- }
87
- if (this.active === null &&
88
- this.activeFastRealtime === null &&
89
- this.pending.length === 0) {
90
- return this.start(directEntry);
91
- }
92
- return new Promise((resolve, reject) => {
93
- const entry = { ...directEntry, enqueuedAt: this.now(), resolve, reject };
94
- this.pending.push(entry);
95
- this.drain();
96
- });
97
- }
98
- stop() {
99
- this.stopped = true;
100
- while (this.pending.length > 0)
101
- this.pending.shift()?.resolve(0);
102
- }
103
- drain() {
104
- if (this.stopped || this.pending.length === 0)
105
- return;
106
- if (this.active !== null) {
107
- const fastRealtime = this.nextFastRealtime();
108
- if (fastRealtime && this.canStartFastRealtime(fastRealtime)) {
109
- this.pending.splice(this.pending.indexOf(fastRealtime), 1);
110
- this.startFastRealtime(fastRealtime);
111
- }
112
- return;
113
- }
114
- if (this.activeFastRealtime !== null)
115
- return;
116
- const next = this.takeNext();
117
- if (next)
118
- this.start(next);
119
- }
120
- takeNext() {
121
- const now = this.now();
122
- const starvedChore = this.pending.findIndex((entry) => entry.workClass === "chore" && now - entry.enqueuedAt >= this.starvationCapMs);
123
- if (starvedChore >= 0)
124
- return this.pending.splice(starvedChore, 1)[0];
125
- // A receiver's bulk drain is still realtime work, but its slow lane is the
126
- // progress boundary for that delivery. Preserve its established turn
127
- // before refilling later fast realtime arrivals; this does not let a
128
- // chore overtake any realtime work.
129
- const realtime = this.nextRealtimeIndex();
130
- if (realtime >= 0)
131
- return this.pending.splice(realtime, 1)[0];
132
- return this.pending.shift();
133
- }
134
- nextRealtimeIndex() {
135
- const slowRealtime = this.pending.findIndex((entry) => entry.workClass === "realtime" && entry.lane === "slow");
136
- return slowRealtime >= 0
137
- ? slowRealtime
138
- : this.pending.findIndex((entry) => entry.workClass === "realtime");
139
- }
140
- nextFastRealtime() {
141
- return this.pending.find((entry) => entry.workClass === "realtime" && entry.lane === "fast");
142
- }
143
- hasStarvedChore() {
144
- const now = this.now();
145
- return this.pending.some((entry) => entry.workClass === "chore" && now - entry.enqueuedAt >= this.starvationCapMs);
146
- }
147
- canStartFastRealtime(entry) {
148
- return (!this._handoffActive &&
149
- this.activeFastRealtime === null &&
150
- entry.workClass === "realtime" &&
151
- entry.lane === "fast" &&
152
- entry.allowFastRealtimeOvertake &&
153
- this.hasActiveSlowRealtime &&
154
- !this.hasStarvedChore());
155
- }
156
- start(entry) {
157
- this.active = entry;
158
- let current;
159
- try {
160
- current = this.execute(entry);
161
- }
162
- catch (error) {
163
- current = Promise.reject(error);
164
- }
165
- void current.then((outcome) => {
166
- entry.resolve(outcome);
167
- this.complete(entry);
168
- }, (error) => {
169
- entry.reject(error);
170
- this.complete(entry);
171
- }).catch(() => undefined);
172
- return current;
173
- }
174
- complete(entry) {
175
- if (this.active === entry)
176
- this.active = null;
177
- this.drain();
178
- }
179
- startFastRealtime(entry) {
180
- this.activeFastRealtime = entry;
181
- let current;
182
- try {
183
- current = this.execute(entry);
184
- }
185
- catch (error) {
186
- current = Promise.reject(error);
187
- }
188
- void current.then((outcome) => {
189
- entry.resolve(outcome);
190
- this.completeFastRealtime(entry);
191
- }, (error) => {
192
- entry.reject(error);
193
- this.completeFastRealtime(entry);
194
- });
195
- return current;
196
- }
197
- completeFastRealtime(entry) {
198
- if (this.activeFastRealtime === entry)
199
- this.activeFastRealtime = null;
200
- this.drain();
201
- }
202
- execute(entry) {
203
- return entry.task({ checkpoint: () => this.checkpoint(entry) });
204
- }
205
- async checkpoint(entry) {
206
- if (this.stopped ||
207
- entry.workClass !== "chore" ||
208
- this.active !== entry ||
209
- !this.pending.some((pending) => pending.workClass === "realtime")) {
210
- return false;
211
- }
212
- // `entry` is suspended at a caller-declared durable boundary. It remains
213
- // the sole full-pass continuation and will resume after this bounded burst.
214
- const deferredAt = this.now();
215
- this.active = null;
216
- this._handoffActive = true;
217
- let handedOff = false;
218
- try {
219
- while (!this.stopped && this.now() - deferredAt < this.starvationCapMs) {
220
- const realtimeIndex = this.nextRealtimeIndex();
221
- if (realtimeIndex < 0)
222
- break;
223
- const realtime = this.pending.splice(realtimeIndex, 1)[0];
224
- handedOff = true;
225
- this.active = realtime;
226
- try {
227
- realtime.resolve(await this.execute(realtime));
228
- }
229
- catch (error) {
230
- realtime.reject(error);
231
- }
232
- finally {
233
- if (this.active === realtime)
234
- this.active = null;
235
- }
236
- }
237
- }
238
- finally {
239
- this._handoffActive = false;
240
- if (!this.stopped)
241
- this.active = entry;
242
- }
243
- return handedOff;
244
- }
245
- }
246
33
  const MAX_FULL_RECONCILE_REASONS = 7;
247
34
  /**
248
35
  * Area storage is AUTO by default and only applies after durable cutover.
@@ -722,7 +509,6 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
722
509
  const idleHeartbeatIntervalMs = deps.idleHeartbeatIntervalMs ?? WATCH_IDLE_HEARTBEAT_INTERVAL_MS;
723
510
  const monotonicNow = deps.monotonicNow ?? (() => performance.now());
724
511
  const runPass = deps.runPass ?? runtime.runPassWithOperationLockAlreadyHeld;
725
- const runPassSupportsCooperativeCheckpoint = deps.runPass === undefined || deps.runPassSupportsCooperativeCheckpoint === true;
726
512
  const pollIdx = argv.indexOf("--poll-remote-ms");
727
513
  // An explicit --poll-remote-ms pins a fixed interval (menubar/CLI still set
728
514
  // one). When it is omitted, the interval is resolved fresh each cycle from
@@ -733,11 +519,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
733
519
  const eventPush = argv.includes("--event-push");
734
520
  const fullReconcileIntervalMs = resolveFullReconcileMs();
735
521
  const companiesMode = argv.includes("--companies");
736
- const companyIdx = argv.indexOf("--company");
737
- const companyMode = companyIdx >= 0;
738
- // The tenant a single-company run owns. Carried into route selection so a
739
- // watcher or receiver event under another company cannot queue a pass for it.
740
- const companySlug = companyMode ? argv[companyIdx + 1] : undefined;
522
+ const companyMode = argv.includes("--company");
741
523
  const personalMode = argv.includes("--personal");
742
524
  // Keep this at the watcher ingress rather than importing the runner's
743
525
  // resolver: sync-runner imports this loop, and a reverse value import would
@@ -746,7 +528,6 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
746
528
  const watchRouteSelection = {
747
529
  companies: companiesMode,
748
530
  company: companyMode,
749
- ...(companySlug !== undefined ? { companySlug } : {}),
750
531
  personal: personalMode,
751
532
  skipPersonal: parsed.skipPersonal === true ||
752
533
  skipPersonalEnv === "1" ||
@@ -891,27 +672,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
891
672
  resolveNoOperationLockUsers = resolve;
892
673
  });
893
674
  };
894
- const passScheduler = new CooperativePassScheduler(monotonicNow);
895
- const runPassWithLock = async (passArgvForRun, prepare, lane = "slow", workClass = "chore", checkpoint) => {
896
- const reportedAppliedUpserts = new Set();
897
- const recordDurableAppliedUpserts = (upserts) => {
898
- for (const applied of upserts) {
899
- const key = `${applied.relativePath}\u0000${applied.contentHash}`;
900
- if (reportedAppliedUpserts.has(key))
901
- continue;
902
- reportedAppliedUpserts.add(key);
903
- rememberAppliedUpsert(applied.relativePath, applied.contentHash);
904
- }
905
- };
906
- const invokePass = () => {
907
- if (!runPassSupportsCooperativeCheckpoint)
908
- return runPass(passArgvForRun);
909
- // This callback is emitted by the production runner after journal
910
- // repair/seed and before fanout. It starts ingress early, but never
911
- // admits a second durable writer: queued realtime still waits for the
912
- // existing scheduler's cooperative hand-off checkpoint.
913
- return runPass(passArgvForRun, checkpoint, () => activateEventPushSurfaces(), recordDurableAppliedUpserts);
914
- };
675
+ const runPassWithLock = async (passArgvForRun, prepare, lane = "slow") => {
915
676
  // Observe the heap at pass boundaries — a gated checkpoint immediately before
916
677
  // admitting a pass and immediately after it returns. A pass that runs a long
917
678
  // synchronous stretch starves the interval timer, so these boundary
@@ -923,7 +684,9 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
923
684
  heapCheckpoint?.();
924
685
  if (typeof outcome === "number")
925
686
  return;
926
- recordDurableAppliedUpserts(outcome.result?.appliedUpserts ?? []);
687
+ for (const applied of outcome.result?.appliedUpserts ?? []) {
688
+ rememberAppliedUpsert(applied.relativePath, applied.contentHash);
689
+ }
927
690
  };
928
691
  // Pre-admit boundary observation.
929
692
  heapCheckpoint?.();
@@ -932,31 +695,13 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
932
695
  // its keyed journal delta and per-object mutation are independent from the
933
696
  // slow pass's bulk paths, and the slow pass already owns the root lock.
934
697
  // Never bypass the lock when no slow pass is active.
935
- if (
936
- // A full chore is paused at a caller-declared durable checkpoint. Keep
937
- // the root lock held by this process, but lend it to exactly one
938
- // realtime pass; the suspended chore cannot mutate until this returns.
939
- workClass === "realtime" &&
940
- passScheduler.handoffActive &&
941
- processOwnsOperationLock) {
942
- acquireOperationLockUser();
943
- try {
944
- prepare?.();
945
- const outcome = await invokePass();
946
- recordAppliedUpserts(outcome);
947
- return outcome;
948
- }
949
- finally {
950
- releaseOperationLockUser();
951
- }
952
- }
953
698
  if (lane === "fast" &&
954
699
  processOwnsOperationLock &&
955
700
  coordinatorWholeMutationChores === 0) {
956
701
  acquireOperationLockUser();
957
702
  try {
958
703
  prepare?.();
959
- const outcome = await invokePass();
704
+ const outcome = await runPass(passArgvForRun);
960
705
  recordAppliedUpserts(outcome);
961
706
  return outcome;
962
707
  }
@@ -976,7 +721,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
976
721
  acquireOperationLockUser();
977
722
  }
978
723
  prepare?.();
979
- const outcome = await invokePass();
724
+ const outcome = await runPass(passArgvForRun);
980
725
  recordAppliedUpserts(outcome);
981
726
  return outcome;
982
727
  }
@@ -1010,7 +755,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1010
755
  }
1011
756
  try {
1012
757
  prepare?.();
1013
- const outcome = await invokePass();
758
+ const outcome = await runPass(passArgvForRun);
1014
759
  recordAppliedUpserts(outcome);
1015
760
  return outcome;
1016
761
  }
@@ -1033,18 +778,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1033
778
  throw err;
1034
779
  }
1035
780
  };
1036
- const runTaskWithLock = async (task, workClass = "chore") => {
1037
- if (workClass === "realtime" &&
1038
- passScheduler.handoffActive &&
1039
- processOwnsOperationLock) {
1040
- acquireOperationLockUser();
1041
- try {
1042
- return await task();
1043
- }
1044
- finally {
1045
- releaseOperationLockUser();
1046
- }
1047
- }
781
+ const runTaskWithLock = async (task) => {
1048
782
  try {
1049
783
  const handle = acquireOperationLock(hqRoot, "sync", {
1050
784
  timeoutSec: 0,
@@ -1138,15 +872,102 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1138
872
  heapCheckpoint?.();
1139
873
  }
1140
874
  };
875
+ let activeSlowPass = null;
876
+ let activeFastPass = null;
877
+ const pendingSlowPasses = [];
878
+ const pendingFastPasses = [];
1141
879
  const resolveStoppedQueue = () => {
1142
- passScheduler.stop();
880
+ for (const queue of [pendingFastPasses, pendingSlowPasses]) {
881
+ while (queue.length > 0)
882
+ queue.shift()?.resolve(0);
883
+ }
1143
884
  };
1144
- const runGuardedTask = (task, lane = "slow", workClass = "chore", allowFastRealtimeOvertake = false) =>
1145
- // Lanes still choose the pass execution profile, but no longer decide
1146
- // chore ownership. Realtime normally waits for a chore hand-off; the
1147
- // watcher fast path explicitly opts into its established scoped exception.
1148
- passScheduler.submit(task, workClass, lane, allowFastRealtimeOvertake);
1149
- const runGuarded = (passArgvForRun, prepare, lane = "slow", workClass = "chore", allowFastRealtimeOvertake = false) => runGuardedTask((context) => runPassWithLock(passArgvForRun, prepare, lane, workClass, workClass === "chore" ? context.checkpoint : undefined), lane, workClass, allowFastRealtimeOvertake);
885
+ const drainQueuedPasses = () => {
886
+ if (stopped) {
887
+ resolveStoppedQueue();
888
+ return;
889
+ }
890
+ // A queued slow pass is the bulk-drain progress boundary. Admit it before
891
+ // refilling the realtime queue; otherwise a steady stream of fast work
892
+ // perpetually leapfrogs a receiver's bulk batch and its SQS poll loop
893
+ // cannot issue another receive.
894
+ if (activeSlowPass === null &&
895
+ activeFastPass === null &&
896
+ pendingSlowPasses.length > 0) {
897
+ const slow = pendingSlowPasses.shift();
898
+ if (slow) {
899
+ const current = startGuardedTask(slow.task, "slow");
900
+ void current.then(slow.resolve, slow.reject);
901
+ }
902
+ }
903
+ if (activeFastPass === null) {
904
+ const fast = pendingFastPasses.shift();
905
+ if (fast) {
906
+ const current = startGuardedTask(fast.task, "fast");
907
+ void current.then(fast.resolve, fast.reject);
908
+ }
909
+ }
910
+ // A fast pass that acquired the operation lock normally (rather than
911
+ // bypassing an in-process slow holder) still owns the cross-process
912
+ // boundary. Slow work must queue behind it; only fast work is allowed to
913
+ // overlap an already-lock-owning slow pass.
914
+ if (activeSlowPass === null && activeFastPass === null) {
915
+ const slow = pendingSlowPasses.shift();
916
+ if (slow) {
917
+ const current = startGuardedTask(slow.task, "slow");
918
+ void current.then(slow.resolve, slow.reject);
919
+ }
920
+ }
921
+ };
922
+ const startGuardedTask = (task, lane) => {
923
+ // Claim the lane before invoking `task`: a task can synchronously activate
924
+ // a watcher/scheduler callback before returning its promise. Without this
925
+ // placeholder that re-entrant callback sees an idle lane and starts a
926
+ // second lock contender.
927
+ const placeholder = Promise.resolve(0);
928
+ if (lane === "fast")
929
+ activeFastPass = placeholder;
930
+ else
931
+ activeSlowPass = placeholder;
932
+ let current;
933
+ try {
934
+ current = stopped ? placeholder : task();
935
+ }
936
+ catch (err) {
937
+ current = Promise.reject(err);
938
+ }
939
+ if (lane === "fast")
940
+ activeFastPass = current;
941
+ else
942
+ activeSlowPass = current;
943
+ void current
944
+ .finally(() => {
945
+ if (lane === "fast" && activeFastPass === current) {
946
+ activeFastPass = null;
947
+ drainQueuedPasses();
948
+ }
949
+ else if (lane === "slow" && activeSlowPass === current) {
950
+ activeSlowPass = null;
951
+ drainQueuedPasses();
952
+ }
953
+ })
954
+ .catch(() => undefined);
955
+ return current;
956
+ };
957
+ const runGuardedTask = (task, lane = "slow") => {
958
+ const active = lane === "fast"
959
+ ? activeFastPass
960
+ : activeSlowPass ?? activeFastPass;
961
+ const queued = lane === "fast" ? pendingFastPasses : pendingSlowPasses;
962
+ if (active === null && queued.length === 0) {
963
+ return startGuardedTask(task, lane);
964
+ }
965
+ return new Promise((resolve, reject) => {
966
+ queued.push({ task, lane, resolve, reject });
967
+ drainQueuedPasses();
968
+ });
969
+ };
970
+ const runGuarded = (passArgvForRun, prepare, lane = "slow") => runGuardedTask(() => runPassWithLock(passArgvForRun, prepare, lane), lane);
1150
971
  /*
1151
972
  * Area storage and the parallel coordinator default to AUTO. AUTO turns
1152
973
  * them on after durable personal-vault cutover; explicit off is the kill
@@ -1338,7 +1159,6 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1338
1159
  });
1339
1160
  },
1340
1161
  onCutOver: () => {
1341
- releaseScopedJournalStore(PERSONAL_VAULT_JOURNAL_SLUG);
1342
1162
  parallelCoordinatorEnabled = refreshCoordinatorEnabled();
1343
1163
  if (parallelCoordinatorEnabled && coordinator === null) {
1344
1164
  coordinator = createCoordinator();
@@ -1425,11 +1245,6 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1425
1245
  let drainedUntrackedDeletes = 0;
1426
1246
  const pendingWatcherPaths = new Map();
1427
1247
  const pendingWatcherChanges = new Map();
1428
- // A warm-up scan observes filesystem state over time. Keep an ingress
1429
- // sequence so its older observations cannot overwrite a live watcher event
1430
- // that arrived while the scan was yielding.
1431
- const pendingWatcherChangeSequences = new Map();
1432
- let nextPendingWatcherChangeSequence = 0;
1433
1248
  let pendingWatcherOriginalBatch = null;
1434
1249
  // Every coalesced watcher batch gets one durable in-process generation.
1435
1250
  // A wake can cause the scheduler to revisit a drained batch, but an event
@@ -1453,11 +1268,11 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1453
1268
  const paths = new Map();
1454
1269
  const changes = new Map();
1455
1270
  for (const [absolutePath, relativePath] of batch.paths.entries()) {
1456
- const change = batch.changes?.get(absolutePath);
1457
- const route = routeChangeToTarget(relativePath, change?.kind);
1271
+ const route = routeChangeToTarget(relativePath);
1458
1272
  if (!route || !isWatchRouteSelected(route, watchRouteSelection))
1459
1273
  continue;
1460
1274
  paths.set(absolutePath, relativePath);
1275
+ const change = batch.changes?.get(absolutePath);
1461
1276
  if (change)
1462
1277
  changes.set(absolutePath, change);
1463
1278
  }
@@ -1465,7 +1280,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1465
1280
  return null;
1466
1281
  return { ...batch, paths, changes };
1467
1282
  };
1468
- const addPendingWatcherChange = (changedRelPath, batch, options) => {
1283
+ const addPendingWatcherChange = (changedRelPath, batch) => {
1469
1284
  if (batch) {
1470
1285
  const selectedBatch = selectWatchBatch(batch);
1471
1286
  if (!selectedBatch)
@@ -1478,30 +1293,20 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1478
1293
  return false;
1479
1294
  }
1480
1295
  }
1296
+ pendingWatcherBatchGeneration ??= ++nextWatcherBatchGeneration;
1481
1297
  if (batch) {
1482
- const entries = [...batch.paths.entries()].filter(([absolutePath]) => {
1483
- const previousSequence = pendingWatcherChangeSequences.get(absolutePath);
1484
- return previousSequence === undefined ||
1485
- options?.preserveChangesAfter === undefined ||
1486
- previousSequence <= options.preserveChangesAfter;
1487
- });
1488
- if (entries.length === 0)
1489
- return false;
1490
- pendingWatcherBatchGeneration ??= ++nextWatcherBatchGeneration;
1491
1298
  pendingWatcherOriginalBatch =
1492
1299
  pendingWatcherPaths.size === 0 &&
1493
1300
  !pendingWatcherBareChange &&
1494
1301
  !pendingWatcherOverflowed &&
1495
- !batch.overflowed &&
1496
- entries.length === batch.paths.size
1302
+ !batch.overflowed
1497
1303
  ? batch
1498
1304
  : null;
1499
- for (const [absolutePath, relativePath] of entries) {
1305
+ for (const [absolutePath, relativePath] of batch.paths.entries()) {
1500
1306
  pendingWatcherPaths.set(absolutePath, relativePath);
1501
1307
  const change = batch.changes?.get(absolutePath);
1502
1308
  if (change)
1503
1309
  pendingWatcherChanges.set(absolutePath, change);
1504
- pendingWatcherChangeSequences.set(absolutePath, ++nextPendingWatcherChangeSequence);
1505
1310
  }
1506
1311
  if (batch.overflowed) {
1507
1312
  pendingWatcherOverflowed = true;
@@ -1518,7 +1323,6 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1518
1323
  }
1519
1324
  return true;
1520
1325
  }
1521
- pendingWatcherBatchGeneration ??= ++nextWatcherBatchGeneration;
1522
1326
  if (changedRelPath) {
1523
1327
  pendingWatcherOriginalBatch = null;
1524
1328
  // Codec boundary: `changedRelPath` is a canonical vault key (the
@@ -1527,7 +1331,6 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1527
1331
  // point at the impossible colon path on Windows.
1528
1332
  const absolutePath = localPathForVaultKey(hqRoot, changedRelPath);
1529
1333
  pendingWatcherPaths.set(absolutePath, changedRelPath);
1530
- pendingWatcherChangeSequences.set(absolutePath, ++nextPendingWatcherChangeSequence);
1531
1334
  // A bare path signal carries no delete intent; drop any change record
1532
1335
  // a prior batch left for this path so the stale intent cannot leak
1533
1336
  // into the coalesced batch (#227).
@@ -1569,7 +1372,6 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1569
1372
  const generation = pendingWatcherBatchGeneration;
1570
1373
  pendingWatcherPaths.clear();
1571
1374
  pendingWatcherChanges.clear();
1572
- pendingWatcherChangeSequences.clear();
1573
1375
  pendingWatcherOriginalBatch = null;
1574
1376
  pendingWatcherBareChange = false;
1575
1377
  pendingWatcherWarmupCatchup = false;
@@ -1588,7 +1390,6 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1588
1390
  pendingWatcherOriginalBatch = null;
1589
1391
  for (const [absolutePath, relativePath] of paths.entries()) {
1590
1392
  pendingWatcherPaths.set(absolutePath, relativePath);
1591
- pendingWatcherChangeSequences.set(absolutePath, ++nextPendingWatcherChangeSequence);
1592
1393
  }
1593
1394
  };
1594
1395
  /**
@@ -1669,8 +1470,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1669
1470
  }
1670
1471
  return null;
1671
1472
  };
1672
- const journalCoordinates = (relativePath, kind) => {
1673
- const route = routeChangeToTarget(relativePath, kind);
1473
+ const journalCoordinates = (relativePath) => {
1474
+ const route = routeChangeToTarget(relativePath);
1674
1475
  if (!route || !isWatchRouteSelected(route, watchRouteSelection))
1675
1476
  return null;
1676
1477
  if (route.kind === "personal") {
@@ -1705,8 +1506,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1705
1506
  declaredSize >= 0) {
1706
1507
  return declaredSize;
1707
1508
  }
1708
- const size = journalRows(readJournalScoped(coordinates.slug, [coordinates.key]))
1709
- .get(coordinates.key)?.size;
1509
+ const size = journalRows(readJournal(coordinates.slug)).get(coordinates.key)?.size;
1710
1510
  return typeof size === "number" && Number.isSafeInteger(size) && size >= 0 ? size : null;
1711
1511
  };
1712
1512
  /**
@@ -1753,8 +1553,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1753
1553
  return session.get(coordinates.key) === undefined;
1754
1554
  };
1755
1555
  const captureLocalDeleteSnapshots = (relativePath, kind) => {
1756
- const coordinates = journalCoordinates(relativePath, kind);
1757
- const route = routeChangeToTarget(relativePath, kind);
1556
+ const coordinates = journalCoordinates(relativePath);
1557
+ const route = routeChangeToTarget(relativePath);
1758
1558
  if (!coordinates || !route)
1759
1559
  return [];
1760
1560
  // Desktop workspace Off toggles are authoritative before journal I/O.
@@ -1875,8 +1675,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1875
1675
  * it scopes a push or pull for the affected batch.
1876
1676
  */
1877
1677
  const captureQueuedLocalDeleteSnapshots = async (relativePath, kind, isCurrent) => {
1878
- const coordinates = journalCoordinates(relativePath, kind);
1879
- const route = routeChangeToTarget(relativePath, kind);
1678
+ const coordinates = journalCoordinates(relativePath);
1679
+ const route = routeChangeToTarget(relativePath);
1880
1680
  if (!coordinates || !route)
1881
1681
  return [];
1882
1682
  if (route.kind === "company" &&
@@ -1970,7 +1770,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
1970
1770
  }
1971
1771
  return snapshots;
1972
1772
  };
1973
- const warmupJournalPaths = function* () {
1773
+ const warmupJournalPaths = () => {
1774
+ const paths = [];
1974
1775
  const journals = listJournals();
1975
1776
  const vaultJournalPresent = journals.some(({ slug }) => slug === PERSONAL_VAULT_JOURNAL_SLUG);
1976
1777
  for (const { slug, journal } of journals) {
@@ -2003,9 +1804,10 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2003
1804
  // ~12.8k of them per runner start after a transcript prune.
2004
1805
  if (route.kind === "personal" && isPersonalVaultDeletionExcluded(key))
2005
1806
  continue;
2006
- yield route.kind === "personal" ? key : `companies/${route.slug}/${key}`;
1807
+ paths.push(route.kind === "personal" ? key : `companies/${route.slug}/${key}`);
2007
1808
  }
2008
1809
  }
1810
+ return paths;
2009
1811
  };
2010
1812
  const publishedContentHashLookup = (relativePath) => {
2011
1813
  const coordinates = journalCoordinates(relativePath);
@@ -2018,34 +1820,13 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2018
1820
  return publishedContentHashFromJournalEntryBeforeSession(entry, serviceStartedAt);
2019
1821
  };
2020
1822
  const prepareWatcherChanges = (changes) => {
2021
- const keysByJournal = new Map();
2022
- const rememberKey = (slug, key) => {
2023
- const keys = keysByJournal.get(slug) ?? new Set();
2024
- keys.add(key);
2025
- keysByJournal.set(slug, keys);
2026
- };
2027
- for (const [absolutePath, change] of changes.entries()) {
2028
- if (change.kind === "unlink" || change.kind === "unlinkDir") {
2029
- for (const snapshot of change.deleteSnapshots ?? []) {
2030
- rememberKey(snapshot.journalSlug, snapshot.journalPath);
2031
- }
2032
- continue;
2033
- }
2034
- const relativePath = path
2035
- .relative(hqRoot, absolutePath)
2036
- .split(path.sep)
2037
- .join("/");
2038
- const coordinates = journalCoordinates(relativePath);
2039
- if (coordinates)
2040
- rememberKey(coordinates.slug, coordinates.key);
2041
- }
2042
1823
  const journals = new Map();
2043
1824
  const dirty = new Set();
2044
1825
  const journalFor = (slug) => {
2045
1826
  const loaded = journals.get(slug);
2046
1827
  if (loaded)
2047
1828
  return loaded;
2048
- const journal = readJournalScoped(slug, [...(keysByJournal.get(slug) ?? [])]);
1829
+ const journal = readJournal(slug);
2049
1830
  journals.set(slug, journal);
2050
1831
  return journal;
2051
1832
  };
@@ -2103,8 +1884,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2103
1884
  const groupBatchByRoute = (batch) => {
2104
1885
  const grouped = new Map();
2105
1886
  for (const [absolutePath, relativePath] of batch.paths.entries()) {
2106
- const change = batch.changes?.get(absolutePath);
2107
- const route = routeChangeToTarget(relativePath, change?.kind);
1887
+ const route = routeChangeToTarget(relativePath);
2108
1888
  if (!route)
2109
1889
  continue;
2110
1890
  const key = routeKey(route);
@@ -2117,6 +1897,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2117
1897
  deleteScopeRoots: [],
2118
1898
  };
2119
1899
  existing.paths.set(absolutePath, relativePath);
1900
+ const change = batch.changes?.get(absolutePath);
2120
1901
  if (change)
2121
1902
  existing.changes.set(absolutePath, change);
2122
1903
  existing.relPaths = [...new Set([...existing.paths.values()])];
@@ -2200,7 +1981,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2200
1981
  }
2201
1982
  return false;
2202
1983
  };
2203
- const runScopedDrain = async (batch, watcherBatchGeneration, wakeOnPushFailure = false, allowFastRealtimeOvertake = false) => {
1984
+ const runScopedDrain = async (batch, watcherBatchGeneration, wakeOnPushFailure = false) => {
2204
1985
  if (batch)
2205
1986
  await resolveDeleteSnapshotCaptures(batch);
2206
1987
  const grouped = batch && batch.paths.size > 0 ? groupBatchByRoute(batch) : null;
@@ -2229,11 +2010,11 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2229
2010
  const scopedArgv = buildScopedPushArgv(group.route, admittedPaths, passArgv, group.deleteScopeRoots);
2230
2011
  return runGuarded(scopedArgv, admittedChanges.size > 0
2231
2012
  ? () => prepareWatcherChanges(admittedChanges)
2232
- : undefined, lane, "realtime", allowFastRealtimeOvertake);
2013
+ : undefined, lane);
2233
2014
  })
2234
2015
  : { result: await runGuarded(buildScopedPushArgv(group.route, group.relPaths, passArgv, group.deleteScopeRoots), group.changes.size > 0
2235
2016
  ? () => prepareWatcherChanges(group.changes)
2236
- : undefined, lane, "realtime", allowFastRealtimeOvertake), accepted: true, coordinatorRefusedPaths: [] };
2017
+ : undefined, lane), accepted: true, coordinatorRefusedPaths: [] };
2237
2018
  const result = execution.result;
2238
2019
  if (execution.accepted && passExitCode(result) === 0) {
2239
2020
  const refusedPaths = new Set(typeof result === "number"
@@ -2303,8 +2084,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2303
2084
  ? "fast"
2304
2085
  : "slow";
2305
2086
  const execution = parallelCoordinatorEnabled
2306
- ? await runRealtimePass("watcher", "pull", group.relPaths, lane, (slice) => runGuarded(buildTargetedPullArgv(group.route, passArgv, slice.paths.map((path) => path.canonicalPath)), undefined, lane, "realtime"))
2307
- : { result: await runGuarded(pullArgv, undefined, lane, "realtime"), accepted: true };
2087
+ ? await runRealtimePass("watcher", "pull", group.relPaths, lane, (slice) => runGuarded(buildTargetedPullArgv(group.route, passArgv, slice.paths.map((path) => path.canonicalPath)), undefined, lane))
2088
+ : { result: await runGuarded(pullArgv, undefined, lane), accepted: true };
2308
2089
  const exit = execution.accepted ? passExitCode(execution.result) : 1;
2309
2090
  if (exit !== 0 && worstExit === 0)
2310
2091
  worstExit = exit;
@@ -2509,13 +2290,13 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2509
2290
  return;
2510
2291
  }
2511
2292
  if (lanes?.fast &&
2512
- (passScheduler.hasActiveChore || passScheduler.hasActiveSlowRealtime)) {
2293
+ activeSlowPass !== null) {
2513
2294
  const generation = ++nextWatcherBatchGeneration;
2514
2295
  // Fast dispatch bypasses the pending-batch ingress, so preserve both
2515
2296
  // of its contracts here: only selected routes may run, and V2 sees
2516
2297
  // the same dirty signal as the compatibility drain.
2517
2298
  realtimeState.scheduler?.signal("watcher");
2518
- void runScopedDrain(lanes.fast, generation, true, true).then((exit) => {
2299
+ void runScopedDrain(lanes.fast, generation, true).then((exit) => {
2519
2300
  if (exit === 0)
2520
2301
  return;
2521
2302
  restorePendingWatcherPaths(lanes.fast.paths, generation);
@@ -2564,36 +2345,29 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2564
2345
  watcherRediscoveryRetries = 0;
2565
2346
  process.stderr.write(`hq-sync-runner: event-push watcher active (watched-paths=${watchedPaths ?? "unknown"})\n`);
2566
2347
  const startedAt = Date.now();
2567
- const liveChangeSequenceAtWarmupStart = nextPendingWatcherChangeSequence;
2568
- void (async () => {
2569
- try {
2570
- const catchup = watcher?.collectWarmupCatchupAsync
2571
- ? await watcher.collectWarmupCatchupAsync(serviceStartedAt - WATCHER_WARMUP_SAFETY_MARGIN_MS, warmupJournalPaths())
2572
- : watcher?.collectWarmupCatchup?.(serviceStartedAt - WATCHER_WARMUP_SAFETY_MARGIN_MS, warmupJournalPaths());
2573
- if (!catchup || catchup.cancelled || stopped || watcher === null)
2574
- return;
2575
- const queued = addPendingWatcherChange(undefined, catchup.batch, {
2576
- preserveChangesAfter: liveChangeSequenceAtWarmupStart,
2577
- });
2578
- pendingWatcherWarmupCatchup ||= queued;
2579
- process.stderr.write(JSON.stringify({
2580
- type: "watcher-warmup-catchup",
2581
- reason: "watch-set-ready",
2582
- pathsFound: catchup.batch.paths.size,
2583
- scannedPaths: catchup.scannedPaths,
2584
- elapsedMs: Date.now() - startedAt,
2585
- queued,
2586
- }) + "\n");
2587
- if (queued)
2588
- interruptWait();
2589
- }
2590
- catch (err) {
2591
- process.stderr.write(`hq-sync-runner: watcher warm-up catch-up failed: ${describeError(err)}\n`);
2592
- // A scan failure must not silently extend the warm-up blind spot.
2593
- pendingWatcherWarmupCatchupFailed = true;
2348
+ try {
2349
+ const catchup = watcher.collectWarmupCatchup?.(serviceStartedAt - WATCHER_WARMUP_SAFETY_MARGIN_MS, warmupJournalPaths());
2350
+ if (!catchup)
2351
+ return;
2352
+ const queued = addPendingWatcherChange(undefined, catchup.batch);
2353
+ pendingWatcherWarmupCatchup ||= queued;
2354
+ process.stderr.write(JSON.stringify({
2355
+ type: "watcher-warmup-catchup",
2356
+ reason: "watch-set-ready",
2357
+ pathsFound: catchup.batch.paths.size,
2358
+ scannedPaths: catchup.scannedPaths,
2359
+ elapsedMs: Date.now() - startedAt,
2360
+ queued,
2361
+ }) + "\n");
2362
+ if (queued)
2594
2363
  interruptWait();
2595
- }
2596
- })();
2364
+ }
2365
+ catch (err) {
2366
+ process.stderr.write(`hq-sync-runner: watcher warm-up catch-up failed: ${describeError(err)}\n`);
2367
+ // A scan failure must not silently extend the warm-up blind spot.
2368
+ pendingWatcherWarmupCatchupFailed = true;
2369
+ interruptWait();
2370
+ }
2597
2371
  };
2598
2372
  if (watcher.onReady)
2599
2373
  watcher.onReady(reportWatcherReady);
@@ -2641,7 +2415,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2641
2415
  // Submit every route in this lane before awaiting any result. In
2642
2416
  // particular, a later route's fast receipt must never wait for an
2643
2417
  // earlier route's slow pull to finish.
2644
- const completed = await Promise.all([...eventsByRoute.values()].map(({ route, events }) => runRealtimePass("receiver", "pull", events.map((event) => event.relativePath), lane, (slice) => runGuarded(buildTargetedPullArgv(route, passArgv, slice.paths.map((path) => path.canonicalPath)), undefined, lane, "realtime"))));
2418
+ const completed = await Promise.all([...eventsByRoute.values()].map(({ route, events }) => runRealtimePass("receiver", "pull", events.map((event) => event.relativePath), lane, (slice) => runGuarded(buildTargetedPullArgv(route, passArgv, slice.paths.map((path) => path.canonicalPath)), undefined, lane))));
2645
2419
  for (const execution of completed) {
2646
2420
  if (!execution.accepted || passExitCode(execution.result) !== 0) {
2647
2421
  throw new Error(`targeted pull failed with exit code ${passExitCode(execution.result)}`);
@@ -2695,7 +2469,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2695
2469
  }
2696
2470
  for (const { route, paths } of pathsByRoute.values()) {
2697
2471
  const targetedArgv = buildTargetedPullArgv(route, passArgv, paths);
2698
- const result = await runGuarded(targetedArgv, undefined, lane, "realtime");
2472
+ const result = await runGuarded(targetedArgv, undefined, lane);
2699
2473
  if (passExitCode(result) !== 0) {
2700
2474
  throw new Error(`targeted pull failed with exit code ${passExitCode(result)}`);
2701
2475
  }
@@ -2743,14 +2517,14 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2743
2517
  const execution = await runRealtimePass("wake", "pull", [], "slow", async () => runGuardedTask(() => runTaskWithLock(async () => {
2744
2518
  await drain();
2745
2519
  return 0;
2746
- }, "realtime"), "slow", "realtime"));
2520
+ })));
2747
2521
  outcome = execution.accepted ? execution.result : 1;
2748
2522
  }
2749
2523
  else {
2750
2524
  outcome = await runGuardedTask(() => runTaskWithLock(async () => {
2751
2525
  await drain();
2752
2526
  return 0;
2753
- }, "realtime"), "slow", "realtime");
2527
+ }));
2754
2528
  }
2755
2529
  }
2756
2530
  catch (err) {
@@ -2946,8 +2720,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
2946
2720
  targetedJournalSessions: () => targetedJournalSessionCensusSizes().targetedJournalSessions,
2947
2721
  targetedJournalRows: () => targetedJournalSessionCensusSizes().targetedJournalRows,
2948
2722
  cachedAreaJournalMigrations: () => journalCacheCensusSizes().cachedAreaJournalMigrations,
2949
- pendingSlowPasses: () => passScheduler.pendingLaneCount("slow"),
2950
- pendingFastPasses: () => passScheduler.pendingLaneCount("fast"),
2723
+ pendingSlowPasses: () => pendingSlowPasses.length,
2724
+ pendingFastPasses: () => pendingFastPasses.length,
2951
2725
  },
2952
2726
  eventSyncSizes: () => eventSync?.censusSizes?.(),
2953
2727
  watcherSizes: () => watcher?.censusSizes?.(),
@@ -3297,14 +3071,11 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3297
3071
  quarantinedDeleteRoots.delete(root);
3298
3072
  }
3299
3073
  // Delete snapshot capture needs a fresh keyed view while an episode is
3300
- // being assembled, but completed watch cycles do not. Area-authoritative
3301
- // journals are different: their bridge owns the packed aggregate that a
3302
- // later watcher callback reuses. Dropping that bridge at every poll
3303
- // makes the next scoped callback synchronously re-pack the whole vault.
3304
- // Keep it for this runner's lifetime; layout/phase changes still close
3305
- // it in openAreaMigrationIfAuthoritative(), and final shutdown releases
3306
- // every bridge below.
3074
+ // being assembled, but completed watch cycles do not. Releasing it here
3075
+ // prevents the watcher from retaining another full-vault projection next
3076
+ // to the targeted-pull and area-ledger views.
3307
3077
  releaseWatcherJournalStores();
3078
+ releaseCachedAreaJournalMigrations();
3308
3079
  const exitCode = passExitCode(result);
3309
3080
  if (exitCode === runtime.authRequiredPassExit) {
3310
3081
  // The pass already emitted auth-error. Hand the sentinel to the
@@ -3339,19 +3110,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3339
3110
  : "") +
3340
3111
  "; staying alive, retrying next poll\n");
3341
3112
  }
3342
- else if (exitCode === UNRESOLVED_COMPANY_EXIT) {
3343
- // HQ-SYNC-COMPANY-ROUTE-UID: routed paths are derived from local
3344
- // company directory names, which may not be memberships. The runner
3345
- // has already emitted the resolution error; terminating here would
3346
- // restart the outpost watcher every 60 seconds for each such route.
3347
- // Keep the watcher alive while preserving the one-shot non-zero code.
3348
- process.stderr.write("hq-sync-runner: watch pass skipped — unresolved company target; retrying next poll\n");
3349
- }
3350
3113
  else if (exitCode !== 0) {
3351
- // A scoped company-resolution or per-pass failure is isolated to that
3352
- // pass. The error was emitted by the runner; terminating the daemon
3353
- // recreates the startup blind window and strands newer watcher work.
3354
- process.stderr.write(`hq-sync-runner: watch pass failed (exit=${exitCode}); staying alive, retrying next poll\n`);
3114
+ return exitCode;
3355
3115
  }
3356
3116
  const completedPostRepairPass = !enqueuedFullChore && (typeof result === "number"
3357
3117
  ? exitCode === 0
@@ -3455,10 +3215,6 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
3455
3215
  releaseWatcherJournalStores();
3456
3216
  releaseCachedAreaJournalMigrations();
3457
3217
  await shutdown();
3458
- // Coordinator shutdown waits for any active pass before the retained
3459
- // projections are dropped. A shutdown must never evict a store that a
3460
- // final scoped writer is still refreshing or committing through.
3461
- disposeScopedJournalStores();
3462
3218
  detachSignal?.();
3463
3219
  }
3464
3220
  }