gh-glance 0.15.0 → 0.15.2

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 (4) hide show
  1. package/CHANGELOG.md +49 -1
  2. package/README.md +33 -1
  3. package/index.mjs +1120 -417
  4. package/package.json +2 -2
package/index.mjs CHANGED
@@ -53,6 +53,7 @@ import { promisify } from "node:util";
53
53
 
54
54
  const execFileAsync = promisify(execFile);
55
55
  const requestIdentityStorage = new AsyncLocalStorage();
56
+ const acquisitionFenceStorage = new AsyncLocalStorage();
56
57
 
57
58
  // Running as the CLI vs. being imported by a test. Everything with a side
58
59
  // effect -- argv handling, the TTY guard, the preflight, entering the alternate
@@ -778,6 +779,15 @@ async function runGh(args, { signal, operation, input = null, execute = null } =
778
779
  const ready = inspectAdmittedHttpStart(bound, operation, requestNow);
779
780
  if (!ready.ok) throw new Error(`API budget paused (${ready.reason})`);
780
781
  }
782
+ const acquisitionFence = acquisitionFenceStorage.getStore();
783
+ if (acquisitionFence && !CONTROL_OPERATIONS.includes(operation)) {
784
+ const current = acquisitionFence();
785
+ if (!current.ok) {
786
+ const error = new Error(`acquisition claim fenced (${current.reason})`);
787
+ error.name = "AbortError";
788
+ throw error;
789
+ }
790
+ }
781
791
  assertBoundCredential(bound);
782
792
  const executeChild = execute ?? bound?.executeGh ?? execFileAsync;
783
793
  const pending = executeChild("gh", args, {
@@ -866,11 +876,13 @@ function invalidateBoundGitHubApp(error, parsed) {
866
876
  return true;
867
877
  }
868
878
 
869
- function responseHeaderObservation(rateLimit, cost, receivedAt = Date.now()) {
879
+ function responseHeaderObservation(rateLimit, cost, receivedAt = null) {
880
+ const scopedAt = scopeNow(requestIdentityStorage.getStore());
870
881
  return rateLimit ? {
871
882
  ...rateLimit,
872
883
  source: "response-header",
873
- receivedAt,
884
+ receivedAt: Number.isFinite(receivedAt) ? receivedAt :
885
+ Number.isFinite(scopedAt) ? scopedAt : Date.now(),
874
886
  cost,
875
887
  } : null;
876
888
  }
@@ -1568,6 +1580,19 @@ function publishStagedEntities(entities, staged, transitionKind) {
1568
1580
  return true;
1569
1581
  }
1570
1582
 
1583
+ function stagedAcquisitionPublicationView({ entities, stagedEntities, transitionKind,
1584
+ pageState, loadedPages, hasNextPage, unchangedCount }) {
1585
+ const nextEntities = new Map(entities);
1586
+ publishStagedEntities(nextEntities, stagedEntities, transitionKind);
1587
+ return {
1588
+ entities: nextEntities,
1589
+ pageState: Number.isFinite(loadedPages)
1590
+ ? { pages: loadedPages, hasNextPage: hasNextPage === true }
1591
+ : pageState,
1592
+ unchangedCount: advanceUnchangedCount(unchangedCount, transitionKind),
1593
+ };
1594
+ }
1595
+
1571
1596
  const ACTIONS_QUERY_VERSION = 2;
1572
1597
 
1573
1598
  async function fetchActions(signal, {
@@ -2129,6 +2154,7 @@ const BUDGET_SNAPSHOT_TTL_MS = 65_000;
2129
2154
  const GRAPHQL_BUDGET_SNAPSHOT_TTL_MS = 2 * BUDGET_PROBE_MS + 5_000;
2130
2155
  const GOVERNOR_HEARTBEAT_MS = 20_000;
2131
2156
  const GOVERNOR_LEASE_TTL_MS = 90_000;
2157
+ const GOVERNOR_ADMISSION_WAIT_MS = 2_000;
2132
2158
  const GOVERNOR_PROBE_LEASE_MS = 70_000;
2133
2159
  const GOVERNOR_ACTIVE_PROBE_LEASE_MS = 35_000;
2134
2160
  const BUDGET_RESET_GRACE_MS = 2_000;
@@ -2496,6 +2522,7 @@ function scheduleIntents({
2496
2522
  nowMs,
2497
2523
  maxGrants = Number.POSITIVE_INFINITY,
2498
2524
  manualStreak = 0,
2525
+ deferFutureBackground = false,
2499
2526
  }) {
2500
2527
  const valid = [];
2501
2528
  const prunedIntentIds = [];
@@ -2605,6 +2632,17 @@ function scheduleIntents({
2605
2632
  continue;
2606
2633
  }
2607
2634
 
2635
+ // A background request may wait for a future phase or lane slot, but
2636
+ // reserving that slot now would make later active work queue behind it.
2637
+ // Keep its persisted intent until the slot arrives so each new planning
2638
+ // pass can apply priority to work that has not started yet.
2639
+ if (deferFutureBackground && priority === REQUEST_PRIORITIES.background &&
2640
+ TAB_KEYS.includes(intent.tab) && notBefore > nowMs) {
2641
+ if (pass.record) denied.push({ intentId: intent.id, mode: "waiting",
2642
+ reason: "priority", retryAt: notBefore, notBefore });
2643
+ continue;
2644
+ }
2645
+
2608
2646
  const reservation = {
2609
2647
  id: `reservation:${intent.id}`,
2610
2648
  intentId: intent.id,
@@ -3545,6 +3583,14 @@ const GOVERNOR_MAX_LEASES = 128;
3545
3583
  const GOVERNOR_MAX_INTENTS = 512;
3546
3584
  const GOVERNOR_MAX_RESERVATIONS = 512;
3547
3585
  const GOVERNOR_LOCK_WAIT_MS = 250;
3586
+ // How old an unreadable lock record may be before it counts as abandoned. A
3587
+ // creator owns the path from open("wx") until its owner record is written, a
3588
+ // gap of microseconds; a record still unreadable this long after the file was
3589
+ // last touched was left by a creator whose write failed (a full disk) or that
3590
+ // died between the two calls. Without this bound such a file was immortal: no
3591
+ // PID to test, so dead-owner recovery never fired, and every pane on the
3592
+ // machine reported busy on every request until someone deleted it by hand.
3593
+ const GOVERNOR_LOCK_ORPHAN_MS = 10_000;
3548
3594
  const GOVERNOR_PROBE_DRAIN_MS = 30_000;
3549
3595
  const GOVERNOR_PUBLICATION_REINSPECT_MS = 1_000;
3550
3596
  const GOVERNOR_PROBE_TRANSITION_MS = 5_000;
@@ -4108,17 +4154,80 @@ function pidIsDead(pid, kill = process.kill.bind(process)) {
4108
4154
  }
4109
4155
  }
4110
4156
 
4157
+ // An unreadable lock record whose file has not been touched for
4158
+ // GOVERNOR_LOCK_ORPHAN_MS. Readable records are never orphans here, whatever
4159
+ // their age: a live owner may legitimately hold a lock for as long as its
4160
+ // critical section runs, and a dead one is the PID path's business.
4161
+ function lockRecordOrphaned(path, now = Date.now()) {
4162
+ try {
4163
+ return lockOwner(path) === null && statSync(path).mtimeMs + GOVERNOR_LOCK_ORPHAN_MS <= now;
4164
+ } catch {
4165
+ return false;
4166
+ }
4167
+ }
4168
+
4169
+ // Creates the lock file and its owner record as one step from every other
4170
+ // process's point of view: either the path holds a complete record naming this
4171
+ // owner, or it does not exist. open("wx") makes the path exist before the
4172
+ // record is written, and a failure in between -- a full disk, most often --
4173
+ // used to propagate straight past the release, leaving an empty file that no
4174
+ // PID check could ever reclaim. Every failure after open now removes the exact
4175
+ // inode this call created before reporting, so nothing outlives the attempt.
4176
+ function claimGovernorLock(lockPath, owner) {
4177
+ let descriptor;
4178
+ let created = null;
4179
+ try {
4180
+ descriptor = openSync(lockPath, "wx", 0o600);
4181
+ } catch (error) {
4182
+ return error?.code === "EEXIST" ? "held" : "failed";
4183
+ }
4184
+ try {
4185
+ created = fstatSync(descriptor).ino;
4186
+ writeFileSync(descriptor, JSON.stringify(owner), "utf8");
4187
+ closeSync(descriptor);
4188
+ descriptor = undefined;
4189
+ chmodSync(lockPath, 0o600);
4190
+ // A stalled creator can be reclaimed as an orphan while it sits between
4191
+ // open and write. Confirm the record on disk is still this one before the
4192
+ // caller enters its critical section, and take back a record that was
4193
+ // restored after the check below found it missing (see the own-record
4194
+ // branch in withGovernorLock for the rest of that recovery).
4195
+ return sameLockOwner(lockOwner(lockPath), owner) ? "acquired" : "held";
4196
+ } catch {
4197
+ if (descriptor !== undefined) {
4198
+ try { closeSync(descriptor); } catch { /* descriptor was already closed */ }
4199
+ }
4200
+ try {
4201
+ if (created !== null && statSync(lockPath).ino === created) unlinkSync(lockPath);
4202
+ } catch { /* already gone, or not the file this call created */ }
4203
+ return "failed";
4204
+ }
4205
+ }
4206
+
4111
4207
  function releaseGovernorLock(lockPath, nonce) {
4112
4208
  const owner = lockOwner(lockPath);
4113
4209
  if (!owner || owner.nonce !== nonce) return false;
4114
4210
  const releasePath = `${lockPath}.release-${nonce}`;
4115
4211
  try {
4116
4212
  renameSync(lockPath, releasePath);
4117
- unlinkSync(releasePath);
4118
- return true;
4119
4213
  } catch {
4120
- return false;
4214
+ // The rename-then-unlink order exists so the unlink can never hit a
4215
+ // successor's lock. A rename can still fail on its own -- a full APFS
4216
+ // volume needs fresh blocks for the directory update -- and giving up there
4217
+ // left a complete record naming a live owner that would wait on itself, and
4218
+ // every other pane on it, until that process exited. A lock still carrying
4219
+ // this nonce has no successor to protect, so the direct unlink is safe.
4220
+ try {
4221
+ if (lockOwner(lockPath)?.nonce !== nonce) return false;
4222
+ unlinkSync(lockPath);
4223
+ return true;
4224
+ } catch {
4225
+ return false;
4226
+ }
4121
4227
  }
4228
+ // The path is free from here on; the uniquely named leftover is private.
4229
+ try { unlinkSync(releasePath); } catch { /* exact private release file only */ }
4230
+ return true;
4122
4231
  }
4123
4232
 
4124
4233
  function governorRecoveryPaths(lockPath) {
@@ -4136,7 +4245,18 @@ function governorRecoveryActive(lockPath, kill) {
4136
4245
  let active = false;
4137
4246
  for (const recoveryPath of governorRecoveryPaths(lockPath)) {
4138
4247
  const owner = lockOwner(recoveryPath);
4139
- if (!owner || !pidIsDead(owner.pid, kill)) {
4248
+ if (!owner) {
4249
+ // A marker whose record never arrived. Marker names are unique, so an
4250
+ // unlink can only ever remove this one; the age bound is what separates a
4251
+ // recoverer mid-write from one whose write failed and left this behind.
4252
+ if (lockRecordOrphaned(recoveryPath)) {
4253
+ try { unlinkSync(recoveryPath); } catch { /* re-listed below */ }
4254
+ continue;
4255
+ }
4256
+ active = true;
4257
+ continue;
4258
+ }
4259
+ if (!pidIsDead(owner.pid, kill)) {
4140
4260
  active = true;
4141
4261
  continue;
4142
4262
  }
@@ -4153,29 +4273,35 @@ function observeGovernorArtifact(observer, kind, path) {
4153
4273
  try { observer?.(kind, path); } catch { /* test observation cannot affect locking */ }
4154
4274
  }
4155
4275
 
4156
- function quarantineDeadGovernorLock(lockPath, expectedOwner, {
4276
+ // Removes a lock nobody is going to release: either a complete record naming
4277
+ // a dead PID (expectedOwner), or -- with expectedOwner null -- an unreadable
4278
+ // record past GOVERNOR_LOCK_ORPHAN_MS, which is what a creator leaves when its
4279
+ // write fails or it dies between open and write. Both go through the same
4280
+ // recovery marker and the same re-check on both sides of the rename, so a
4281
+ // record that becomes valid at any point is restored rather than lost.
4282
+ function quarantineAbandonedGovernorLock(lockPath, expectedOwner, {
4157
4283
  pid = process.pid,
4158
4284
  kill = process.kill.bind(process),
4159
4285
  observeArtifact = null,
4160
4286
  } = {}) {
4161
4287
  const recoveryNonce = randomUUID();
4162
4288
  const recoveryPath = `${lockPath}.recovery-${recoveryNonce}`;
4163
- let descriptor;
4164
- try {
4165
- descriptor = openSync(recoveryPath, "wx", 0o600);
4166
- try {
4167
- writeFileSync(descriptor, JSON.stringify({ pid, nonce: recoveryNonce }), "utf8");
4168
- } finally {
4169
- closeSync(descriptor);
4170
- }
4171
- chmodSync(recoveryPath, 0o600);
4172
- observeGovernorArtifact(observeArtifact, "recovery", recoveryPath);
4173
- } catch (error) {
4174
- if (descriptor !== undefined) {
4175
- try { closeSync(descriptor); } catch { /* descriptor was already closed */ }
4176
- }
4177
- return error?.code === "EEXIST" ? "busy" : "failed";
4178
- }
4289
+ const claim = claimGovernorLock(recoveryPath, { pid, nonce: recoveryNonce });
4290
+ if (claim !== "acquired") return claim === "held" ? "busy" : "failed";
4291
+ observeGovernorArtifact(observeArtifact, "recovery", recoveryPath);
4292
+
4293
+ // The lock is still the abandoned one this call was asked to remove. For a
4294
+ // dead owner that is the same PID and nonce, confirmed dead again; for an
4295
+ // orphan it is a record that is still unreadable and still past the age
4296
+ // bound -- a creator that has since finished writing must be left alone.
4297
+ const stillAbandoned = (path) => expectedOwner === null
4298
+ ? lockRecordOrphaned(path)
4299
+ : sameLockOwner(lockOwner(path), expectedOwner) && pidIsDead(expectedOwner.pid, kill);
4300
+ // What the quarantined file must hold for the removal to be right: the same
4301
+ // dead record, or still no record at all. Anything else is a successor.
4302
+ const quarantinedAsExpected = (path) => expectedOwner === null
4303
+ ? lockOwner(path) === null
4304
+ : sameLockOwner(lockOwner(path), expectedOwner);
4179
4305
 
4180
4306
  const quarantinePath = `${lockPath}.quarantine-${randomUUID()}`;
4181
4307
  try {
@@ -4183,17 +4309,13 @@ function quarantineDeadGovernorLock(lockPath, expectedOwner, {
4183
4309
  // A killed recovery process leaves a uniquely named marker that another
4184
4310
  // process can remove only after its PID is confirmed dead; the path can
4185
4311
  // never be reused by a successor.
4186
- // Re-read both fields after taking that marker and again immediately before
4187
- // rename, so an owner that changed since the initial ESRCH result is never
4188
- // selected as the abandoned lock.
4189
- const confirmed = lockOwner(lockPath);
4190
- if (!sameLockOwner(confirmed, expectedOwner) || !pidIsDead(confirmed.pid, kill)) return "changed";
4191
- const beforeRename = lockOwner(lockPath);
4192
- if (!sameLockOwner(beforeRename, expectedOwner)) return "changed";
4312
+ // Re-read after taking that marker, immediately before the rename, so an
4313
+ // owner that changed since the initial check is never selected as the
4314
+ // abandoned lock.
4315
+ if (!stillAbandoned(lockPath)) return "changed";
4193
4316
  renameSync(lockPath, quarantinePath);
4194
4317
  observeGovernorArtifact(observeArtifact, "quarantine", quarantinePath);
4195
- const quarantined = lockOwner(quarantinePath);
4196
- if (!sameLockOwner(quarantined, expectedOwner)) {
4318
+ if (!quarantinedAsExpected(quarantinePath)) {
4197
4319
  // Acquisitions that raced the recovery marker cannot enter their critical
4198
4320
  // section. Restoring here preserves that successor rather than deleting
4199
4321
  // or leaving it under an abandoned quarantine name.
@@ -4209,30 +4331,17 @@ function quarantineDeadGovernorLock(lockPath, expectedOwner, {
4209
4331
  }
4210
4332
  }
4211
4333
 
4212
- function withGovernorLock(scope, operation, {
4334
+ function withFileLock(lockPath, operation, {
4213
4335
  pid = process.pid,
4214
4336
  nonce = randomUUID(),
4215
4337
  waitMs = GOVERNOR_LOCK_WAIT_MS,
4216
- kill = scope?.kill ?? process.kill.bind(process),
4338
+ kill = process.kill.bind(process),
4217
4339
  observeArtifact = null,
4218
4340
  } = {}) {
4219
- if (scope?.coordinationRoot && !scope.rootLocked) {
4220
- return withIdentityRegistry(scope.coordinationRoot, (registry, at) => {
4221
- const migration = inspectLegacyMigration(scope.coordinationRoot, registry, at);
4222
- if (!migration.ok) return migration;
4223
- if (registry.identities[scope.credentialKey]) {
4224
- try { statSync(scope.path); } catch { return { ok: false, reason: "corrupt" }; }
4225
- }
4226
- return withGovernorLock({ ...scope, rootLocked: true }, operation, { pid, nonce, waitMs, kill, observeArtifact });
4227
- }, { now: scopeNow(scope), kill });
4228
- }
4229
- const current = currentGovernorScope(scope);
4230
- if (!current.ok) return current;
4231
- const lockPath = `${scope.path}.lock`;
4232
4341
  const deadline = Date.now() + waitMs;
4233
4342
  try {
4234
- mkdirSync(dirname(scope.path), { recursive: true, mode: 0o700 });
4235
- chmodSync(dirname(scope.path), 0o700);
4343
+ mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 });
4344
+ if (process.platform !== "win32") chmodSync(dirname(lockPath), 0o700);
4236
4345
  } catch {
4237
4346
  return { ok: false, reason: "unwritable" };
4238
4347
  }
@@ -4241,19 +4350,15 @@ function withGovernorLock(scope, operation, {
4241
4350
  Atomics.wait(persistenceWaitCell, 0, 0, 5);
4242
4351
  return true;
4243
4352
  };
4353
+ const owner = { pid, nonce };
4244
4354
  for (;;) {
4245
4355
  if (governorRecoveryActive(lockPath, kill)) {
4246
4356
  if (!waitForLock()) return { ok: false, reason: "busy" };
4247
4357
  continue;
4248
4358
  }
4249
- try {
4250
- const descriptor = openSync(lockPath, "wx", 0o600);
4251
- try {
4252
- writeFileSync(descriptor, JSON.stringify({ pid, nonce }), "utf8");
4253
- } finally {
4254
- closeSync(descriptor);
4255
- }
4256
- chmodSync(lockPath, 0o600);
4359
+ const claim = claimGovernorLock(lockPath, owner);
4360
+ if (claim === "failed") return { ok: false, reason: "unwritable" };
4361
+ if (claim === "acquired") {
4257
4362
  observeGovernorArtifact(observeArtifact, "canonical", lockPath);
4258
4363
  if (governorRecoveryActive(lockPath, kill)) {
4259
4364
  releaseGovernorLock(lockPath, nonce);
@@ -4262,31 +4367,65 @@ function withGovernorLock(scope, operation, {
4262
4367
  }
4263
4368
  try {
4264
4369
  return operation();
4370
+ } catch {
4371
+ return { ok: false, reason: "unwritable" };
4265
4372
  } finally {
4266
4373
  releaseGovernorLock(lockPath, nonce);
4267
4374
  }
4268
- } catch (error) {
4269
- if (error?.code !== "EEXIST") return { ok: false, reason: "unwritable" };
4270
- const owner = lockOwner(lockPath);
4271
- // The creator owns the canonical path as soon as open("wx") succeeds,
4272
- // before its small JSON owner record is fully visible. Treat an unreadable
4273
- // record as owned during the bounded wait; stealing it could overlap the
4274
- // creator, while returning busy is always fail-closed.
4275
- if (!owner) {
4276
- if (!waitForLock()) return { ok: false, reason: "busy" };
4277
- continue;
4278
- }
4279
- if (pidIsDead(owner.pid, kill)) {
4280
- const recovery = quarantineDeadGovernorLock(lockPath, owner, { pid, kill, observeArtifact });
4375
+ }
4376
+ const holder = lockOwner(lockPath);
4377
+ if (sameLockOwner(holder, owner)) {
4378
+ // This call's own record, found on the path without having been
4379
+ // acquired: an orphan reclaim renamed it away while claimGovernorLock was
4380
+ // verifying, then restored it. Nobody else will ever release it.
4381
+ releaseGovernorLock(lockPath, nonce);
4382
+ continue;
4383
+ }
4384
+ // The creator owns the canonical path as soon as open("wx") succeeds,
4385
+ // before its small JSON owner record is fully visible. Treat an unreadable
4386
+ // record as owned during the bounded wait; stealing it could overlap the
4387
+ // creator, while returning busy is always fail-closed. Past the age bound
4388
+ // there is no creator left to overlap, and waiting would be forever.
4389
+ if (!holder) {
4390
+ if (lockRecordOrphaned(lockPath)) {
4391
+ const recovery = quarantineAbandonedGovernorLock(lockPath, null, { pid, kill, observeArtifact });
4281
4392
  if (recovery === "quarantined" || recovery === "changed") continue;
4282
- if (!waitForLock()) return { ok: false, reason: "busy" };
4283
- continue;
4284
4393
  }
4285
4394
  if (!waitForLock()) return { ok: false, reason: "busy" };
4395
+ continue;
4396
+ }
4397
+ if (pidIsDead(holder.pid, kill)) {
4398
+ const recovery = quarantineAbandonedGovernorLock(lockPath, holder, { pid, kill, observeArtifact });
4399
+ if (recovery === "quarantined" || recovery === "changed") continue;
4400
+ if (!waitForLock()) return { ok: false, reason: "busy" };
4401
+ continue;
4286
4402
  }
4403
+ if (!waitForLock()) return { ok: false, reason: "busy" };
4287
4404
  }
4288
4405
  }
4289
4406
 
4407
+ function withGovernorLock(scope, operation, {
4408
+ pid = process.pid,
4409
+ nonce = randomUUID(),
4410
+ waitMs = GOVERNOR_LOCK_WAIT_MS,
4411
+ kill = scope?.kill ?? process.kill.bind(process),
4412
+ observeArtifact = null,
4413
+ } = {}) {
4414
+ if (scope?.coordinationRoot && !scope.rootLocked) {
4415
+ return withIdentityRegistry(scope.coordinationRoot, (registry, at) => {
4416
+ const migration = inspectLegacyMigration(scope.coordinationRoot, registry, at);
4417
+ if (!migration.ok) return migration;
4418
+ if (registry.identities[scope.credentialKey]) {
4419
+ try { statSync(scope.path); } catch { return { ok: false, reason: "corrupt" }; }
4420
+ }
4421
+ return withGovernorLock({ ...scope, rootLocked: true }, operation, { pid, nonce, waitMs, kill, observeArtifact });
4422
+ }, { now: scopeNow(scope), kill });
4423
+ }
4424
+ const current = currentGovernorScope(scope);
4425
+ if (!current.ok) return current;
4426
+ return withFileLock(`${scope.path}.lock`, operation, { pid, nonce, waitMs, kill, observeArtifact });
4427
+ }
4428
+
4290
4429
  function mutateGovernor(scope, nowMs, mutate) {
4291
4430
  const requestedAt = scopeNow(scope, nowMs);
4292
4431
  if (!Number.isFinite(requestedAt) || requestedAt < 0) return { ok: false, reason: "corrupt" };
@@ -4346,7 +4485,34 @@ function returnPacingCredit(state, resource, unusedCost, nowMs) {
4346
4485
  const callsPerMs = decision?.callsPerMs;
4347
4486
  if (!Number.isFinite(callsPerMs) || callsPerMs <= 0) return;
4348
4487
  const credit = Math.min(unusedCost, GOVERNOR_MAX_ATOMIC_COST) / callsPerMs;
4349
- budget.laneNextAt = Math.max(nowMs + HTTP_START_GAP_MS, budget.laneNextAt - credit);
4488
+ const oldLaneNextAt = budget.laneNextAt;
4489
+ budget.laneNextAt = Math.max(nowMs + HTTP_START_GAP_MS, oldLaneNextAt - credit);
4490
+ const returnedMs = oldLaneNextAt - budget.laneNextAt;
4491
+ if (!(returnedMs > 0)) return;
4492
+
4493
+ // Future grants are not transport starts. When an earlier request proves it
4494
+ // used less than its reservation, move those grants into the freed paced
4495
+ // space as well; moving only laneNextAt leaves their original deadlines
4496
+ // stranded even though the lane is available. Keep their IDs and order.
4497
+ const scheduled = Object.values(state.reservations)
4498
+ .filter((reservation) => reservation.status === "scheduled" &&
4499
+ reservation.costs[resource] > 0)
4500
+ .sort((left, right) => left.notBefore - right.notBefore);
4501
+ let nextSafeAt = nowMs + HTTP_START_GAP_MS;
4502
+ for (const reservation of scheduled) {
4503
+ const lease = state.leases[reservation.leaseId];
4504
+ const phaseAt = lease?.phaseSeed && reservation.epochs[resource] === budget.epoch
4505
+ ? governorEpochPhaseAt(lease.phaseSeed, decision)
4506
+ : reservation.notBefore;
4507
+ if (reservation.notBefore > nowMs &&
4508
+ RATE_RESOURCES.every((other) => other === resource || reservation.costs[other] === 0)) {
4509
+ reservation.notBefore = Math.min(reservation.notBefore,
4510
+ Math.max(nextSafeAt, phaseAt, reservation.notBefore - returnedMs));
4511
+ }
4512
+ nextSafeAt = Math.max(nextSafeAt,
4513
+ Math.max(nowMs, reservation.notBefore) + reservation.costs[resource] / callsPerMs);
4514
+ }
4515
+ budget.laneNextAt = Math.max(budget.laneNextAt, nextSafeAt);
4350
4516
  }
4351
4517
 
4352
4518
  function scheduleGovernorState(state, nowMs) {
@@ -4362,7 +4528,8 @@ function scheduleGovernorState(state, nowMs) {
4362
4528
  ));
4363
4529
  const deferredBackground = state.observers.graphql.outcome === "failed"
4364
4530
  ? Object.entries(state.intents)
4365
- .filter(([, intent]) => intentPriority(intent) === REQUEST_PRIORITIES.background)
4531
+ .filter(([, intent]) => intentPriority(intent) === REQUEST_PRIORITIES.background &&
4532
+ intent.costs?.graphql > 0)
4366
4533
  .map(([id]) => id)
4367
4534
  : [];
4368
4535
  const deferred = new Set(deferredBackground);
@@ -4382,6 +4549,7 @@ function scheduleGovernorState(state, nowMs) {
4382
4549
  nowMs,
4383
4550
  maxGrants: GOVERNOR_MAX_RESERVATIONS - reservationCount,
4384
4551
  manualStreak: state.fairness.manualStreak,
4552
+ deferFutureBackground: true,
4385
4553
  });
4386
4554
  state.fairness.manualStreak = result.manualStreak;
4387
4555
  result.denied.push(...deferredBackground.map((intentId) => ({
@@ -4932,6 +5100,12 @@ function startReservation(scope, reservationId, nowMs) {
4932
5100
  if (liveClaim) {
4933
5101
  return { value: { status: "waiting", reason: "probe", notBefore: liveClaim.leaseUntil } };
4934
5102
  }
5103
+ const unavailableObserver = RATE_RESOURCES.find((resource) =>
5104
+ reservation.costs[resource] > 0 && state.observers[resource]?.outcome !== "healthy");
5105
+ if (unavailableObserver) {
5106
+ return { value: { status: "waiting", reason: "observer",
5107
+ notBefore: Math.max(at + 1000, state.observers[unavailableObserver]?.nextAt ?? at + 1000) } };
5108
+ }
4935
5109
  for (const resource of RATE_RESOURCES.filter((name) => reservation.costs[name] > 0)) {
4936
5110
  const budget = state.budgets[resource];
4937
5111
  if (
@@ -5142,7 +5316,10 @@ function inspectGovernor(scope, nowMs) {
5142
5316
  }
5143
5317
 
5144
5318
  function governorHealth(result, nowMs = Date.now()) {
5145
- if (!result?.ok) return { status: "unavailable", leases: 0, resources: {} };
5319
+ // The doctor is the one surface that may name the raw reason: a bare
5320
+ // "unavailable" could not tell a lock nobody released (busy) from a file that
5321
+ // failed validation (corrupt), and those are fixed in different places.
5322
+ if (!result?.ok) return { status: `unavailable (${result?.reason ?? "unknown"})`, leases: 0, resources: {} };
5146
5323
  const state = result.value;
5147
5324
  let status = "healthy";
5148
5325
  if (RATE_RESOURCES.some((resource) => state.probeClaims[resource]?.leaseUntil > nowMs)) status = "waiting for probe";
@@ -8311,9 +8488,9 @@ function createCollectorAcquisitionRuntime({
8311
8488
  return { ...context, readBudgets };
8312
8489
  }
8313
8490
 
8314
- function refreshProviderBudget(context, signal) {
8491
+ function refreshProviderBudget(context, signal, tab) {
8315
8492
  const budgetReader = context.readBudgets ?? readBudgets;
8316
- return refreshSharedBudget(context.scope, context.leaseId, signal,
8493
+ return refreshCollectorRequiredBudget(context.scope, context.leaseId, signal, tab,
8317
8494
  { ...(budgetReader ? { readBudgets: budgetReader } : {}),
8318
8495
  onObserverPublished: context.onObserverPublished, now,
8319
8496
  wait: (delay) => abortableDelay(delay, signal, {
@@ -8392,8 +8569,8 @@ function createCollectorAcquisitionRuntime({
8392
8569
  const at = now();
8393
8570
  const lease = maintainControlLease(context.scope, context.leaseId, demand.floorMs, resource, at);
8394
8571
  if (!lease.ok) throw new Error("collector governor unavailable");
8395
- const refreshed = await refreshProviderBudget(context, signal);
8396
- if (!refreshed.ok) {
8572
+ const refreshed = await refreshProviderBudget(context, signal, resource);
8573
+ if (!collectorBudgetRefreshAllowsAdmission(refreshed, context.scope, resource, now())) {
8397
8574
  const error = new Error(refreshed.reason === "provider-capability"
8398
8575
  ? `collector provider capability unavailable (${refreshed.capability})`
8399
8576
  : "collector budget unavailable");
@@ -8414,6 +8591,29 @@ function createCollectorAcquisitionRuntime({
8414
8591
  admitGovernorOperation(admitScope, admitLeaseId, operation, priority, at,
8415
8592
  createId("collector-intent", admitLeaseId, operation, priority, at)),
8416
8593
  deferredRetryAt: collectorObserverRetryAt(context.scope, resource, now()),
8594
+ retainUntil: claim.claimedAt + ACQUISITION_UNSTARTED_DEADLINE_MS - 5_000,
8595
+ onRetainedWait: async () => {
8596
+ const inspected = engine.inspect(claim.subscriptionId);
8597
+ if (closed || claim.state.closed || signal.aborted ||
8598
+ claim.state.bindingRevision !== claim.bindingRevision) return false;
8599
+ if (
8600
+ context.identity.accessKey !== claim.accessKey ||
8601
+ context.coordinator.current()?.accessKey !== claim.accessKey) return false;
8602
+ if (
8603
+ !inspected.ok || inspected.value?.claim?.nonce !== claim.nonce ||
8604
+ inspected.value.claim.generation !== claim.generation ||
8605
+ inspected.value.claim.started) return false;
8606
+ const renewed = maintainControlLease(context.scope, context.leaseId,
8607
+ demand.floorMs, resource, now());
8608
+ if (!renewed.ok) return false;
8609
+ await refreshProviderBudget(context, signal, resource);
8610
+ // A failure from the other resource does not invalidate this exact
8611
+ // reservation. startReservation still checks this tab's own budget.
8612
+ const current = engine.inspect(claim.subscriptionId);
8613
+ return !signal.aborted && context.coordinator.current()?.accessKey === claim.accessKey &&
8614
+ current.ok && current.value?.claim?.nonce === claim.nonce &&
8615
+ current.value.claim.generation === claim.generation && !current.value.claim.started;
8616
+ },
8417
8617
  });
8418
8618
  const reservationId = admitted.reservationId;
8419
8619
  const governor = inspectGovernor(context.scope, now());
@@ -8422,7 +8622,7 @@ function createCollectorAcquisitionRuntime({
8422
8622
  accessKey: context.identity.accessKey,
8423
8623
  epochs: governor.ok ? governor.value.epochs : {},
8424
8624
  });
8425
- if (!started.ok) {
8625
+ if (!started.ok || started.value?.status !== "started") {
8426
8626
  completeReservation({ ...context.scope, identityProvider: null }, reservationId,
8427
8627
  { outcome: "rejected" }, now());
8428
8628
  throw new Error("collector acquisition start failed");
@@ -8432,8 +8632,12 @@ function createCollectorAcquisitionRuntime({
8432
8632
  const entities = new Map((force ? [] : snapshot?.entities ?? []).map((entity) => [entity.key, entity]));
8433
8633
  const securityPolicy = resource === "security" && context.definition?.type === "github-app"
8434
8634
  ? githubAppSecurityPolicy(context.definition, now()) : null;
8435
- let result = await requestIdentityStorage.run(context.scope, () => descriptor.fetch({
8635
+ const boundedSignal = AbortSignal.any([
8436
8636
  signal,
8637
+ AbortSignal.timeout(ACQUISITION_STARTED_DEADLINE_MS),
8638
+ ]);
8639
+ let result = await requestIdentityStorage.run(context.scope, () => descriptor.fetch({
8640
+ signal: boundedSignal,
8437
8641
  entities,
8438
8642
  previousRaw: force ? null : snapshot?.raw ?? null,
8439
8643
  force,
@@ -8485,7 +8689,7 @@ function createCollectorAcquisitionRuntime({
8485
8689
  const context = provider?.scope ? provider : await defaultResolve(target, signal);
8486
8690
  const lease = maintainControlLease(context.scope, context.leaseId, REFRESH_MS, "issues", now());
8487
8691
  if (!lease.ok) throw new Error("collector identity governor unavailable");
8488
- const refreshed = await refreshProviderBudget(context, signal);
8692
+ const refreshed = await refreshProviderBudget(context, signal, "issues");
8489
8693
  if (!refreshed.ok) {
8490
8694
  const error = new Error(refreshed.reason === "provider-capability"
8491
8695
  ? `collector provider capability unavailable (${refreshed.capability})`
@@ -8588,6 +8792,8 @@ function createCollectorAcquisitionRuntime({
8588
8792
  function deliverCollectorSnapshot(item, snapshot, bindingRevision, { publication = false } = {}) {
8589
8793
  if (bindingRevision !== item.bindingRevision) return false;
8590
8794
  item.nextDueAt = snapshot.nextDueAt;
8795
+ if (Number.isSafeInteger(snapshot.generation)) item.lastDeliveredGeneration = snapshot.generation;
8796
+ if (Number.isFinite(snapshot.lastSuccessAt)) item.lastDeliveredSuccessAt = snapshot.lastSuccessAt;
8591
8797
  item.onSnapshot(snapshot);
8592
8798
  if (!publication || !Number.isSafeInteger(snapshot.generation)) return true;
8593
8799
  for (const waiter of item.invalidations) {
@@ -8610,6 +8816,14 @@ function createCollectorAcquisitionRuntime({
8610
8816
  if (closed || item.closed || !demandEnabled(item.demand)) return null;
8611
8817
  item.pollPromise = poll(item, { manual, force }).finally(() => {
8612
8818
  item.pollPromise = null;
8819
+ if (item.promotionGeneration !== null) {
8820
+ if (item.demand.active && ((item.lastDeliveredGeneration ?? 0) <= item.promotionGeneration ||
8821
+ (item.lastDeliveredSuccessAt ?? Number.NEGATIVE_INFINITY) < item.promotionAt)) {
8822
+ item.queuedRefresh = { force: item.queuedRefresh?.force === true };
8823
+ }
8824
+ item.promotionGeneration = null;
8825
+ item.promotionAt = null;
8826
+ }
8613
8827
  const queued = item.queuedRefresh;
8614
8828
  item.queuedRefresh = null;
8615
8829
  if (queued && !closed && !item.closed) queueMicrotask(() => startPoll(item, { manual: true, force: queued.force }));
@@ -8672,22 +8886,31 @@ function createCollectorAcquisitionRuntime({
8672
8886
  nonce: ownership.value.nonce,
8673
8887
  generation: ownership.value.generation,
8674
8888
  accessKey: item.identity.accessKey,
8889
+ claimedAt: now(),
8890
+ subscriptionId,
8891
+ bindingRevision,
8675
8892
  };
8676
8893
  item.pollPhase = "claimed";
8677
8894
  const producer = produce ?? defaultProduce;
8678
8895
  item.controller = new AbortController();
8679
8896
  try {
8680
- const produceBound = () => producer({
8897
+ const produceBound = () => acquisitionFenceStorage.run(
8898
+ () => engine.currentClaim(subscriptionId, claim), () => producer({
8681
8899
  target: item.target,
8682
8900
  resource: item.resource,
8683
- demand: ownership.value.demand,
8901
+ demand: { ...ownership.value.demand, background: item.demand.background },
8684
8902
  snapshot: ownership.value.snapshot,
8685
8903
  claim: { ...claim, state: item },
8686
8904
  provider: item.provider,
8687
8905
  signal: item.controller.signal,
8688
8906
  force,
8689
- markStarted: (receipt) => engine.refresh(subscriptionId, { started: { ...claim, ...(receipt ? { receipt } : {}) } }),
8690
- });
8907
+ markStarted: async (receipt) => {
8908
+ const started = await engine.refresh(subscriptionId, { started: { ...claim,
8909
+ ...(receipt ? { receipt } : {}) } });
8910
+ if (started.ok && started.value?.status === "started") claim.receipt = receipt;
8911
+ return started;
8912
+ },
8913
+ }));
8691
8914
  const produced = item.provider?.scope
8692
8915
  ? await requestIdentityStorage.run(item.provider.scope, produceBound)
8693
8916
  : await produceBound();
@@ -8818,7 +9041,8 @@ function createCollectorAcquisitionRuntime({
8818
9041
  const item = { target, resource, demand: normalizedDemand, onSnapshot, onHold, id: null, identity: null,
8819
9042
  provider: null, polling: false, pollPhase: null, pollPromise: null, controller: null,
8820
9043
  initializePromise: null, closed: false, timer: null, queuedRefresh: null,
8821
- bindingRevision: 0, invalidations: new Set() };
9044
+ bindingRevision: 0, invalidations: new Set(), lastDeliveredGeneration: null,
9045
+ lastDeliveredSuccessAt: null, promotionGeneration: null, promotionAt: null };
8822
9046
  items.add(item);
8823
9047
  void startInitialize(item);
8824
9048
  return {
@@ -8826,7 +9050,12 @@ function createCollectorAcquisitionRuntime({
8826
9050
  const normalized = normalizeCollectorDemand(next);
8827
9051
  if (!normalized) return { ok: false, reason: "invalid" };
8828
9052
  const wasEnabled = demandEnabled(item.demand);
9053
+ const wasActive = item.demand.active;
8829
9054
  item.demand = normalized;
9055
+ if (!normalized.active) {
9056
+ item.promotionGeneration = null;
9057
+ item.promotionAt = null;
9058
+ }
8830
9059
  if (!demandEnabled(normalized) && item.timer !== null) {
8831
9060
  clearTimeout_(item.timer);
8832
9061
  item.timer = null;
@@ -8836,6 +9065,15 @@ function createCollectorAcquisitionRuntime({
8836
9065
  }
8837
9066
  const updated = item.id ? engine.updateDemand(item.id, normalized) : { ok: true };
8838
9067
  if (!wasEnabled && demandEnabled(normalized)) void startPoll(item);
9068
+ else if (!wasActive && normalized.active && updated.ok) {
9069
+ // A background snapshot may remain fresh by its old quiet cadence.
9070
+ // Promotion owes a new active observation, with normal governor admission.
9071
+ if (item.pollPromise) {
9072
+ item.promotionGeneration = item.lastDeliveredGeneration ?? 0;
9073
+ item.promotionAt = now();
9074
+ }
9075
+ else void startPoll(item, { manual: true });
9076
+ }
8839
9077
  return updated;
8840
9078
  },
8841
9079
  refresh(force = false) { return startPoll(item, { manual: true, force: force === true }); },
@@ -9712,66 +9950,11 @@ const ACQUISITION_HOLD_REASONS = new Set([
9712
9950
  const ACQUISITION_STORE_VERSION = 1;
9713
9951
  const ACQUISITION_MAX_UNCERTAIN_RECEIPTS = 1024;
9714
9952
 
9715
- function normalizeAcquisitionDiagnosticMetadata(raw) {
9716
- if (!isRecord(raw) || raw.version !== ACQUISITION_STORE_VERSION ||
9717
- typeof raw.producerEpoch !== "string" ||
9718
- !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(raw.producerEpoch) ||
9719
- !isRecord(raw.subscriptions) || !isRecord(raw.queries)) return null;
9720
- const metrics = normalizeAcquisitionMetrics(raw.metrics);
9721
- if (!metrics) return null;
9722
- const receipts = raw.uncertainReceipts ?? {};
9723
- if (!isRecord(receipts) || Object.keys(receipts).length > ACQUISITION_MAX_UNCERTAIN_RECEIPTS) return null;
9724
- const uncertainReceipts = {};
9725
- const outstanding = { core: 0, graphql: 0 };
9726
- for (const [id, receipt] of Object.entries(receipts)) {
9727
- const normalized = normalizeAcquisitionReceipt(receipt, id);
9728
- if (!normalized) return null;
9729
- uncertainReceipts[id] = normalized;
9730
- outstanding[normalized.resource] += normalized.units;
9731
- }
9732
- if (metrics.uncertainCoreUnits !== outstanding.core ||
9733
- metrics.uncertainGraphqlUnits !== outstanding.graphql) return null;
9734
- const queries = {};
9735
- for (const [queryKey, value] of Object.entries(raw.queries)) {
9736
- if (!isRecord(value) || !isRecord(value.query) || value.query.queryKey !== queryKey ||
9737
- !TAB_KEYS.includes(value.query.resource)) return null;
9738
- const hold = normalizeAcquisitionHold(value.hold);
9739
- if (value.hold !== undefined && value.hold !== null && !hold) return null;
9740
- const snapshot = value.snapshot;
9741
- if (snapshot !== null && (!isRecord(snapshot) ||
9742
- !Number.isFinite(snapshot.lastSuccessAt) || !Number.isFinite(snapshot.lastChangedAt) ||
9743
- !Number.isFinite(snapshot.nextDueAt))) return null;
9744
- const claim = value.claim;
9745
- if (claim !== null && (!isRecord(claim) || !Number.isSafeInteger(claim.generation) ||
9746
- (claim.receiptIds !== undefined && (!Array.isArray(claim.receiptIds) ||
9747
- claim.receiptIds.some((id) => typeof id !== "string" || !uncertainReceipts[id]))))) return null;
9748
- queries[queryKey] = {
9749
- query: { queryKey, resource: value.query.resource },
9750
- snapshot: snapshot === null ? null : {
9751
- lastSuccessAt: snapshot.lastSuccessAt,
9752
- lastChangedAt: snapshot.lastChangedAt,
9753
- nextDueAt: snapshot.nextDueAt,
9754
- },
9755
- claim: claim === null ? null : { generation: claim.generation },
9756
- hold,
9757
- };
9758
- }
9759
- const subscriptions = {};
9760
- for (const [id, value] of Object.entries(raw.subscriptions)) {
9761
- if (!isRecord(value) || !queries[value.queryKey] || !Number.isFinite(value.expiresAt)) return null;
9762
- const waitingGeneration = value.waitingGeneration ?? null;
9763
- const waitingSinceAt = value.waitingSinceAt ?? null;
9764
- if (waitingGeneration !== null && (!Number.isSafeInteger(waitingGeneration) ||
9765
- !Number.isFinite(waitingSinceAt))) return null;
9766
- if (waitingGeneration === null && waitingSinceAt !== null) return null;
9767
- subscriptions[id] = {
9768
- queryKey: value.queryKey,
9769
- expiresAt: value.expiresAt,
9770
- waitingGeneration,
9771
- waitingSinceAt,
9772
- };
9773
- }
9774
- return { producerEpoch: raw.producerEpoch, metrics, uncertainReceipts, queries, subscriptions };
9953
+ function normalizeAcquisitionDiagnosticMetadata(raw, path) {
9954
+ const normalized = normalizeAcquisitionStore(raw);
9955
+ if (!normalized) return null;
9956
+ const hydrated = hydrateAcquisitionStore(path, normalized);
9957
+ return hydrated.ok ? hydrated.value : null;
9775
9958
  }
9776
9959
 
9777
9960
  function unavailableAcquisitionDiagnostic(reason, source = "standalone") {
@@ -9779,18 +9962,71 @@ function unavailableAcquisitionDiagnostic(reason, source = "standalone") {
9779
9962
  activeSubscribers: 0, metrics: emptyAcquisitionMetrics(), queries: [] };
9780
9963
  }
9781
9964
 
9965
+ function doctorAcquisitionLockDiagnostic(lockPath, {
9966
+ nowMs = Date.now(), kill = process.kill.bind(process),
9967
+ } = {}) {
9968
+ const parent = dirname(lockPath);
9969
+ try {
9970
+ const directory = statSync(parent);
9971
+ if (!directory.isDirectory() || process.platform !== "win32" && (directory.mode & 0o222) === 0) {
9972
+ return { status: "unwritable", ageMs: null, reason: "acquisition directory is not writable" };
9973
+ }
9974
+ } catch (error) {
9975
+ if (error?.code !== "ENOENT") {
9976
+ return { status: "unwritable", ageMs: null, reason: "acquisition directory is unavailable" };
9977
+ }
9978
+ }
9979
+ const inspect = (path, marker = false) => {
9980
+ let file;
9981
+ try { file = lstatSync(path); } catch (error) {
9982
+ return error?.code === "ENOENT" ? null
9983
+ : { status: "busy", ageMs: null, reason: "lock path cannot be inspected" };
9984
+ }
9985
+ if (!file.isFile()) return { status: "busy", ageMs: null, reason: "lock path is not a regular file" };
9986
+ const ageMs = Math.max(0, Math.floor(nowMs - file.mtimeMs));
9987
+ const owner = lockOwner(path);
9988
+ if (owner) {
9989
+ const ownerStatus = acquisitionOwnerStatus(owner.pid, kill);
9990
+ if (ownerStatus === "dead") return { status: "orphaned", ageMs,
9991
+ reason: marker ? "abandoned recovery marker" : "confirmed dead lock owner" };
9992
+ return { status: "busy", ageMs,
9993
+ reason: marker ? "recovery owner is live or unknown" : "lock owner is live or unknown" };
9994
+ }
9995
+ if (ageMs >= GOVERNOR_LOCK_ORPHAN_MS) return { status: "orphaned", ageMs,
9996
+ reason: marker ? "aged unreadable recovery marker" : "aged unreadable lock record" };
9997
+ return { status: "busy", ageMs,
9998
+ reason: marker ? "recent unreadable recovery marker" : "recent unreadable lock record" };
9999
+ };
10000
+ for (const markerPath of governorRecoveryPaths(lockPath)) {
10001
+ const marker = inspect(markerPath, true);
10002
+ if (marker) return marker;
10003
+ }
10004
+ return inspect(lockPath) ?? { status: "unobstructed", ageMs: null,
10005
+ reason: "no lock-path blocker observed" };
10006
+ }
10007
+
9782
10008
  function doctorAcquisitionDiagnostic({ nowMs = Date.now(), source = "standalone" } = {}) {
10009
+ const path = join(identityRegistryRoot(), "acquisition.json");
10010
+ const lock = doctorAcquisitionLockDiagnostic(`${path}.lock`, { nowMs });
10011
+ let metadata;
10012
+ let diagnostic;
9783
10013
  try {
9784
- const raw = JSON.parse(readFileSync(join(identityRegistryRoot(), "acquisition.json"), "utf8"));
9785
- const normalized = normalizeAcquisitionDiagnosticMetadata(raw);
9786
- return normalized
10014
+ const raw = JSON.parse(readFileSync(path, "utf8"));
10015
+ const normalized = normalizeAcquisitionDiagnosticMetadata(raw, path);
10016
+ metadata = normalized ? "healthy" : "corrupt";
10017
+ diagnostic = normalized
9787
10018
  ? acquisitionDiagnostics(normalized, { nowMs, source })
9788
10019
  : unavailableAcquisitionDiagnostic("corrupt", source);
9789
10020
  } catch (error) {
9790
- return unavailableAcquisitionDiagnostic(error?.code === "ENOENT"
9791
- ? "missing"
9792
- : error instanceof SyntaxError ? "corrupt" : "unreadable", source);
10021
+ const reason = error?.code === "ENOENT" ? "missing"
10022
+ : error instanceof SyntaxError ? "corrupt" : "unreadable";
10023
+ metadata = reason === "corrupt" ? "corrupt" : "unavailable";
10024
+ diagnostic = unavailableAcquisitionDiagnostic(reason, source);
9793
10025
  }
10026
+ return { ...diagnostic, metadata, lock,
10027
+ status: metadata === "healthy" && lock.status === "unobstructed" ? "healthy"
10028
+ : metadata === "corrupt" ? "corrupt" : lock.status === "unobstructed"
10029
+ ? diagnostic.status : "unavailable" };
9794
10030
  }
9795
10031
 
9796
10032
  function collectorSocketDiagnostic(pathOptions = {}) {
@@ -9954,6 +10190,11 @@ async function runDoctor({ probeEndpoints = false } = {}) {
9954
10190
  ...section("Acquisition metrics"),
9955
10191
  field("source", acquisitionDiagnostic?.source ?? "standalone"),
9956
10192
  field("status", acquisitionDiagnostic?.status ?? "unavailable"),
10193
+ field("metadata", acquisitionDiagnostic?.metadata ?? "unavailable"),
10194
+ field("lock", acquisitionDiagnostic?.lock?.status ?? "unavailable"),
10195
+ field("lock age", acquisitionDiagnostic?.lock?.ageMs == null
10196
+ ? "unknown" : `${Math.floor(acquisitionDiagnostic.lock.ageMs / 1000)}s`),
10197
+ field("reason", acquisitionDiagnostic?.lock?.reason ?? "lock inspection unavailable"),
9957
10198
  field("epoch", acquisitionDiagnostic?.epoch ?? "unavailable"),
9958
10199
  field("active queries", acquisitionDiagnostic?.activeQueries ?? 0),
9959
10200
  field("subscribers", acquisitionDiagnostic?.activeSubscribers ?? 0),
@@ -10434,18 +10675,9 @@ if (IS_MAIN) {
10434
10675
 
10435
10676
  if (opts.serve) runtime.headlessMode = { type: "serve", config: opts.config };
10436
10677
  else if (opts.collectorStdio) runtime.headlessMode = { type: "stdio" };
10678
+ else if (opts.doctor) runtime.headlessMode = { type: "doctor", probe: opts.probe };
10437
10679
  else {
10438
10680
 
10439
- // A reporting command, like --help and --version: gather, print, exit. It sits
10440
- // here on purpose -- ahead of the non-TTY refusal, because the whole point is
10441
- // `gh-glance --doctor > report.txt`, and ahead of preflight(), because a
10442
- // missing gh and a cwd outside a repository are exactly the conditions worth
10443
- // reporting rather than exiting 3 over.
10444
- if (opts.doctor) {
10445
- console.log(await runDoctor({ probeEndpoints: opts.probe }));
10446
- process.exit(0);
10447
- }
10448
-
10449
10681
  // A local collector subscription needs an exact offline target before Ink
10450
10682
  // mounts. Resolve git remotes locally and fail with the actionable flag;
10451
10683
  // otherwise a cold invocation paints a dashboard that can only say
@@ -12075,6 +12307,18 @@ function shouldCheckpointFreshness({ persistedAt, completedAt }) {
12075
12307
  // a unit with its validators, but an inability to claim it must never fall back
12076
12308
  // to an independent request: that would defeat its only security property.
12077
12309
  const ACQUISITION_CLAIM_TTL_MS = 45_000;
12310
+ const ACQUISITION_UNSTARTED_DEADLINE_MS = 2 * GOVERNOR_LEASE_TTL_MS;
12311
+ // Lists and Security have at most three sequential requests. Each may wait
12312
+ // for the shared HTTP permit and then spend a full gh timeout; the two nested
12313
+ // requests may also spend the bounded governor admission wait.
12314
+ const ACQUISITION_STARTED_DEADLINE_MS =
12315
+ 3 * (HTTP_PERMIT_MAX_MS + GH_TIMEOUT_MS) + 2 * GOVERNOR_ADMISSION_WAIT_MS;
12316
+
12317
+ function acquisitionClaimDeadline(claim) {
12318
+ return claim.started
12319
+ ? (claim.startedAt ?? claim.claimedAt) + ACQUISITION_STARTED_DEADLINE_MS
12320
+ : claim.claimedAt + ACQUISITION_UNSTARTED_DEADLINE_MS;
12321
+ }
12078
12322
  const ACQUISITION_HEARTBEAT_MS = 10_000;
12079
12323
  const ACQUISITION_INSPECT_MS = 1_000;
12080
12324
  const ACQUISITION_MAX_BYTES = 32 * 1024 * 1024;
@@ -12849,38 +13093,12 @@ function writeAcquisitionStore(path, state) {
12849
13093
  }
12850
13094
  }
12851
13095
 
12852
- function acquisitionLockOwner(path) {
12853
- const owner = lockOwner(path);
12854
- return owner && validGovernorId(owner.nonce) ? owner : null;
12855
- }
12856
-
12857
13096
  function withAcquisitionStore(path, operation, {
12858
13097
  now = Date.now(),
12859
13098
  pid = process.pid,
12860
13099
  kill = process.kill.bind(process),
12861
13100
  } = {}) {
12862
- const lockPath = `${path}.lock`;
12863
- const nonce = governorId();
12864
- for (let attempt = 0; attempt < 3; attempt += 1) {
12865
- try {
12866
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
12867
- writeFileSync(lockPath, JSON.stringify({ pid, nonce }), { encoding: "utf8", mode: 0o600, flag: "wx" });
12868
- break;
12869
- } catch (error) {
12870
- if (error?.code !== "EEXIST") return { ok: false, reason: "unwritable", error };
12871
- const owner = acquisitionLockOwner(lockPath);
12872
- // Another process can observe the lock file between its exclusive create
12873
- // and completed JSON write. Treat an unreadable owner as contention; it
12874
- // cannot authorize stealing and must not turn a healthy race into a
12875
- // corrupt-store verdict.
12876
- if (!owner || !pidIsDead(owner.pid, kill)) return { ok: false, reason: "busy" };
12877
- const abandoned = `${lockPath}.dead-${owner.nonce}`;
12878
- try { renameSync(lockPath, abandoned); } catch { continue; }
12879
- try { unlinkSync(abandoned); } catch { /* exact quarantined path */ }
12880
- }
12881
- }
12882
- if (acquisitionLockOwner(lockPath)?.nonce !== nonce) return { ok: false, reason: "busy" };
12883
- try {
13101
+ return withFileLock(`${path}.lock`, () => {
12884
13102
  const loaded = loadAcquisitionMetadata(path, { now });
12885
13103
  if (!loaded.ok) return loaded;
12886
13104
  const result = operation(loaded.value);
@@ -12890,11 +13108,7 @@ function withAcquisitionStore(path, operation, {
12890
13108
  }
12891
13109
  const written = writeAcquisitionStore(path, loaded.value);
12892
13110
  return written.ok ? { ok: true, value: result?.value, state: written.persisted, written: true } : written;
12893
- } finally {
12894
- if (acquisitionLockOwner(lockPath)?.nonce === nonce) {
12895
- try { unlinkSync(lockPath); } catch { /* exact owned lock */ }
12896
- }
12897
- }
13111
+ }, { pid, kill });
12898
13112
  }
12899
13113
 
12900
13114
  function acquisitionOwnerStatus(pid, kill) {
@@ -12980,6 +13194,7 @@ function trimAcquisitionStore(state, protectedQueryKey = null) {
12980
13194
  async function runStartedAcquisitionTransport(markStarted, transport) {
12981
13195
  const started = await markStarted();
12982
13196
  if (!started.ok) return started;
13197
+ if (started.value?.status !== "started") return { ok: false, reason: "already-started" };
12983
13198
  return { ok: true, value: await transport() };
12984
13199
  }
12985
13200
 
@@ -13081,7 +13296,8 @@ function createAcquisitionEngine({
13081
13296
  }
13082
13297
  const query = state.queries[item.queryKey];
13083
13298
  if (query?.claim?.pid === pid && query.claim.nonce === item.claimNonce &&
13084
- query.claim.leaseUntil !== at + ACQUISITION_CLAIM_TTL_MS) {
13299
+ query.claim.leaseUntil !== at + ACQUISITION_CLAIM_TTL_MS &&
13300
+ at < acquisitionClaimDeadline(query.claim)) {
13085
13301
  query.claim.leaseUntil = at + ACQUISITION_CLAIM_TTL_MS;
13086
13302
  changed = true;
13087
13303
  }
@@ -13128,8 +13344,64 @@ function createAcquisitionEngine({
13128
13344
  reapAcquisitionSubscriptions(state, now(), kill);
13129
13345
  const repositoryId = state.aliases[acquisitionAliasKey(queryInput.host, queryInput.repository)] ??
13130
13346
  queryInput.repositoryId;
13131
- const queryKey = acquisitionQueryKey({ ...queryInput, repositoryId });
13132
- const query = queryKey ? normalizeAcquisitionQuery({ ...queryInput, repositoryId, queryKey }) : null;
13347
+ const canonicalKey = acquisitionQueryKey({ ...queryInput, repositoryId });
13348
+ const canonicalQuery = canonicalKey
13349
+ ? normalizeAcquisitionQuery({ ...queryInput, repositoryId, queryKey: canonicalKey }) : null;
13350
+ // A different tab can prove the repository ID while an Actions or
13351
+ // Security producer still owns its slug-key claim. Moving that live
13352
+ // record would strand its receipt in another process. Join its existing
13353
+ // query instead of opening a second producer under the canonical key.
13354
+ let matching = canonicalQuery ? Object.entries(state.queries).filter(([, record]) => {
13355
+ const knownId = state.aliases[acquisitionAliasKey(record.query.host, record.query.repository)] ??
13356
+ record.query.repositoryId;
13357
+ return knownId === repositoryId && acquisitionQueryKey({
13358
+ ...record.query, repositoryId,
13359
+ }) === canonicalKey;
13360
+ }) : [];
13361
+ const liveKeys = new Set(Object.values(state.subscriptions).map((subscription) =>
13362
+ subscription.queryKey));
13363
+ if (matching.length > 0 && matching.every(([key, record]) =>
13364
+ !record.claim && !liveKeys.has(key)) &&
13365
+ (matching.length > 1 || matching[0][0] !== canonicalKey)) {
13366
+ // Once every former owner is gone, fold legacy split records into the
13367
+ // canonical key. Snapshot bytes are read before their old artifact
13368
+ // paths can be removed, and no live receipt or subscription moves.
13369
+ const generation = Math.max(...matching.map(([, record]) => record.generation));
13370
+ const newest = [...matching].sort(([, left], [, right]) =>
13371
+ (right.snapshot?.lastSuccessAt ?? 0) - (left.snapshot?.lastSuccessAt ?? 0))[0][1];
13372
+ const loaded = loadRecordSnapshot(newest);
13373
+ if (!loaded.ok) return loaded;
13374
+ const canonical = {
13375
+ ...canonicalQuery,
13376
+ targetKey: privateIdentityDigest("acquisition-target-v1", canonicalQuery.host, repositoryId),
13377
+ };
13378
+ const holds = matching.map(([, record]) => record.hold).filter(Boolean)
13379
+ .sort((left, right) => right.at - left.at);
13380
+ const merged = {
13381
+ query: canonical,
13382
+ generation,
13383
+ claim: null,
13384
+ snapshot: loaded.value ? { ...loaded.value, queryKey: canonicalKey, generation } : null,
13385
+ hold: holds[0] ?? null,
13386
+ lastUsedAt: Math.max(...matching.map(([, record]) => record.lastUsedAt)),
13387
+ };
13388
+ for (const [key] of matching) delete state.queries[key];
13389
+ state.queries[canonicalKey] = merged;
13390
+ matching = [[canonicalKey, merged]];
13391
+ }
13392
+ const subscribersFor = (key, activeOnly = false) => Object.values(state.subscriptions)
13393
+ .filter((subscription) => subscription.queryKey === key &&
13394
+ (!activeOnly || subscription.demand.active)).length;
13395
+ matching.sort(([leftKey, left], [rightKey, right]) =>
13396
+ Number(Boolean(right.claim?.started)) - Number(Boolean(left.claim?.started)) ||
13397
+ subscribersFor(rightKey, true) - subscribersFor(leftKey, true) ||
13398
+ subscribersFor(rightKey) - subscribersFor(leftKey) ||
13399
+ Number(Boolean(right.claim)) - Number(Boolean(left.claim)) ||
13400
+ (right.snapshot?.lastSuccessAt ?? 0) - (left.snapshot?.lastSuccessAt ?? 0) ||
13401
+ right.generation - left.generation ||
13402
+ Number(rightKey === canonicalKey) - Number(leftKey === canonicalKey));
13403
+ const [queryKey, existingMatch] = matching[0] ?? [canonicalKey, null];
13404
+ const query = existingMatch?.query ?? canonicalQuery;
13133
13405
  if (!query) return { ok: false, reason: "invalid" };
13134
13406
  if (Object.keys(state.subscriptions).length >= ACQUISITION_MAX_SUBSCRIPTIONS) {
13135
13407
  return { ok: false, reason: "capacity" };
@@ -13169,7 +13441,8 @@ function createAcquisitionEngine({
13169
13441
  };
13170
13442
  state.queries[queryKey].lastUsedAt = now();
13171
13443
  if (existingQuery?.snapshot) acquisitionMetricState(state).cacheHits += 1;
13172
- return { value: { id, queryKey, snapshot: state.queries[queryKey].snapshot } };
13444
+ return { value: { id, queryKey, snapshot: state.queries[queryKey].snapshot,
13445
+ aliasConflict: matching.length > 1 } };
13173
13446
  });
13174
13447
  if (!result.ok) return result;
13175
13448
  const record = result.state.queries[result.value.queryKey];
@@ -13209,6 +13482,11 @@ function createAcquisitionEngine({
13209
13482
  record.claim.generation !== claim.generation || record.query.accessKey !== claim.accessKey) {
13210
13483
  return { value: { ok: false, reason: "stale", definitive: true } };
13211
13484
  }
13485
+ if (now() >= acquisitionClaimDeadline(record.claim)) {
13486
+ record.claim = null;
13487
+ settleAcquisitionWaiters(state, item.queryKey, claim.generation, now());
13488
+ return { value: { ok: false, reason: "stale", definitive: true } };
13489
+ }
13212
13490
  let remapped = {};
13213
13491
  let effectiveClaim = claim;
13214
13492
  if (produced?.repositoryIdentity !== undefined) {
@@ -13308,7 +13586,10 @@ function createAcquisitionEngine({
13308
13586
 
13309
13587
  function cancel(id, claim) {
13310
13588
  const item = local.get(id);
13311
- if (!item || !isRecord(claim)) return { ok: false, reason: "invalid" };
13589
+ if (!isRecord(claim)) return { ok: false, reason: "invalid" };
13590
+ // Successful unsubscribe may already have removed this local item and its
13591
+ // shared claim while a queued cross-store cleanup was waiting to retry.
13592
+ if (!item) return { ok: false, reason: "stale" };
13312
13593
  const result = transact((state) => {
13313
13594
  const record = state.queries[item.queryKey];
13314
13595
  if (!record || record.claim?.nonce !== claim.nonce || record.claim.generation !== claim.generation) {
@@ -13396,13 +13677,22 @@ function createAcquisitionEngine({
13396
13677
  if (!record || record.claim?.nonce !== claim.nonce || record.claim.generation !== claim.generation) {
13397
13678
  return { ok: false, reason: "stale" };
13398
13679
  }
13399
- if (record.claim.started) return { changed: false, value: true };
13400
13680
  const costs = tabRequestCost(record.query.resource);
13401
13681
  const supplied = claim.receipt;
13402
13682
  if (supplied !== undefined && (!isRecord(supplied) || supplied.accessKey !== record.query.accessKey ||
13403
13683
  typeof supplied.reservationId !== "string" || supplied.reservationId.length < 1 ||
13404
13684
  !isRecord(supplied.epochs))) return { ok: false, reason: "invalid" };
13405
13685
  const reservationId = supplied?.reservationId ?? `claim:${record.claim.nonce}`;
13686
+ const startReceipt = { reservationId, accessKey: record.query.accessKey,
13687
+ epochs: Object.fromEntries(RATE_RESOURCES.filter((resource) => costs[resource] > 0)
13688
+ .map((resource) => [resource, typeof supplied?.epochs?.[resource] === "string" &&
13689
+ supplied.epochs[resource].length > 0 ? supplied.epochs[resource] : "unknown"])) };
13690
+ if (record.claim.started) return JSON.stringify(record.claim.startReceipt) === JSON.stringify(startReceipt)
13691
+ ? { changed: false, value: { status: "already-started", reservationId } }
13692
+ : { ok: false, reason: "receipt-mismatch" };
13693
+ if (now() >= acquisitionClaimDeadline(record.claim)) {
13694
+ return { ok: false, reason: "stale" };
13695
+ }
13406
13696
  const receiptIds = [];
13407
13697
  for (const resource of RATE_RESOURCES) {
13408
13698
  if (costs[resource] <= 0) continue;
@@ -13421,8 +13711,11 @@ function createAcquisitionEngine({
13421
13711
  receiptIds.push(receipt.id);
13422
13712
  }
13423
13713
  record.claim.started = true;
13714
+ record.claim.startedAt = now();
13715
+ record.claim.reservationId = reservationId;
13716
+ record.claim.startReceipt = startReceipt;
13424
13717
  record.claim.receiptIds = receiptIds;
13425
- return { value: true };
13718
+ return { value: { status: "started", reservationId } };
13426
13719
  });
13427
13720
  }
13428
13721
 
@@ -13434,9 +13727,25 @@ function createAcquisitionEngine({
13434
13727
  return cancel(id, pending.claim);
13435
13728
  }
13436
13729
 
13730
+ function currentClaim(id, claim) {
13731
+ const item = local.get(id);
13732
+ if (!item || !isRecord(claim)) return { ok: false, reason: "stale" };
13733
+ const loaded = load();
13734
+ if (!loaded.ok) return loaded;
13735
+ const persisted = loaded.value.queries[item.queryKey]?.claim;
13736
+ return persisted?.nonce === claim.nonce && persisted.generation === claim.generation &&
13737
+ persisted.started && persisted.reservationId ===
13738
+ (claim.receipt?.reservationId ?? `claim:${claim.nonce}`) &&
13739
+ now() < acquisitionClaimDeadline(persisted)
13740
+ ? { ok: true } : { ok: false, reason: "stale" };
13741
+ }
13742
+
13437
13743
  async function refresh(id, { force = false, acquire = null, claim = null, publish: publication,
13438
13744
  cancel: cancellation = null, failure = null, started = null } = {}) {
13439
13745
  const item = local.get(id);
13746
+ if (!item && cancellation !== null && isRecord(cancellation)) {
13747
+ return { ok: false, reason: "stale" };
13748
+ }
13440
13749
  if (!item || acquire !== null && typeof acquire !== "function") return { ok: false, reason: "invalid" };
13441
13750
  // A retired scope cannot start new work, but its already-started producer
13442
13751
  // must still be able to publish for remaining consumers or cancel its
@@ -13471,9 +13780,10 @@ function createAcquisitionEngine({
13471
13780
  sharingCount: acquisitionSharingCount(state, item.queryKey) } };
13472
13781
  }
13473
13782
  if (record.claim) {
13783
+ const absoluteDeadline = acquisitionClaimDeadline(record.claim);
13474
13784
  const expired = record.claim.leaseUntil <= now();
13475
13785
  const owner = acquisitionOwnerStatus(record.claim.pid, kill);
13476
- if (!expired || owner !== "dead") {
13786
+ if (now() < absoluteDeadline && (!expired || owner !== "dead")) {
13477
13787
  const joined = subscription.waitingGeneration !== record.claim.generation;
13478
13788
  if (joined) {
13479
13789
  subscription.waitingGeneration = record.claim.generation;
@@ -13633,6 +13943,7 @@ function createAcquisitionEngine({
13633
13943
  subscribe,
13634
13944
  updateDemand,
13635
13945
  refresh,
13946
+ currentClaim,
13636
13947
  unsubscribe,
13637
13948
  inspect,
13638
13949
  diagnostics,
@@ -13845,6 +14156,32 @@ function retryPollAfterAdmissionFailure({
13845
14156
  };
13846
14157
  }
13847
14158
 
14159
+ function retryFailedPollBatch({ due, outcomes, dueAt, backgroundIndex,
14160
+ previousBackgroundIndex, retryAt }) {
14161
+ let next = { dueAt, backgroundIndex };
14162
+ let retryNeeded = false;
14163
+ for (const [index, poll] of due.entries()) {
14164
+ const outcome = outcomes?.[index];
14165
+ if (outcome?.status !== "rejected" &&
14166
+ !(outcome?.status === "fulfilled" && outcome.value?.retry === true) &&
14167
+ outcomes !== null) continue;
14168
+ next = retryPollAfterAdmissionFailure({
14169
+ ...poll, retryAt, ...next, previousBackgroundIndex,
14170
+ });
14171
+ retryNeeded = true;
14172
+ }
14173
+ return { ...next, retryNeeded };
14174
+ }
14175
+
14176
+ function sharedAcquisitionPollDeadline(localDueAt, sourceDueAt, nowMs, active) {
14177
+ if (!active || !Number.isFinite(sourceDueAt)) return localDueAt;
14178
+ return Math.min(localDueAt, sourceDueAt > nowMs ? sourceDueAt : nowMs + 1_000);
14179
+ }
14180
+
14181
+ function followerNeedsClaimRecheck(reason) {
14182
+ return reason !== "fresh";
14183
+ }
14184
+
13848
14185
  function governorWakeTimes(state, nowMs, floorMs, leaseId = null) {
13849
14186
  const budgets = Object.values(state?.budgets ?? {});
13850
14187
  // A failed observer's own retry, not its stale sample's cadence, says when
@@ -13867,17 +14204,21 @@ function governorWakeTimes(state, nowMs, floorMs, leaseId = null) {
13867
14204
  (leaseId == null || reservation.leaseId === leaseId))
13868
14205
  .reduce((earliest, reservation) => Math.min(earliest, reservation.notBefore), Number.POSITIVE_INFINITY);
13869
14206
  return {
13870
- controlAt: Number.isFinite(controlAt) ? Math.max(nowMs + 1, controlAt) : nowMs + floorMs,
13871
- dataAt: Number.isFinite(reservationAt) ? Math.max(nowMs + 1, reservationAt) : Number.POSITIVE_INFINITY,
14207
+ controlAt: Number.isFinite(controlAt)
14208
+ ? (controlAt <= nowMs ? nowMs + 1000 : controlAt)
14209
+ : nowMs + floorMs,
14210
+ dataAt: Number.isFinite(reservationAt)
14211
+ ? (reservationAt <= nowMs ? nowMs + 1000 : reservationAt)
14212
+ : Number.POSITIVE_INFINITY,
13872
14213
  };
13873
14214
  }
13874
14215
 
13875
- function governorProtocolReady(refreshResult, snapshot, nowMs) {
14216
+ function governorProtocolReady(refreshResult, snapshot, nowMs, resources = RATE_RESOURCES) {
13876
14217
  if (!refreshResult?.ok || !snapshot?.ok) return false;
13877
14218
  if (["waiting", "paused", "probe"].includes(refreshResult.value?.status)) return false;
13878
- if (RATE_RESOURCES.some((resource) => snapshot.value.probeClaims[resource]) ||
13879
- RATE_RESOURCES.some((resource) => snapshot.value.observers[resource]?.outcome !== "healthy")) return false;
13880
- return RATE_RESOURCES.every((resource) => {
14219
+ if (resources.some((resource) => snapshot.value.probeClaims[resource]) ||
14220
+ resources.some((resource) => snapshot.value.observers[resource]?.outcome !== "healthy")) return false;
14221
+ return resources.every((resource) => {
13881
14222
  const budget = snapshot.value.budgets[resource];
13882
14223
  return budget && nowMs - budget.observedAt <= budgetSnapshotTtl(resource);
13883
14224
  });
@@ -13901,17 +14242,27 @@ function tabEpochChanged(previous, next, key) {
13901
14242
  }
13902
14243
 
13903
14244
  function governorDataReady(refreshResult, snapshot, activeKey, nowMs) {
13904
- if (!governorProtocolReady(refreshResult, snapshot, nowMs)) return false;
13905
14245
  const costs = tabRequestCost(activeKey);
14246
+ if (!costs) return false;
14247
+ if (!governorProtocolReady(refreshResult, snapshot, nowMs,
14248
+ RATE_RESOURCES.filter((resource) => costs[resource] > 0))) return false;
13906
14249
  return RATE_RESOURCES.every((resource) => {
13907
14250
  if (costs[resource] <= 0) return true;
13908
14251
  const budget = snapshot.value.budgets[resource];
14252
+ const available = availableForGrant({ budget, resource, nowMs });
13909
14253
  return budget && nowMs - budget.observedAt <= budgetSnapshotTtl(resource) &&
13910
- budget.blockUntil <= nowMs &&
13911
- availableForGrant({ budget, resource, nowMs }).mode === "open";
14254
+ budget.blockUntil <= nowMs && available.mode === "open" &&
14255
+ available.spendable >= costs[resource];
13912
14256
  });
13913
14257
  }
13914
14258
 
14259
+ function governorTabControlReady(snapshot, key, nowMs) {
14260
+ const costs = tabRequestCost(key);
14261
+ return Boolean(costs) && governorProtocolReady(
14262
+ { ok: true, value: { status: "published" } }, snapshot, nowMs,
14263
+ RATE_RESOURCES.filter((resource) => costs[resource] > 0));
14264
+ }
14265
+
13915
14266
  function governorControlRetryAt(nowMs, floorMs) {
13916
14267
  return nowMs + Math.min(floorMs, 1000);
13917
14268
  }
@@ -13952,6 +14303,26 @@ function createWakeScheduler({
13952
14303
  };
13953
14304
  }
13954
14305
 
14306
+ function createRecurringWake({ scheduler, now = Date.now, intervalMs, run,
14307
+ onError = () => {}, kind = "liveness" }) {
14308
+ const interval = Math.max(1000, intervalMs);
14309
+ let stopped = false;
14310
+ async function tick() {
14311
+ if (stopped) return;
14312
+ try {
14313
+ await run();
14314
+ } catch (error) {
14315
+ try { onError(error); } catch { /* Keep the liveness timer alive. */ }
14316
+ } finally {
14317
+ if (!stopped) scheduler.arm(kind, now() + interval, tick);
14318
+ }
14319
+ }
14320
+ return {
14321
+ start() { stopped = false; scheduler.arm(kind, now() + interval, tick); },
14322
+ stop() { stopped = true; scheduler.clear(kind); },
14323
+ };
14324
+ }
14325
+
13955
14326
  function createSingleFlightWake(run, onSettled = () => {}) {
13956
14327
  let running = false;
13957
14328
  let pendingArgs = null;
@@ -13977,7 +14348,7 @@ function createSingleFlightWake(run, onSettled = () => {}) {
13977
14348
  }
13978
14349
 
13979
14350
  function pendingFailureIsTerminal(reason) {
13980
- return reason === "stale";
14351
+ return ["stale", "already-started", "receipt-mismatch"].includes(reason);
13981
14352
  }
13982
14353
 
13983
14354
  function cancelCoordinatedPending(item, { cancelGovernor, cancelAcquisition: cancelShared } = {}) {
@@ -13987,6 +14358,27 @@ function cancelCoordinatedPending(item, { cancelGovernor, cancelAcquisition: can
13987
14358
  return true;
13988
14359
  }
13989
14360
 
14361
+ function coalescedAcquisitionIntentMatches(existing, decision, acquisition, scope) {
14362
+ return existing?.intentId === decision?.intentId &&
14363
+ existing.acquisition?.claim.nonce === acquisition?.claim.nonce &&
14364
+ existing.acquisition?.claim.generation === acquisition?.claim.generation &&
14365
+ existing.acquisition?.claim.accessKey === acquisition?.claim.accessKey &&
14366
+ existing.cleanupScope?.accessKey === scope?.accessKey &&
14367
+ existing.cleanupScope?.hash === scope?.hash &&
14368
+ existing.cleanupPending !== true;
14369
+ }
14370
+
14371
+ function frozenGovernorScope(scope) {
14372
+ return scope ? { ...scope, identityProvider: null } : null;
14373
+ }
14374
+
14375
+ function pendingAcquisitionMatches(item, subscription, queryKey, accessKey) {
14376
+ return item?.acquisition?.id === subscription?.id &&
14377
+ item.acquisition.queryKey === queryKey &&
14378
+ (subscription.requestedKey ?? subscription.queryKey) === queryKey &&
14379
+ item.acquisition.claim.accessKey === accessKey;
14380
+ }
14381
+
13990
14382
  function runtimeIntentGate(liveScheduling, { force = false, protocolReady = false } = {}) {
13991
14383
  return liveScheduling || protocolReady
13992
14384
  ? { registerIntent: true, requestProbe: false }
@@ -14064,6 +14456,16 @@ function coordinationNotice(reason) {
14064
14456
  return "Can't coordinate API use — retrying";
14065
14457
  }
14066
14458
 
14459
+ function acquisitionFailureCopy(reason) {
14460
+ if (reason === "busy") return { short: "lock busy", narrow: "Shared lock busy", full: "Acquisition store is busy; checking the shared lock" };
14461
+ if (reason === "unwritable") return { short: "store blocked", narrow: "Store blocked", full: "Acquisition store cannot be written; check config permissions" };
14462
+ if (reason === "corrupt") return { short: "store corrupt", narrow: "Store corrupt", full: "Acquisition metadata is corrupt; run gh-glance --doctor" };
14463
+ if (reason === "already-started" || reason === "receipt-mismatch" || reason === "stale") {
14464
+ return { short: "claim changed", narrow: "Claim changed", full: "Shared acquisition claim changed; retrying" };
14465
+ }
14466
+ return { short: "coordination", narrow: "Acquisition paused", full: "Shared acquisition is unavailable; run gh-glance --doctor" };
14467
+ }
14468
+
14067
14469
  function retryRateLimitBlockPublication(pending, nowMs, publish) {
14068
14470
  const blocks = Array.isArray(pending?.blocks) ? pending.blocks : [];
14069
14471
  if (blocks.length === 0 || blocks.some((block) =>
@@ -14136,7 +14538,6 @@ function rateLimitBlockProbeRecovered(pending, state, nowMs) {
14136
14538
  // single time and the feature never runs at all. A named `notBefore` is a
14137
14539
  // schedule, not a refusal; anything past this bound still declines, and
14138
14540
  // startReservation revalidates the budget when the wait is over.
14139
- const GOVERNOR_ADMISSION_WAIT_MS = 2_000;
14140
14541
 
14141
14542
  function measuredNestedOperationCost(operation, value) {
14142
14543
  const declared = operationCost(operation);
@@ -14208,7 +14609,56 @@ async function awaitCollectorReservation({
14208
14609
  start = startReservation,
14209
14610
  cancel = cancelIntent,
14210
14611
  deferredRetryAt = null,
14612
+ retainUntil = null,
14613
+ onRetainedWait = null,
14614
+ inspect = readIntentDecision,
14211
14615
  }) {
14616
+ if (Number.isFinite(retainUntil)) {
14617
+ let admitted = admit(scope, leaseId, operation, priority, now());
14618
+ const intentId = admitted.value?.intentId ?? admitted.value?.reservationId?.slice("reservation:".length);
14619
+ const reservationId = admitted.value?.reservationId ??
14620
+ (validGovernorId(intentId) ? `reservation:${intentId}` : null);
14621
+ let hasReservation = typeof admitted.value?.reservationId === "string";
14622
+ let retryAt = admitted.value?.notBefore ?? admitted.value?.retryAt ?? deferredRetryAt;
14623
+ let started = false;
14624
+ let waitFailure = null;
14625
+ try {
14626
+ while (admitted.ok && !signal?.aborted && now() < retainUntil) {
14627
+ if (admitted.value?.status === "started") {
14628
+ started = true;
14629
+ return admitted.value;
14630
+ }
14631
+ const status = admitted.value?.status;
14632
+ if (!["scheduled", "waiting", "pending", "paused", "probe"].includes(status)) break;
14633
+ const slot = admitted.value?.notBefore ?? admitted.value?.retryAt ??
14634
+ (Number.isFinite(retryAt) ? retryAt : now() + 1_000);
14635
+ retryAt = slot;
14636
+ const untilSlot = slot - now();
14637
+ // An earlier measured 304 can return paced capacity and pull this
14638
+ // reservation forward while we sleep. Active work rechecks once per
14639
+ // second so it can use that safe slot before its old deadline.
14640
+ const recheckMs = priority === "background" ? 10_000 : 1_000;
14641
+ const delay = Math.max(1, Math.min(recheckMs, retainUntil - now(),
14642
+ untilSlot > 0 ? untilSlot : 1_000));
14643
+ if (!(await wait(delay, signal)) || signal?.aborted) break;
14644
+ if (onRetainedWait && await onRetainedWait() !== true) break;
14645
+ admitted = hasReservation
14646
+ ? start(scope, reservationId, now())
14647
+ : inspect(scope, intentId, now());
14648
+ if (typeof admitted.value?.reservationId === "string") hasReservation = true;
14649
+ }
14650
+ } catch (error) {
14651
+ waitFailure = error;
14652
+ } finally {
14653
+ if (!started && validGovernorId(intentId)) cancel(scope, intentId, now());
14654
+ }
14655
+ const error = new Error(signal?.aborted ? "collector admission aborted" : "collector admission deferred");
14656
+ if (signal?.aborted) error.name = "AbortError";
14657
+ if (waitFailure) error.cause = waitFailure;
14658
+ error.notStarted = true;
14659
+ error.retryAt = Number.isFinite(retryAt) ? retryAt : now() + 1_000;
14660
+ throw error;
14661
+ }
14212
14662
  const attempt = await resolveGovernorAdmission({
14213
14663
  scope, leaseId, operation, priority, signal, waitMs, now, wait,
14214
14664
  admit, start, cancel, requireElapsed: true,
@@ -14238,6 +14688,21 @@ function collectorObserverRetryAt(scope, resource, at) {
14238
14688
  return deadlines.length > 0 ? Math.max(...deadlines) : null;
14239
14689
  }
14240
14690
 
14691
+ function collectorBudgetRefreshAllowsAdmission(refreshed, scope, resource, at, inspect = inspectGovernor) {
14692
+ return refreshed?.ok === true || governorTabControlReady(inspect(scope, at), resource, at);
14693
+ }
14694
+
14695
+ async function refreshCollectorRequiredBudget(scope, leaseId, signal, tab, options = {},
14696
+ refresh = refreshResourceBudget) {
14697
+ const cost = tabRequestCost(tab);
14698
+ if (!cost) return { ok: false, reason: "invalid" };
14699
+ const required = RATE_RESOURCES.filter((resource) => cost[resource] > 0);
14700
+ const outcomes = await Promise.all(required.map((resource) =>
14701
+ refresh(scope, leaseId, signal, resource, options)));
14702
+ return outcomes.find((result) => !result.ok) ??
14703
+ { ok: true, value: { status: "published", resources: required } };
14704
+ }
14705
+
14241
14706
  async function resolveGovernorAdmission({
14242
14707
  scope,
14243
14708
  leaseId,
@@ -14570,12 +15035,17 @@ function refreshStatus({
14570
15035
  : null
14571
15036
  );
14572
15037
  if (mode === "paused" || activeError?.verdict === "rate-limited") {
14573
- return status("paused", "paused", "Paused", "attention", false, detailKind);
15038
+ return status("paused", "paused", "Paused", "attention", false,
15039
+ governorDecision?.cause ? "cause" : detailKind);
14574
15040
  }
14575
15041
  if (disconnected) {
14576
15042
  return status("disconnected", "failed", "Disconnected", "attention", false, stale ? "stale" : null);
14577
15043
  }
14578
15044
  if (stale) return status("stale", "limited", "Stale", "attention", false, "stale");
15045
+ // A scheduled retry cannot make a failed or incomplete observation healthy.
15046
+ // Keep the cause visible until a successful result clears it.
15047
+ if (activeError) return status("failed", "failed", "Failed", "attention");
15048
+ if (securityIncomplete) return status("limited", "limited", "Limited", "attention");
14579
15049
  if (sharedData) return status("shared", "watching", "Shared", "inert", false, "sharing");
14580
15050
  if (["waiting", "pending", "probe"].includes(mode)) {
14581
15051
  const sharing = sharedLaneProvenance(governorDecision).waitCause === "shared-lane";
@@ -14584,8 +15054,6 @@ function refreshStatus({
14584
15054
  : Number.isFinite(governorDecision?.notBefore) ? "next" : detailKind;
14585
15055
  return status("watching", "watching", "Watching", "inert", false, watchingDetail);
14586
15056
  }
14587
- if (activeError) return status("failed", "failed", "Failed", "attention");
14588
- if (securityIncomplete) return status("limited", "limited", "Limited", "attention");
14589
15057
  return status("watching", "watching", "Watching", "inert");
14590
15058
  }
14591
15059
 
@@ -14599,6 +15067,7 @@ function statusInterval(at, nowMs = Date.now()) {
14599
15067
 
14600
15068
  function statusDetailVariants(status, detail, nowMs) {
14601
15069
  if (!status?.detailKind) return [];
15070
+ if (status.detailKind === "cause") return detail?.cause ? [detail.cause] : [];
14602
15071
  if (status.detailKind === "probing") return ["probing"];
14603
15072
  if (status.detailKind === "sharing") {
14604
15073
  const sharing = sharedLaneProvenance(detail);
@@ -14626,8 +15095,10 @@ function statusBarLayout({
14626
15095
  const hints = Array.isArray(availableHints) ? availableHints : [];
14627
15096
  const mandatory = hints.filter(isMandatoryHint);
14628
15097
  const optional = hints.filter((hint) => !isMandatoryHint(hint));
14629
- const stateWidth = width >= WIDE_STATE_MIN_COLS
14630
- ? Math.min(width, WIDE_STATE_WIDTH)
15098
+ const stateWidth = width >= 70 && typeof detail?.cause === "string"
15099
+ ? Math.min(width, 32)
15100
+ : width >= WIDE_STATE_MIN_COLS
15101
+ ? Math.min(width, WIDE_STATE_WIDTH)
14631
15102
  : Math.min(width, REFRESH_STATUS_WIDTH);
14632
15103
  let used = stateWidth;
14633
15104
  const selectedHints = [];
@@ -14661,7 +15132,10 @@ function statusBarLayout({
14661
15132
  ? stateWidth - REFRESH_STATUS_WIDTH - 1
14662
15133
  : 0;
14663
15134
  const staleText = typeof stale === "string" ? stale : null;
14664
- const selectedStale = staleText && [...staleText].length <= payloadWidth ? staleText : null;
15135
+ const combinedStale = staleText && typeof detail?.cause === "string"
15136
+ ? `${staleText} ${detail.cause}` : null;
15137
+ const selectedStale = [combinedStale, staleText]
15138
+ .find((candidate) => candidate && [...candidate].length <= payloadWidth) ?? null;
14665
15139
  let selectedDetail = null;
14666
15140
  if (!selectedStale) {
14667
15141
  selectedDetail = statusDetailVariants(status, detail, nowMs)
@@ -15060,6 +15534,7 @@ function App({ onCreateRemote = () => {} } = {}) {
15060
15534
  const [loading, setLoading] = useState({ actions: false, issues: false, prs: false, security: false });
15061
15535
  const [waiting, setWaiting] = useState({ actions: true, issues: true, prs: true, security: true });
15062
15536
  const [governorDecisions, setGovernorDecisions] = useState(probingGovernorDecisions);
15537
+ const [acquisitionFailures, setAcquisitionFailures] = useState({});
15063
15538
  const [governorEpochs, setGovernorEpochs] = useState(null);
15064
15539
  const [requestStatuses, setRequestStatuses] = useState(() =>
15065
15540
  Object.fromEntries(TABS.map((candidate) => [candidate.key, null])),
@@ -15078,7 +15553,13 @@ function App({ onCreateRemote = () => {} } = {}) {
15078
15553
 
15079
15554
  const tab = TABS[activeIndex];
15080
15555
  const tabError = errors[tab.key];
15081
- const activeGovernorDecision = governorDecisions[tab.key];
15556
+ const activeFailure = acquisitionFailures[tab.key] ?? null;
15557
+ const activeGovernorDecision = activeFailure &&
15558
+ !(governorDecisions[tab.key]?.mode === "paused" &&
15559
+ governorDecisions[tab.key]?.coordinationError !== true)
15560
+ ? { mode: "paused", reason: activeFailure, coordinationError: true,
15561
+ cause: acquisitionFailureCopy(activeFailure).short }
15562
+ : governorDecisions[tab.key];
15082
15563
  const activeRequestStatus = requestStatuses[tab.key];
15083
15564
  // Once any endpoint proves the folder has no remote, the whole dashboard is
15084
15565
  // in setup mode. Keeping this tab-local made a quick tab switch replace the
@@ -15654,7 +16135,9 @@ function App({ onCreateRemote = () => {} } = {}) {
15654
16135
  return null;
15655
16136
  }
15656
16137
 
15657
- function pauseCoordination(key, reason, diagnosticHold = null) {
16138
+ function pauseCoordination(key, reason, diagnosticHold = null, acquisitionFailure = false) {
16139
+ if (acquisitionFailure) setAcquisitionFailures((current) => current[key] === reason
16140
+ ? current : { ...current, [key]: reason ?? "unavailable" });
15658
16141
  const hold = diagnosticHold ?? (
15659
16142
  /secondary|abuse/.test(reason ?? "")
15660
16143
  ? "secondary"
@@ -15691,20 +16174,22 @@ function App({ onCreateRemote = () => {} } = {}) {
15691
16174
  }
15692
16175
 
15693
16176
  function publishControlStatus(key, refreshed, snapshot, nowMs) {
15694
- if (pendingBlockPublications.size > 0) {
16177
+ if (RATE_RESOURCES.some((resource) => tabRequestCost(key)[resource] > 0 &&
16178
+ pendingBlockPublications.has(resource))) {
15695
16179
  pauseCoordination(key, "block-unpublished");
15696
16180
  return;
15697
16181
  }
15698
- if (!refreshed?.ok) {
15699
- pauseCoordination(key, refreshed?.reason, "observer");
15700
- return;
15701
- }
15702
16182
  if (!snapshot?.ok) {
15703
16183
  pauseCoordination(key, snapshot?.reason);
15704
16184
  return;
15705
16185
  }
16186
+ const tabReady = governorTabControlReady(snapshot, key, nowMs);
16187
+ if (!refreshed?.ok && !tabReady) {
16188
+ pauseCoordination(key, refreshed?.reason, "observer");
16189
+ return;
16190
+ }
15706
16191
  publishGovernorEpochs(snapshot);
15707
- if (refreshed.value?.status === "waiting") {
16192
+ if (refreshed.value?.status === "waiting" && !tabReady) {
15708
16193
  setTabGovernorDecision(key, { mode: "waiting", probing: true });
15709
16194
  return;
15710
16195
  }
@@ -15829,7 +16314,11 @@ function App({ onCreateRemote = () => {} } = {}) {
15829
16314
  [key]: { automaticStatusVisible },
15830
16315
  }));
15831
16316
  }
15832
- return requestIdentityStorage.run(scope, run)
16317
+ const fencedRun = acquisition
16318
+ ? () => acquisitionFenceStorage.run(
16319
+ () => acquisitionEngine.currentClaim(acquisition.id, acquisition.claim), run)
16320
+ : run;
16321
+ return requestIdentityStorage.run(scope, fencedRun)
15833
16322
  .then(async (result) => {
15834
16323
  settleReservationWithBudgetObservations(settlementScope, leaseId, reservationId, result?.measuredSuccess === false
15835
16324
  ? { outcome: "rejected", observations: result?.observations ?? [] }
@@ -15893,55 +16382,6 @@ function App({ onCreateRemote = () => {} } = {}) {
15893
16382
  limit,
15894
16383
  completedAt,
15895
16384
  });
15896
- // The cadence follows the observation, not the tab: an error or an
15897
- // unusable payload leaves the counter where it was, so a broken tab
15898
- // is never slowed down for looking quiet.
15899
- unchangedPolls[key] = advanceUnchangedCount(unchangedPolls[key], transition.kind);
15900
- // Recomputed now that the outcome is known: the schedule chose this
15901
- // tab's deadline before the observation that decides its cadence.
15902
- // Shared snapshots publish a deadline from completion. Anchor the
15903
- // local wake to that same observation so waking just before the
15904
- // shared deadline cannot consume a fresh follower result and add a
15905
- // second full cadence interval.
15906
- rescheduleTab(key, completedAt, {
15907
- actionRows: key === "actions" && transition.kind === "changed" ? transition.data : undefined,
15908
- });
15909
- if (result?.catalog) workflowCatalogRef.current = result.catalog;
15910
- if (Number.isFinite(result?.loadedPages)) {
15911
- pageStateRef.current[key] = {
15912
- pages: result.loadedPages,
15913
- hasNextPage: result.hasNextPage === true,
15914
- };
15915
- }
15916
- publishStagedEntities(entityRef.current, stagedEntities, transition.kind);
15917
- if (transition.kind === "unchanged") {
15918
- lastOkRef.current[key] = completedAt;
15919
- const publication = await finishAcquisition(key, acquisition, transition, completedAt);
15920
- if (!publication.ok) pauseCoordination(key, publication.reason);
15921
- // Clear on the first success or a single failure latches the ladder.
15922
- clearBackoff(`tab:${key}`);
15923
- setErrors((x) => (x[key] === null ? x : { ...x, [key]: null }));
15924
- const cachedTab = pick(
15925
- pick(dashboardCacheRef.current, dashboardCacheTargetRef.current, null)?.tabs ?? {},
15926
- key,
15927
- null,
15928
- );
15929
- if (
15930
- cachedTab &&
15931
- shouldCheckpointFreshness({ persistedAt: cachedTab.lastOk, completedAt })
15932
- ) {
15933
- cacheSuccessfulTab(
15934
- key,
15935
- dataRef.current[key],
15936
- metaRef.current[key],
15937
- completedAt,
15938
- key === "security"
15939
- ? { notes: securityNotesRef.current, blind: securityBlindRef.current }
15940
- : {},
15941
- );
15942
- }
15943
- return;
15944
- }
15945
16385
  // Empty or truncated JSON is not a user-actionable fetch error. Keep
15946
16386
  // the last-good rows and freshness clock, and clear the raw comparison
15947
16387
  // so the next admitted tick parses its response instead of taking the
@@ -15973,6 +16413,45 @@ function App({ onCreateRemote = () => {} } = {}) {
15973
16413
  setSecurityBlind(true);
15974
16414
  return;
15975
16415
  }
16416
+ // The shared nonce/generation is the publication fence. A superseded
16417
+ // owner must not update any local freshness, rows, cadence, or cache.
16418
+ const publicationView = stagedAcquisitionPublicationView({
16419
+ entities: entityRef.current,
16420
+ stagedEntities,
16421
+ transitionKind: transition.kind,
16422
+ pageState: pageStateRef.current[key],
16423
+ loadedPages: result?.loadedPages,
16424
+ hasNextPage: result?.hasNextPage,
16425
+ unchangedCount: unchangedPolls[key],
16426
+ });
16427
+ const publication = await finishAcquisition(key, acquisition, transition, completedAt,
16428
+ publicationView);
16429
+ if (!publication.ok) {
16430
+ pauseCoordination(key, publication.reason, null, true);
16431
+ return;
16432
+ }
16433
+ unchangedPolls[key] = publicationView.unchangedCount;
16434
+ rescheduleTab(key, completedAt, {
16435
+ actionRows: key === "actions" && transition.kind === "changed" ? transition.data : undefined,
16436
+ });
16437
+ if (result?.catalog) workflowCatalogRef.current = result.catalog;
16438
+ if (Number.isFinite(result?.loadedPages)) pageStateRef.current[key] = publicationView.pageState;
16439
+ publishStagedEntities(entityRef.current, stagedEntities, transition.kind);
16440
+ if (transition.kind === "unchanged") {
16441
+ lastOkRef.current[key] = completedAt;
16442
+ clearBackoff(`tab:${key}`);
16443
+ setErrors((x) => (x[key] === null ? x : { ...x, [key]: null }));
16444
+ const cachedTab = pick(
16445
+ pick(dashboardCacheRef.current, dashboardCacheTargetRef.current, null)?.tabs ?? {},
16446
+ key,
16447
+ null,
16448
+ );
16449
+ if (cachedTab && shouldCheckpointFreshness({ persistedAt: cachedTab.lastOk, completedAt })) {
16450
+ cacheSuccessfulTab(key, dataRef.current[key], metaRef.current[key], completedAt,
16451
+ key === "security" ? { notes: securityNotesRef.current, blind: securityBlindRef.current } : {});
16452
+ }
16453
+ return;
16454
+ }
15976
16455
  const tabData = transition.data;
15977
16456
  const tabMeta = transition.meta;
15978
16457
  lastOkRef.current[key] = completedAt;
@@ -15985,8 +16464,6 @@ function App({ onCreateRemote = () => {} } = {}) {
15985
16464
  if (key === "security") {
15986
16465
  setSecurityBlind((b) => (b === transition.blind ? b : transition.blind));
15987
16466
  }
15988
- const publication = await finishAcquisition(key, acquisition, transition, completedAt);
15989
- if (!publication.ok) pauseCoordination(key, publication.reason);
15990
16467
  cacheSuccessfulTab(key, tabData, tabMeta, completedAt, {
15991
16468
  notes: transition.notes,
15992
16469
  blind: transition.blind,
@@ -16063,6 +16540,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16063
16540
 
16064
16541
  const leaseId = governorId();
16065
16542
  const pending = new Map();
16543
+ const cleanupQueue = new Set();
16066
16544
  // One manual handoff per tab, carrying which key produced it. A keypress
16067
16545
  // during an automatic request must not be lost, but repeated keypresses
16068
16546
  // during the manual request itself must not create a trailing second batch.
@@ -16083,7 +16561,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16083
16561
  // back *different* still clears them. Null means nothing is retained.
16084
16562
  let retainedAccessKey = null;
16085
16563
  let remoteUrls = [];
16086
- let liveScheduling = false;
16564
+ let pollDeadlinesOpen = false;
16087
16565
  let controlEpochs = null;
16088
16566
  // One deadline per tab, not one active slot and one rotating background
16089
16567
  // slot. Two shared timers cannot express a Security tab quiet at 300s next
@@ -16091,6 +16569,9 @@ function App({ onCreateRemote = () => {} } = {}) {
16091
16569
  // of the cadence table.
16092
16570
  const NEVER_DUE = Object.fromEntries(TAB_KEYS.map((key) => [key, Number.POSITIVE_INFINITY]));
16093
16571
  let pollDueAt = { ...NEVER_DUE };
16572
+ let sharedSnapshotDueAt = { ...NEVER_DUE };
16573
+ let scheduledWakePolls = [];
16574
+ let scheduledWakeBackgroundIndex = 0;
16094
16575
  let backgroundIndex = 0;
16095
16576
  // Only the unchanged run counts live here. Whether Actions has work in
16096
16577
  // flight is read from the rows themselves, so there is no second copy of it
@@ -16101,6 +16582,23 @@ function App({ onCreateRemote = () => {} } = {}) {
16101
16582
 
16102
16583
  function adoptAcquisitionSnapshot(key, snapshot, receipt = null) {
16103
16584
  if (!snapshot || cancelled) return;
16585
+ if (runtime.connect === null && Number.isFinite(snapshot.nextDueAt)) {
16586
+ sharedSnapshotDueAt = { ...sharedSnapshotDueAt, [key]: snapshot.nextDueAt };
16587
+ if (pollDeadlinesOpen && key === TABS[activeIndexRef.current].key) {
16588
+ const at = Date.now();
16589
+ const due = sharedAcquisitionPollDeadline(pollDueAt[key], snapshot.nextDueAt, at, true);
16590
+ if (due < pollDueAt[key]) {
16591
+ pollDueAt = { ...pollDueAt, [key]: due };
16592
+ armWake("data", due, dataWake);
16593
+ }
16594
+ }
16595
+ }
16596
+ setAcquisitionFailures((current) => {
16597
+ if (!Object.hasOwn(current, key)) return current;
16598
+ const next = { ...current };
16599
+ delete next[key];
16600
+ return next;
16601
+ });
16104
16602
  const snapshotMeta = snapshot.meta ?? { at: snapshot.lastChangedAt, truncated: false };
16105
16603
  const receiptAge = Number.isFinite(receipt?.sourceAgeMs) ? Math.max(0, receipt.sourceAgeMs) : null;
16106
16604
  if (receiptAge === null) {
@@ -16273,19 +16771,19 @@ function App({ onCreateRemote = () => {} } = {}) {
16273
16771
  return { ok: true, value: subscription };
16274
16772
  }
16275
16773
 
16276
- function acquisitionPublication(key, acquisition, transition, completedAt) {
16774
+ function acquisitionPublication(key, acquisition, transition, completedAt, publicationView) {
16277
16775
  const rows = transition.kind === "changed" ? transition.data : dataRef.current[key];
16278
16776
  const meta = transition.kind === "changed" ? transition.meta : metaRef.current[key];
16279
16777
  if (!Array.isArray(rows) || !meta) return null;
16280
16778
  const prefix = `${key}\0`;
16281
- const entities = [...entityRef.current.entries()]
16779
+ const entities = [...publicationView.entities.entries()]
16282
16780
  .filter(([entityKey_]) => entityKey_.startsWith(prefix))
16283
16781
  .map(([entityKey_, entity]) => ({ key: entityKey_, etag: entity.etag, body: entity.body }));
16284
16782
  return {
16285
16783
  rows,
16286
16784
  pageInfo: {
16287
- loadedPages: pageStateRef.current[key]?.pages ?? 1,
16288
- hasNextPage: pageStateRef.current[key]?.hasNextPage === true,
16785
+ loadedPages: publicationView.pageState?.pages ?? 1,
16786
+ hasNextPage: publicationView.pageState?.hasNextPage === true,
16289
16787
  },
16290
16788
  raw: JSON.stringify(rows),
16291
16789
  entities,
@@ -16295,7 +16793,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16295
16793
  tab: key,
16296
16794
  floorMs: acquisition.demand?.floorMs ?? runtime.refreshMs,
16297
16795
  demand: acquisition.demand?.active ? "active" : "inactive",
16298
- unchangedCount: unchangedPolls[key],
16796
+ unchangedCount: publicationView.unchangedCount,
16299
16797
  inProgressCI: key === "actions" && actionsInProgress(rows),
16300
16798
  background: runtime.background,
16301
16799
  }),
@@ -16314,9 +16812,9 @@ function App({ onCreateRemote = () => {} } = {}) {
16314
16812
  };
16315
16813
  }
16316
16814
 
16317
- function finishAcquisition(key, acquisition, transition, completedAt) {
16815
+ function finishAcquisition(key, acquisition, transition, completedAt, publicationView) {
16318
16816
  if (!acquisition) return { ok: true };
16319
- const publication = acquisitionPublication(key, acquisition, transition, completedAt);
16817
+ const publication = acquisitionPublication(key, acquisition, transition, completedAt, publicationView);
16320
16818
  if (!publication) {
16321
16819
  return acquisitionEngine.refresh(acquisition.id, { cancel: acquisition.claim });
16322
16820
  }
@@ -16327,7 +16825,9 @@ function App({ onCreateRemote = () => {} } = {}) {
16327
16825
  }
16328
16826
 
16329
16827
  function cancelAcquisition(acquisition) {
16330
- if (acquisition) void acquisitionEngine.refresh(acquisition.id, { cancel: acquisition.claim });
16828
+ return acquisition
16829
+ ? acquisitionEngine.refresh(acquisition.id, { cancel: acquisition.claim })
16830
+ : Promise.resolve({ ok: true });
16331
16831
  }
16332
16832
 
16333
16833
  function failAcquisition(acquisition, failure) {
@@ -16340,26 +16840,37 @@ function App({ onCreateRemote = () => {} } = {}) {
16340
16840
  async function startPendingAcquisitionTransport(key, item, currentScope, reservationId, nowMs, signal,
16341
16841
  automaticStatusVisible) {
16342
16842
  const descriptor = tabForKey(key);
16843
+ let acquisitionStartAttempted = false;
16343
16844
  const transported = await runStartedAcquisitionTransport(
16344
16845
  () => {
16345
16846
  const admitted = inspectGovernor(currentScope, nowMs);
16346
16847
  if (!admitted.ok) return admitted;
16347
- return acquisitionEngine.refresh(item.acquisition.id, { started: {
16848
+ acquisitionStartAttempted = true;
16849
+ const receipt = {
16850
+ reservationId,
16851
+ accessKey: currentScope.accessKey,
16852
+ epochs: admitted.value.epochs,
16853
+ };
16854
+ const started = acquisitionEngine.refresh(item.acquisition.id, { started: {
16348
16855
  ...item.acquisition.claim,
16349
- receipt: {
16350
- reservationId,
16351
- accessKey: currentScope.accessKey,
16352
- epochs: admitted.value.epochs,
16353
- },
16856
+ receipt,
16354
16857
  } });
16858
+ return started.then((result) => {
16859
+ if (result.ok && result.value?.status === "started") item.acquisition.claim.receipt = receipt;
16860
+ return result;
16861
+ });
16355
16862
  },
16356
16863
  () => {
16357
16864
  replaceActivePoll(item.kind, nowMs);
16358
16865
  if (item.force) coordinator.invalidate();
16866
+ const boundedSignal = AbortSignal.any([
16867
+ signal,
16868
+ AbortSignal.timeout(ACQUISITION_STARTED_DEADLINE_MS),
16869
+ ]);
16359
16870
  return commit(
16360
16871
  key,
16361
16872
  () => descriptor.fetch({
16362
- signal,
16873
+ signal: boundedSignal,
16363
16874
  entities: entityRef.current,
16364
16875
  force: item.force,
16365
16876
  catalog: workflowCatalogRef.current,
@@ -16381,13 +16892,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16381
16892
  },
16382
16893
  );
16383
16894
  if (transported.ok) return transported;
16384
- pauseCoordination(key, transported.reason);
16385
- if (pendingFailureIsTerminal(transported.reason)) {
16386
- cancelAcquisition(item.acquisition);
16387
- finishPending(key, item.intentId);
16388
- } else {
16389
- armWake("data", governorControlRetryAt(nowMs, runtime.refreshMs), dataWake);
16390
- }
16895
+ pauseCoordination(key, transported.reason, null, acquisitionStartAttempted);
16896
+ // The governor reservation is already started. Even a transient failed
16897
+ // claim write cannot safely rebind it; retain its charge and get a fresh
16898
+ // admission for a new claim generation.
16899
+ cancelPending(key, currentScope, nowMs);
16391
16900
  return transported;
16392
16901
  }
16393
16902
 
@@ -16412,9 +16921,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16412
16921
 
16413
16922
  function rescheduleTab(key, at, options = {}) {
16414
16923
  const step = pollIntervalFor(key, TABS[activeIndexRef.current].key, options);
16924
+ const active = key === TABS[activeIndexRef.current].key;
16925
+ const localDueAt = Number.isFinite(step) ? at + step : Number.POSITIVE_INFINITY;
16415
16926
  pollDueAt = {
16416
16927
  ...pollDueAt,
16417
- [key]: Number.isFinite(step) ? at + step : Number.POSITIVE_INFINITY,
16928
+ [key]: sharedAcquisitionPollDeadline(localDueAt, sharedSnapshotDueAt[key], at, active),
16418
16929
  };
16419
16930
  }
16420
16931
 
@@ -16436,19 +16947,21 @@ function App({ onCreateRemote = () => {} } = {}) {
16436
16947
  slot += 1;
16437
16948
  }
16438
16949
  pollDueAt = opened;
16950
+ pollDeadlinesOpen = true;
16439
16951
  }
16440
16952
 
16441
16953
  function armWake(kind, at, run) {
16442
- if (!cancelled) wakeScheduler.arm(kind, at, run);
16954
+ if (cancelled) return;
16955
+ const wakeAt = ["control", "data"].includes(kind) && at <= Date.now()
16956
+ ? Date.now() + 1000
16957
+ : at;
16958
+ wakeScheduler.arm(kind, wakeAt, run);
16443
16959
  }
16444
16960
 
16445
16961
  function failClosedRateLimit(key, reason) {
16446
- liveScheduling = false;
16447
- controlEpochs = null;
16448
- pollDueAt = { ...NEVER_DUE };
16449
- wakeScheduler.clear("data");
16450
16962
  pauseCoordination(key, reason);
16451
16963
  armWake("control", governorControlRetryAt(Date.now(), runtime.refreshMs), controlWake);
16964
+ armFromState();
16452
16965
  }
16453
16966
 
16454
16967
  function attemptPendingBlockPublications(currentScope, nowMs, state = null, requestProbe = false) {
@@ -16517,7 +17030,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16517
17030
  // for a moment must not: the rows are still ours, and throwing away the
16518
17031
  // ETags with them makes the recovery round cost full price.
16519
17032
  function resetVisibleScope(nowMs, { immediate = false, discardData = true } = {}) {
17033
+ for (const item of pending.values()) {
17034
+ if (item.cleanupPending) cleanupQueue.add(item);
17035
+ }
16520
17036
  pending.clear();
17037
+ queuedManual.clear();
16521
17038
  coordinator.invalidate();
16522
17039
  setFailureContext(null);
16523
17040
  if (discardData) {
@@ -16537,10 +17054,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16537
17054
  retainedAccessKey = null;
16538
17055
  }
16539
17056
  pendingBlockPublications.clear();
16540
- liveScheduling = false;
17057
+ pollDeadlinesOpen = false;
16541
17058
  pollDueAt = { ...NEVER_DUE };
17059
+ sharedSnapshotDueAt = { ...NEVER_DUE };
16542
17060
  wakeScheduler.clear("data");
16543
- wakeScheduler.clear("heartbeat");
17061
+ heartbeatWake.stop();
16544
17062
  wakeScheduler.clear("control");
16545
17063
  setGovernorEpochs(null);
16546
17064
  setGovernorDecisions(probingGovernorDecisions());
@@ -16647,7 +17165,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16647
17165
  const wakes = governorWakeTimes(snapshot.value, nowMs, runtime.refreshMs, leaseId);
16648
17166
  armWake("control", wakes.controlAt, controlWake);
16649
17167
  const nextDataAt = Math.min(wakes.dataAt, ...Object.values(pollDueAt));
16650
- if (liveScheduling) armWake("data", nextDataAt, dataWake);
17168
+ armWake("data", nextDataAt, dataWake);
16651
17169
  }
16652
17170
 
16653
17171
  function finishPending(key, intentId) {
@@ -16655,14 +17173,71 @@ function App({ onCreateRemote = () => {} } = {}) {
16655
17173
  armFromState();
16656
17174
  }
16657
17175
 
17176
+ function finishPendingCleanup(key, item) {
17177
+ if (pending.get(key) === item) pending.delete(key);
17178
+ cleanupQueue.delete(item);
17179
+ setAcquisitionFailures((current) => {
17180
+ if (!Object.hasOwn(current, key)) return current;
17181
+ const next = { ...current };
17182
+ delete next[key];
17183
+ return next;
17184
+ });
17185
+ const handoff = queuedManual.get(key);
17186
+ if (handoff && !cancelled && !pending.has(key) &&
17187
+ identity()?.accessKey === item.cleanupScope?.accessKey) {
17188
+ queuedManual.delete(key);
17189
+ void requestTab(key, "manual", { force: handoff.force });
17190
+ } else if (handoff && identity()?.accessKey !== item.cleanupScope?.accessKey) {
17191
+ queuedManual.delete(key);
17192
+ } else if (item.retryOnCleanup && !cancelled && !pending.has(key)) {
17193
+ const current = identity();
17194
+ const query = current && acquisitionQueryForTab(key, current,
17195
+ effectiveRuntimeRepository({ remoteUrls }), pageStateRef.current[key]?.pages ?? 1);
17196
+ if (query && pendingAcquisitionMatches(item, acquisitionSubscriptions.get(key),
17197
+ acquisitionQueryKey(query), current.accessKey) &&
17198
+ Number.isFinite(pollIntervalFor(key, TABS[activeIndexRef.current].key))) {
17199
+ const retryAt = governorControlRetryAt(Date.now(), runtime.refreshMs);
17200
+ pollDueAt = { ...pollDueAt, [key]: Math.min(pollDueAt[key], retryAt) };
17201
+ armWake("data", retryAt, dataWake);
17202
+ }
17203
+ }
17204
+ }
17205
+
17206
+ function retryPendingCleanup(key, item, at = Date.now()) {
17207
+ if (item.cleanupInFlight) return false;
17208
+ item.cleanupPending = true;
17209
+ item.cleanupKey = key;
17210
+ cleanupQueue.add(item);
17211
+ if (!item.governorClean && item.intentId) {
17212
+ const removed = cancelIntent(item.cleanupScope, item.intentId, at);
17213
+ item.governorClean = removed.ok || removed.reason === "stale";
17214
+ } else if (!item.intentId) item.governorClean = true;
17215
+ if (!item.acquisitionClean) {
17216
+ item.cleanupInFlight = true;
17217
+ void cancelAcquisition(item.acquisition).then((removed) => {
17218
+ item.acquisitionClean = removed.ok || removed.reason === "stale";
17219
+ }).catch(() => {
17220
+ item.acquisitionClean = false;
17221
+ }).finally(() => {
17222
+ item.cleanupInFlight = false;
17223
+ if (item.governorClean && item.acquisitionClean) {
17224
+ finishPendingCleanup(key, item);
17225
+ } else {
17226
+ armWake("data", governorControlRetryAt(Date.now(), runtime.refreshMs), dataWake);
17227
+ }
17228
+ });
17229
+ } else if (item.governorClean) {
17230
+ finishPendingCleanup(key, item);
17231
+ }
17232
+ if (!item.governorClean) armWake("data", governorControlRetryAt(at, runtime.refreshMs), dataWake);
17233
+ return !pending.has(key);
17234
+ }
17235
+
16658
17236
  function cancelPending(key, currentScope, at = Date.now()) {
16659
17237
  const item = pending.get(key);
16660
- if (!cancelCoordinatedPending(item, {
16661
- cancelGovernor: (intentId) => cancelIntent(currentScope, intentId, at),
16662
- cancelAcquisition,
16663
- })) return false;
16664
- pending.delete(key);
16665
- return true;
17238
+ if (!item) return false;
17239
+ item.cleanupScope ??= frozenGovernorScope(currentScope);
17240
+ return retryPendingCleanup(key, item, at);
16666
17241
  }
16667
17242
 
16668
17243
  // Manual and tab-switch work replaces the automatic check that would
@@ -16681,7 +17256,8 @@ function App({ onCreateRemote = () => {} } = {}) {
16681
17256
  const manual = kind === "manual";
16682
17257
  const signal = controller.signal;
16683
17258
  const monotonicNow = performance.now();
16684
- if (pendingBlockPublications.size > 0) {
17259
+ if (RATE_RESOURCES.some((resource) => tabRequestCost(key)[resource] > 0 &&
17260
+ pendingBlockPublications.has(resource))) {
16685
17261
  pauseCoordination(key, "block-unpublished");
16686
17262
  return Promise.resolve({ persisted: false, retry: false, kind, key });
16687
17263
  }
@@ -16710,27 +17286,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16710
17286
  pauseCoordination(key, "unknown-scope");
16711
17287
  return Promise.resolve({ persisted: false, retry: true, kind, key });
16712
17288
  }
16713
- let protocolReady = liveScheduling;
16714
- let resourceReady = liveScheduling;
16715
- if (!liveScheduling || manual) {
16716
- const snapshot = inspectGovernor(currentScope, nowMs);
16717
- protocolReady = governorProtocolReady(
16718
- { ok: true, value: { status: "published" } },
16719
- snapshot,
16720
- nowMs,
16721
- );
16722
- resourceReady = governorDataReady(
16723
- { ok: true, value: { status: "published" } }, snapshot, key, nowMs);
16724
- if (!liveScheduling && resourceReady) {
16725
- liveScheduling = true;
16726
- openPollDeadlines(nowMs - 1);
16727
- armWake("heartbeat", nowMs + GOVERNOR_HEARTBEAT_MS, heartbeatWake);
16728
- }
16729
- }
16730
- const intentGate = runtimeIntentGate(liveScheduling, { force: manual, protocolReady });
16731
- if (intentGate.requestProbe || manual && !resourceReady) {
16732
- const snapshot = inspectGovernor(currentScope, nowMs);
16733
- const costs = tabRequestCost(key);
17289
+ const snapshot = inspectGovernor(currentScope, nowMs);
17290
+ const costs = tabRequestCost(key);
17291
+ const resourceReady = governorDataReady(
17292
+ { ok: true, value: { status: "published" } }, snapshot, key, nowMs);
17293
+ if (manual && !resourceReady) {
16734
17294
  for (const resource of RATE_RESOURCES) {
16735
17295
  const budget = snapshot.value?.budgets?.[resource];
16736
17296
  if (costs[resource] > 0 && budget) {
@@ -16739,21 +17299,20 @@ function App({ onCreateRemote = () => {} } = {}) {
16739
17299
  }
16740
17300
  armWake("control", nowMs + 1, controlWake);
16741
17301
  }
16742
- if (!intentGate.registerIntent) {
16743
- setTabGovernorDecision(key, { mode: "waiting", probing: true });
16744
- setTabWaiting(key, true);
16745
- return Promise.resolve({ persisted: false, retry: false, kind, key });
16746
- }
17302
+ // Durable acquisition takeover must run even while an observer is
17303
+ // unavailable. The governor fences transport at startReservation.
16747
17304
  const existing = pending.get(key);
16748
17305
  if (existing) {
16749
17306
  if (!manual || existing.kind === "manual") {
16750
17307
  return Promise.resolve({ persisted: true, retry: false, kind, key });
16751
17308
  }
17309
+ queuedManual.set(key, { force: force || queuedManual.get(key)?.force === true });
16752
17310
  cancelPending(key, currentScope, nowMs);
17311
+ if (pending.has(key)) return { persisted: true, retry: false, kind, key };
16753
17312
  }
16754
17313
  const acquisitionSubscription = ensureAcquisitionSubscription(key, identity());
16755
17314
  if (!acquisitionSubscription?.ok) {
16756
- pauseCoordination(key, acquisitionSubscription?.reason ?? "acquisition-unavailable");
17315
+ pauseCoordination(key, acquisitionSubscription?.reason ?? "acquisition-unavailable", null, true);
16757
17316
  return { persisted: false, retry: true, kind, key };
16758
17317
  }
16759
17318
  const ownership = await acquisitionEngine.refresh(acquisitionSubscription.value.id, {
@@ -16763,7 +17322,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16763
17322
  force: manual || force,
16764
17323
  });
16765
17324
  if (!ownership.ok) {
16766
- pauseCoordination(key, ownership.reason);
17325
+ pauseCoordination(key, ownership.reason, null, true);
16767
17326
  return { persisted: false, retry: true, kind, key };
16768
17327
  }
16769
17328
  setRuntimeAcquisitionHold(
@@ -16790,10 +17349,15 @@ function App({ onCreateRemote = () => {} } = {}) {
16790
17349
  }
16791
17350
  setTabWaiting(key, false);
16792
17351
  rescheduleTab(key, nowMs);
16793
- return { persisted: true, retry: false, kind, key };
17352
+ // A follower of an uncommitted claim must revisit that claim even when
17353
+ // its resource is healthy. The owner can die before this tab's normal
17354
+ // quiet/background cadence, and it has no local governor intent wake.
17355
+ return { persisted: true,
17356
+ retry: followerNeedsClaimRecheck(ownership.value.reason), kind, key };
16794
17357
  }
16795
17358
  const acquisition = {
16796
17359
  id: acquisitionSubscription.value.id,
17360
+ queryKey: acquisitionSubscription.value.requestedKey ?? acquisitionSubscription.value.queryKey,
16797
17361
  demand: ownership.value.demand,
16798
17362
  claim: {
16799
17363
  nonce: ownership.value.nonce,
@@ -16813,13 +17377,42 @@ function App({ onCreateRemote = () => {} } = {}) {
16813
17377
  };
16814
17378
  const registered = registerIntent(currentScope, request);
16815
17379
  if (!registered.ok) {
16816
- cancelAcquisition(acquisition);
17380
+ pending.set(key, { intentId: null, acquisition,
17381
+ cleanupScope: frozenGovernorScope(currentScope),
17382
+ governorClean: true, cleanupPending: true });
17383
+ cancelPending(key, currentScope, nowMs);
16817
17384
  pauseCoordination(key, registered.reason);
16818
17385
  return Promise.resolve({ persisted: false, retry: true, kind, key });
16819
17386
  }
16820
- const decision = registered.value;
17387
+ let decision = registered.value;
17388
+ if (decision.coalesced) {
17389
+ const sameClaim = pending.get(key);
17390
+ const matches = coalescedAcquisitionIntentMatches(sameClaim, decision, acquisition,
17391
+ currentScope);
17392
+ if (!matches) {
17393
+ const removed = cancelIntent(currentScope, decision.intentId, nowMs);
17394
+ if (removed.ok || removed.reason === "stale") {
17395
+ const fresh = registerIntent(currentScope, request);
17396
+ if (fresh.ok && !fresh.value.coalesced) decision = fresh.value;
17397
+ else {
17398
+ pending.set(key, { intentId: fresh.value?.intentId ?? decision.intentId, acquisition,
17399
+ cleanupScope: frozenGovernorScope(currentScope), cleanupPending: true });
17400
+ cancelPending(key, currentScope, nowMs);
17401
+ pauseCoordination(key, fresh.reason ?? "stale");
17402
+ return { persisted: false, retry: true, kind, key };
17403
+ }
17404
+ } else {
17405
+ pending.set(key, { intentId: decision.intentId, acquisition,
17406
+ cleanupScope: frozenGovernorScope(currentScope), cleanupPending: true });
17407
+ cancelPending(key, currentScope, nowMs);
17408
+ pauseCoordination(key, removed.reason);
17409
+ return { persisted: false, retry: true, kind, key };
17410
+ }
17411
+ }
17412
+ }
16821
17413
  pending.set(key, {
16822
- intentId,
17414
+ intentId: decision.intentId ?? intentId,
17415
+ cleanupScope: frozenGovernorScope(currentScope),
16823
17416
  kind,
16824
17417
  force,
16825
17418
  manual,
@@ -16844,8 +17437,9 @@ function App({ onCreateRemote = () => {} } = {}) {
16844
17437
  if (!started.ok) {
16845
17438
  pauseCoordination(key, started.reason);
16846
17439
  if (pendingFailureIsTerminal(started.reason)) {
16847
- cancelAcquisition(acquisition);
16848
- finishPending(key, intentId);
17440
+ const item = pending.get(key);
17441
+ if (item) item.retryOnCleanup = true;
17442
+ cancelPending(key, currentScope, nowMs);
16849
17443
  }
16850
17444
  else armWake("data", governorControlRetryAt(nowMs, runtime.refreshMs), dataWake);
16851
17445
  return Promise.resolve({ persisted: true, retry: false, kind, key });
@@ -16896,21 +17490,42 @@ function App({ onCreateRemote = () => {} } = {}) {
16896
17490
  };
16897
17491
 
16898
17492
  async function resumePending(nowMs) {
17493
+ for (const item of [...cleanupQueue]) retryPendingCleanup(item.cleanupKey, item, nowMs);
16899
17494
  for (const [key, item] of [...pending]) {
17495
+ if (item.cleanupPending) {
17496
+ retryPendingCleanup(key, item, nowMs);
17497
+ continue;
17498
+ }
16900
17499
  if (inFlightRef.current[key]) continue;
16901
17500
  const currentScope = ensureScope(nowMs);
16902
17501
  if (!currentScope) continue;
17502
+ if (currentScope.accessKey !== item.cleanupScope?.accessKey) {
17503
+ cancelPending(key, item.cleanupScope, nowMs);
17504
+ continue;
17505
+ }
17506
+ const expectedQuery = acquisitionQueryForTab(key, identity(),
17507
+ effectiveRuntimeRepository({ remoteUrls }), pageStateRef.current[key]?.pages ?? 1);
17508
+ const expectedKey = expectedQuery && acquisitionQueryKey(expectedQuery);
17509
+ if (!pendingAcquisitionMatches(item, acquisitionSubscriptions.get(key), expectedKey,
17510
+ currentScope.accessKey)) {
17511
+ cancelPending(key, currentScope, nowMs);
17512
+ continue;
17513
+ }
16903
17514
  const decision = readIntentDecision(currentScope, item.intentId, nowMs, item);
16904
17515
  if (!decision.ok) {
16905
17516
  pauseCoordination(key, decision.reason);
16906
17517
  if (pendingFailureIsTerminal(decision.reason)) {
16907
- cancelAcquisition(item.acquisition);
16908
- pending.delete(key);
17518
+ item.retryOnCleanup = true;
17519
+ cancelPending(key, currentScope, nowMs);
16909
17520
  }
16910
17521
  else armWake("data", governorControlRetryAt(nowMs, runtime.refreshMs), dataWake);
16911
17522
  continue;
16912
17523
  }
16913
17524
  Object.assign(item, sharedLaneEvidence(decision.value));
17525
+ if (decision.value.status === "started") {
17526
+ cancelPending(key, currentScope, nowMs);
17527
+ continue;
17528
+ }
16914
17529
  if (decision.value.status !== "scheduled" || decision.value.notBefore > nowMs) {
16915
17530
  item.wasDeferred = true;
16916
17531
  setTabGovernorDecision(key, visibleGovernorDecision({
@@ -16925,8 +17540,8 @@ function App({ onCreateRemote = () => {} } = {}) {
16925
17540
  if (!started.ok) {
16926
17541
  pauseCoordination(key, started.reason);
16927
17542
  if (pendingFailureIsTerminal(started.reason)) {
16928
- cancelAcquisition(item.acquisition);
16929
- pending.delete(key);
17543
+ item.retryOnCleanup = true;
17544
+ cancelPending(key, currentScope, nowMs);
16930
17545
  }
16931
17546
  else armWake("data", governorControlRetryAt(nowMs, runtime.refreshMs), dataWake);
16932
17547
  armFromState(nowMs);
@@ -16966,17 +17581,21 @@ function App({ onCreateRemote = () => {} } = {}) {
16966
17581
  const wakes = governorWakeTimes(snapshot.value, nowMs, runtime.refreshMs, leaseId);
16967
17582
  const heldResources = Object.fromEntries(RATE_RESOURCES.map((resource) => {
16968
17583
  const budget = snapshot.value.budgets[resource];
17584
+ const observer = snapshot.value.observers[resource];
16969
17585
  const decision = budget
16970
17586
  ? availableForGrant({ budget, resource, nowMs })
16971
17587
  : { mode: "paused" };
16972
- const held = !budget || budget.blockUntil > nowMs || decision.mode !== "open";
17588
+ const held = pendingBlockPublications.has(resource) ||
17589
+ budget?.blockUntil > nowMs ||
17590
+ decision.mode === "paused" && decision.reason === "budget-exhausted";
16973
17591
  const retryAt = budget?.blockUntil > nowMs
16974
17592
  ? budget.blockUntil
16975
- : decision.retryAt ?? wakes.controlAt;
16976
- return [resource, { held, retryAt }];
17593
+ : observer?.nextAt ?? decision.retryAt ?? wakes.controlAt;
17594
+ return [resource, { held, retryAt: Math.max(nowMs + 1000, retryAt) }];
16977
17595
  }));
16978
17596
  const active = TABS[activeIndexRef.current].key;
16979
17597
  const previousBackgroundIndex = backgroundIndex;
17598
+ scheduledWakeBackgroundIndex = previousBackgroundIndex;
16980
17599
  const planned = pollSchedule({
16981
17600
  nowMs,
16982
17601
  floorMs: runtime.refreshMs,
@@ -16990,25 +17609,18 @@ function App({ onCreateRemote = () => {} } = {}) {
16990
17609
  inProgressCI: key === "actions" && actionsInProgress(),
16991
17610
  }])),
16992
17611
  });
17612
+ scheduledWakePolls = planned.due;
16993
17613
  pollDueAt = planned.dueAt;
16994
17614
  backgroundIndex = planned.backgroundIndex;
16995
17615
  const outcomes = await Promise.allSettled(
16996
17616
  planned.due.map(({ key, kind }) => requestTab(key, kind)),
16997
17617
  );
16998
17618
  const retryAt = governorControlRetryAt(Date.now(), runtime.refreshMs);
16999
- let retryNeeded = false;
17000
- for (const outcome of outcomes) {
17001
- if (outcome.status !== "fulfilled" || outcome.value?.retry !== true) continue;
17002
- ({ dueAt: pollDueAt, backgroundIndex } = retryPollAfterAdmissionFailure({
17003
- key: outcome.value.key,
17004
- kind: outcome.value.kind,
17005
- retryAt,
17006
- dueAt: pollDueAt,
17007
- backgroundIndex,
17008
- previousBackgroundIndex,
17009
- }));
17010
- retryNeeded = true;
17011
- }
17619
+ const retried = retryFailedPollBatch({ due: planned.due, outcomes,
17620
+ dueAt: pollDueAt, backgroundIndex, previousBackgroundIndex, retryAt });
17621
+ ({ dueAt: pollDueAt, backgroundIndex } = retried);
17622
+ scheduledWakePolls = [];
17623
+ const retryNeeded = retried.retryNeeded;
17012
17624
  if (retryNeeded) armWake("data", retryAt, dataWake);
17013
17625
  if (!cancelled) {
17014
17626
  setNow((prev) =>
@@ -17022,12 +17634,39 @@ function App({ onCreateRemote = () => {} } = {}) {
17022
17634
  // pollSchedule(). Let one callback own that transition; the settled hook
17023
17635
  // retains every later useful wake. Without this guard one pane could
17024
17636
  // consume two freshly opened lane slots while another pane received none.
17025
- const dataWake = createSingleFlightWake(runDataWake, () => {
17026
- if (!cancelled) armFromState(Date.now());
17637
+ const dataWake = createSingleFlightWake(async () => {
17638
+ try {
17639
+ await runDataWake();
17640
+ } catch (error) {
17641
+ if (!cancelled) {
17642
+ const retryAt = governorControlRetryAt(Date.now(), runtime.refreshMs);
17643
+ const due = scheduledWakePolls.length > 0 ? scheduledWakePolls :
17644
+ TAB_KEYS.filter((key) => pollDueAt[key] <= Date.now())
17645
+ .map((key) => ({ key, kind: key === TABS[activeIndexRef.current].key
17646
+ ? "active" : "background" }));
17647
+ const retried = retryFailedPollBatch({ due, outcomes: null,
17648
+ dueAt: pollDueAt, backgroundIndex,
17649
+ previousBackgroundIndex: scheduledWakeBackgroundIndex,
17650
+ retryAt });
17651
+ ({ dueAt: pollDueAt, backgroundIndex } = retried);
17652
+ scheduledWakePolls = [];
17653
+ pauseCoordination(TABS[activeIndexRef.current].key, error?.reason ?? "stale");
17654
+ armWake("data", retryAt, dataWake);
17655
+ }
17656
+ }
17657
+ }, () => {
17658
+ if (cancelled) return;
17659
+ try {
17660
+ armFromState(Date.now());
17661
+ } catch (error) {
17662
+ pauseCoordination(TABS[activeIndexRef.current].key, error?.reason ?? "stale");
17663
+ armWake("data", Date.now() + 1000, dataWake);
17664
+ }
17027
17665
  });
17028
17666
 
17029
- async function controlWake() {
17667
+ async function runControlWake() {
17030
17668
  if (cancelled) return;
17669
+ for (const item of [...cleanupQueue]) retryPendingCleanup(item.cleanupKey, item);
17031
17670
  await runtimeIdentityCoordinator.refresh();
17032
17671
  if (cancelled) return;
17033
17672
  const nowMs = Date.now();
@@ -17054,27 +17693,18 @@ function App({ onCreateRemote = () => {} } = {}) {
17054
17693
  attemptPendingBlockPublications(currentScope, checkedAt, snapshot.value);
17055
17694
  }
17056
17695
  publishControlStatus(activeKey, refreshed, snapshot, checkedAt);
17057
- const controlReady = pendingBlockPublications.size === 0 && governorControlReady(
17058
- refreshed,
17059
- snapshot,
17060
- checkedAt,
17061
- );
17062
- const activeEpochChanged = controlReady && controlEpochs !== null &&
17063
- tabEpochChanged(controlEpochs, snapshot.value.epochs, activeKey);
17064
- if (controlReady) controlEpochs = { ...snapshot.value.epochs };
17065
- if (controlReady && (!liveScheduling || activeEpochChanged)) {
17066
- const starting = !liveScheduling;
17067
- liveScheduling = true;
17068
- if (starting) {
17696
+ if (currentScope && snapshot.ok) {
17697
+ if (!pollDeadlinesOpen) {
17069
17698
  openPollDeadlines(checkedAt);
17070
- armWake("heartbeat", checkedAt + GOVERNOR_HEARTBEAT_MS, heartbeatWake);
17071
- } else {
17072
- // A new accounting epoch for a resource the active tab spends: check
17073
- // it now rather than at whatever cadence it had settled into. Set
17074
- // directly rather than through rescheduleTab, whose whole job is to
17075
- // apply that cadence.
17076
- pollDueAt = { ...pollDueAt, [TABS[activeIndexRef.current].key]: checkedAt + 1 };
17699
+ heartbeatWake.start();
17700
+ } else if (controlEpochs !== null) {
17701
+ for (const key of TAB_KEYS) {
17702
+ if (tabEpochChanged(controlEpochs, snapshot.value.epochs, key)) {
17703
+ pollDueAt = { ...pollDueAt, [key]: checkedAt + 1 };
17704
+ }
17705
+ }
17077
17706
  }
17707
+ controlEpochs = { ...snapshot.value.epochs };
17078
17708
  }
17079
17709
  if (!refreshed.ok) {
17080
17710
  armWake("control", governorControlRetryAt(checkedAt, runtime.refreshMs), controlWake);
@@ -17082,20 +17712,59 @@ function App({ onCreateRemote = () => {} } = {}) {
17082
17712
  if (pendingBlockPublications.size > 0) {
17083
17713
  armWake("control", governorControlRetryAt(checkedAt, runtime.refreshMs), controlWake);
17084
17714
  }
17715
+ if (cleanupQueue.size > 0) {
17716
+ armWake("control", governorControlRetryAt(checkedAt, runtime.refreshMs), controlWake);
17717
+ }
17085
17718
  setNow((previous) => checkedAt - previous.getTime() >= 60_000 ? new Date(checkedAt) : previous);
17086
17719
  armFromState(checkedAt);
17087
17720
  }
17088
17721
 
17089
- function heartbeatWake() {
17090
- if (cancelled) return;
17091
- const nowMs = Date.now();
17092
- const currentScope = ensureScope(nowMs);
17093
- const activeTab = TABS[activeIndexRef.current].key;
17094
- if (currentScope) heartbeatLease(currentScope, leaseId, tabRequestCost(activeTab), nowMs, activeTab);
17095
- armWake("heartbeat", nowMs + GOVERNOR_HEARTBEAT_MS, heartbeatWake);
17096
- }
17722
+ const controlWake = createSingleFlightWake(async () => {
17723
+ try {
17724
+ await runControlWake();
17725
+ } catch (error) {
17726
+ if (!cancelled) pauseCoordination(TABS[activeIndexRef.current].key, error?.reason ?? "stale");
17727
+ } finally {
17728
+ if (!cancelled && !Number.isFinite(wakeScheduler.at("control"))) {
17729
+ armWake("control", governorControlRetryAt(Date.now(), runtime.refreshMs), controlWake);
17730
+ }
17731
+ }
17732
+ });
17097
17733
 
17098
- async function bootstrap() {
17734
+ const heartbeatWake = createRecurringWake({
17735
+ scheduler: wakeScheduler,
17736
+ kind: "heartbeat",
17737
+ intervalMs: GOVERNOR_HEARTBEAT_MS,
17738
+ run: () => {
17739
+ if (cancelled) return;
17740
+ const nowMs = Date.now();
17741
+ const currentScope = ensureScope(nowMs);
17742
+ const activeTab = TABS[activeIndexRef.current].key;
17743
+ if (currentScope) heartbeatLease(currentScope, leaseId, tabRequestCost(activeTab), nowMs, activeTab);
17744
+ },
17745
+ onError: (error) => {
17746
+ if (!cancelled) pauseCoordination(TABS[activeIndexRef.current].key, error?.reason ?? "stale");
17747
+ },
17748
+ });
17749
+
17750
+ const livenessWake = createRecurringWake({
17751
+ scheduler: wakeScheduler,
17752
+ intervalMs: Math.min(runtime.refreshMs, 5000),
17753
+ run: async () => {
17754
+ if (cancelled || remoteSetupRef.current) return;
17755
+ const at = Date.now();
17756
+ if (wakeScheduler.at("control") <= at ||
17757
+ !Number.isFinite(wakeScheduler.at("control"))) await controlWake();
17758
+ if (pending.size > 0 || cleanupQueue.size > 0 ||
17759
+ wakeScheduler.at("data") <= Date.now() ||
17760
+ Object.values(pollDueAt).some((due) => due <= Date.now())) await dataWake();
17761
+ },
17762
+ onError: (error) => {
17763
+ if (!cancelled) pauseCoordination(TABS[activeIndexRef.current].key, error?.reason ?? "stale");
17764
+ },
17765
+ });
17766
+
17767
+ async function runBootstrap() {
17099
17768
  remoteUrls = runtimeRemoteUrls.length > 0 ? runtimeRemoteUrls : await gitRemoteUrls();
17100
17769
  runtimeRemoteUrls = remoteUrls;
17101
17770
  if (cancelled) return;
@@ -17116,16 +17785,10 @@ function App({ onCreateRemote = () => {} } = {}) {
17116
17785
  const snapshot = inspectGovernor(currentScope, checkedAt);
17117
17786
  reconcileRuntimeUncertainty(currentScope, snapshot);
17118
17787
  publishControlStatus(activeKey, refreshed, snapshot, checkedAt);
17119
- const controlReady = governorControlReady(
17120
- refreshed,
17121
- snapshot,
17122
- checkedAt,
17123
- );
17124
- if (controlReady) {
17788
+ if (snapshot.ok) {
17125
17789
  controlEpochs = { ...snapshot.value.epochs };
17126
- liveScheduling = true;
17127
17790
  openPollDeadlines(checkedAt);
17128
- armWake("heartbeat", checkedAt + GOVERNOR_HEARTBEAT_MS, heartbeatWake);
17791
+ heartbeatWake.start();
17129
17792
  }
17130
17793
  if (!refreshed.ok) {
17131
17794
  armWake("control", governorControlRetryAt(checkedAt, runtime.refreshMs), controlWake);
@@ -17133,9 +17796,25 @@ function App({ onCreateRemote = () => {} } = {}) {
17133
17796
  armFromState(checkedAt);
17134
17797
  }
17135
17798
 
17799
+ async function bootstrap() {
17800
+ try {
17801
+ await runBootstrap();
17802
+ } catch (error) {
17803
+ if (!cancelled) pauseCoordination(TABS[activeIndexRef.current].key, error?.reason ?? "stale");
17804
+ } finally {
17805
+ if (!cancelled && !remoteSetupRef.current &&
17806
+ !Number.isFinite(wakeScheduler.at("control"))) {
17807
+ armWake("control", governorControlRetryAt(Date.now(), runtime.refreshMs), controlWake);
17808
+ }
17809
+ }
17810
+ }
17811
+
17812
+ livenessWake.start();
17136
17813
  void bootstrap();
17137
17814
  return () => {
17138
17815
  cancelled = true;
17816
+ livenessWake.stop();
17817
+ heartbeatWake.stop();
17139
17818
  queuedManual.clear();
17140
17819
  manualInFlight.clear();
17141
17820
  wakeScheduler.clearAll();
@@ -17227,7 +17906,7 @@ function App({ onCreateRemote = () => {} } = {}) {
17227
17906
  const items = data[tab.key];
17228
17907
  const displayError = formatTabErrorForWidth(tabError, failureContext, Math.max(1, cols - 5));
17229
17908
  const coordinationError = activeGovernorDecision?.coordinationError && !remoteSetup;
17230
- const coordinationReason = activeGovernorDecision?.reason ?? "unavailable";
17909
+ const coordinationReason = activeFailure ?? activeGovernorDecision?.reason ?? "unavailable";
17231
17910
  const coordinationCondition = coordinationError ? `${tab.key}\0${coordinationReason}` : null;
17232
17911
  useEffect(() => {
17233
17912
  setVisibleCoordinationCondition(null);
@@ -17241,7 +17920,8 @@ function App({ onCreateRemote = () => {} } = {}) {
17241
17920
  const showCoordinationNotice = coordinationCondition !== null &&
17242
17921
  visibleCoordinationCondition === coordinationCondition;
17243
17922
  const noticeLine = showCoordinationNotice
17244
- ? coordinationNotice(coordinationReason)
17923
+ ? activeFailure ? acquisitionFailureCopy(activeFailure)[cols < 40 ? "narrow" : "full"]
17924
+ : coordinationNotice(coordinationReason)
17245
17925
  : !remoteSetup && displayError ? displayError : "";
17246
17926
  const noticeTone = showCoordinationNotice ? ATTENTION : displayError ? ERROR_TEXT : undefined;
17247
17927
  const spin = SPINNER[frame % SPINNER.length];
@@ -17308,9 +17988,11 @@ function App({ onCreateRemote = () => {} } = {}) {
17308
17988
  });
17309
17989
  const staleLabel =
17310
17990
  staleFor != null && staleAt != null && now.getTime() > staleAt
17311
- ? `stale ${formatDuration(Math.min(staleFor, 359_999_000))}`
17991
+ ? formatDuration(staleFor)
17312
17992
  : null;
17313
- if (staleLabel && ["watching", "shared"].includes(semanticStatus.kind)) {
17993
+ if (staleLabel && (["watching", "shared"].includes(semanticStatus.kind) ||
17994
+ activeFailure && semanticStatus.kind === "paused" &&
17995
+ activeGovernorDecision?.cause)) {
17314
17996
  semanticStatus = refreshStatus({
17315
17997
  stale: true,
17316
17998
  disconnected: data[tab.key] !== null && !runtimeIdentityCoordinator?.current(),
@@ -17733,7 +18415,11 @@ function installCrashHandlers(unmountApp) {
17733
18415
 
17734
18416
  if (IS_MAIN && runtime.headlessMode) {
17735
18417
  try {
17736
- if (runtime.headlessMode.type === "serve") {
18418
+ if (runtime.headlessMode.type === "doctor") {
18419
+ // Run after all validators initialize, while retaining the reporting
18420
+ // command's non-TTY and missing-gh behavior from argument dispatch.
18421
+ console.log(await runDoctor({ probeEndpoints: runtime.headlessMode.probe }));
18422
+ } else if (runtime.headlessMode.type === "serve") {
17737
18423
  await runCollectorForeground(runtime.headlessMode.config);
17738
18424
  } else {
17739
18425
  await runCollectorStdioBridge();
@@ -17956,6 +18642,7 @@ export {
17956
18642
  GOVERNOR_MAX_LEASES,
17957
18643
  GOVERNOR_MAX_INTENTS,
17958
18644
  GOVERNOR_MAX_RESERVATIONS,
18645
+ GOVERNOR_LOCK_ORPHAN_MS,
17959
18646
  GOVERNOR_LOCK_WAIT_MS,
17960
18647
  GOVERNOR_PROBE_DRAIN_MS,
17961
18648
  GOVERNOR_ID_PATTERN,
@@ -17972,7 +18659,9 @@ export {
17972
18659
  readGovernorState,
17973
18660
  writeGovernorState,
17974
18661
  pidIsDead,
18662
+ claimGovernorLock,
17975
18663
  releaseGovernorLock,
18664
+ withFileLock,
17976
18665
  withGovernorLock,
17977
18666
  registerLease,
17978
18667
  heartbeatLease,
@@ -18051,6 +18740,7 @@ export {
18051
18740
  fetchConditionalEntity,
18052
18741
  conditionalBatchResult,
18053
18742
  publishStagedEntities,
18743
+ stagedAcquisitionPublicationView,
18054
18744
  fetchAlertSource,
18055
18745
  fetchSecurity,
18056
18746
  COLLECTOR_PROTOCOL_VERSION,
@@ -18088,6 +18778,8 @@ export {
18088
18778
  createCollectorService,
18089
18779
  createCollectorAcquisitionRuntime,
18090
18780
  collectorPublicationFromResult,
18781
+ collectorBudgetRefreshAllowsAdmission,
18782
+ refreshCollectorRequiredBudget,
18091
18783
  runCollectorForeground,
18092
18784
  runCollectorStdioBridge,
18093
18785
  createLocalCollectorClient,
@@ -18138,6 +18830,7 @@ export {
18138
18830
  shouldCheckpointFreshness,
18139
18831
  ACQUISITION_STORE_VERSION,
18140
18832
  ACQUISITION_CLAIM_TTL_MS,
18833
+ ACQUISITION_STARTED_DEADLINE_MS,
18141
18834
  ACQUISITION_HEARTBEAT_MS,
18142
18835
  ACQUISITION_MAX_BYTES,
18143
18836
  ACQUISITION_MAX_ENTITY_BYTES,
@@ -18149,6 +18842,7 @@ export {
18149
18842
  acquisitionQueryForTab,
18150
18843
  acquisitionHold,
18151
18844
  acquisitionDiagnostics,
18845
+ doctorAcquisitionLockDiagnostic,
18152
18846
  acquisitionRequestMetrics,
18153
18847
  acquisitionFailureHold,
18154
18848
  setRuntimeAcquisitionHold,
@@ -18167,15 +18861,24 @@ export {
18167
18861
  conditionalRecoveryPlan,
18168
18862
  pollSchedule,
18169
18863
  retryPollAfterAdmissionFailure,
18864
+ retryFailedPollBatch,
18865
+ sharedAcquisitionPollDeadline,
18866
+ followerNeedsClaimRecheck,
18170
18867
  governorWakeTimes,
18171
18868
  governorControlReady,
18172
18869
  tabEpochChanged,
18173
18870
  governorDataReady,
18871
+ governorTabControlReady,
18174
18872
  governorControlRetryAt,
18175
18873
  createWakeScheduler,
18874
+ createRecurringWake,
18176
18875
  createSingleFlightWake,
18177
18876
  pendingFailureIsTerminal,
18178
18877
  cancelCoordinatedPending,
18878
+ coalescedAcquisitionIntentMatches,
18879
+ frozenGovernorScope,
18880
+ pendingAcquisitionMatches,
18881
+ runStartedAcquisitionTransport,
18179
18882
  runtimeIntentGate,
18180
18883
  rateLimitBlockDecision,
18181
18884
  coordinationNotice,