gh-glance 0.15.1 → 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 +26 -1
  2. package/README.md +33 -1
  3. package/index.mjs +969 -363
  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,
@@ -4293,30 +4331,17 @@ function quarantineAbandonedGovernorLock(lockPath, expectedOwner, {
4293
4331
  }
4294
4332
  }
4295
4333
 
4296
- function withGovernorLock(scope, operation, {
4334
+ function withFileLock(lockPath, operation, {
4297
4335
  pid = process.pid,
4298
4336
  nonce = randomUUID(),
4299
4337
  waitMs = GOVERNOR_LOCK_WAIT_MS,
4300
- kill = scope?.kill ?? process.kill.bind(process),
4338
+ kill = process.kill.bind(process),
4301
4339
  observeArtifact = null,
4302
4340
  } = {}) {
4303
- if (scope?.coordinationRoot && !scope.rootLocked) {
4304
- return withIdentityRegistry(scope.coordinationRoot, (registry, at) => {
4305
- const migration = inspectLegacyMigration(scope.coordinationRoot, registry, at);
4306
- if (!migration.ok) return migration;
4307
- if (registry.identities[scope.credentialKey]) {
4308
- try { statSync(scope.path); } catch { return { ok: false, reason: "corrupt" }; }
4309
- }
4310
- return withGovernorLock({ ...scope, rootLocked: true }, operation, { pid, nonce, waitMs, kill, observeArtifact });
4311
- }, { now: scopeNow(scope), kill });
4312
- }
4313
- const current = currentGovernorScope(scope);
4314
- if (!current.ok) return current;
4315
- const lockPath = `${scope.path}.lock`;
4316
4341
  const deadline = Date.now() + waitMs;
4317
4342
  try {
4318
- mkdirSync(dirname(scope.path), { recursive: true, mode: 0o700 });
4319
- chmodSync(dirname(scope.path), 0o700);
4343
+ mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 });
4344
+ if (process.platform !== "win32") chmodSync(dirname(lockPath), 0o700);
4320
4345
  } catch {
4321
4346
  return { ok: false, reason: "unwritable" };
4322
4347
  }
@@ -4379,6 +4404,28 @@ function withGovernorLock(scope, operation, {
4379
4404
  }
4380
4405
  }
4381
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
+
4382
4429
  function mutateGovernor(scope, nowMs, mutate) {
4383
4430
  const requestedAt = scopeNow(scope, nowMs);
4384
4431
  if (!Number.isFinite(requestedAt) || requestedAt < 0) return { ok: false, reason: "corrupt" };
@@ -4438,7 +4485,34 @@ function returnPacingCredit(state, resource, unusedCost, nowMs) {
4438
4485
  const callsPerMs = decision?.callsPerMs;
4439
4486
  if (!Number.isFinite(callsPerMs) || callsPerMs <= 0) return;
4440
4487
  const credit = Math.min(unusedCost, GOVERNOR_MAX_ATOMIC_COST) / callsPerMs;
4441
- 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);
4442
4516
  }
4443
4517
 
4444
4518
  function scheduleGovernorState(state, nowMs) {
@@ -4454,7 +4528,8 @@ function scheduleGovernorState(state, nowMs) {
4454
4528
  ));
4455
4529
  const deferredBackground = state.observers.graphql.outcome === "failed"
4456
4530
  ? Object.entries(state.intents)
4457
- .filter(([, intent]) => intentPriority(intent) === REQUEST_PRIORITIES.background)
4531
+ .filter(([, intent]) => intentPriority(intent) === REQUEST_PRIORITIES.background &&
4532
+ intent.costs?.graphql > 0)
4458
4533
  .map(([id]) => id)
4459
4534
  : [];
4460
4535
  const deferred = new Set(deferredBackground);
@@ -4474,6 +4549,7 @@ function scheduleGovernorState(state, nowMs) {
4474
4549
  nowMs,
4475
4550
  maxGrants: GOVERNOR_MAX_RESERVATIONS - reservationCount,
4476
4551
  manualStreak: state.fairness.manualStreak,
4552
+ deferFutureBackground: true,
4477
4553
  });
4478
4554
  state.fairness.manualStreak = result.manualStreak;
4479
4555
  result.denied.push(...deferredBackground.map((intentId) => ({
@@ -5024,6 +5100,12 @@ function startReservation(scope, reservationId, nowMs) {
5024
5100
  if (liveClaim) {
5025
5101
  return { value: { status: "waiting", reason: "probe", notBefore: liveClaim.leaseUntil } };
5026
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
+ }
5027
5109
  for (const resource of RATE_RESOURCES.filter((name) => reservation.costs[name] > 0)) {
5028
5110
  const budget = state.budgets[resource];
5029
5111
  if (
@@ -8406,9 +8488,9 @@ function createCollectorAcquisitionRuntime({
8406
8488
  return { ...context, readBudgets };
8407
8489
  }
8408
8490
 
8409
- function refreshProviderBudget(context, signal) {
8491
+ function refreshProviderBudget(context, signal, tab) {
8410
8492
  const budgetReader = context.readBudgets ?? readBudgets;
8411
- return refreshSharedBudget(context.scope, context.leaseId, signal,
8493
+ return refreshCollectorRequiredBudget(context.scope, context.leaseId, signal, tab,
8412
8494
  { ...(budgetReader ? { readBudgets: budgetReader } : {}),
8413
8495
  onObserverPublished: context.onObserverPublished, now,
8414
8496
  wait: (delay) => abortableDelay(delay, signal, {
@@ -8487,8 +8569,8 @@ function createCollectorAcquisitionRuntime({
8487
8569
  const at = now();
8488
8570
  const lease = maintainControlLease(context.scope, context.leaseId, demand.floorMs, resource, at);
8489
8571
  if (!lease.ok) throw new Error("collector governor unavailable");
8490
- const refreshed = await refreshProviderBudget(context, signal);
8491
- if (!refreshed.ok) {
8572
+ const refreshed = await refreshProviderBudget(context, signal, resource);
8573
+ if (!collectorBudgetRefreshAllowsAdmission(refreshed, context.scope, resource, now())) {
8492
8574
  const error = new Error(refreshed.reason === "provider-capability"
8493
8575
  ? `collector provider capability unavailable (${refreshed.capability})`
8494
8576
  : "collector budget unavailable");
@@ -8509,6 +8591,29 @@ function createCollectorAcquisitionRuntime({
8509
8591
  admitGovernorOperation(admitScope, admitLeaseId, operation, priority, at,
8510
8592
  createId("collector-intent", admitLeaseId, operation, priority, at)),
8511
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
+ },
8512
8617
  });
8513
8618
  const reservationId = admitted.reservationId;
8514
8619
  const governor = inspectGovernor(context.scope, now());
@@ -8517,7 +8622,7 @@ function createCollectorAcquisitionRuntime({
8517
8622
  accessKey: context.identity.accessKey,
8518
8623
  epochs: governor.ok ? governor.value.epochs : {},
8519
8624
  });
8520
- if (!started.ok) {
8625
+ if (!started.ok || started.value?.status !== "started") {
8521
8626
  completeReservation({ ...context.scope, identityProvider: null }, reservationId,
8522
8627
  { outcome: "rejected" }, now());
8523
8628
  throw new Error("collector acquisition start failed");
@@ -8527,8 +8632,12 @@ function createCollectorAcquisitionRuntime({
8527
8632
  const entities = new Map((force ? [] : snapshot?.entities ?? []).map((entity) => [entity.key, entity]));
8528
8633
  const securityPolicy = resource === "security" && context.definition?.type === "github-app"
8529
8634
  ? githubAppSecurityPolicy(context.definition, now()) : null;
8530
- let result = await requestIdentityStorage.run(context.scope, () => descriptor.fetch({
8635
+ const boundedSignal = AbortSignal.any([
8531
8636
  signal,
8637
+ AbortSignal.timeout(ACQUISITION_STARTED_DEADLINE_MS),
8638
+ ]);
8639
+ let result = await requestIdentityStorage.run(context.scope, () => descriptor.fetch({
8640
+ signal: boundedSignal,
8532
8641
  entities,
8533
8642
  previousRaw: force ? null : snapshot?.raw ?? null,
8534
8643
  force,
@@ -8580,7 +8689,7 @@ function createCollectorAcquisitionRuntime({
8580
8689
  const context = provider?.scope ? provider : await defaultResolve(target, signal);
8581
8690
  const lease = maintainControlLease(context.scope, context.leaseId, REFRESH_MS, "issues", now());
8582
8691
  if (!lease.ok) throw new Error("collector identity governor unavailable");
8583
- const refreshed = await refreshProviderBudget(context, signal);
8692
+ const refreshed = await refreshProviderBudget(context, signal, "issues");
8584
8693
  if (!refreshed.ok) {
8585
8694
  const error = new Error(refreshed.reason === "provider-capability"
8586
8695
  ? `collector provider capability unavailable (${refreshed.capability})`
@@ -8683,6 +8792,8 @@ function createCollectorAcquisitionRuntime({
8683
8792
  function deliverCollectorSnapshot(item, snapshot, bindingRevision, { publication = false } = {}) {
8684
8793
  if (bindingRevision !== item.bindingRevision) return false;
8685
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;
8686
8797
  item.onSnapshot(snapshot);
8687
8798
  if (!publication || !Number.isSafeInteger(snapshot.generation)) return true;
8688
8799
  for (const waiter of item.invalidations) {
@@ -8705,6 +8816,14 @@ function createCollectorAcquisitionRuntime({
8705
8816
  if (closed || item.closed || !demandEnabled(item.demand)) return null;
8706
8817
  item.pollPromise = poll(item, { manual, force }).finally(() => {
8707
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
+ }
8708
8827
  const queued = item.queuedRefresh;
8709
8828
  item.queuedRefresh = null;
8710
8829
  if (queued && !closed && !item.closed) queueMicrotask(() => startPoll(item, { manual: true, force: queued.force }));
@@ -8767,22 +8886,31 @@ function createCollectorAcquisitionRuntime({
8767
8886
  nonce: ownership.value.nonce,
8768
8887
  generation: ownership.value.generation,
8769
8888
  accessKey: item.identity.accessKey,
8889
+ claimedAt: now(),
8890
+ subscriptionId,
8891
+ bindingRevision,
8770
8892
  };
8771
8893
  item.pollPhase = "claimed";
8772
8894
  const producer = produce ?? defaultProduce;
8773
8895
  item.controller = new AbortController();
8774
8896
  try {
8775
- const produceBound = () => producer({
8897
+ const produceBound = () => acquisitionFenceStorage.run(
8898
+ () => engine.currentClaim(subscriptionId, claim), () => producer({
8776
8899
  target: item.target,
8777
8900
  resource: item.resource,
8778
- demand: ownership.value.demand,
8901
+ demand: { ...ownership.value.demand, background: item.demand.background },
8779
8902
  snapshot: ownership.value.snapshot,
8780
8903
  claim: { ...claim, state: item },
8781
8904
  provider: item.provider,
8782
8905
  signal: item.controller.signal,
8783
8906
  force,
8784
- markStarted: (receipt) => engine.refresh(subscriptionId, { started: { ...claim, ...(receipt ? { receipt } : {}) } }),
8785
- });
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
+ }));
8786
8914
  const produced = item.provider?.scope
8787
8915
  ? await requestIdentityStorage.run(item.provider.scope, produceBound)
8788
8916
  : await produceBound();
@@ -8913,7 +9041,8 @@ function createCollectorAcquisitionRuntime({
8913
9041
  const item = { target, resource, demand: normalizedDemand, onSnapshot, onHold, id: null, identity: null,
8914
9042
  provider: null, polling: false, pollPhase: null, pollPromise: null, controller: null,
8915
9043
  initializePromise: null, closed: false, timer: null, queuedRefresh: null,
8916
- bindingRevision: 0, invalidations: new Set() };
9044
+ bindingRevision: 0, invalidations: new Set(), lastDeliveredGeneration: null,
9045
+ lastDeliveredSuccessAt: null, promotionGeneration: null, promotionAt: null };
8917
9046
  items.add(item);
8918
9047
  void startInitialize(item);
8919
9048
  return {
@@ -8921,7 +9050,12 @@ function createCollectorAcquisitionRuntime({
8921
9050
  const normalized = normalizeCollectorDemand(next);
8922
9051
  if (!normalized) return { ok: false, reason: "invalid" };
8923
9052
  const wasEnabled = demandEnabled(item.demand);
9053
+ const wasActive = item.demand.active;
8924
9054
  item.demand = normalized;
9055
+ if (!normalized.active) {
9056
+ item.promotionGeneration = null;
9057
+ item.promotionAt = null;
9058
+ }
8925
9059
  if (!demandEnabled(normalized) && item.timer !== null) {
8926
9060
  clearTimeout_(item.timer);
8927
9061
  item.timer = null;
@@ -8931,6 +9065,15 @@ function createCollectorAcquisitionRuntime({
8931
9065
  }
8932
9066
  const updated = item.id ? engine.updateDemand(item.id, normalized) : { ok: true };
8933
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
+ }
8934
9077
  return updated;
8935
9078
  },
8936
9079
  refresh(force = false) { return startPoll(item, { manual: true, force: force === true }); },
@@ -9807,66 +9950,11 @@ const ACQUISITION_HOLD_REASONS = new Set([
9807
9950
  const ACQUISITION_STORE_VERSION = 1;
9808
9951
  const ACQUISITION_MAX_UNCERTAIN_RECEIPTS = 1024;
9809
9952
 
9810
- function normalizeAcquisitionDiagnosticMetadata(raw) {
9811
- if (!isRecord(raw) || raw.version !== ACQUISITION_STORE_VERSION ||
9812
- typeof raw.producerEpoch !== "string" ||
9813
- !/^[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) ||
9814
- !isRecord(raw.subscriptions) || !isRecord(raw.queries)) return null;
9815
- const metrics = normalizeAcquisitionMetrics(raw.metrics);
9816
- if (!metrics) return null;
9817
- const receipts = raw.uncertainReceipts ?? {};
9818
- if (!isRecord(receipts) || Object.keys(receipts).length > ACQUISITION_MAX_UNCERTAIN_RECEIPTS) return null;
9819
- const uncertainReceipts = {};
9820
- const outstanding = { core: 0, graphql: 0 };
9821
- for (const [id, receipt] of Object.entries(receipts)) {
9822
- const normalized = normalizeAcquisitionReceipt(receipt, id);
9823
- if (!normalized) return null;
9824
- uncertainReceipts[id] = normalized;
9825
- outstanding[normalized.resource] += normalized.units;
9826
- }
9827
- if (metrics.uncertainCoreUnits !== outstanding.core ||
9828
- metrics.uncertainGraphqlUnits !== outstanding.graphql) return null;
9829
- const queries = {};
9830
- for (const [queryKey, value] of Object.entries(raw.queries)) {
9831
- if (!isRecord(value) || !isRecord(value.query) || value.query.queryKey !== queryKey ||
9832
- !TAB_KEYS.includes(value.query.resource)) return null;
9833
- const hold = normalizeAcquisitionHold(value.hold);
9834
- if (value.hold !== undefined && value.hold !== null && !hold) return null;
9835
- const snapshot = value.snapshot;
9836
- if (snapshot !== null && (!isRecord(snapshot) ||
9837
- !Number.isFinite(snapshot.lastSuccessAt) || !Number.isFinite(snapshot.lastChangedAt) ||
9838
- !Number.isFinite(snapshot.nextDueAt))) return null;
9839
- const claim = value.claim;
9840
- if (claim !== null && (!isRecord(claim) || !Number.isSafeInteger(claim.generation) ||
9841
- (claim.receiptIds !== undefined && (!Array.isArray(claim.receiptIds) ||
9842
- claim.receiptIds.some((id) => typeof id !== "string" || !uncertainReceipts[id]))))) return null;
9843
- queries[queryKey] = {
9844
- query: { queryKey, resource: value.query.resource },
9845
- snapshot: snapshot === null ? null : {
9846
- lastSuccessAt: snapshot.lastSuccessAt,
9847
- lastChangedAt: snapshot.lastChangedAt,
9848
- nextDueAt: snapshot.nextDueAt,
9849
- },
9850
- claim: claim === null ? null : { generation: claim.generation },
9851
- hold,
9852
- };
9853
- }
9854
- const subscriptions = {};
9855
- for (const [id, value] of Object.entries(raw.subscriptions)) {
9856
- if (!isRecord(value) || !queries[value.queryKey] || !Number.isFinite(value.expiresAt)) return null;
9857
- const waitingGeneration = value.waitingGeneration ?? null;
9858
- const waitingSinceAt = value.waitingSinceAt ?? null;
9859
- if (waitingGeneration !== null && (!Number.isSafeInteger(waitingGeneration) ||
9860
- !Number.isFinite(waitingSinceAt))) return null;
9861
- if (waitingGeneration === null && waitingSinceAt !== null) return null;
9862
- subscriptions[id] = {
9863
- queryKey: value.queryKey,
9864
- expiresAt: value.expiresAt,
9865
- waitingGeneration,
9866
- waitingSinceAt,
9867
- };
9868
- }
9869
- 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;
9870
9958
  }
9871
9959
 
9872
9960
  function unavailableAcquisitionDiagnostic(reason, source = "standalone") {
@@ -9874,18 +9962,71 @@ function unavailableAcquisitionDiagnostic(reason, source = "standalone") {
9874
9962
  activeSubscribers: 0, metrics: emptyAcquisitionMetrics(), queries: [] };
9875
9963
  }
9876
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
+
9877
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;
9878
10013
  try {
9879
- const raw = JSON.parse(readFileSync(join(identityRegistryRoot(), "acquisition.json"), "utf8"));
9880
- const normalized = normalizeAcquisitionDiagnosticMetadata(raw);
9881
- return normalized
10014
+ const raw = JSON.parse(readFileSync(path, "utf8"));
10015
+ const normalized = normalizeAcquisitionDiagnosticMetadata(raw, path);
10016
+ metadata = normalized ? "healthy" : "corrupt";
10017
+ diagnostic = normalized
9882
10018
  ? acquisitionDiagnostics(normalized, { nowMs, source })
9883
10019
  : unavailableAcquisitionDiagnostic("corrupt", source);
9884
10020
  } catch (error) {
9885
- return unavailableAcquisitionDiagnostic(error?.code === "ENOENT"
9886
- ? "missing"
9887
- : 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);
9888
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" };
9889
10030
  }
9890
10031
 
9891
10032
  function collectorSocketDiagnostic(pathOptions = {}) {
@@ -10049,6 +10190,11 @@ async function runDoctor({ probeEndpoints = false } = {}) {
10049
10190
  ...section("Acquisition metrics"),
10050
10191
  field("source", acquisitionDiagnostic?.source ?? "standalone"),
10051
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"),
10052
10198
  field("epoch", acquisitionDiagnostic?.epoch ?? "unavailable"),
10053
10199
  field("active queries", acquisitionDiagnostic?.activeQueries ?? 0),
10054
10200
  field("subscribers", acquisitionDiagnostic?.activeSubscribers ?? 0),
@@ -10529,18 +10675,9 @@ if (IS_MAIN) {
10529
10675
 
10530
10676
  if (opts.serve) runtime.headlessMode = { type: "serve", config: opts.config };
10531
10677
  else if (opts.collectorStdio) runtime.headlessMode = { type: "stdio" };
10678
+ else if (opts.doctor) runtime.headlessMode = { type: "doctor", probe: opts.probe };
10532
10679
  else {
10533
10680
 
10534
- // A reporting command, like --help and --version: gather, print, exit. It sits
10535
- // here on purpose -- ahead of the non-TTY refusal, because the whole point is
10536
- // `gh-glance --doctor > report.txt`, and ahead of preflight(), because a
10537
- // missing gh and a cwd outside a repository are exactly the conditions worth
10538
- // reporting rather than exiting 3 over.
10539
- if (opts.doctor) {
10540
- console.log(await runDoctor({ probeEndpoints: opts.probe }));
10541
- process.exit(0);
10542
- }
10543
-
10544
10681
  // A local collector subscription needs an exact offline target before Ink
10545
10682
  // mounts. Resolve git remotes locally and fail with the actionable flag;
10546
10683
  // otherwise a cold invocation paints a dashboard that can only say
@@ -12170,6 +12307,18 @@ function shouldCheckpointFreshness({ persistedAt, completedAt }) {
12170
12307
  // a unit with its validators, but an inability to claim it must never fall back
12171
12308
  // to an independent request: that would defeat its only security property.
12172
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
+ }
12173
12322
  const ACQUISITION_HEARTBEAT_MS = 10_000;
12174
12323
  const ACQUISITION_INSPECT_MS = 1_000;
12175
12324
  const ACQUISITION_MAX_BYTES = 32 * 1024 * 1024;
@@ -12944,38 +13093,12 @@ function writeAcquisitionStore(path, state) {
12944
13093
  }
12945
13094
  }
12946
13095
 
12947
- function acquisitionLockOwner(path) {
12948
- const owner = lockOwner(path);
12949
- return owner && validGovernorId(owner.nonce) ? owner : null;
12950
- }
12951
-
12952
13096
  function withAcquisitionStore(path, operation, {
12953
13097
  now = Date.now(),
12954
13098
  pid = process.pid,
12955
13099
  kill = process.kill.bind(process),
12956
13100
  } = {}) {
12957
- const lockPath = `${path}.lock`;
12958
- const nonce = governorId();
12959
- for (let attempt = 0; attempt < 3; attempt += 1) {
12960
- try {
12961
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
12962
- writeFileSync(lockPath, JSON.stringify({ pid, nonce }), { encoding: "utf8", mode: 0o600, flag: "wx" });
12963
- break;
12964
- } catch (error) {
12965
- if (error?.code !== "EEXIST") return { ok: false, reason: "unwritable", error };
12966
- const owner = acquisitionLockOwner(lockPath);
12967
- // Another process can observe the lock file between its exclusive create
12968
- // and completed JSON write. Treat an unreadable owner as contention; it
12969
- // cannot authorize stealing and must not turn a healthy race into a
12970
- // corrupt-store verdict.
12971
- if (!owner || !pidIsDead(owner.pid, kill)) return { ok: false, reason: "busy" };
12972
- const abandoned = `${lockPath}.dead-${owner.nonce}`;
12973
- try { renameSync(lockPath, abandoned); } catch { continue; }
12974
- try { unlinkSync(abandoned); } catch { /* exact quarantined path */ }
12975
- }
12976
- }
12977
- if (acquisitionLockOwner(lockPath)?.nonce !== nonce) return { ok: false, reason: "busy" };
12978
- try {
13101
+ return withFileLock(`${path}.lock`, () => {
12979
13102
  const loaded = loadAcquisitionMetadata(path, { now });
12980
13103
  if (!loaded.ok) return loaded;
12981
13104
  const result = operation(loaded.value);
@@ -12985,11 +13108,7 @@ function withAcquisitionStore(path, operation, {
12985
13108
  }
12986
13109
  const written = writeAcquisitionStore(path, loaded.value);
12987
13110
  return written.ok ? { ok: true, value: result?.value, state: written.persisted, written: true } : written;
12988
- } finally {
12989
- if (acquisitionLockOwner(lockPath)?.nonce === nonce) {
12990
- try { unlinkSync(lockPath); } catch { /* exact owned lock */ }
12991
- }
12992
- }
13111
+ }, { pid, kill });
12993
13112
  }
12994
13113
 
12995
13114
  function acquisitionOwnerStatus(pid, kill) {
@@ -13075,6 +13194,7 @@ function trimAcquisitionStore(state, protectedQueryKey = null) {
13075
13194
  async function runStartedAcquisitionTransport(markStarted, transport) {
13076
13195
  const started = await markStarted();
13077
13196
  if (!started.ok) return started;
13197
+ if (started.value?.status !== "started") return { ok: false, reason: "already-started" };
13078
13198
  return { ok: true, value: await transport() };
13079
13199
  }
13080
13200
 
@@ -13176,7 +13296,8 @@ function createAcquisitionEngine({
13176
13296
  }
13177
13297
  const query = state.queries[item.queryKey];
13178
13298
  if (query?.claim?.pid === pid && query.claim.nonce === item.claimNonce &&
13179
- query.claim.leaseUntil !== at + ACQUISITION_CLAIM_TTL_MS) {
13299
+ query.claim.leaseUntil !== at + ACQUISITION_CLAIM_TTL_MS &&
13300
+ at < acquisitionClaimDeadline(query.claim)) {
13180
13301
  query.claim.leaseUntil = at + ACQUISITION_CLAIM_TTL_MS;
13181
13302
  changed = true;
13182
13303
  }
@@ -13223,8 +13344,64 @@ function createAcquisitionEngine({
13223
13344
  reapAcquisitionSubscriptions(state, now(), kill);
13224
13345
  const repositoryId = state.aliases[acquisitionAliasKey(queryInput.host, queryInput.repository)] ??
13225
13346
  queryInput.repositoryId;
13226
- const queryKey = acquisitionQueryKey({ ...queryInput, repositoryId });
13227
- 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;
13228
13405
  if (!query) return { ok: false, reason: "invalid" };
13229
13406
  if (Object.keys(state.subscriptions).length >= ACQUISITION_MAX_SUBSCRIPTIONS) {
13230
13407
  return { ok: false, reason: "capacity" };
@@ -13264,7 +13441,8 @@ function createAcquisitionEngine({
13264
13441
  };
13265
13442
  state.queries[queryKey].lastUsedAt = now();
13266
13443
  if (existingQuery?.snapshot) acquisitionMetricState(state).cacheHits += 1;
13267
- return { value: { id, queryKey, snapshot: state.queries[queryKey].snapshot } };
13444
+ return { value: { id, queryKey, snapshot: state.queries[queryKey].snapshot,
13445
+ aliasConflict: matching.length > 1 } };
13268
13446
  });
13269
13447
  if (!result.ok) return result;
13270
13448
  const record = result.state.queries[result.value.queryKey];
@@ -13304,6 +13482,11 @@ function createAcquisitionEngine({
13304
13482
  record.claim.generation !== claim.generation || record.query.accessKey !== claim.accessKey) {
13305
13483
  return { value: { ok: false, reason: "stale", definitive: true } };
13306
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
+ }
13307
13490
  let remapped = {};
13308
13491
  let effectiveClaim = claim;
13309
13492
  if (produced?.repositoryIdentity !== undefined) {
@@ -13403,7 +13586,10 @@ function createAcquisitionEngine({
13403
13586
 
13404
13587
  function cancel(id, claim) {
13405
13588
  const item = local.get(id);
13406
- 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" };
13407
13593
  const result = transact((state) => {
13408
13594
  const record = state.queries[item.queryKey];
13409
13595
  if (!record || record.claim?.nonce !== claim.nonce || record.claim.generation !== claim.generation) {
@@ -13491,13 +13677,22 @@ function createAcquisitionEngine({
13491
13677
  if (!record || record.claim?.nonce !== claim.nonce || record.claim.generation !== claim.generation) {
13492
13678
  return { ok: false, reason: "stale" };
13493
13679
  }
13494
- if (record.claim.started) return { changed: false, value: true };
13495
13680
  const costs = tabRequestCost(record.query.resource);
13496
13681
  const supplied = claim.receipt;
13497
13682
  if (supplied !== undefined && (!isRecord(supplied) || supplied.accessKey !== record.query.accessKey ||
13498
13683
  typeof supplied.reservationId !== "string" || supplied.reservationId.length < 1 ||
13499
13684
  !isRecord(supplied.epochs))) return { ok: false, reason: "invalid" };
13500
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
+ }
13501
13696
  const receiptIds = [];
13502
13697
  for (const resource of RATE_RESOURCES) {
13503
13698
  if (costs[resource] <= 0) continue;
@@ -13516,8 +13711,11 @@ function createAcquisitionEngine({
13516
13711
  receiptIds.push(receipt.id);
13517
13712
  }
13518
13713
  record.claim.started = true;
13714
+ record.claim.startedAt = now();
13715
+ record.claim.reservationId = reservationId;
13716
+ record.claim.startReceipt = startReceipt;
13519
13717
  record.claim.receiptIds = receiptIds;
13520
- return { value: true };
13718
+ return { value: { status: "started", reservationId } };
13521
13719
  });
13522
13720
  }
13523
13721
 
@@ -13529,9 +13727,25 @@ function createAcquisitionEngine({
13529
13727
  return cancel(id, pending.claim);
13530
13728
  }
13531
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
+
13532
13743
  async function refresh(id, { force = false, acquire = null, claim = null, publish: publication,
13533
13744
  cancel: cancellation = null, failure = null, started = null } = {}) {
13534
13745
  const item = local.get(id);
13746
+ if (!item && cancellation !== null && isRecord(cancellation)) {
13747
+ return { ok: false, reason: "stale" };
13748
+ }
13535
13749
  if (!item || acquire !== null && typeof acquire !== "function") return { ok: false, reason: "invalid" };
13536
13750
  // A retired scope cannot start new work, but its already-started producer
13537
13751
  // must still be able to publish for remaining consumers or cancel its
@@ -13566,9 +13780,10 @@ function createAcquisitionEngine({
13566
13780
  sharingCount: acquisitionSharingCount(state, item.queryKey) } };
13567
13781
  }
13568
13782
  if (record.claim) {
13783
+ const absoluteDeadline = acquisitionClaimDeadline(record.claim);
13569
13784
  const expired = record.claim.leaseUntil <= now();
13570
13785
  const owner = acquisitionOwnerStatus(record.claim.pid, kill);
13571
- if (!expired || owner !== "dead") {
13786
+ if (now() < absoluteDeadline && (!expired || owner !== "dead")) {
13572
13787
  const joined = subscription.waitingGeneration !== record.claim.generation;
13573
13788
  if (joined) {
13574
13789
  subscription.waitingGeneration = record.claim.generation;
@@ -13728,6 +13943,7 @@ function createAcquisitionEngine({
13728
13943
  subscribe,
13729
13944
  updateDemand,
13730
13945
  refresh,
13946
+ currentClaim,
13731
13947
  unsubscribe,
13732
13948
  inspect,
13733
13949
  diagnostics,
@@ -13940,6 +14156,32 @@ function retryPollAfterAdmissionFailure({
13940
14156
  };
13941
14157
  }
13942
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
+
13943
14185
  function governorWakeTimes(state, nowMs, floorMs, leaseId = null) {
13944
14186
  const budgets = Object.values(state?.budgets ?? {});
13945
14187
  // A failed observer's own retry, not its stale sample's cadence, says when
@@ -13962,17 +14204,21 @@ function governorWakeTimes(state, nowMs, floorMs, leaseId = null) {
13962
14204
  (leaseId == null || reservation.leaseId === leaseId))
13963
14205
  .reduce((earliest, reservation) => Math.min(earliest, reservation.notBefore), Number.POSITIVE_INFINITY);
13964
14206
  return {
13965
- controlAt: Number.isFinite(controlAt) ? Math.max(nowMs + 1, controlAt) : nowMs + floorMs,
13966
- 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,
13967
14213
  };
13968
14214
  }
13969
14215
 
13970
- function governorProtocolReady(refreshResult, snapshot, nowMs) {
14216
+ function governorProtocolReady(refreshResult, snapshot, nowMs, resources = RATE_RESOURCES) {
13971
14217
  if (!refreshResult?.ok || !snapshot?.ok) return false;
13972
14218
  if (["waiting", "paused", "probe"].includes(refreshResult.value?.status)) return false;
13973
- if (RATE_RESOURCES.some((resource) => snapshot.value.probeClaims[resource]) ||
13974
- RATE_RESOURCES.some((resource) => snapshot.value.observers[resource]?.outcome !== "healthy")) return false;
13975
- 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) => {
13976
14222
  const budget = snapshot.value.budgets[resource];
13977
14223
  return budget && nowMs - budget.observedAt <= budgetSnapshotTtl(resource);
13978
14224
  });
@@ -13996,17 +14242,27 @@ function tabEpochChanged(previous, next, key) {
13996
14242
  }
13997
14243
 
13998
14244
  function governorDataReady(refreshResult, snapshot, activeKey, nowMs) {
13999
- if (!governorProtocolReady(refreshResult, snapshot, nowMs)) return false;
14000
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;
14001
14249
  return RATE_RESOURCES.every((resource) => {
14002
14250
  if (costs[resource] <= 0) return true;
14003
14251
  const budget = snapshot.value.budgets[resource];
14252
+ const available = availableForGrant({ budget, resource, nowMs });
14004
14253
  return budget && nowMs - budget.observedAt <= budgetSnapshotTtl(resource) &&
14005
- budget.blockUntil <= nowMs &&
14006
- availableForGrant({ budget, resource, nowMs }).mode === "open";
14254
+ budget.blockUntil <= nowMs && available.mode === "open" &&
14255
+ available.spendable >= costs[resource];
14007
14256
  });
14008
14257
  }
14009
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
+
14010
14266
  function governorControlRetryAt(nowMs, floorMs) {
14011
14267
  return nowMs + Math.min(floorMs, 1000);
14012
14268
  }
@@ -14047,6 +14303,26 @@ function createWakeScheduler({
14047
14303
  };
14048
14304
  }
14049
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
+
14050
14326
  function createSingleFlightWake(run, onSettled = () => {}) {
14051
14327
  let running = false;
14052
14328
  let pendingArgs = null;
@@ -14072,7 +14348,7 @@ function createSingleFlightWake(run, onSettled = () => {}) {
14072
14348
  }
14073
14349
 
14074
14350
  function pendingFailureIsTerminal(reason) {
14075
- return reason === "stale";
14351
+ return ["stale", "already-started", "receipt-mismatch"].includes(reason);
14076
14352
  }
14077
14353
 
14078
14354
  function cancelCoordinatedPending(item, { cancelGovernor, cancelAcquisition: cancelShared } = {}) {
@@ -14082,6 +14358,27 @@ function cancelCoordinatedPending(item, { cancelGovernor, cancelAcquisition: can
14082
14358
  return true;
14083
14359
  }
14084
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
+
14085
14382
  function runtimeIntentGate(liveScheduling, { force = false, protocolReady = false } = {}) {
14086
14383
  return liveScheduling || protocolReady
14087
14384
  ? { registerIntent: true, requestProbe: false }
@@ -14159,6 +14456,16 @@ function coordinationNotice(reason) {
14159
14456
  return "Can't coordinate API use — retrying";
14160
14457
  }
14161
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
+
14162
14469
  function retryRateLimitBlockPublication(pending, nowMs, publish) {
14163
14470
  const blocks = Array.isArray(pending?.blocks) ? pending.blocks : [];
14164
14471
  if (blocks.length === 0 || blocks.some((block) =>
@@ -14231,7 +14538,6 @@ function rateLimitBlockProbeRecovered(pending, state, nowMs) {
14231
14538
  // single time and the feature never runs at all. A named `notBefore` is a
14232
14539
  // schedule, not a refusal; anything past this bound still declines, and
14233
14540
  // startReservation revalidates the budget when the wait is over.
14234
- const GOVERNOR_ADMISSION_WAIT_MS = 2_000;
14235
14541
 
14236
14542
  function measuredNestedOperationCost(operation, value) {
14237
14543
  const declared = operationCost(operation);
@@ -14303,7 +14609,56 @@ async function awaitCollectorReservation({
14303
14609
  start = startReservation,
14304
14610
  cancel = cancelIntent,
14305
14611
  deferredRetryAt = null,
14612
+ retainUntil = null,
14613
+ onRetainedWait = null,
14614
+ inspect = readIntentDecision,
14306
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
+ }
14307
14662
  const attempt = await resolveGovernorAdmission({
14308
14663
  scope, leaseId, operation, priority, signal, waitMs, now, wait,
14309
14664
  admit, start, cancel, requireElapsed: true,
@@ -14333,6 +14688,21 @@ function collectorObserverRetryAt(scope, resource, at) {
14333
14688
  return deadlines.length > 0 ? Math.max(...deadlines) : null;
14334
14689
  }
14335
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
+
14336
14706
  async function resolveGovernorAdmission({
14337
14707
  scope,
14338
14708
  leaseId,
@@ -14665,12 +15035,17 @@ function refreshStatus({
14665
15035
  : null
14666
15036
  );
14667
15037
  if (mode === "paused" || activeError?.verdict === "rate-limited") {
14668
- return status("paused", "paused", "Paused", "attention", false, detailKind);
15038
+ return status("paused", "paused", "Paused", "attention", false,
15039
+ governorDecision?.cause ? "cause" : detailKind);
14669
15040
  }
14670
15041
  if (disconnected) {
14671
15042
  return status("disconnected", "failed", "Disconnected", "attention", false, stale ? "stale" : null);
14672
15043
  }
14673
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");
14674
15049
  if (sharedData) return status("shared", "watching", "Shared", "inert", false, "sharing");
14675
15050
  if (["waiting", "pending", "probe"].includes(mode)) {
14676
15051
  const sharing = sharedLaneProvenance(governorDecision).waitCause === "shared-lane";
@@ -14679,8 +15054,6 @@ function refreshStatus({
14679
15054
  : Number.isFinite(governorDecision?.notBefore) ? "next" : detailKind;
14680
15055
  return status("watching", "watching", "Watching", "inert", false, watchingDetail);
14681
15056
  }
14682
- if (activeError) return status("failed", "failed", "Failed", "attention");
14683
- if (securityIncomplete) return status("limited", "limited", "Limited", "attention");
14684
15057
  return status("watching", "watching", "Watching", "inert");
14685
15058
  }
14686
15059
 
@@ -14694,6 +15067,7 @@ function statusInterval(at, nowMs = Date.now()) {
14694
15067
 
14695
15068
  function statusDetailVariants(status, detail, nowMs) {
14696
15069
  if (!status?.detailKind) return [];
15070
+ if (status.detailKind === "cause") return detail?.cause ? [detail.cause] : [];
14697
15071
  if (status.detailKind === "probing") return ["probing"];
14698
15072
  if (status.detailKind === "sharing") {
14699
15073
  const sharing = sharedLaneProvenance(detail);
@@ -14721,8 +15095,10 @@ function statusBarLayout({
14721
15095
  const hints = Array.isArray(availableHints) ? availableHints : [];
14722
15096
  const mandatory = hints.filter(isMandatoryHint);
14723
15097
  const optional = hints.filter((hint) => !isMandatoryHint(hint));
14724
- const stateWidth = width >= WIDE_STATE_MIN_COLS
14725
- ? 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)
14726
15102
  : Math.min(width, REFRESH_STATUS_WIDTH);
14727
15103
  let used = stateWidth;
14728
15104
  const selectedHints = [];
@@ -14756,7 +15132,10 @@ function statusBarLayout({
14756
15132
  ? stateWidth - REFRESH_STATUS_WIDTH - 1
14757
15133
  : 0;
14758
15134
  const staleText = typeof stale === "string" ? stale : null;
14759
- 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;
14760
15139
  let selectedDetail = null;
14761
15140
  if (!selectedStale) {
14762
15141
  selectedDetail = statusDetailVariants(status, detail, nowMs)
@@ -15155,6 +15534,7 @@ function App({ onCreateRemote = () => {} } = {}) {
15155
15534
  const [loading, setLoading] = useState({ actions: false, issues: false, prs: false, security: false });
15156
15535
  const [waiting, setWaiting] = useState({ actions: true, issues: true, prs: true, security: true });
15157
15536
  const [governorDecisions, setGovernorDecisions] = useState(probingGovernorDecisions);
15537
+ const [acquisitionFailures, setAcquisitionFailures] = useState({});
15158
15538
  const [governorEpochs, setGovernorEpochs] = useState(null);
15159
15539
  const [requestStatuses, setRequestStatuses] = useState(() =>
15160
15540
  Object.fromEntries(TABS.map((candidate) => [candidate.key, null])),
@@ -15173,7 +15553,13 @@ function App({ onCreateRemote = () => {} } = {}) {
15173
15553
 
15174
15554
  const tab = TABS[activeIndex];
15175
15555
  const tabError = errors[tab.key];
15176
- 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];
15177
15563
  const activeRequestStatus = requestStatuses[tab.key];
15178
15564
  // Once any endpoint proves the folder has no remote, the whole dashboard is
15179
15565
  // in setup mode. Keeping this tab-local made a quick tab switch replace the
@@ -15749,7 +16135,9 @@ function App({ onCreateRemote = () => {} } = {}) {
15749
16135
  return null;
15750
16136
  }
15751
16137
 
15752
- 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" });
15753
16141
  const hold = diagnosticHold ?? (
15754
16142
  /secondary|abuse/.test(reason ?? "")
15755
16143
  ? "secondary"
@@ -15786,20 +16174,22 @@ function App({ onCreateRemote = () => {} } = {}) {
15786
16174
  }
15787
16175
 
15788
16176
  function publishControlStatus(key, refreshed, snapshot, nowMs) {
15789
- if (pendingBlockPublications.size > 0) {
16177
+ if (RATE_RESOURCES.some((resource) => tabRequestCost(key)[resource] > 0 &&
16178
+ pendingBlockPublications.has(resource))) {
15790
16179
  pauseCoordination(key, "block-unpublished");
15791
16180
  return;
15792
16181
  }
15793
- if (!refreshed?.ok) {
15794
- pauseCoordination(key, refreshed?.reason, "observer");
15795
- return;
15796
- }
15797
16182
  if (!snapshot?.ok) {
15798
16183
  pauseCoordination(key, snapshot?.reason);
15799
16184
  return;
15800
16185
  }
16186
+ const tabReady = governorTabControlReady(snapshot, key, nowMs);
16187
+ if (!refreshed?.ok && !tabReady) {
16188
+ pauseCoordination(key, refreshed?.reason, "observer");
16189
+ return;
16190
+ }
15801
16191
  publishGovernorEpochs(snapshot);
15802
- if (refreshed.value?.status === "waiting") {
16192
+ if (refreshed.value?.status === "waiting" && !tabReady) {
15803
16193
  setTabGovernorDecision(key, { mode: "waiting", probing: true });
15804
16194
  return;
15805
16195
  }
@@ -15924,7 +16314,11 @@ function App({ onCreateRemote = () => {} } = {}) {
15924
16314
  [key]: { automaticStatusVisible },
15925
16315
  }));
15926
16316
  }
15927
- 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)
15928
16322
  .then(async (result) => {
15929
16323
  settleReservationWithBudgetObservations(settlementScope, leaseId, reservationId, result?.measuredSuccess === false
15930
16324
  ? { outcome: "rejected", observations: result?.observations ?? [] }
@@ -15988,55 +16382,6 @@ function App({ onCreateRemote = () => {} } = {}) {
15988
16382
  limit,
15989
16383
  completedAt,
15990
16384
  });
15991
- // The cadence follows the observation, not the tab: an error or an
15992
- // unusable payload leaves the counter where it was, so a broken tab
15993
- // is never slowed down for looking quiet.
15994
- unchangedPolls[key] = advanceUnchangedCount(unchangedPolls[key], transition.kind);
15995
- // Recomputed now that the outcome is known: the schedule chose this
15996
- // tab's deadline before the observation that decides its cadence.
15997
- // Shared snapshots publish a deadline from completion. Anchor the
15998
- // local wake to that same observation so waking just before the
15999
- // shared deadline cannot consume a fresh follower result and add a
16000
- // second full cadence interval.
16001
- rescheduleTab(key, completedAt, {
16002
- actionRows: key === "actions" && transition.kind === "changed" ? transition.data : undefined,
16003
- });
16004
- if (result?.catalog) workflowCatalogRef.current = result.catalog;
16005
- if (Number.isFinite(result?.loadedPages)) {
16006
- pageStateRef.current[key] = {
16007
- pages: result.loadedPages,
16008
- hasNextPage: result.hasNextPage === true,
16009
- };
16010
- }
16011
- publishStagedEntities(entityRef.current, stagedEntities, transition.kind);
16012
- if (transition.kind === "unchanged") {
16013
- lastOkRef.current[key] = completedAt;
16014
- const publication = await finishAcquisition(key, acquisition, transition, completedAt);
16015
- if (!publication.ok) pauseCoordination(key, publication.reason);
16016
- // Clear on the first success or a single failure latches the ladder.
16017
- clearBackoff(`tab:${key}`);
16018
- setErrors((x) => (x[key] === null ? x : { ...x, [key]: null }));
16019
- const cachedTab = pick(
16020
- pick(dashboardCacheRef.current, dashboardCacheTargetRef.current, null)?.tabs ?? {},
16021
- key,
16022
- null,
16023
- );
16024
- if (
16025
- cachedTab &&
16026
- shouldCheckpointFreshness({ persistedAt: cachedTab.lastOk, completedAt })
16027
- ) {
16028
- cacheSuccessfulTab(
16029
- key,
16030
- dataRef.current[key],
16031
- metaRef.current[key],
16032
- completedAt,
16033
- key === "security"
16034
- ? { notes: securityNotesRef.current, blind: securityBlindRef.current }
16035
- : {},
16036
- );
16037
- }
16038
- return;
16039
- }
16040
16385
  // Empty or truncated JSON is not a user-actionable fetch error. Keep
16041
16386
  // the last-good rows and freshness clock, and clear the raw comparison
16042
16387
  // so the next admitted tick parses its response instead of taking the
@@ -16068,6 +16413,45 @@ function App({ onCreateRemote = () => {} } = {}) {
16068
16413
  setSecurityBlind(true);
16069
16414
  return;
16070
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
+ }
16071
16455
  const tabData = transition.data;
16072
16456
  const tabMeta = transition.meta;
16073
16457
  lastOkRef.current[key] = completedAt;
@@ -16080,8 +16464,6 @@ function App({ onCreateRemote = () => {} } = {}) {
16080
16464
  if (key === "security") {
16081
16465
  setSecurityBlind((b) => (b === transition.blind ? b : transition.blind));
16082
16466
  }
16083
- const publication = await finishAcquisition(key, acquisition, transition, completedAt);
16084
- if (!publication.ok) pauseCoordination(key, publication.reason);
16085
16467
  cacheSuccessfulTab(key, tabData, tabMeta, completedAt, {
16086
16468
  notes: transition.notes,
16087
16469
  blind: transition.blind,
@@ -16158,6 +16540,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16158
16540
 
16159
16541
  const leaseId = governorId();
16160
16542
  const pending = new Map();
16543
+ const cleanupQueue = new Set();
16161
16544
  // One manual handoff per tab, carrying which key produced it. A keypress
16162
16545
  // during an automatic request must not be lost, but repeated keypresses
16163
16546
  // during the manual request itself must not create a trailing second batch.
@@ -16178,7 +16561,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16178
16561
  // back *different* still clears them. Null means nothing is retained.
16179
16562
  let retainedAccessKey = null;
16180
16563
  let remoteUrls = [];
16181
- let liveScheduling = false;
16564
+ let pollDeadlinesOpen = false;
16182
16565
  let controlEpochs = null;
16183
16566
  // One deadline per tab, not one active slot and one rotating background
16184
16567
  // slot. Two shared timers cannot express a Security tab quiet at 300s next
@@ -16186,6 +16569,9 @@ function App({ onCreateRemote = () => {} } = {}) {
16186
16569
  // of the cadence table.
16187
16570
  const NEVER_DUE = Object.fromEntries(TAB_KEYS.map((key) => [key, Number.POSITIVE_INFINITY]));
16188
16571
  let pollDueAt = { ...NEVER_DUE };
16572
+ let sharedSnapshotDueAt = { ...NEVER_DUE };
16573
+ let scheduledWakePolls = [];
16574
+ let scheduledWakeBackgroundIndex = 0;
16189
16575
  let backgroundIndex = 0;
16190
16576
  // Only the unchanged run counts live here. Whether Actions has work in
16191
16577
  // flight is read from the rows themselves, so there is no second copy of it
@@ -16196,6 +16582,23 @@ function App({ onCreateRemote = () => {} } = {}) {
16196
16582
 
16197
16583
  function adoptAcquisitionSnapshot(key, snapshot, receipt = null) {
16198
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
+ });
16199
16602
  const snapshotMeta = snapshot.meta ?? { at: snapshot.lastChangedAt, truncated: false };
16200
16603
  const receiptAge = Number.isFinite(receipt?.sourceAgeMs) ? Math.max(0, receipt.sourceAgeMs) : null;
16201
16604
  if (receiptAge === null) {
@@ -16368,19 +16771,19 @@ function App({ onCreateRemote = () => {} } = {}) {
16368
16771
  return { ok: true, value: subscription };
16369
16772
  }
16370
16773
 
16371
- function acquisitionPublication(key, acquisition, transition, completedAt) {
16774
+ function acquisitionPublication(key, acquisition, transition, completedAt, publicationView) {
16372
16775
  const rows = transition.kind === "changed" ? transition.data : dataRef.current[key];
16373
16776
  const meta = transition.kind === "changed" ? transition.meta : metaRef.current[key];
16374
16777
  if (!Array.isArray(rows) || !meta) return null;
16375
16778
  const prefix = `${key}\0`;
16376
- const entities = [...entityRef.current.entries()]
16779
+ const entities = [...publicationView.entities.entries()]
16377
16780
  .filter(([entityKey_]) => entityKey_.startsWith(prefix))
16378
16781
  .map(([entityKey_, entity]) => ({ key: entityKey_, etag: entity.etag, body: entity.body }));
16379
16782
  return {
16380
16783
  rows,
16381
16784
  pageInfo: {
16382
- loadedPages: pageStateRef.current[key]?.pages ?? 1,
16383
- hasNextPage: pageStateRef.current[key]?.hasNextPage === true,
16785
+ loadedPages: publicationView.pageState?.pages ?? 1,
16786
+ hasNextPage: publicationView.pageState?.hasNextPage === true,
16384
16787
  },
16385
16788
  raw: JSON.stringify(rows),
16386
16789
  entities,
@@ -16390,7 +16793,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16390
16793
  tab: key,
16391
16794
  floorMs: acquisition.demand?.floorMs ?? runtime.refreshMs,
16392
16795
  demand: acquisition.demand?.active ? "active" : "inactive",
16393
- unchangedCount: unchangedPolls[key],
16796
+ unchangedCount: publicationView.unchangedCount,
16394
16797
  inProgressCI: key === "actions" && actionsInProgress(rows),
16395
16798
  background: runtime.background,
16396
16799
  }),
@@ -16409,9 +16812,9 @@ function App({ onCreateRemote = () => {} } = {}) {
16409
16812
  };
16410
16813
  }
16411
16814
 
16412
- function finishAcquisition(key, acquisition, transition, completedAt) {
16815
+ function finishAcquisition(key, acquisition, transition, completedAt, publicationView) {
16413
16816
  if (!acquisition) return { ok: true };
16414
- const publication = acquisitionPublication(key, acquisition, transition, completedAt);
16817
+ const publication = acquisitionPublication(key, acquisition, transition, completedAt, publicationView);
16415
16818
  if (!publication) {
16416
16819
  return acquisitionEngine.refresh(acquisition.id, { cancel: acquisition.claim });
16417
16820
  }
@@ -16422,7 +16825,9 @@ function App({ onCreateRemote = () => {} } = {}) {
16422
16825
  }
16423
16826
 
16424
16827
  function cancelAcquisition(acquisition) {
16425
- 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 });
16426
16831
  }
16427
16832
 
16428
16833
  function failAcquisition(acquisition, failure) {
@@ -16435,26 +16840,37 @@ function App({ onCreateRemote = () => {} } = {}) {
16435
16840
  async function startPendingAcquisitionTransport(key, item, currentScope, reservationId, nowMs, signal,
16436
16841
  automaticStatusVisible) {
16437
16842
  const descriptor = tabForKey(key);
16843
+ let acquisitionStartAttempted = false;
16438
16844
  const transported = await runStartedAcquisitionTransport(
16439
16845
  () => {
16440
16846
  const admitted = inspectGovernor(currentScope, nowMs);
16441
16847
  if (!admitted.ok) return admitted;
16442
- 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: {
16443
16855
  ...item.acquisition.claim,
16444
- receipt: {
16445
- reservationId,
16446
- accessKey: currentScope.accessKey,
16447
- epochs: admitted.value.epochs,
16448
- },
16856
+ receipt,
16449
16857
  } });
16858
+ return started.then((result) => {
16859
+ if (result.ok && result.value?.status === "started") item.acquisition.claim.receipt = receipt;
16860
+ return result;
16861
+ });
16450
16862
  },
16451
16863
  () => {
16452
16864
  replaceActivePoll(item.kind, nowMs);
16453
16865
  if (item.force) coordinator.invalidate();
16866
+ const boundedSignal = AbortSignal.any([
16867
+ signal,
16868
+ AbortSignal.timeout(ACQUISITION_STARTED_DEADLINE_MS),
16869
+ ]);
16454
16870
  return commit(
16455
16871
  key,
16456
16872
  () => descriptor.fetch({
16457
- signal,
16873
+ signal: boundedSignal,
16458
16874
  entities: entityRef.current,
16459
16875
  force: item.force,
16460
16876
  catalog: workflowCatalogRef.current,
@@ -16476,13 +16892,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16476
16892
  },
16477
16893
  );
16478
16894
  if (transported.ok) return transported;
16479
- pauseCoordination(key, transported.reason);
16480
- if (pendingFailureIsTerminal(transported.reason)) {
16481
- cancelAcquisition(item.acquisition);
16482
- finishPending(key, item.intentId);
16483
- } else {
16484
- armWake("data", governorControlRetryAt(nowMs, runtime.refreshMs), dataWake);
16485
- }
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);
16486
16900
  return transported;
16487
16901
  }
16488
16902
 
@@ -16507,9 +16921,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16507
16921
 
16508
16922
  function rescheduleTab(key, at, options = {}) {
16509
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;
16510
16926
  pollDueAt = {
16511
16927
  ...pollDueAt,
16512
- [key]: Number.isFinite(step) ? at + step : Number.POSITIVE_INFINITY,
16928
+ [key]: sharedAcquisitionPollDeadline(localDueAt, sharedSnapshotDueAt[key], at, active),
16513
16929
  };
16514
16930
  }
16515
16931
 
@@ -16531,19 +16947,21 @@ function App({ onCreateRemote = () => {} } = {}) {
16531
16947
  slot += 1;
16532
16948
  }
16533
16949
  pollDueAt = opened;
16950
+ pollDeadlinesOpen = true;
16534
16951
  }
16535
16952
 
16536
16953
  function armWake(kind, at, run) {
16537
- 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);
16538
16959
  }
16539
16960
 
16540
16961
  function failClosedRateLimit(key, reason) {
16541
- liveScheduling = false;
16542
- controlEpochs = null;
16543
- pollDueAt = { ...NEVER_DUE };
16544
- wakeScheduler.clear("data");
16545
16962
  pauseCoordination(key, reason);
16546
16963
  armWake("control", governorControlRetryAt(Date.now(), runtime.refreshMs), controlWake);
16964
+ armFromState();
16547
16965
  }
16548
16966
 
16549
16967
  function attemptPendingBlockPublications(currentScope, nowMs, state = null, requestProbe = false) {
@@ -16612,7 +17030,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16612
17030
  // for a moment must not: the rows are still ours, and throwing away the
16613
17031
  // ETags with them makes the recovery round cost full price.
16614
17032
  function resetVisibleScope(nowMs, { immediate = false, discardData = true } = {}) {
17033
+ for (const item of pending.values()) {
17034
+ if (item.cleanupPending) cleanupQueue.add(item);
17035
+ }
16615
17036
  pending.clear();
17037
+ queuedManual.clear();
16616
17038
  coordinator.invalidate();
16617
17039
  setFailureContext(null);
16618
17040
  if (discardData) {
@@ -16632,10 +17054,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16632
17054
  retainedAccessKey = null;
16633
17055
  }
16634
17056
  pendingBlockPublications.clear();
16635
- liveScheduling = false;
17057
+ pollDeadlinesOpen = false;
16636
17058
  pollDueAt = { ...NEVER_DUE };
17059
+ sharedSnapshotDueAt = { ...NEVER_DUE };
16637
17060
  wakeScheduler.clear("data");
16638
- wakeScheduler.clear("heartbeat");
17061
+ heartbeatWake.stop();
16639
17062
  wakeScheduler.clear("control");
16640
17063
  setGovernorEpochs(null);
16641
17064
  setGovernorDecisions(probingGovernorDecisions());
@@ -16742,7 +17165,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16742
17165
  const wakes = governorWakeTimes(snapshot.value, nowMs, runtime.refreshMs, leaseId);
16743
17166
  armWake("control", wakes.controlAt, controlWake);
16744
17167
  const nextDataAt = Math.min(wakes.dataAt, ...Object.values(pollDueAt));
16745
- if (liveScheduling) armWake("data", nextDataAt, dataWake);
17168
+ armWake("data", nextDataAt, dataWake);
16746
17169
  }
16747
17170
 
16748
17171
  function finishPending(key, intentId) {
@@ -16750,14 +17173,71 @@ function App({ onCreateRemote = () => {} } = {}) {
16750
17173
  armFromState();
16751
17174
  }
16752
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
+
16753
17236
  function cancelPending(key, currentScope, at = Date.now()) {
16754
17237
  const item = pending.get(key);
16755
- if (!cancelCoordinatedPending(item, {
16756
- cancelGovernor: (intentId) => cancelIntent(currentScope, intentId, at),
16757
- cancelAcquisition,
16758
- })) return false;
16759
- pending.delete(key);
16760
- return true;
17238
+ if (!item) return false;
17239
+ item.cleanupScope ??= frozenGovernorScope(currentScope);
17240
+ return retryPendingCleanup(key, item, at);
16761
17241
  }
16762
17242
 
16763
17243
  // Manual and tab-switch work replaces the automatic check that would
@@ -16776,7 +17256,8 @@ function App({ onCreateRemote = () => {} } = {}) {
16776
17256
  const manual = kind === "manual";
16777
17257
  const signal = controller.signal;
16778
17258
  const monotonicNow = performance.now();
16779
- if (pendingBlockPublications.size > 0) {
17259
+ if (RATE_RESOURCES.some((resource) => tabRequestCost(key)[resource] > 0 &&
17260
+ pendingBlockPublications.has(resource))) {
16780
17261
  pauseCoordination(key, "block-unpublished");
16781
17262
  return Promise.resolve({ persisted: false, retry: false, kind, key });
16782
17263
  }
@@ -16805,27 +17286,11 @@ function App({ onCreateRemote = () => {} } = {}) {
16805
17286
  pauseCoordination(key, "unknown-scope");
16806
17287
  return Promise.resolve({ persisted: false, retry: true, kind, key });
16807
17288
  }
16808
- let protocolReady = liveScheduling;
16809
- let resourceReady = liveScheduling;
16810
- if (!liveScheduling || manual) {
16811
- const snapshot = inspectGovernor(currentScope, nowMs);
16812
- protocolReady = governorProtocolReady(
16813
- { ok: true, value: { status: "published" } },
16814
- snapshot,
16815
- nowMs,
16816
- );
16817
- resourceReady = governorDataReady(
16818
- { ok: true, value: { status: "published" } }, snapshot, key, nowMs);
16819
- if (!liveScheduling && resourceReady) {
16820
- liveScheduling = true;
16821
- openPollDeadlines(nowMs - 1);
16822
- armWake("heartbeat", nowMs + GOVERNOR_HEARTBEAT_MS, heartbeatWake);
16823
- }
16824
- }
16825
- const intentGate = runtimeIntentGate(liveScheduling, { force: manual, protocolReady });
16826
- if (intentGate.requestProbe || manual && !resourceReady) {
16827
- const snapshot = inspectGovernor(currentScope, nowMs);
16828
- 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) {
16829
17294
  for (const resource of RATE_RESOURCES) {
16830
17295
  const budget = snapshot.value?.budgets?.[resource];
16831
17296
  if (costs[resource] > 0 && budget) {
@@ -16834,21 +17299,20 @@ function App({ onCreateRemote = () => {} } = {}) {
16834
17299
  }
16835
17300
  armWake("control", nowMs + 1, controlWake);
16836
17301
  }
16837
- if (!intentGate.registerIntent) {
16838
- setTabGovernorDecision(key, { mode: "waiting", probing: true });
16839
- setTabWaiting(key, true);
16840
- return Promise.resolve({ persisted: false, retry: false, kind, key });
16841
- }
17302
+ // Durable acquisition takeover must run even while an observer is
17303
+ // unavailable. The governor fences transport at startReservation.
16842
17304
  const existing = pending.get(key);
16843
17305
  if (existing) {
16844
17306
  if (!manual || existing.kind === "manual") {
16845
17307
  return Promise.resolve({ persisted: true, retry: false, kind, key });
16846
17308
  }
17309
+ queuedManual.set(key, { force: force || queuedManual.get(key)?.force === true });
16847
17310
  cancelPending(key, currentScope, nowMs);
17311
+ if (pending.has(key)) return { persisted: true, retry: false, kind, key };
16848
17312
  }
16849
17313
  const acquisitionSubscription = ensureAcquisitionSubscription(key, identity());
16850
17314
  if (!acquisitionSubscription?.ok) {
16851
- pauseCoordination(key, acquisitionSubscription?.reason ?? "acquisition-unavailable");
17315
+ pauseCoordination(key, acquisitionSubscription?.reason ?? "acquisition-unavailable", null, true);
16852
17316
  return { persisted: false, retry: true, kind, key };
16853
17317
  }
16854
17318
  const ownership = await acquisitionEngine.refresh(acquisitionSubscription.value.id, {
@@ -16858,7 +17322,7 @@ function App({ onCreateRemote = () => {} } = {}) {
16858
17322
  force: manual || force,
16859
17323
  });
16860
17324
  if (!ownership.ok) {
16861
- pauseCoordination(key, ownership.reason);
17325
+ pauseCoordination(key, ownership.reason, null, true);
16862
17326
  return { persisted: false, retry: true, kind, key };
16863
17327
  }
16864
17328
  setRuntimeAcquisitionHold(
@@ -16885,10 +17349,15 @@ function App({ onCreateRemote = () => {} } = {}) {
16885
17349
  }
16886
17350
  setTabWaiting(key, false);
16887
17351
  rescheduleTab(key, nowMs);
16888
- 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 };
16889
17357
  }
16890
17358
  const acquisition = {
16891
17359
  id: acquisitionSubscription.value.id,
17360
+ queryKey: acquisitionSubscription.value.requestedKey ?? acquisitionSubscription.value.queryKey,
16892
17361
  demand: ownership.value.demand,
16893
17362
  claim: {
16894
17363
  nonce: ownership.value.nonce,
@@ -16908,13 +17377,42 @@ function App({ onCreateRemote = () => {} } = {}) {
16908
17377
  };
16909
17378
  const registered = registerIntent(currentScope, request);
16910
17379
  if (!registered.ok) {
16911
- cancelAcquisition(acquisition);
17380
+ pending.set(key, { intentId: null, acquisition,
17381
+ cleanupScope: frozenGovernorScope(currentScope),
17382
+ governorClean: true, cleanupPending: true });
17383
+ cancelPending(key, currentScope, nowMs);
16912
17384
  pauseCoordination(key, registered.reason);
16913
17385
  return Promise.resolve({ persisted: false, retry: true, kind, key });
16914
17386
  }
16915
- 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
+ }
16916
17413
  pending.set(key, {
16917
- intentId,
17414
+ intentId: decision.intentId ?? intentId,
17415
+ cleanupScope: frozenGovernorScope(currentScope),
16918
17416
  kind,
16919
17417
  force,
16920
17418
  manual,
@@ -16939,8 +17437,9 @@ function App({ onCreateRemote = () => {} } = {}) {
16939
17437
  if (!started.ok) {
16940
17438
  pauseCoordination(key, started.reason);
16941
17439
  if (pendingFailureIsTerminal(started.reason)) {
16942
- cancelAcquisition(acquisition);
16943
- finishPending(key, intentId);
17440
+ const item = pending.get(key);
17441
+ if (item) item.retryOnCleanup = true;
17442
+ cancelPending(key, currentScope, nowMs);
16944
17443
  }
16945
17444
  else armWake("data", governorControlRetryAt(nowMs, runtime.refreshMs), dataWake);
16946
17445
  return Promise.resolve({ persisted: true, retry: false, kind, key });
@@ -16991,21 +17490,42 @@ function App({ onCreateRemote = () => {} } = {}) {
16991
17490
  };
16992
17491
 
16993
17492
  async function resumePending(nowMs) {
17493
+ for (const item of [...cleanupQueue]) retryPendingCleanup(item.cleanupKey, item, nowMs);
16994
17494
  for (const [key, item] of [...pending]) {
17495
+ if (item.cleanupPending) {
17496
+ retryPendingCleanup(key, item, nowMs);
17497
+ continue;
17498
+ }
16995
17499
  if (inFlightRef.current[key]) continue;
16996
17500
  const currentScope = ensureScope(nowMs);
16997
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
+ }
16998
17514
  const decision = readIntentDecision(currentScope, item.intentId, nowMs, item);
16999
17515
  if (!decision.ok) {
17000
17516
  pauseCoordination(key, decision.reason);
17001
17517
  if (pendingFailureIsTerminal(decision.reason)) {
17002
- cancelAcquisition(item.acquisition);
17003
- pending.delete(key);
17518
+ item.retryOnCleanup = true;
17519
+ cancelPending(key, currentScope, nowMs);
17004
17520
  }
17005
17521
  else armWake("data", governorControlRetryAt(nowMs, runtime.refreshMs), dataWake);
17006
17522
  continue;
17007
17523
  }
17008
17524
  Object.assign(item, sharedLaneEvidence(decision.value));
17525
+ if (decision.value.status === "started") {
17526
+ cancelPending(key, currentScope, nowMs);
17527
+ continue;
17528
+ }
17009
17529
  if (decision.value.status !== "scheduled" || decision.value.notBefore > nowMs) {
17010
17530
  item.wasDeferred = true;
17011
17531
  setTabGovernorDecision(key, visibleGovernorDecision({
@@ -17020,8 +17540,8 @@ function App({ onCreateRemote = () => {} } = {}) {
17020
17540
  if (!started.ok) {
17021
17541
  pauseCoordination(key, started.reason);
17022
17542
  if (pendingFailureIsTerminal(started.reason)) {
17023
- cancelAcquisition(item.acquisition);
17024
- pending.delete(key);
17543
+ item.retryOnCleanup = true;
17544
+ cancelPending(key, currentScope, nowMs);
17025
17545
  }
17026
17546
  else armWake("data", governorControlRetryAt(nowMs, runtime.refreshMs), dataWake);
17027
17547
  armFromState(nowMs);
@@ -17061,17 +17581,21 @@ function App({ onCreateRemote = () => {} } = {}) {
17061
17581
  const wakes = governorWakeTimes(snapshot.value, nowMs, runtime.refreshMs, leaseId);
17062
17582
  const heldResources = Object.fromEntries(RATE_RESOURCES.map((resource) => {
17063
17583
  const budget = snapshot.value.budgets[resource];
17584
+ const observer = snapshot.value.observers[resource];
17064
17585
  const decision = budget
17065
17586
  ? availableForGrant({ budget, resource, nowMs })
17066
17587
  : { mode: "paused" };
17067
- 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";
17068
17591
  const retryAt = budget?.blockUntil > nowMs
17069
17592
  ? budget.blockUntil
17070
- : decision.retryAt ?? wakes.controlAt;
17071
- return [resource, { held, retryAt }];
17593
+ : observer?.nextAt ?? decision.retryAt ?? wakes.controlAt;
17594
+ return [resource, { held, retryAt: Math.max(nowMs + 1000, retryAt) }];
17072
17595
  }));
17073
17596
  const active = TABS[activeIndexRef.current].key;
17074
17597
  const previousBackgroundIndex = backgroundIndex;
17598
+ scheduledWakeBackgroundIndex = previousBackgroundIndex;
17075
17599
  const planned = pollSchedule({
17076
17600
  nowMs,
17077
17601
  floorMs: runtime.refreshMs,
@@ -17085,25 +17609,18 @@ function App({ onCreateRemote = () => {} } = {}) {
17085
17609
  inProgressCI: key === "actions" && actionsInProgress(),
17086
17610
  }])),
17087
17611
  });
17612
+ scheduledWakePolls = planned.due;
17088
17613
  pollDueAt = planned.dueAt;
17089
17614
  backgroundIndex = planned.backgroundIndex;
17090
17615
  const outcomes = await Promise.allSettled(
17091
17616
  planned.due.map(({ key, kind }) => requestTab(key, kind)),
17092
17617
  );
17093
17618
  const retryAt = governorControlRetryAt(Date.now(), runtime.refreshMs);
17094
- let retryNeeded = false;
17095
- for (const outcome of outcomes) {
17096
- if (outcome.status !== "fulfilled" || outcome.value?.retry !== true) continue;
17097
- ({ dueAt: pollDueAt, backgroundIndex } = retryPollAfterAdmissionFailure({
17098
- key: outcome.value.key,
17099
- kind: outcome.value.kind,
17100
- retryAt,
17101
- dueAt: pollDueAt,
17102
- backgroundIndex,
17103
- previousBackgroundIndex,
17104
- }));
17105
- retryNeeded = true;
17106
- }
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;
17107
17624
  if (retryNeeded) armWake("data", retryAt, dataWake);
17108
17625
  if (!cancelled) {
17109
17626
  setNow((prev) =>
@@ -17117,12 +17634,39 @@ function App({ onCreateRemote = () => {} } = {}) {
17117
17634
  // pollSchedule(). Let one callback own that transition; the settled hook
17118
17635
  // retains every later useful wake. Without this guard one pane could
17119
17636
  // consume two freshly opened lane slots while another pane received none.
17120
- const dataWake = createSingleFlightWake(runDataWake, () => {
17121
- 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
+ }
17122
17665
  });
17123
17666
 
17124
- async function controlWake() {
17667
+ async function runControlWake() {
17125
17668
  if (cancelled) return;
17669
+ for (const item of [...cleanupQueue]) retryPendingCleanup(item.cleanupKey, item);
17126
17670
  await runtimeIdentityCoordinator.refresh();
17127
17671
  if (cancelled) return;
17128
17672
  const nowMs = Date.now();
@@ -17149,27 +17693,18 @@ function App({ onCreateRemote = () => {} } = {}) {
17149
17693
  attemptPendingBlockPublications(currentScope, checkedAt, snapshot.value);
17150
17694
  }
17151
17695
  publishControlStatus(activeKey, refreshed, snapshot, checkedAt);
17152
- const controlReady = pendingBlockPublications.size === 0 && governorControlReady(
17153
- refreshed,
17154
- snapshot,
17155
- checkedAt,
17156
- );
17157
- const activeEpochChanged = controlReady && controlEpochs !== null &&
17158
- tabEpochChanged(controlEpochs, snapshot.value.epochs, activeKey);
17159
- if (controlReady) controlEpochs = { ...snapshot.value.epochs };
17160
- if (controlReady && (!liveScheduling || activeEpochChanged)) {
17161
- const starting = !liveScheduling;
17162
- liveScheduling = true;
17163
- if (starting) {
17696
+ if (currentScope && snapshot.ok) {
17697
+ if (!pollDeadlinesOpen) {
17164
17698
  openPollDeadlines(checkedAt);
17165
- armWake("heartbeat", checkedAt + GOVERNOR_HEARTBEAT_MS, heartbeatWake);
17166
- } else {
17167
- // A new accounting epoch for a resource the active tab spends: check
17168
- // it now rather than at whatever cadence it had settled into. Set
17169
- // directly rather than through rescheduleTab, whose whole job is to
17170
- // apply that cadence.
17171
- 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
+ }
17172
17706
  }
17707
+ controlEpochs = { ...snapshot.value.epochs };
17173
17708
  }
17174
17709
  if (!refreshed.ok) {
17175
17710
  armWake("control", governorControlRetryAt(checkedAt, runtime.refreshMs), controlWake);
@@ -17177,20 +17712,59 @@ function App({ onCreateRemote = () => {} } = {}) {
17177
17712
  if (pendingBlockPublications.size > 0) {
17178
17713
  armWake("control", governorControlRetryAt(checkedAt, runtime.refreshMs), controlWake);
17179
17714
  }
17715
+ if (cleanupQueue.size > 0) {
17716
+ armWake("control", governorControlRetryAt(checkedAt, runtime.refreshMs), controlWake);
17717
+ }
17180
17718
  setNow((previous) => checkedAt - previous.getTime() >= 60_000 ? new Date(checkedAt) : previous);
17181
17719
  armFromState(checkedAt);
17182
17720
  }
17183
17721
 
17184
- function heartbeatWake() {
17185
- if (cancelled) return;
17186
- const nowMs = Date.now();
17187
- const currentScope = ensureScope(nowMs);
17188
- const activeTab = TABS[activeIndexRef.current].key;
17189
- if (currentScope) heartbeatLease(currentScope, leaseId, tabRequestCost(activeTab), nowMs, activeTab);
17190
- armWake("heartbeat", nowMs + GOVERNOR_HEARTBEAT_MS, heartbeatWake);
17191
- }
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
+ });
17192
17733
 
17193
- 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() {
17194
17768
  remoteUrls = runtimeRemoteUrls.length > 0 ? runtimeRemoteUrls : await gitRemoteUrls();
17195
17769
  runtimeRemoteUrls = remoteUrls;
17196
17770
  if (cancelled) return;
@@ -17211,16 +17785,10 @@ function App({ onCreateRemote = () => {} } = {}) {
17211
17785
  const snapshot = inspectGovernor(currentScope, checkedAt);
17212
17786
  reconcileRuntimeUncertainty(currentScope, snapshot);
17213
17787
  publishControlStatus(activeKey, refreshed, snapshot, checkedAt);
17214
- const controlReady = governorControlReady(
17215
- refreshed,
17216
- snapshot,
17217
- checkedAt,
17218
- );
17219
- if (controlReady) {
17788
+ if (snapshot.ok) {
17220
17789
  controlEpochs = { ...snapshot.value.epochs };
17221
- liveScheduling = true;
17222
17790
  openPollDeadlines(checkedAt);
17223
- armWake("heartbeat", checkedAt + GOVERNOR_HEARTBEAT_MS, heartbeatWake);
17791
+ heartbeatWake.start();
17224
17792
  }
17225
17793
  if (!refreshed.ok) {
17226
17794
  armWake("control", governorControlRetryAt(checkedAt, runtime.refreshMs), controlWake);
@@ -17228,9 +17796,25 @@ function App({ onCreateRemote = () => {} } = {}) {
17228
17796
  armFromState(checkedAt);
17229
17797
  }
17230
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();
17231
17813
  void bootstrap();
17232
17814
  return () => {
17233
17815
  cancelled = true;
17816
+ livenessWake.stop();
17817
+ heartbeatWake.stop();
17234
17818
  queuedManual.clear();
17235
17819
  manualInFlight.clear();
17236
17820
  wakeScheduler.clearAll();
@@ -17322,7 +17906,7 @@ function App({ onCreateRemote = () => {} } = {}) {
17322
17906
  const items = data[tab.key];
17323
17907
  const displayError = formatTabErrorForWidth(tabError, failureContext, Math.max(1, cols - 5));
17324
17908
  const coordinationError = activeGovernorDecision?.coordinationError && !remoteSetup;
17325
- const coordinationReason = activeGovernorDecision?.reason ?? "unavailable";
17909
+ const coordinationReason = activeFailure ?? activeGovernorDecision?.reason ?? "unavailable";
17326
17910
  const coordinationCondition = coordinationError ? `${tab.key}\0${coordinationReason}` : null;
17327
17911
  useEffect(() => {
17328
17912
  setVisibleCoordinationCondition(null);
@@ -17336,7 +17920,8 @@ function App({ onCreateRemote = () => {} } = {}) {
17336
17920
  const showCoordinationNotice = coordinationCondition !== null &&
17337
17921
  visibleCoordinationCondition === coordinationCondition;
17338
17922
  const noticeLine = showCoordinationNotice
17339
- ? coordinationNotice(coordinationReason)
17923
+ ? activeFailure ? acquisitionFailureCopy(activeFailure)[cols < 40 ? "narrow" : "full"]
17924
+ : coordinationNotice(coordinationReason)
17340
17925
  : !remoteSetup && displayError ? displayError : "";
17341
17926
  const noticeTone = showCoordinationNotice ? ATTENTION : displayError ? ERROR_TEXT : undefined;
17342
17927
  const spin = SPINNER[frame % SPINNER.length];
@@ -17403,9 +17988,11 @@ function App({ onCreateRemote = () => {} } = {}) {
17403
17988
  });
17404
17989
  const staleLabel =
17405
17990
  staleFor != null && staleAt != null && now.getTime() > staleAt
17406
- ? `stale ${formatDuration(Math.min(staleFor, 359_999_000))}`
17991
+ ? formatDuration(staleFor)
17407
17992
  : null;
17408
- if (staleLabel && ["watching", "shared"].includes(semanticStatus.kind)) {
17993
+ if (staleLabel && (["watching", "shared"].includes(semanticStatus.kind) ||
17994
+ activeFailure && semanticStatus.kind === "paused" &&
17995
+ activeGovernorDecision?.cause)) {
17409
17996
  semanticStatus = refreshStatus({
17410
17997
  stale: true,
17411
17998
  disconnected: data[tab.key] !== null && !runtimeIdentityCoordinator?.current(),
@@ -17828,7 +18415,11 @@ function installCrashHandlers(unmountApp) {
17828
18415
 
17829
18416
  if (IS_MAIN && runtime.headlessMode) {
17830
18417
  try {
17831
- 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") {
17832
18423
  await runCollectorForeground(runtime.headlessMode.config);
17833
18424
  } else {
17834
18425
  await runCollectorStdioBridge();
@@ -18070,6 +18661,7 @@ export {
18070
18661
  pidIsDead,
18071
18662
  claimGovernorLock,
18072
18663
  releaseGovernorLock,
18664
+ withFileLock,
18073
18665
  withGovernorLock,
18074
18666
  registerLease,
18075
18667
  heartbeatLease,
@@ -18148,6 +18740,7 @@ export {
18148
18740
  fetchConditionalEntity,
18149
18741
  conditionalBatchResult,
18150
18742
  publishStagedEntities,
18743
+ stagedAcquisitionPublicationView,
18151
18744
  fetchAlertSource,
18152
18745
  fetchSecurity,
18153
18746
  COLLECTOR_PROTOCOL_VERSION,
@@ -18185,6 +18778,8 @@ export {
18185
18778
  createCollectorService,
18186
18779
  createCollectorAcquisitionRuntime,
18187
18780
  collectorPublicationFromResult,
18781
+ collectorBudgetRefreshAllowsAdmission,
18782
+ refreshCollectorRequiredBudget,
18188
18783
  runCollectorForeground,
18189
18784
  runCollectorStdioBridge,
18190
18785
  createLocalCollectorClient,
@@ -18235,6 +18830,7 @@ export {
18235
18830
  shouldCheckpointFreshness,
18236
18831
  ACQUISITION_STORE_VERSION,
18237
18832
  ACQUISITION_CLAIM_TTL_MS,
18833
+ ACQUISITION_STARTED_DEADLINE_MS,
18238
18834
  ACQUISITION_HEARTBEAT_MS,
18239
18835
  ACQUISITION_MAX_BYTES,
18240
18836
  ACQUISITION_MAX_ENTITY_BYTES,
@@ -18246,6 +18842,7 @@ export {
18246
18842
  acquisitionQueryForTab,
18247
18843
  acquisitionHold,
18248
18844
  acquisitionDiagnostics,
18845
+ doctorAcquisitionLockDiagnostic,
18249
18846
  acquisitionRequestMetrics,
18250
18847
  acquisitionFailureHold,
18251
18848
  setRuntimeAcquisitionHold,
@@ -18264,15 +18861,24 @@ export {
18264
18861
  conditionalRecoveryPlan,
18265
18862
  pollSchedule,
18266
18863
  retryPollAfterAdmissionFailure,
18864
+ retryFailedPollBatch,
18865
+ sharedAcquisitionPollDeadline,
18866
+ followerNeedsClaimRecheck,
18267
18867
  governorWakeTimes,
18268
18868
  governorControlReady,
18269
18869
  tabEpochChanged,
18270
18870
  governorDataReady,
18871
+ governorTabControlReady,
18271
18872
  governorControlRetryAt,
18272
18873
  createWakeScheduler,
18874
+ createRecurringWake,
18273
18875
  createSingleFlightWake,
18274
18876
  pendingFailureIsTerminal,
18275
18877
  cancelCoordinatedPending,
18878
+ coalescedAcquisitionIntentMatches,
18879
+ frozenGovernorScope,
18880
+ pendingAcquisitionMatches,
18881
+ runStartedAcquisitionTransport,
18276
18882
  runtimeIntentGate,
18277
18883
  rateLimitBlockDecision,
18278
18884
  coordinationNotice,