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