@cortexkit/anthropic-auth-core 2.1.0 → 2.1.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/dist/index.d.ts +2 -0
- package/dist/index.js +386 -186
- package/dist/internal-types/fs/atomic-write.d.ts +25 -0
- package/dist/internal-types/fs/index.d.ts +9 -0
- package/dist/internal-types/fs/lock-constants.d.ts +14 -0
- package/dist/internal-types/fs/private-files.d.ts +66 -0
- package/dist/internal-types/fs/with-lock.d.ts +36 -0
- package/dist/native-account-runtime.d.ts +5 -2
- package/dist/native-local-credential-service.d.ts +2 -0
- package/dist/native-lock-observer.d.ts +16 -1
- package/dist/native-runtime.d.ts +12 -5
- package/dist/native-transient-account.d.ts +2 -1
- package/dist/native-vault-runtime.d.ts +2 -0
- package/dist/sticky-routing.d.ts +12 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13922,18 +13922,43 @@ function nativeLocalCredentialValidationMatches(validation, binding, material) {
|
|
|
13922
13922
|
}
|
|
13923
13923
|
|
|
13924
13924
|
// src/native-lock-observer.ts
|
|
13925
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
13926
|
+
var detachedLocks = new AsyncLocalStorage2;
|
|
13927
|
+
function withDetachedNativeLocks(work) {
|
|
13928
|
+
return detachedLocks.run(true, work);
|
|
13929
|
+
}
|
|
13925
13930
|
function nativeLockObserver(observer) {
|
|
13926
13931
|
if (!observer)
|
|
13927
13932
|
return;
|
|
13928
13933
|
return (event) => {
|
|
13929
13934
|
try {
|
|
13930
|
-
const result = observer(event);
|
|
13935
|
+
const result = observer(detachedLocks.getStore() ? { ...event, detached: true } : event);
|
|
13931
13936
|
if (result !== null && (typeof result === "object" || typeof result === "function") && "then" in result && typeof result.then === "function") {
|
|
13932
13937
|
Promise.resolve(result).catch(() => {});
|
|
13933
13938
|
}
|
|
13934
13939
|
} catch {}
|
|
13935
13940
|
};
|
|
13936
13941
|
}
|
|
13942
|
+
async function withNativeLock(path2, options, run, acquire = withLock) {
|
|
13943
|
+
const observer = nativeLockObserver(options.onLockEvent);
|
|
13944
|
+
if (!observer)
|
|
13945
|
+
return acquire(path2, options, run);
|
|
13946
|
+
observer({ type: "acquiring", name: options.name, path: path2 });
|
|
13947
|
+
let acquired = false;
|
|
13948
|
+
try {
|
|
13949
|
+
return await acquire(path2, options, async (lease) => {
|
|
13950
|
+
acquired = true;
|
|
13951
|
+
observer({ type: "acquired", name: options.name, path: path2 });
|
|
13952
|
+
return run(lease);
|
|
13953
|
+
});
|
|
13954
|
+
} finally {
|
|
13955
|
+
if (acquired)
|
|
13956
|
+
observer({ type: "released", name: options.name, path: path2 });
|
|
13957
|
+
}
|
|
13958
|
+
}
|
|
13959
|
+
function observeNativeLocks(acquire, observer) {
|
|
13960
|
+
return (path2, options, run) => withNativeLock(path2, { ...options, onLockEvent: options.onLockEvent ?? observer }, run, acquire());
|
|
13961
|
+
}
|
|
13937
13962
|
|
|
13938
13963
|
// src/native-vault-seed.ts
|
|
13939
13964
|
import { lstat as lstat4, readFile as readFile8 } from "node:fs/promises";
|
|
@@ -14932,18 +14957,27 @@ async function renameWithWindowsSharingRetry(from, to, options = {}, beforeRetry
|
|
|
14932
14957
|
}
|
|
14933
14958
|
|
|
14934
14959
|
// src/native-runtime.ts
|
|
14960
|
+
var withLock2 = observeNativeLocks(() => withLock);
|
|
14961
|
+
|
|
14935
14962
|
class NativeRuntimeError extends Error {
|
|
14936
14963
|
code;
|
|
14964
|
+
status;
|
|
14965
|
+
retryable;
|
|
14937
14966
|
constructor(code) {
|
|
14938
14967
|
super({
|
|
14939
14968
|
"invalid-runtime": "Anthropic runtime state is invalid",
|
|
14940
14969
|
"unsafe-runtime": "Anthropic runtime file is not owner-only regular storage",
|
|
14941
14970
|
"runtime-io": "Anthropic runtime storage is unavailable",
|
|
14942
14971
|
"runtime-conflict": "Anthropic runtime observation is older than persisted state",
|
|
14943
|
-
"publication-refused": "Anthropic runtime publication was refused"
|
|
14972
|
+
"publication-refused": "Anthropic runtime publication was refused",
|
|
14973
|
+
"relay-inconsistent": "request refused: relay configuration is inconsistent; no request was sent"
|
|
14944
14974
|
}[code]);
|
|
14945
14975
|
this.code = code;
|
|
14946
14976
|
this.name = "NativeRuntimeError";
|
|
14977
|
+
if (code === "relay-inconsistent") {
|
|
14978
|
+
this.status = 400;
|
|
14979
|
+
this.retryable = false;
|
|
14980
|
+
}
|
|
14947
14981
|
}
|
|
14948
14982
|
}
|
|
14949
14983
|
function record6(value) {
|
|
@@ -15090,7 +15124,7 @@ function entry(value, id2, storageId) {
|
|
|
15090
15124
|
return true;
|
|
15091
15125
|
}
|
|
15092
15126
|
function decodeNativeRuntime(value, storageId) {
|
|
15093
|
-
if (!record6(value) || !keys3(value, ["version", "storageId", "accounts"], ["relay"]) || value.version !== 1 || !digest(storageId) || value.storageId !== storageId || !record6(value.accounts) || !optional3(value, "relay", (entry2) => record6(entry2) && keys3(entry2, ["token"]) && typeof entry2.token === "string"))
|
|
15127
|
+
if (!record6(value) || !keys3(value, ["version", "storageId", "accounts"], ["relay", "relayGeneration"]) || value.version !== 1 || !digest(storageId) || value.storageId !== storageId || !record6(value.accounts) || !optional3(value, "relayGeneration", text3) || !optional3(value, "relay", (entry2) => record6(entry2) && keys3(entry2, ["token"]) && typeof entry2.token === "string"))
|
|
15094
15128
|
throw new NativeRuntimeError("invalid-runtime");
|
|
15095
15129
|
const accounts = Object.create(null);
|
|
15096
15130
|
for (const [id2, item] of Object.entries(value.accounts)) {
|
|
@@ -15102,6 +15136,7 @@ function decodeNativeRuntime(value, storageId) {
|
|
|
15102
15136
|
version: 1,
|
|
15103
15137
|
storageId,
|
|
15104
15138
|
accounts,
|
|
15139
|
+
...typeof value.relayGeneration === "string" ? { relayGeneration: value.relayGeneration } : {},
|
|
15105
15140
|
...record6(value.relay) && typeof value.relay.token === "string" ? { relay: { token: value.relay.token } } : {}
|
|
15106
15141
|
};
|
|
15107
15142
|
}
|
|
@@ -15218,7 +15253,8 @@ async function updateNativeRuntime(path2, storageId, change, hooks = {}) {
|
|
|
15218
15253
|
async function updateNativeRuntimeOptional(path2, storageId, change, hooks) {
|
|
15219
15254
|
return writeNativeRuntimeOutcome(path2, storageId, change, {
|
|
15220
15255
|
beforeRename: hooks.beforeRename,
|
|
15221
|
-
publication: hooks.publication
|
|
15256
|
+
publication: hooks.publication,
|
|
15257
|
+
onLockEvent: hooks.onLockEvent
|
|
15222
15258
|
});
|
|
15223
15259
|
}
|
|
15224
15260
|
async function writeNativeRuntime(path2, storageId, change, hooks, firstIdentity) {
|
|
@@ -15229,7 +15265,8 @@ async function writeNativeRuntime(path2, storageId, change, hooks, firstIdentity
|
|
|
15229
15265
|
},
|
|
15230
15266
|
beforePublish: hooks.beforePublish,
|
|
15231
15267
|
afterRename: hooks.afterRename,
|
|
15232
|
-
publication: hooks.publication
|
|
15268
|
+
publication: hooks.publication,
|
|
15269
|
+
onLockEvent: hooks.onLockEvent
|
|
15233
15270
|
}, firstIdentity);
|
|
15234
15271
|
if (outcome.status !== "published")
|
|
15235
15272
|
throw new NativeRuntimeError("publication-refused");
|
|
@@ -15240,7 +15277,13 @@ async function writeNativeRuntimeOutcome(path2, storageId, change, hooks, firstI
|
|
|
15240
15277
|
const publication2 = capturePublicationOptions(hooks.publication);
|
|
15241
15278
|
try {
|
|
15242
15279
|
await mkdir9(dirname9(path2), { recursive: true, mode: 448 });
|
|
15243
|
-
return await
|
|
15280
|
+
return await withLock2(path2, {
|
|
15281
|
+
name: "native-runtime",
|
|
15282
|
+
ttlMs: 1e4,
|
|
15283
|
+
timeoutMs: 15000,
|
|
15284
|
+
renew: true,
|
|
15285
|
+
onLockEvent: hooks.onLockEvent
|
|
15286
|
+
}, async (lock) => {
|
|
15244
15287
|
const read = await readNativeRuntime(path2, storageId);
|
|
15245
15288
|
const current = read.status === "ready" ? read.state : { version: 1, storageId, accounts: {} };
|
|
15246
15289
|
const next = decodeNativeRuntime(await change(structuredClone(current)), storageId);
|
|
@@ -15638,7 +15681,11 @@ async function publishNativeLocalRefreshFailure(paths, observation, policy, hook
|
|
|
15638
15681
|
}
|
|
15639
15682
|
async function publishLocalEntry(paths, subject, matches, replacement, firstIdentity, superseded, hooks = {}) {
|
|
15640
15683
|
try {
|
|
15641
|
-
const store = createNativePoolStore({
|
|
15684
|
+
const store = createNativePoolStore({
|
|
15685
|
+
paths,
|
|
15686
|
+
quota: nativeQuotaCodec,
|
|
15687
|
+
onLockEvent: hooks.onLockEvent
|
|
15688
|
+
});
|
|
15642
15689
|
const [config, state] = nativePoolStoreLocks(paths);
|
|
15643
15690
|
const checkPool = async () => {
|
|
15644
15691
|
const read = await store.read();
|
|
@@ -15654,7 +15701,15 @@ async function publishLocalEntry(paths, subject, matches, replacement, firstIden
|
|
|
15654
15701
|
throw new NativeRuntimeError("publication-refused");
|
|
15655
15702
|
}
|
|
15656
15703
|
};
|
|
15657
|
-
return await
|
|
15704
|
+
return await withLock2(config.path, {
|
|
15705
|
+
...POOL_LOCK_DEFAULTS,
|
|
15706
|
+
name: config.name,
|
|
15707
|
+
onLockEvent: hooks.onLockEvent
|
|
15708
|
+
}, async (configLease) => withLock2(state.path, {
|
|
15709
|
+
...POOL_LOCK_DEFAULTS,
|
|
15710
|
+
name: state.name,
|
|
15711
|
+
onLockEvent: hooks.onLockEvent
|
|
15712
|
+
}, async (stateLease) => {
|
|
15658
15713
|
await checkPool();
|
|
15659
15714
|
return writeNativeRuntime(paths.runtime, paths.storageId, (current) => {
|
|
15660
15715
|
const id2 = subject.binding.rowId;
|
|
@@ -15666,7 +15721,8 @@ async function publishLocalEntry(paths, subject, matches, replacement, firstIden
|
|
|
15666
15721
|
await configLease.assertOwned();
|
|
15667
15722
|
await stateLease.assertOwned();
|
|
15668
15723
|
},
|
|
15669
|
-
beforePublish: hooks.beforePublish
|
|
15724
|
+
beforePublish: hooks.beforePublish,
|
|
15725
|
+
onLockEvent: hooks.onLockEvent
|
|
15670
15726
|
}, firstIdentity);
|
|
15671
15727
|
}));
|
|
15672
15728
|
} catch (error) {
|
|
@@ -20196,7 +20252,8 @@ var optionKeys = [
|
|
|
20196
20252
|
"failurePolicy",
|
|
20197
20253
|
"now",
|
|
20198
20254
|
"refreshToken",
|
|
20199
|
-
"resolveIdentity"
|
|
20255
|
+
"resolveIdentity",
|
|
20256
|
+
"onLockEvent"
|
|
20200
20257
|
];
|
|
20201
20258
|
var pathKeys = [
|
|
20202
20259
|
"legacyConfig",
|
|
@@ -20283,6 +20340,7 @@ function createNativeLocalCredentialService(options) {
|
|
|
20283
20340
|
const publicationGuard = requiredFunction(descriptors, "publicationGuard");
|
|
20284
20341
|
const externalPolicy = requiredFunction(descriptors, "externalPolicy");
|
|
20285
20342
|
const failurePolicy = requiredFunction(descriptors, "failurePolicy");
|
|
20343
|
+
const onLockEvent = optionalFunction(descriptors, "onLockEvent");
|
|
20286
20344
|
const now = optionalFunction(descriptors, "now");
|
|
20287
20345
|
const refreshToken = optionalFunction(descriptors, "refreshToken");
|
|
20288
20346
|
const resolveIdentity = optionalFunction(descriptors, "resolveIdentity");
|
|
@@ -20290,6 +20348,7 @@ function createNativeLocalCredentialService(options) {
|
|
|
20290
20348
|
if (await publicationGuard(event) !== true)
|
|
20291
20349
|
return;
|
|
20292
20350
|
const hooks = {
|
|
20351
|
+
onLockEvent,
|
|
20293
20352
|
beforePublish: async () => {
|
|
20294
20353
|
if (await publicationGuard(event) !== true)
|
|
20295
20354
|
throw new NativeRuntimeError("publication-refused");
|
|
@@ -20324,6 +20383,7 @@ function createNativeLocalCredentialService(options) {
|
|
|
20324
20383
|
const coordinator = createNativeRefreshCoordinator({
|
|
20325
20384
|
paths,
|
|
20326
20385
|
quota: nativeQuotaCodec,
|
|
20386
|
+
onLockEvent,
|
|
20327
20387
|
...now ? { now } : {},
|
|
20328
20388
|
...refreshToken ? { refreshToken } : {},
|
|
20329
20389
|
...resolveIdentity ? { resolveIdentity } : {},
|
|
@@ -20636,7 +20696,8 @@ function isNativeTransientAccountError(value) {
|
|
|
20636
20696
|
return typeof value === "object" && value !== null && transientAccountErrors.has(value);
|
|
20637
20697
|
}
|
|
20638
20698
|
var NATIVE_POOL_WRITE_JOIN_BUDGET_MS = 2000;
|
|
20639
|
-
async function joinNativePoolWrite(paths, signals, check) {
|
|
20699
|
+
async function joinNativePoolWrite(paths, signals, check, onLockEvent) {
|
|
20700
|
+
const observer = nativeLockObserver(onLockEvent);
|
|
20640
20701
|
const started = performance.now();
|
|
20641
20702
|
const remaining = () => NATIVE_POOL_WRITE_JOIN_BUDGET_MS - (performance.now() - started);
|
|
20642
20703
|
const held = [];
|
|
@@ -20650,14 +20711,17 @@ async function joinNativePoolWrite(paths, signals, check) {
|
|
|
20650
20711
|
outcome = "deadline";
|
|
20651
20712
|
break;
|
|
20652
20713
|
}
|
|
20714
|
+
observer?.({ type: "acquiring", path: lock.path, name: lock.name });
|
|
20653
20715
|
const lease = await acquireRefreshFileLock({
|
|
20654
20716
|
path: lock.path,
|
|
20655
20717
|
name: lock.name,
|
|
20656
20718
|
ttlMs: POOL_LOCK_DEFAULTS.ttlMs,
|
|
20657
20719
|
renew: false
|
|
20658
20720
|
});
|
|
20659
|
-
if (lease)
|
|
20721
|
+
if (lease) {
|
|
20660
20722
|
held.push(lease);
|
|
20723
|
+
observer?.({ type: "acquired", path: lock.path, name: lock.name });
|
|
20724
|
+
}
|
|
20661
20725
|
if (remaining() <= 0) {
|
|
20662
20726
|
outcome = "deadline";
|
|
20663
20727
|
break;
|
|
@@ -21261,7 +21325,17 @@ function createNativeVaultRuntime(options) {
|
|
|
21261
21325
|
const connectionFile = nativeRuntimeConnectionFile(options.host, env);
|
|
21262
21326
|
const identity2 = options.identity ? { ...options.identity } : undefined;
|
|
21263
21327
|
const shutdown = new AbortController;
|
|
21264
|
-
const store = createNativePoolStore({
|
|
21328
|
+
const store = createNativePoolStore({
|
|
21329
|
+
paths,
|
|
21330
|
+
quota: nativeQuotaCodec,
|
|
21331
|
+
onLockEvent: options.onLockEvent
|
|
21332
|
+
});
|
|
21333
|
+
const observeLock = nativeLockObserver(options.onLockEvent);
|
|
21334
|
+
const observeRosterLock = () => observeLock?.({
|
|
21335
|
+
type: "acquiring",
|
|
21336
|
+
name: "claustrum-roster-write",
|
|
21337
|
+
path: paths.roster
|
|
21338
|
+
});
|
|
21265
21339
|
let rosterWatch;
|
|
21266
21340
|
let rosterWatching = false;
|
|
21267
21341
|
let rosterView;
|
|
@@ -21420,7 +21494,7 @@ function createNativeVaultRuntime(options) {
|
|
|
21420
21494
|
function poll() {
|
|
21421
21495
|
if (polling)
|
|
21422
21496
|
return polling;
|
|
21423
|
-
polling = (async () => {
|
|
21497
|
+
polling = withDetachedNativeLocks(async () => {
|
|
21424
21498
|
const allowed = await Promise.all([...watchers].map(async (watch) => !watch.shouldRefresh || await watch.shouldRefresh()));
|
|
21425
21499
|
if (shutdown.signal.aborted || !allowed.some(Boolean))
|
|
21426
21500
|
return;
|
|
@@ -21440,7 +21514,7 @@ function createNativeVaultRuntime(options) {
|
|
|
21440
21514
|
return watch.onRefresh?.();
|
|
21441
21515
|
}).catch(() => {});
|
|
21442
21516
|
}
|
|
21443
|
-
})
|
|
21517
|
+
}).finally(() => {
|
|
21444
21518
|
polling = undefined;
|
|
21445
21519
|
});
|
|
21446
21520
|
return polling;
|
|
@@ -21474,6 +21548,7 @@ function createNativeVaultRuntime(options) {
|
|
|
21474
21548
|
await requireActive(signal);
|
|
21475
21549
|
if (absentOnly && !await nativeVaultFileAbsent(paths.config))
|
|
21476
21550
|
return;
|
|
21551
|
+
observeRosterLock();
|
|
21477
21552
|
await mutateVaultRoster(paths.roster, async (roster, lease) => {
|
|
21478
21553
|
if (absentOnly && !await nativeVaultFileAbsent(paths.config))
|
|
21479
21554
|
return { result: undefined };
|
|
@@ -21487,6 +21562,7 @@ function createNativeVaultRuntime(options) {
|
|
|
21487
21562
|
const writer = createNativePoolStore({
|
|
21488
21563
|
paths,
|
|
21489
21564
|
quota: nativeQuotaCodec,
|
|
21565
|
+
onLockEvent: options.onLockEvent,
|
|
21490
21566
|
onStep: async (point) => {
|
|
21491
21567
|
if (point === "before-config-write" || point === "before-state-write") {
|
|
21492
21568
|
await requireActive(signal);
|
|
@@ -21583,6 +21659,12 @@ function createNativeVaultRuntime(options) {
|
|
|
21583
21659
|
if (!await active())
|
|
21584
21660
|
return;
|
|
21585
21661
|
check(signal);
|
|
21662
|
+
if (!refreshing)
|
|
21663
|
+
observeLock?.({
|
|
21664
|
+
type: "acquiring",
|
|
21665
|
+
name: "claustrum-roster",
|
|
21666
|
+
path: paths.roster
|
|
21667
|
+
});
|
|
21586
21668
|
refreshing ??= refreshVaultRoster({
|
|
21587
21669
|
path: paths.roster,
|
|
21588
21670
|
custody: options.discover ? { discover: options.discover } : custody,
|
|
@@ -21873,6 +21955,7 @@ function createNativeVaultRuntime(options) {
|
|
|
21873
21955
|
]);
|
|
21874
21956
|
if ((versions.get(key) ?? 0) > receipt.recordVersion)
|
|
21875
21957
|
return false;
|
|
21958
|
+
observeRosterLock();
|
|
21876
21959
|
return mutateVaultRoster(paths.roster, async (roster, lease) => {
|
|
21877
21960
|
const row = await rowFor(roster, route.logicalId);
|
|
21878
21961
|
if (!row?.accountIdentity || row.routeId !== route.routeId || row.accountIdentity !== receipt.assertedAccountIdentity || row.credentialId !== receipt.credentialId && !row.aliases?.includes(receipt.credentialId))
|
|
@@ -21900,6 +21983,7 @@ function createNativeVaultRuntime(options) {
|
|
|
21900
21983
|
kept = true;
|
|
21901
21984
|
return current;
|
|
21902
21985
|
}, {
|
|
21986
|
+
onLockEvent: options.onLockEvent,
|
|
21903
21987
|
beforeRename: async () => {
|
|
21904
21988
|
await requireActive();
|
|
21905
21989
|
const pinned = await rowFor(roster, route.logicalId);
|
|
@@ -22032,6 +22116,7 @@ function nativeVaultRuntimeKey(options) {
|
|
|
22032
22116
|
options.discover,
|
|
22033
22117
|
options.readToken,
|
|
22034
22118
|
options.now,
|
|
22119
|
+
options.onLockEvent,
|
|
22035
22120
|
options.setTimeout,
|
|
22036
22121
|
options.clearTimeout,
|
|
22037
22122
|
options.onRosterNoticeHandled
|
|
@@ -22354,7 +22439,9 @@ class StickySessionRouter {
|
|
|
22354
22439
|
loaded = false;
|
|
22355
22440
|
state;
|
|
22356
22441
|
stateMtimeNs;
|
|
22357
|
-
|
|
22442
|
+
publicationChain = Promise.resolve();
|
|
22443
|
+
seedPublication;
|
|
22444
|
+
changes = new Map;
|
|
22358
22445
|
seedFinalized = false;
|
|
22359
22446
|
constructor(options) {
|
|
22360
22447
|
this.options = options;
|
|
@@ -22400,17 +22487,22 @@ class StickySessionRouter {
|
|
|
22400
22487
|
if (before !== after)
|
|
22401
22488
|
continue;
|
|
22402
22489
|
this.state = state;
|
|
22490
|
+
this.overlayChanges();
|
|
22403
22491
|
this.stateMtimeNs = after;
|
|
22404
22492
|
this.loaded = true;
|
|
22405
22493
|
return;
|
|
22406
22494
|
}
|
|
22407
22495
|
this.state = await this.readState();
|
|
22496
|
+
this.overlayChanges();
|
|
22408
22497
|
this.stateMtimeNs = await this.readStateMtimeNs();
|
|
22409
22498
|
this.loaded = true;
|
|
22410
22499
|
}
|
|
22411
|
-
async acquireLock(wait = true) {
|
|
22500
|
+
async acquireLock(wait = true, detached = false) {
|
|
22501
|
+
const observer = nativeLockObserver(this.options.onLockEvent);
|
|
22502
|
+
const event = { name: "write", path: this.options.path, detached };
|
|
22503
|
+
observer?.({ ...event, type: "acquiring" });
|
|
22412
22504
|
await mkdir10(dirname12(this.options.path), { recursive: true, mode: 448 });
|
|
22413
|
-
const deadline =
|
|
22505
|
+
const deadline = performance.now() + STICKY_ROUTING_LOCK_WAIT_MS;
|
|
22414
22506
|
while (true) {
|
|
22415
22507
|
const lock = await acquireRefreshFileLock2({
|
|
22416
22508
|
name: "write",
|
|
@@ -22418,32 +22510,42 @@ class StickySessionRouter {
|
|
|
22418
22510
|
ttlMs: STICKY_ROUTING_LOCK_TTL_MS,
|
|
22419
22511
|
renew: true
|
|
22420
22512
|
});
|
|
22421
|
-
if (lock)
|
|
22422
|
-
|
|
22423
|
-
|
|
22513
|
+
if (lock) {
|
|
22514
|
+
observer?.({ ...event, type: "acquired" });
|
|
22515
|
+
return {
|
|
22516
|
+
assertOwned: () => lock.assertOwned(),
|
|
22517
|
+
release: async () => {
|
|
22518
|
+
try {
|
|
22519
|
+
await lock.release();
|
|
22520
|
+
} finally {
|
|
22521
|
+
observer?.({ ...event, type: "released" });
|
|
22522
|
+
}
|
|
22523
|
+
}
|
|
22524
|
+
};
|
|
22525
|
+
}
|
|
22526
|
+
observer?.({ ...event, type: "contended" });
|
|
22527
|
+
if (!wait || performance.now() >= deadline) {
|
|
22424
22528
|
throw new Error("Timed out acquiring sticky routing state lock");
|
|
22425
22529
|
}
|
|
22426
22530
|
await new Promise((resolve9) => setTimeout(resolve9, STICKY_ROUTING_LOCK_POLL_MS));
|
|
22427
22531
|
}
|
|
22428
22532
|
}
|
|
22429
|
-
|
|
22533
|
+
recoverLocalPins() {
|
|
22430
22534
|
const source2 = this.options.localRecoveryPath;
|
|
22431
22535
|
if (!source2 || source2 === this.options.path)
|
|
22432
22536
|
return;
|
|
22433
22537
|
const key = JSON.stringify([this.options.path, source2]);
|
|
22434
22538
|
let recovery = localRecoveries.get(key);
|
|
22435
22539
|
if (!recovery) {
|
|
22436
|
-
recovery = this.
|
|
22437
|
-
logger.warn("sticky-routing", "local pin recovery unavailable", {
|
|
22438
|
-
reason: "recovery-failed"
|
|
22439
|
-
});
|
|
22440
|
-
});
|
|
22540
|
+
recovery = this.enqueuePublication("recovery", () => this.fillLocalPins(source2));
|
|
22441
22541
|
localRecoveries.set(key, recovery);
|
|
22542
|
+
} else {
|
|
22543
|
+
const shared2 = recovery;
|
|
22544
|
+
this.publicationChain = this.publicationChain.then(() => shared2);
|
|
22442
22545
|
}
|
|
22443
|
-
await recovery;
|
|
22444
22546
|
}
|
|
22445
22547
|
async fillLocalPins(source2) {
|
|
22446
|
-
const lock = await this.acquireLock(
|
|
22548
|
+
const lock = await this.acquireLock(true, true);
|
|
22447
22549
|
try {
|
|
22448
22550
|
let legacy;
|
|
22449
22551
|
try {
|
|
@@ -22498,9 +22600,7 @@ class StickySessionRouter {
|
|
|
22498
22600
|
} finally {
|
|
22499
22601
|
await rm7(tempPath, { force: true }).catch(() => {});
|
|
22500
22602
|
}
|
|
22501
|
-
this.
|
|
22502
|
-
this.stateMtimeNs = await this.readStateMtimeNs();
|
|
22503
|
-
this.loaded = true;
|
|
22603
|
+
this.loaded = false;
|
|
22504
22604
|
}
|
|
22505
22605
|
async writeResolutionState(state, assertOwned, operation) {
|
|
22506
22606
|
let ownershipLost = false;
|
|
@@ -22512,63 +22612,59 @@ class StickySessionRouter {
|
|
|
22512
22612
|
ownershipLost = true;
|
|
22513
22613
|
throw error;
|
|
22514
22614
|
}
|
|
22515
|
-
});
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
throw error;
|
|
22615
|
+
}, false);
|
|
22616
|
+
return true;
|
|
22617
|
+
} catch {
|
|
22519
22618
|
logger.warn("sticky-routing", "assignment write refused", {
|
|
22520
22619
|
operation,
|
|
22521
|
-
reason: "ownership-lost"
|
|
22620
|
+
reason: ownershipLost ? "ownership-lost" : "storage-unavailable"
|
|
22522
22621
|
});
|
|
22622
|
+
return false;
|
|
22523
22623
|
}
|
|
22524
22624
|
}
|
|
22525
22625
|
async prepareLegacySeed(knownRoster) {
|
|
22526
22626
|
const seed = this.options.vaultSeed;
|
|
22527
|
-
if (!seed || this.seedFinalized)
|
|
22627
|
+
if (!seed || this.seedFinalized || this.seedPublication)
|
|
22528
22628
|
return;
|
|
22529
|
-
|
|
22530
|
-
await this.refreshStateIfChanged();
|
|
22531
|
-
} catch {
|
|
22532
|
-
this.seedFinalized = true;
|
|
22629
|
+
if (knownRoster !== undefined && !knownRoster?.complete)
|
|
22533
22630
|
return;
|
|
22534
|
-
|
|
22535
|
-
|
|
22536
|
-
|
|
22537
|
-
return;
|
|
22538
|
-
}
|
|
22539
|
-
const roster = knownRoster === undefined ? await readVaultRoster(seed.roster) : knownRoster;
|
|
22540
|
-
if (!roster?.complete)
|
|
22541
|
-
return;
|
|
22542
|
-
const lock = await this.acquireLock();
|
|
22543
|
-
try {
|
|
22544
|
-
const state = await this.readState();
|
|
22545
|
-
if (state.vaultLegacySeed !== "pending") {
|
|
22546
|
-
this.seedFinalized = true;
|
|
22631
|
+
this.seedPublication = this.enqueuePublication("seed", async () => {
|
|
22632
|
+
const roster = knownRoster === undefined ? await readVaultRoster(seed.roster) : knownRoster;
|
|
22633
|
+
if (!roster?.complete)
|
|
22547
22634
|
return;
|
|
22548
|
-
|
|
22549
|
-
|
|
22550
|
-
|
|
22551
|
-
|
|
22552
|
-
|
|
22553
|
-
|
|
22554
|
-
if (!/^[0-9a-f]{64}$/.test(key) || Object.hasOwn(state.assignments, key))
|
|
22555
|
-
continue;
|
|
22556
|
-
const assignment = normalizeAssignment(raw);
|
|
22557
|
-
const target2 = assignment && routes.get(assignment.accountId);
|
|
22558
|
-
if (assignment && target2)
|
|
22559
|
-
state.assignments[key] = {
|
|
22560
|
-
...assignment,
|
|
22561
|
-
accountId: target2.routeId,
|
|
22562
|
-
accountIdentity: target2.accountIdentity
|
|
22563
|
-
};
|
|
22635
|
+
const lock = await this.acquireLock(true, true);
|
|
22636
|
+
try {
|
|
22637
|
+
const state = await this.readState();
|
|
22638
|
+
if (state.vaultLegacySeed !== "pending") {
|
|
22639
|
+
this.seedFinalized = true;
|
|
22640
|
+
return;
|
|
22564
22641
|
}
|
|
22642
|
+
const source2 = await readNativeVaultLegacyJson(seed.legacyConfig);
|
|
22643
|
+
const routes = nativeVaultLegacyRoutes(source2, roster);
|
|
22644
|
+
const legacy = await readNativeVaultLegacyJson(getStickyRoutingStatePath(seed.legacyConfig));
|
|
22645
|
+
if (isRecord12(legacy) && legacy.version === 1 && isRecord12(legacy.assignments)) {
|
|
22646
|
+
for (const [key, raw] of Object.entries(legacy.assignments)) {
|
|
22647
|
+
if (!/^[0-9a-f]{64}$/.test(key) || Object.hasOwn(state.assignments, key))
|
|
22648
|
+
continue;
|
|
22649
|
+
const assignment = normalizeAssignment(raw);
|
|
22650
|
+
const target2 = assignment && routes.get(assignment.accountId);
|
|
22651
|
+
if (assignment && target2)
|
|
22652
|
+
state.assignments[key] = {
|
|
22653
|
+
...assignment,
|
|
22654
|
+
accountId: target2.routeId,
|
|
22655
|
+
accountIdentity: target2.accountIdentity
|
|
22656
|
+
};
|
|
22657
|
+
}
|
|
22658
|
+
}
|
|
22659
|
+
state.vaultLegacySeed = "complete";
|
|
22660
|
+
await this.writeState(state, () => lock.assertOwned(), false);
|
|
22661
|
+
this.seedFinalized = true;
|
|
22662
|
+
} finally {
|
|
22663
|
+
await lock.release();
|
|
22565
22664
|
}
|
|
22566
|
-
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
} finally {
|
|
22570
|
-
await lock.release();
|
|
22571
|
-
}
|
|
22665
|
+
}).finally(() => {
|
|
22666
|
+
this.seedPublication = undefined;
|
|
22667
|
+
});
|
|
22572
22668
|
}
|
|
22573
22669
|
logicalAccountId(assignment) {
|
|
22574
22670
|
return assignment.accountId === this.options.vaultSeed?.mainRouteId ? STICKY_ROUTING_MAIN_ACCOUNT_ID : assignment.accountId;
|
|
@@ -22577,19 +22673,86 @@ class StickySessionRouter {
|
|
|
22577
22673
|
this.options.vaultSeed = seed;
|
|
22578
22674
|
}
|
|
22579
22675
|
scheduleTouch(key) {
|
|
22580
|
-
|
|
22581
|
-
|
|
22676
|
+
const assignment = this.state.assignments[key];
|
|
22677
|
+
if (assignment)
|
|
22678
|
+
this.scheduleChange(key, assignment, "touch");
|
|
22679
|
+
}
|
|
22680
|
+
overlayChanges() {
|
|
22681
|
+
for (const [key, change] of this.changes) {
|
|
22682
|
+
if (change.assignment)
|
|
22683
|
+
this.state.assignments[key] = { ...change.assignment };
|
|
22684
|
+
else
|
|
22685
|
+
delete this.state.assignments[key];
|
|
22686
|
+
}
|
|
22687
|
+
}
|
|
22688
|
+
enqueuePublication(operation, work) {
|
|
22689
|
+
const pending = this.publicationChain.then(work).catch(() => {
|
|
22690
|
+
if (operation === "recovery") {
|
|
22691
|
+
logger.warn("sticky-routing", "local pin recovery unavailable", {
|
|
22692
|
+
reason: "recovery-failed"
|
|
22693
|
+
});
|
|
22694
|
+
return;
|
|
22695
|
+
}
|
|
22696
|
+
logger.warn("sticky-routing", "assignment write refused", {
|
|
22697
|
+
operation,
|
|
22698
|
+
reason: "storage-unavailable"
|
|
22699
|
+
});
|
|
22700
|
+
});
|
|
22701
|
+
this.publicationChain = pending;
|
|
22702
|
+
return pending;
|
|
22703
|
+
}
|
|
22704
|
+
scheduleChange(key, assignment, operation) {
|
|
22705
|
+
const change = {
|
|
22706
|
+
assignment: assignment ? { ...assignment } : undefined,
|
|
22707
|
+
operation,
|
|
22708
|
+
queued: false
|
|
22709
|
+
};
|
|
22710
|
+
this.changes.set(key, change);
|
|
22711
|
+
this.overlayChanges();
|
|
22712
|
+
this.retryChange(key);
|
|
22713
|
+
}
|
|
22714
|
+
retryChange(key) {
|
|
22715
|
+
const change = this.changes.get(key);
|
|
22716
|
+
if (!change || change.queued)
|
|
22717
|
+
return;
|
|
22718
|
+
change.queued = true;
|
|
22719
|
+
this.enqueuePublication(change.operation, async () => {
|
|
22720
|
+
if (this.changes.get(key) !== change)
|
|
22721
|
+
return;
|
|
22722
|
+
const lock = await this.acquireLock(true, true);
|
|
22582
22723
|
try {
|
|
22583
22724
|
const state = await this.readState();
|
|
22584
|
-
|
|
22585
|
-
if (!assignment)
|
|
22725
|
+
if (this.changes.get(key) !== change)
|
|
22586
22726
|
return;
|
|
22587
|
-
|
|
22588
|
-
|
|
22727
|
+
if (change.assignment) {
|
|
22728
|
+
if (change.operation === "touch") {
|
|
22729
|
+
const current = state.assignments[key];
|
|
22730
|
+
if (current?.accountId !== change.assignment.accountId || current.accountIdentity !== change.assignment.accountIdentity || current.affinityModelId !== change.assignment.affinityModelId || current.family !== change.assignment.family) {
|
|
22731
|
+
if (this.changes.get(key) === change)
|
|
22732
|
+
this.changes.delete(key);
|
|
22733
|
+
return;
|
|
22734
|
+
}
|
|
22735
|
+
current.lastSeenAt = Math.max(current.lastSeenAt, change.assignment.lastSeenAt);
|
|
22736
|
+
} else
|
|
22737
|
+
state.assignments[key] = { ...change.assignment };
|
|
22738
|
+
} else
|
|
22739
|
+
delete state.assignments[key];
|
|
22740
|
+
const saved = await this.writeResolutionState(state, () => lock.assertOwned(), change.operation);
|
|
22741
|
+
if (saved && this.changes.get(key) === change)
|
|
22742
|
+
this.changes.delete(key);
|
|
22589
22743
|
} finally {
|
|
22590
|
-
await lock.release();
|
|
22744
|
+
await lock.release().catch(() => {});
|
|
22591
22745
|
}
|
|
22592
|
-
}).
|
|
22746
|
+
}).finally(() => {
|
|
22747
|
+
change.queued = false;
|
|
22748
|
+
});
|
|
22749
|
+
}
|
|
22750
|
+
async drain() {
|
|
22751
|
+
let pending;
|
|
22752
|
+
do {
|
|
22753
|
+
pending = this.publicationChain;
|
|
22754
|
+
await pending;
|
|
22755
|
+
} while (pending !== this.publicationChain);
|
|
22593
22756
|
}
|
|
22594
22757
|
selectCandidate(input) {
|
|
22595
22758
|
let candidates = [...input.candidates];
|
|
@@ -22629,21 +22792,21 @@ class StickySessionRouter {
|
|
|
22629
22792
|
async resolve(input) {
|
|
22630
22793
|
if (!input.sessionId)
|
|
22631
22794
|
return null;
|
|
22632
|
-
|
|
22795
|
+
this.recoverLocalPins();
|
|
22633
22796
|
await this.prepareLegacySeed();
|
|
22634
|
-
await this.refreshStateIfChanged();
|
|
22797
|
+
await this.refreshStateIfChanged().catch(() => {});
|
|
22635
22798
|
const key = sessionKey(input.sessionId);
|
|
22636
22799
|
const affinityModelId = input.affinityModelId ?? input.modelId;
|
|
22637
|
-
const matchesAffinity = (
|
|
22800
|
+
const matchesAffinity = (assignment2) => affinityModelId !== undefined ? assignment2.affinityModelId === affinityModelId : assignment2.family === input.family;
|
|
22638
22801
|
const identityForAccount = (accountId) => {
|
|
22639
22802
|
const identity2 = input.accountIdentities?.get(accountId);
|
|
22640
22803
|
return typeof identity2 === "string" && identity2.length > 0 && identity2.trim() === identity2 ? identity2 : undefined;
|
|
22641
22804
|
};
|
|
22642
|
-
const matchesAccountIdentity = (
|
|
22805
|
+
const matchesAccountIdentity = (assignment2) => {
|
|
22643
22806
|
if (!input.accountIdentities)
|
|
22644
22807
|
return true;
|
|
22645
|
-
const identity2 = identityForAccount(this.logicalAccountId(
|
|
22646
|
-
return identity2 !== undefined &&
|
|
22808
|
+
const identity2 = identityForAccount(this.logicalAccountId(assignment2));
|
|
22809
|
+
return identity2 !== undefined && assignment2.accountIdentity === identity2;
|
|
22647
22810
|
};
|
|
22648
22811
|
const cached = this.state.assignments[key];
|
|
22649
22812
|
if (cached && this.assignmentIsActive(cached) && matchesAffinity(cached) && matchesAccountIdentity(cached) && input.retainAccountIds.has(this.logicalAccountId(cached)) && !input.excludeAccountIds?.has(this.logicalAccountId(cached))) {
|
|
@@ -22651,6 +22814,7 @@ class StickySessionRouter {
|
|
|
22651
22814
|
cached.lastSeenAt = this.now();
|
|
22652
22815
|
this.scheduleTouch(key);
|
|
22653
22816
|
}
|
|
22817
|
+
this.retryChange(key);
|
|
22654
22818
|
return {
|
|
22655
22819
|
accountId: this.logicalAccountId(cached),
|
|
22656
22820
|
assignment: { ...cached },
|
|
@@ -22658,81 +22822,69 @@ class StickySessionRouter {
|
|
|
22658
22822
|
migrated: false
|
|
22659
22823
|
};
|
|
22660
22824
|
}
|
|
22661
|
-
const
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
|
|
22669
|
-
|
|
22670
|
-
accountId: this.logicalAccountId(current),
|
|
22671
|
-
assignment: { ...current },
|
|
22672
|
-
created: false,
|
|
22673
|
-
migrated: false
|
|
22674
|
-
};
|
|
22675
|
-
}
|
|
22676
|
-
const affinityChanged = Boolean(current && !matchesAffinity(current));
|
|
22677
|
-
const identityChanged = Boolean(current && !matchesAccountIdentity(current));
|
|
22678
|
-
if (affinityChanged || identityChanged) {
|
|
22679
|
-
delete state.assignments[key];
|
|
22680
|
-
}
|
|
22681
|
-
const candidates = input.candidates.filter((candidate) => !input.excludeAccountIds?.has(candidate.accountId) && (!input.accountIdentities || identityForAccount(candidate.accountId) !== undefined));
|
|
22682
|
-
const preferred = !affinityChanged && !identityChanged && input.preferredAccountId ? candidates.find((candidate) => candidate.accountId === input.preferredAccountId && stickyRouteCandidateWeight({
|
|
22683
|
-
candidate,
|
|
22684
|
-
family: input.family,
|
|
22685
|
-
modelId: input.modelId,
|
|
22686
|
-
storage: input.storage,
|
|
22687
|
-
now: this.now()
|
|
22688
|
-
}) > 0) : undefined;
|
|
22689
|
-
const selected = preferred ?? this.selectCandidate({ ...input, candidates, state });
|
|
22690
|
-
if (!selected) {
|
|
22691
|
-
if (current) {
|
|
22692
|
-
delete state.assignments[key];
|
|
22693
|
-
await this.writeResolutionState(state, () => lock.assertOwned(), "delete");
|
|
22694
|
-
}
|
|
22695
|
-
return null;
|
|
22696
|
-
}
|
|
22697
|
-
const now = this.now();
|
|
22698
|
-
const selectedQuota = knownQuota(selected.quota);
|
|
22699
|
-
const assignment = {
|
|
22700
|
-
accountId: selected.accountId,
|
|
22701
|
-
...input.accountIdentities ? { accountIdentity: identityForAccount(selected.accountId) } : {},
|
|
22702
|
-
family: input.family,
|
|
22703
|
-
affinityModelId,
|
|
22704
|
-
assignedAt: now,
|
|
22705
|
-
lastSeenAt: now,
|
|
22706
|
-
initialInputBytes: Math.max(1, input.inputBytes),
|
|
22707
|
-
quotaCheckedAt: selectedQuota ? snapshotCheckedAt(selectedQuota) : 0
|
|
22708
|
-
};
|
|
22709
|
-
state.assignments[key] = assignment;
|
|
22710
|
-
await this.writeResolutionState(state, () => lock.assertOwned(), "assign");
|
|
22825
|
+
const state = structuredClone(this.state);
|
|
22826
|
+
const save = (operation) => {
|
|
22827
|
+
this.scheduleChange(key, state.assignments[key], operation);
|
|
22828
|
+
};
|
|
22829
|
+
this.prune(state);
|
|
22830
|
+
const current = state.assignments[key];
|
|
22831
|
+
if (current && matchesAffinity(current) && matchesAccountIdentity(current) && input.retainAccountIds.has(this.logicalAccountId(current)) && !input.excludeAccountIds?.has(this.logicalAccountId(current))) {
|
|
22832
|
+
current.lastSeenAt = this.now();
|
|
22833
|
+
save("touch");
|
|
22711
22834
|
return {
|
|
22712
|
-
accountId:
|
|
22713
|
-
assignment: { ...
|
|
22714
|
-
created:
|
|
22715
|
-
migrated:
|
|
22835
|
+
accountId: this.logicalAccountId(current),
|
|
22836
|
+
assignment: { ...current },
|
|
22837
|
+
created: false,
|
|
22838
|
+
migrated: false
|
|
22716
22839
|
};
|
|
22717
|
-
} finally {
|
|
22718
|
-
await lock.release();
|
|
22719
22840
|
}
|
|
22841
|
+
const affinityChanged = Boolean(current && !matchesAffinity(current));
|
|
22842
|
+
const identityChanged = Boolean(current && !matchesAccountIdentity(current));
|
|
22843
|
+
if (affinityChanged || identityChanged) {
|
|
22844
|
+
delete state.assignments[key];
|
|
22845
|
+
}
|
|
22846
|
+
const candidates = input.candidates.filter((candidate) => !input.excludeAccountIds?.has(candidate.accountId) && (!input.accountIdentities || identityForAccount(candidate.accountId) !== undefined));
|
|
22847
|
+
const preferred = !affinityChanged && !identityChanged && input.preferredAccountId ? candidates.find((candidate) => candidate.accountId === input.preferredAccountId && stickyRouteCandidateWeight({
|
|
22848
|
+
candidate,
|
|
22849
|
+
family: input.family,
|
|
22850
|
+
modelId: input.modelId,
|
|
22851
|
+
storage: input.storage,
|
|
22852
|
+
now: this.now()
|
|
22853
|
+
}) > 0) : undefined;
|
|
22854
|
+
const selected = preferred ?? this.selectCandidate({ ...input, candidates, state });
|
|
22855
|
+
if (!selected) {
|
|
22856
|
+
if (current) {
|
|
22857
|
+
delete state.assignments[key];
|
|
22858
|
+
save("delete");
|
|
22859
|
+
}
|
|
22860
|
+
return null;
|
|
22861
|
+
}
|
|
22862
|
+
const now = this.now();
|
|
22863
|
+
const selectedQuota = knownQuota(selected.quota);
|
|
22864
|
+
const assignment = {
|
|
22865
|
+
accountId: selected.accountId,
|
|
22866
|
+
...input.accountIdentities ? { accountIdentity: identityForAccount(selected.accountId) } : {},
|
|
22867
|
+
family: input.family,
|
|
22868
|
+
affinityModelId,
|
|
22869
|
+
assignedAt: now,
|
|
22870
|
+
lastSeenAt: now,
|
|
22871
|
+
initialInputBytes: Math.max(1, input.inputBytes),
|
|
22872
|
+
quotaCheckedAt: selectedQuota ? snapshotCheckedAt(selectedQuota) : 0
|
|
22873
|
+
};
|
|
22874
|
+
state.assignments[key] = assignment;
|
|
22875
|
+
save("assign");
|
|
22876
|
+
return {
|
|
22877
|
+
accountId: selected.accountId,
|
|
22878
|
+
assignment: { ...assignment },
|
|
22879
|
+
created: !current,
|
|
22880
|
+
migrated: Boolean(current && (identityChanged || current.accountId !== selected.accountId))
|
|
22881
|
+
};
|
|
22720
22882
|
}
|
|
22721
22883
|
async clear(sessionId) {
|
|
22722
22884
|
if (!sessionId)
|
|
22723
22885
|
return;
|
|
22724
|
-
|
|
22725
|
-
|
|
22726
|
-
const lock = await this.acquireLock();
|
|
22727
|
-
try {
|
|
22728
|
-
const state = await this.readState();
|
|
22729
|
-
if (!(key in state.assignments))
|
|
22730
|
-
return;
|
|
22731
|
-
delete state.assignments[key];
|
|
22732
|
-
await this.writeState(state, () => lock.assertOwned());
|
|
22733
|
-
} finally {
|
|
22734
|
-
await lock.release();
|
|
22735
|
-
}
|
|
22886
|
+
this.recoverLocalPins();
|
|
22887
|
+
this.scheduleChange(sessionKey(sessionId), undefined, "delete");
|
|
22736
22888
|
}
|
|
22737
22889
|
}
|
|
22738
22890
|
|
|
@@ -24394,16 +24546,21 @@ function acquireNativeAccountRuntime(options) {
|
|
|
24394
24546
|
options.local?.refreshToken,
|
|
24395
24547
|
options.local?.resolveIdentity,
|
|
24396
24548
|
options.local?.failurePolicy,
|
|
24397
|
-
options.local?.now
|
|
24549
|
+
options.local?.now,
|
|
24550
|
+
options.local?.onLockEvent
|
|
24398
24551
|
].map(nativeRuntimeOptionIdentity)
|
|
24399
24552
|
]);
|
|
24400
24553
|
const { use, close } = sharedAccounts.acquire(key, () => createNativeAccountRuntime(options));
|
|
24401
24554
|
const vault = leaseNativeVaultRuntime(() => use().vault, close);
|
|
24402
24555
|
const quotaObservations = use().quotaObservations;
|
|
24556
|
+
const onLockEvent = use().onLockEvent;
|
|
24557
|
+
const drainRoutingPublications = use().drainRoutingPublications;
|
|
24403
24558
|
return {
|
|
24559
|
+
onLockEvent,
|
|
24404
24560
|
vault,
|
|
24405
24561
|
quotaObservations,
|
|
24406
24562
|
drainQuotaPublications: () => quotaObservations.drain(),
|
|
24563
|
+
drainRoutingPublications,
|
|
24407
24564
|
getRelayConfig: () => use().getRelayConfig(),
|
|
24408
24565
|
getSettingsPath: () => use().getSettingsPath(),
|
|
24409
24566
|
updateRelay: (patch) => use().updateRelay(patch),
|
|
@@ -24428,7 +24585,10 @@ function acquireNativeAccountRuntime(options) {
|
|
|
24428
24585
|
fetchQuota: (id2, transport, signal, dispatch, purpose) => use().fetchQuota(id2, transport, signal, dispatch, purpose),
|
|
24429
24586
|
fetchProfile: (id2, transport, signal) => use().fetchProfile(id2, transport, signal),
|
|
24430
24587
|
fetchProfileForDisplay: (id2, transport, signal) => use().fetchProfileForDisplay(id2, transport, signal),
|
|
24431
|
-
close: () =>
|
|
24588
|
+
close: () => {
|
|
24589
|
+
vault.close();
|
|
24590
|
+
return drainRoutingPublications();
|
|
24591
|
+
}
|
|
24432
24592
|
};
|
|
24433
24593
|
}
|
|
24434
24594
|
function nativeAccountPolicy(account, snapshot, modelId) {
|
|
@@ -24440,6 +24600,8 @@ function nativeAccountPolicy(account, snapshot, modelId) {
|
|
|
24440
24600
|
}
|
|
24441
24601
|
function createNativeAccountRuntime(options) {
|
|
24442
24602
|
const paths = Object.freeze({ ...options.paths });
|
|
24603
|
+
const onLockEvent = nativeLockObserver(options.local?.onLockEvent ?? options.vault?.onLockEvent);
|
|
24604
|
+
const withLock3 = observeNativeLocks(() => withLock, onLockEvent);
|
|
24443
24605
|
const now = options.local?.now ?? options.vault?.now ?? Date.now;
|
|
24444
24606
|
const env = { ...options.vault?.env ?? process.env };
|
|
24445
24607
|
const vaultPaths = nativeVaultPaths(paths);
|
|
@@ -24450,6 +24612,7 @@ function createNativeAccountRuntime(options) {
|
|
|
24450
24612
|
const vaultRouter = new StickySessionRouter({
|
|
24451
24613
|
path: vaultPaths.routing,
|
|
24452
24614
|
vaultSeed: routingSeed,
|
|
24615
|
+
onLockEvent,
|
|
24453
24616
|
now
|
|
24454
24617
|
});
|
|
24455
24618
|
let settingsBootstrap;
|
|
@@ -24457,6 +24620,7 @@ function createNativeAccountRuntime(options) {
|
|
|
24457
24620
|
paths: vaultPaths,
|
|
24458
24621
|
quota: nativeQuotaCodec,
|
|
24459
24622
|
now,
|
|
24623
|
+
onLockEvent: options.local?.onLockEvent,
|
|
24460
24624
|
onStep: async (point) => {
|
|
24461
24625
|
if (point === "before-config-write" || point === "before-state-write")
|
|
24462
24626
|
await requireMode("claustrum");
|
|
@@ -24467,6 +24631,7 @@ function createNativeAccountRuntime(options) {
|
|
|
24467
24631
|
paths,
|
|
24468
24632
|
quota: nativeQuotaCodec,
|
|
24469
24633
|
now,
|
|
24634
|
+
onLockEvent: options.local?.onLockEvent,
|
|
24470
24635
|
onStep: async (point) => {
|
|
24471
24636
|
if (point === "before-config-write" || point === "before-state-write") {
|
|
24472
24637
|
if (options.beforePoolWrite) {
|
|
@@ -24480,7 +24645,8 @@ function createNativeAccountRuntime(options) {
|
|
|
24480
24645
|
const vault = acquireNativeVaultRuntime({
|
|
24481
24646
|
...options.vault,
|
|
24482
24647
|
paths,
|
|
24483
|
-
host: options.host
|
|
24648
|
+
host: options.host,
|
|
24649
|
+
onLockEvent
|
|
24484
24650
|
});
|
|
24485
24651
|
function check(signal) {
|
|
24486
24652
|
if (shutdown.signal.aborted)
|
|
@@ -24547,7 +24713,15 @@ function createNativeAccountRuntime(options) {
|
|
|
24547
24713
|
};
|
|
24548
24714
|
const [config, state] = nativePoolStoreLocks(paths);
|
|
24549
24715
|
try {
|
|
24550
|
-
return await
|
|
24716
|
+
return await withLock3(config.path, {
|
|
24717
|
+
...POOL_LOCK_DEFAULTS,
|
|
24718
|
+
name: config.name,
|
|
24719
|
+
onLockEvent: options.local?.onLockEvent
|
|
24720
|
+
}, async (configLease) => withLock3(state.path, {
|
|
24721
|
+
...POOL_LOCK_DEFAULTS,
|
|
24722
|
+
name: state.name,
|
|
24723
|
+
onLockEvent: options.local?.onLockEvent
|
|
24724
|
+
}, async (stateLease) => {
|
|
24551
24725
|
if (!await matches2())
|
|
24552
24726
|
return false;
|
|
24553
24727
|
await updateNativeRuntime(paths.runtime, paths.storageId, (current) => {
|
|
@@ -24557,6 +24731,7 @@ function createNativeAccountRuntime(options) {
|
|
|
24557
24731
|
current.accounts[binding2.rowId] = change(same && old ? old : { binding: binding2 }, old);
|
|
24558
24732
|
return current;
|
|
24559
24733
|
}, {
|
|
24734
|
+
onLockEvent: options.local?.onLockEvent,
|
|
24560
24735
|
beforeRename: async () => {
|
|
24561
24736
|
if (!await matches2())
|
|
24562
24737
|
throw new NativeRuntimeError("publication-refused");
|
|
@@ -24706,24 +24881,32 @@ function createNativeAccountRuntime(options) {
|
|
|
24706
24881
|
return source2 !== undefined && nativeLocalRouteAwaitsTornWrite(paths, source2.rows, source2.settings, routeId, source2.runtime);
|
|
24707
24882
|
}
|
|
24708
24883
|
const runtime2 = {
|
|
24884
|
+
onLockEvent,
|
|
24709
24885
|
vault,
|
|
24710
24886
|
quotaObservations: vault.quotaObservations,
|
|
24711
24887
|
drainQuotaPublications: () => vault.quotaObservations.drain(),
|
|
24888
|
+
drainRoutingPublications: () => vaultRouter.drain(),
|
|
24712
24889
|
async getRelayConfig() {
|
|
24713
24890
|
const { mode, paths: paths2, store: store2 } = await selectedStorage();
|
|
24714
|
-
|
|
24891
|
+
for (let attempt = 0;attempt < 4; attempt++) {
|
|
24892
|
+
if (attempt)
|
|
24893
|
+
await new Promise((resolve10) => setTimeout(resolve10, 10));
|
|
24715
24894
|
const settings = await store2.readSettings();
|
|
24716
24895
|
if (settings.status !== "ready")
|
|
24717
24896
|
throw new NativeRuntimeError("publication-refused");
|
|
24718
24897
|
const flags2 = relayFlags(settings.settings.relay);
|
|
24898
|
+
const generation = relayGeneration(settings.settings.relay);
|
|
24719
24899
|
const read = await readNativeRuntime(paths2.runtime, paths2.storageId);
|
|
24720
|
-
const token2 = read.status === "ready" ? read.state.relay?.token : undefined;
|
|
24721
24900
|
await requireMode(mode);
|
|
24722
|
-
|
|
24901
|
+
const state = read.status === "ready" ? read.state : undefined;
|
|
24902
|
+
if (generation !== state?.relayGeneration)
|
|
24903
|
+
continue;
|
|
24904
|
+
const token2 = state?.relay?.token;
|
|
24723
24905
|
if (!flags2.enabled || !flags2.url || !token2)
|
|
24724
24906
|
return null;
|
|
24725
24907
|
return { ...flags2, token: token2 };
|
|
24726
|
-
}
|
|
24908
|
+
}
|
|
24909
|
+
throw new NativeRuntimeError("relay-inconsistent");
|
|
24727
24910
|
},
|
|
24728
24911
|
async updateRelay(input) {
|
|
24729
24912
|
const patch = captureNativePlainData(input);
|
|
@@ -24745,11 +24928,13 @@ function createNativeAccountRuntime(options) {
|
|
|
24745
24928
|
if (patch.token !== undefined && patch.token !== null && (typeof patch.token !== "string" || !patch.token.trim() || /\p{Cc}/u.test(patch.token)))
|
|
24746
24929
|
throw new NativeRuntimeError("publication-refused");
|
|
24747
24930
|
const { mode, paths: paths2, store: store2 } = await selectedStorage();
|
|
24748
|
-
await
|
|
24931
|
+
await withLock3(paths2.config, { ...POOL_LOCK_DEFAULTS, name: "native-relay" }, async (lease) => {
|
|
24749
24932
|
const read = await store2.readSettings();
|
|
24750
24933
|
if (read.status !== "ready")
|
|
24751
24934
|
throw new NativeRuntimeError("publication-refused");
|
|
24752
24935
|
const previous = relayFlags(read.settings.relay);
|
|
24936
|
+
const previousGeneration = relayGeneration(read.settings.relay);
|
|
24937
|
+
const generation = randomUUID15();
|
|
24753
24938
|
const metadata2 = await readNativeRuntime(paths2.runtime, paths2.storageId);
|
|
24754
24939
|
const token2 = patch.token === undefined ? metadata2.status === "ready" ? metadata2.state.relay?.token : undefined : patch.token?.trim();
|
|
24755
24940
|
const next = relayFlags({
|
|
@@ -24759,28 +24944,31 @@ function createNativeAccountRuntime(options) {
|
|
|
24759
24944
|
});
|
|
24760
24945
|
if (next.enabled && (!next.url || !token2))
|
|
24761
24946
|
throw new NativeRuntimeError("publication-refused");
|
|
24762
|
-
async function writeFlags(flags2) {
|
|
24947
|
+
async function writeFlags(flags2, stamp) {
|
|
24763
24948
|
await store2.updateSettings(async (settings) => {
|
|
24764
24949
|
await requireMode(mode);
|
|
24765
24950
|
await lease.assertOwned();
|
|
24766
|
-
settings.relay =
|
|
24951
|
+
settings.relay = {
|
|
24952
|
+
...flags2,
|
|
24953
|
+
...stamp === undefined ? {} : { generation: stamp }
|
|
24954
|
+
};
|
|
24767
24955
|
});
|
|
24768
24956
|
}
|
|
24769
|
-
await writeFlags({ ...previous, enabled: false });
|
|
24770
|
-
|
|
24771
|
-
|
|
24772
|
-
|
|
24773
|
-
|
|
24774
|
-
|
|
24775
|
-
|
|
24776
|
-
|
|
24777
|
-
|
|
24778
|
-
|
|
24779
|
-
|
|
24780
|
-
|
|
24781
|
-
|
|
24782
|
-
|
|
24783
|
-
await writeFlags(next);
|
|
24957
|
+
await writeFlags({ ...previous, enabled: false }, previousGeneration);
|
|
24958
|
+
await updateNativeRuntime(paths2.runtime, paths2.storageId, (current) => {
|
|
24959
|
+
if (token2)
|
|
24960
|
+
current.relay = { token: token2 };
|
|
24961
|
+
else
|
|
24962
|
+
delete current.relay;
|
|
24963
|
+
current.relayGeneration = generation;
|
|
24964
|
+
return current;
|
|
24965
|
+
}, {
|
|
24966
|
+
beforeRename: async () => {
|
|
24967
|
+
await requireMode(mode);
|
|
24968
|
+
await lease.assertOwned();
|
|
24969
|
+
}
|
|
24970
|
+
});
|
|
24971
|
+
await writeFlags(next, generation);
|
|
24784
24972
|
});
|
|
24785
24973
|
},
|
|
24786
24974
|
async getSettingsPath() {
|
|
@@ -24801,7 +24989,7 @@ function createNativeAccountRuntime(options) {
|
|
|
24801
24989
|
logger.debug("native-auth", "waiting for an account write to finish", {
|
|
24802
24990
|
phase: "waiting"
|
|
24803
24991
|
});
|
|
24804
|
-
const joined = await joinNativePoolWrite(paths, request.signal ? [shutdown.signal, request.signal] : [shutdown.signal], () => check(request.signal));
|
|
24992
|
+
const joined = await joinNativePoolWrite(paths, request.signal ? [shutdown.signal, request.signal] : [shutdown.signal], () => check(request.signal), onLockEvent);
|
|
24805
24993
|
if (joined === "deadline") {
|
|
24806
24994
|
logger.warn("native-auth", "account write did not finish in time", {
|
|
24807
24995
|
outcome: "deadline"
|
|
@@ -25302,6 +25490,7 @@ function createNativeAccountRuntime(options) {
|
|
|
25302
25490
|
shutdown.abort(new NativeCustodyError("closed"));
|
|
25303
25491
|
vault.close();
|
|
25304
25492
|
}
|
|
25493
|
+
return vaultRouter.drain();
|
|
25305
25494
|
}
|
|
25306
25495
|
};
|
|
25307
25496
|
function readLocalProfile(id2, fetchImpl, signal) {
|
|
@@ -25398,6 +25587,16 @@ function relayFlags(input) {
|
|
|
25398
25587
|
fallbackToDirect: value.fallbackToDirect !== false
|
|
25399
25588
|
};
|
|
25400
25589
|
}
|
|
25590
|
+
function relayGeneration(input) {
|
|
25591
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
25592
|
+
return;
|
|
25593
|
+
const generation = input.generation;
|
|
25594
|
+
if (generation === undefined)
|
|
25595
|
+
return;
|
|
25596
|
+
if (typeof generation !== "string" || !generation.trim())
|
|
25597
|
+
throw new NativeRuntimeError("relay-inconsistent");
|
|
25598
|
+
return generation;
|
|
25599
|
+
}
|
|
25401
25600
|
// src/native-host-auth-write.ts
|
|
25402
25601
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
25403
25602
|
import { lstat as lstat7, open as open9, rename as rename13, unlink as unlink6 } from "node:fs/promises";
|
|
@@ -32065,6 +32264,7 @@ export {
|
|
|
32065
32264
|
OAUTH_SCOPES,
|
|
32066
32265
|
NativeVaultNoAccountError,
|
|
32067
32266
|
NativeTransientAccountError,
|
|
32267
|
+
NativeRuntimeError,
|
|
32068
32268
|
NativeQuotaObservations,
|
|
32069
32269
|
PiSlotError as NativePiAuthSlotError,
|
|
32070
32270
|
NativeMigrationError,
|