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.
- package/CHANGELOG.md +26 -1
- package/README.md +33 -1
- package/index.mjs +969 -363
- 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 =
|
|
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
|
|
4334
|
+
function withFileLock(lockPath, operation, {
|
|
4297
4335
|
pid = process.pid,
|
|
4298
4336
|
nonce = randomUUID(),
|
|
4299
4337
|
waitMs = GOVERNOR_LOCK_WAIT_MS,
|
|
4300
|
-
kill =
|
|
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(
|
|
4319
|
-
chmodSync(dirname(
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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 = () =>
|
|
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) =>
|
|
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
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
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(
|
|
9880
|
-
const normalized = normalizeAcquisitionDiagnosticMetadata(raw);
|
|
9881
|
-
|
|
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
|
-
|
|
9886
|
-
? "
|
|
9887
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
13227
|
-
const
|
|
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 (!
|
|
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:
|
|
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)
|
|
13966
|
-
|
|
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 (
|
|
13974
|
-
|
|
13975
|
-
return
|
|
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
|
-
|
|
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
|
|
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,
|
|
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 >=
|
|
14725
|
-
? Math.min(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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 = [...
|
|
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:
|
|
16383
|
-
hasNextPage:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16481
|
-
|
|
16482
|
-
|
|
16483
|
-
|
|
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]:
|
|
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 (
|
|
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
|
-
|
|
17057
|
+
pollDeadlinesOpen = false;
|
|
16636
17058
|
pollDueAt = { ...NEVER_DUE };
|
|
17059
|
+
sharedSnapshotDueAt = { ...NEVER_DUE };
|
|
16637
17060
|
wakeScheduler.clear("data");
|
|
16638
|
-
|
|
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
|
-
|
|
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 (!
|
|
16756
|
-
|
|
16757
|
-
|
|
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 (
|
|
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
|
-
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
|
|
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
|
-
|
|
16838
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16943
|
-
|
|
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
|
-
|
|
17003
|
-
|
|
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
|
-
|
|
17024
|
-
|
|
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 =
|
|
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
|
-
|
|
17095
|
-
|
|
17096
|
-
|
|
17097
|
-
|
|
17098
|
-
|
|
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(
|
|
17121
|
-
|
|
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
|
|
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
|
-
|
|
17153
|
-
|
|
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
|
-
|
|
17166
|
-
} else {
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
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
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
|
|
17190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
?
|
|
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
|
-
?
|
|
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 === "
|
|
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,
|