@cortexkit/antigravity-auth-core 3.0.11 → 3.0.13
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/account-selector.d.ts +9 -1
- package/dist/account-selector.d.ts.map +1 -1
- package/dist/account-selector.js +23 -8
- package/dist/account-selector.js.map +1 -1
- package/dist/agy-transport.d.ts.map +1 -1
- package/dist/agy-transport.js +4 -0
- package/dist/agy-transport.js.map +1 -1
- package/dist/common-auth-embedded/claustrum/consumer.d.ts +83 -2
- package/dist/common-auth-embedded/claustrum/consumer.js +337 -37
- package/dist/common-auth-embedded/claustrum/custody.d.ts +36 -1
- package/dist/common-auth-embedded/claustrum/custody.js +95 -11
- package/dist/common-auth-embedded/claustrum/enrollment.js +37 -12
- package/dist/common-auth-embedded/claustrum/errors.d.ts +1 -1
- package/dist/common-auth-embedded/claustrum/hub.d.ts +195 -0
- package/dist/common-auth-embedded/claustrum/hub.js +988 -0
- package/dist/common-auth-embedded/claustrum/index.d.ts +13 -3
- package/dist/common-auth-embedded/claustrum/index.js +37 -4
- package/dist/common-auth-embedded/claustrum/quota-writer.d.ts +8 -1
- package/dist/common-auth-embedded/claustrum/quota-writer.js +38 -66
- package/dist/common-auth-embedded/claustrum/roster.d.ts +80 -1
- package/dist/common-auth-embedded/claustrum/roster.js +259 -12
- package/dist/common-auth-embedded/claustrum/shutdown.js +42 -0
- package/dist/common-auth-embedded/commands/builtins.d.ts +37 -0
- package/dist/common-auth-embedded/commands/builtins.js +95 -13
- package/dist/common-auth-embedded/commands/index.d.ts +1 -1
- package/dist/common-auth-embedded/fs/file-read-cache.js +57 -0
- package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +1 -1
- package/dist/common-auth-embedded/fs/refresh-file-lock.js +20 -11
- package/dist/common-auth-embedded/quota/commit-interval.d.ts +9 -0
- package/dist/common-auth-embedded/quota/commit-interval.js +18 -0
- package/dist/common-auth-embedded/quota/pending.js +48 -0
- package/dist/common-auth-embedded/source-output.json +152 -88
- package/dist/common-auth-embedded/store/attribution.d.ts +5 -0
- package/dist/common-auth-embedded/store/attribution.js +39 -25
- package/dist/common-auth-embedded/store/mutate.d.ts +2 -0
- package/dist/common-auth-embedded/store/mutate.js +4 -0
- package/dist/common-auth-embedded/store/pool.d.ts +22 -0
- package/dist/common-auth-embedded/store/pool.js +18 -3
- package/dist/common-auth-embedded/store/pull.js +3 -1
- package/dist/common-auth-embedded/store/quota-writer.js +312 -0
- package/dist/common-auth-embedded/store/runtime.d.ts +2 -0
- package/dist/common-auth-embedded/store/settings.d.ts +1 -1
- package/dist/common-auth-embedded/store/settings.js +16 -6
- package/dist/model-registry.d.ts +2 -2
- package/dist/model-registry.d.ts.map +1 -1
- package/dist/model-registry.js +6 -4
- package/dist/model-registry.js.map +1 -1
- package/dist/pre-response-deadline.d.ts.map +1 -1
- package/dist/pre-response-deadline.js +6 -5
- package/dist/pre-response-deadline.js.map +1 -1
- package/dist/quota-manager.d.ts +1 -1
- package/dist/quota-manager.d.ts.map +1 -1
- package/dist/quota-manager.js +12 -4
- package/dist/quota-manager.js.map +1 -1
- package/dist/quota-presentation.d.ts.map +1 -1
- package/dist/quota-presentation.js +8 -0
- package/dist/quota-presentation.js.map +1 -1
- package/dist/quota-types.d.ts +1 -1
- package/dist/quota-types.d.ts.map +1 -1
- package/dist/quota-types.js +5 -0
- package/dist/quota-types.js.map +1 -1
- package/dist/vault-account-source.d.ts +6 -1
- package/dist/vault-account-source.d.ts.map +1 -1
- package/dist/vault-account-source.js +9 -0
- package/dist/vault-account-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,21 +2,31 @@ import { type ClaustrumClientOptions } from '@cortexkit/claustrum-client';
|
|
|
2
2
|
import type { ClaustrumScopedClient } from './custody.js';
|
|
3
3
|
export { ClaustrumCredentialError, type ClaustrumReporterSource, type EnrollmentTokenFile, } from '@cortexkit/claustrum-client';
|
|
4
4
|
export { ClaustrumUnreachableError, classifyVaultFailure, VAULT_FAILURE_CLASSES, type VaultFailureClass, } from './availability.js';
|
|
5
|
-
export { ClaustrumConsumer, type ClaustrumConsumerOptions, type ClaustrumWatchState, type SendOptions, } from './consumer.js';
|
|
6
|
-
export { type AccountIdentitySource, type ClaustrumFamily, type ClaustrumRosterWatch, type ClaustrumRosterWatchOptions, type ClaustrumScopedAttempt, type ClaustrumScopedClient, ClaustrumScopedCustody, type ClaustrumScopedIdentity, decideScopedRetryAfter401, type IdentityParser, isScopedCredentialRotation, KEPT_RECEIPT_MARGIN_MS, type KeptReceiptState, type ScopedRetryReason, SERVING_MARGIN_MS, type SkippedVaultReason, type SkippedVaultRecord, type VaultCredential, type VaultCredentialType, type VaultGrantReach, type VaultGrantTuple, type VaultInventory, } from './custody.js';
|
|
5
|
+
export { ClaustrumConsumer, type ClaustrumConsumerOptions, type ClaustrumWatchState, DEFAULT_CONNECT_TIMEOUT_MS, type SendOptions, } from './consumer.js';
|
|
6
|
+
export { type AccountIdentitySource, type ClassifyingClaustrumFamily, type ClaustrumFamily, type ClaustrumFamilyInput, type ClaustrumRosterWatch, type ClaustrumRosterWatchOptions, type ClaustrumScopedAttempt, type ClaustrumScopedClient, ClaustrumScopedCustody, type ClaustrumScopedIdentity, decideScopedRetryAfter401, type IdentityParser, isScopedCredentialRotation, KEPT_RECEIPT_MARGIN_MS, type KeptReceiptState, type ScopedRetryReason, SERVING_MARGIN_MS, type SkippedVaultReason, type SkippedVaultRecord, type VaultCredential, type VaultCredentialType, type VaultGrantReach, type VaultGrantTuple, type VaultInventory, type VaultRowFacts, } from './custody.js';
|
|
7
7
|
export { type ClaustrumEnrollmentClient, type ClaustrumEnrollmentConnection, type ClaustrumEnrollmentDisconnectResult, ClaustrumEnrollmentManager, type ClaustrumEnrollmentPaths, type ClaustrumEnrollmentResetResult, type ClaustrumEnrollmentStatus, classifyEnrollmentError, connectClaustrumEnrollmentClient, disconnectClaustrumEnrollment, type EnrollmentDisposition, enrollmentAuthority, enrollmentName, getClaustrumEnrollmentPaths, hostEnrollmentPaths, RETRYABLE_ENROLLMENT_CODES, readClaustrumEnrollmentStatus, readClaustrumEnrollmentToken, resetClaustrumEnrollmentState, TERMINAL_ENROLLMENT_CODES, } from './enrollment.js';
|
|
8
8
|
export { ClaustrumConsumerError, type ClaustrumConsumerFailureKind, type ClaustrumLogger, } from './errors.js';
|
|
9
9
|
export { assertHostSlotMatchesMode, assertNotCustodyPlaceholder, CUSTODY_PLACEHOLDER_PREFIX, classifyHostSlot, custodyPlaceholder, custodyPlaceholderKey, type HostSlotContent, isCustodyPlaceholder, isCustodyPlaceholderValue, } from './host-slot.js';
|
|
10
|
+
export { ClaustrumDiscoveryHub, type ClaustrumDiscoveryHubOptions, type ClaustrumHubMemberOptions, type ClaustrumHubRefusals, type ClaustrumLeasedCustody, type CustodyLease, } from './hub.js';
|
|
10
11
|
export { acquireInsulaQuotaSource, type InsulaQuotaContext, type InsulaQuotaLookup, type InsulaQuotaMiss, type InsulaQuotaOptions, type InsulaQuotaReading, type InsulaQuotaResult, type InsulaQuotaSource, type InsulaUsageEntry, type InsulaUsageTransport, type InsulaUsageWatch, type InsulaUsageWindow, type InsulaWatchOptions, } from './insula.js';
|
|
11
12
|
export { acceptAccount, type DeclinedAccount, declineAccount, isDeclined, } from './interlock.js';
|
|
12
13
|
export { drainVaultQuota } from './quota-writer.js';
|
|
13
|
-
export { type AccountMapper, acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, type MutateVaultRosterOptions, mutateVaultRoster, type ProjectionOptions, projectVaultRoster, type QuotaReceipt, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, resolveVaultPrimary, type VaultPrimary, type VaultPrimaryBinding, type VaultPrimaryUnavailableReason, type VaultRosterFile, type VaultRosterLockContext, type VaultRosterRow, vaultRoutingRows, } from './roster.js';
|
|
14
|
+
export { type AccountMapper, acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, type MutateVaultRosterOptions, mutateVaultRoster, type ProjectionOptions, projectVaultRoster, type QuotaReceipt, type ReorderVaultRosterOptions, type ReorderVaultRosterResult, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, reorderVaultRoster, resolveVaultPrimary, type VaultPrimary, type VaultPrimaryBinding, type VaultPrimaryUnavailableReason, type VaultRosterFile, type VaultRosterLockContext, type VaultRosterRow, type VaultRosterSeed, vaultRosterSeed, vaultRoutingRows, } from './roster.js';
|
|
14
15
|
export { type ChangeNotice, ChangeNoticeReader, RESUBSCRIBE_BACKOFF_MS, SAFETY_POLL_MS, ScopedRosterWatch, type ScopedRosterWatchOptions, type ScopedWatchEnd, type ScopedWatchHandle, type ScopedWatchNoticeShape, type ScopedWatchSource, WATCH_RETRY_MS, type WatchMode, } from './watch.js';
|
|
16
|
+
/** How long `connectClaustrumScopedClient` waits by default. */
|
|
17
|
+
export declare const SCOPED_CONNECT_TIMEOUT_MS = 10000;
|
|
15
18
|
/**
|
|
16
19
|
* Connect the client that lists and fetches this consumer's vault credentials
|
|
17
20
|
* on the request path. `connectionFile` is required: this library
|
|
18
21
|
* reads no environment, so the plugin resolves the vault's connection file.
|
|
22
|
+
*
|
|
23
|
+
* The wait is bounded by `connectTimeoutMs` (default 10 s) so a connect that
|
|
24
|
+
* never settles cannot leave a consumer with a pending connection forever; the
|
|
25
|
+
* caller then gets an unreachable error and may try again. A socket handshake
|
|
26
|
+
* has its own limit, but local reads before it (the connection file) do not,
|
|
27
|
+
* and a client that arrives after this deadline is closed, not returned.
|
|
19
28
|
*/
|
|
20
29
|
export declare function connectClaustrumScopedClient(options: ClaustrumClientOptions & {
|
|
21
30
|
connectionFile: string;
|
|
31
|
+
connectTimeoutMs?: number;
|
|
22
32
|
}): Promise<ClaustrumScopedClient>;
|
|
@@ -4,25 +4,58 @@
|
|
|
4
4
|
// declined-account interlock and the host-slot guard. Needs the optional
|
|
5
5
|
// peer `@cortexkit/claustrum-client`.
|
|
6
6
|
import { ClaustrumClient, } from '@cortexkit/claustrum-client';
|
|
7
|
+
import { ClaustrumUnreachableError } from './availability.js';
|
|
7
8
|
import { insulaUsageTransport } from './insula.js';
|
|
8
9
|
export { ClaustrumCredentialError, } from '@cortexkit/claustrum-client';
|
|
9
10
|
export { ClaustrumUnreachableError, classifyVaultFailure, VAULT_FAILURE_CLASSES, } from './availability.js';
|
|
10
|
-
export { ClaustrumConsumer, } from './consumer.js';
|
|
11
|
+
export { ClaustrumConsumer, DEFAULT_CONNECT_TIMEOUT_MS, } from './consumer.js';
|
|
11
12
|
export { ClaustrumScopedCustody, decideScopedRetryAfter401, isScopedCredentialRotation, KEPT_RECEIPT_MARGIN_MS, SERVING_MARGIN_MS, } from './custody.js';
|
|
12
13
|
export { ClaustrumEnrollmentManager, classifyEnrollmentError, connectClaustrumEnrollmentClient, disconnectClaustrumEnrollment, enrollmentAuthority, enrollmentName, getClaustrumEnrollmentPaths, hostEnrollmentPaths, RETRYABLE_ENROLLMENT_CODES, readClaustrumEnrollmentStatus, readClaustrumEnrollmentToken, resetClaustrumEnrollmentState, TERMINAL_ENROLLMENT_CODES, } from './enrollment.js';
|
|
13
14
|
export { ClaustrumConsumerError, } from './errors.js';
|
|
14
15
|
export { assertHostSlotMatchesMode, assertNotCustodyPlaceholder, CUSTODY_PLACEHOLDER_PREFIX, classifyHostSlot, custodyPlaceholder, custodyPlaceholderKey, isCustodyPlaceholder, isCustodyPlaceholderValue, } from './host-slot.js';
|
|
16
|
+
export { ClaustrumDiscoveryHub, } from './hub.js';
|
|
15
17
|
export { acquireInsulaQuotaSource, } from './insula.js';
|
|
16
18
|
export { acceptAccount, declineAccount, isDeclined, } from './interlock.js';
|
|
17
19
|
export { drainVaultQuota } from './quota-writer.js';
|
|
18
|
-
export { acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, mutateVaultRoster, projectVaultRoster, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, resolveVaultPrimary, vaultRoutingRows, } from './roster.js';
|
|
20
|
+
export { acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, mutateVaultRoster, projectVaultRoster, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, reorderVaultRoster, resolveVaultPrimary, vaultRosterSeed, vaultRoutingRows, } from './roster.js';
|
|
19
21
|
export { ChangeNoticeReader, RESUBSCRIBE_BACKOFF_MS, SAFETY_POLL_MS, ScopedRosterWatch, WATCH_RETRY_MS, } from './watch.js';
|
|
22
|
+
/** How long `connectClaustrumScopedClient` waits by default. */
|
|
23
|
+
export const SCOPED_CONNECT_TIMEOUT_MS = 10_000;
|
|
20
24
|
/**
|
|
21
25
|
* Connect the client that lists and fetches this consumer's vault credentials
|
|
22
26
|
* on the request path. `connectionFile` is required: this library
|
|
23
27
|
* reads no environment, so the plugin resolves the vault's connection file.
|
|
28
|
+
*
|
|
29
|
+
* The wait is bounded by `connectTimeoutMs` (default 10 s) so a connect that
|
|
30
|
+
* never settles cannot leave a consumer with a pending connection forever; the
|
|
31
|
+
* caller then gets an unreachable error and may try again. A socket handshake
|
|
32
|
+
* has its own limit, but local reads before it (the connection file) do not,
|
|
33
|
+
* and a client that arrives after this deadline is closed, not returned.
|
|
24
34
|
*/
|
|
25
35
|
export async function connectClaustrumScopedClient(options) {
|
|
26
|
-
const client =
|
|
27
|
-
|
|
36
|
+
const { connectTimeoutMs, ...client } = options;
|
|
37
|
+
const limit = connectTimeoutMs ?? SCOPED_CONNECT_TIMEOUT_MS;
|
|
38
|
+
const connecting = ClaustrumClient.connect(client);
|
|
39
|
+
let expired = false;
|
|
40
|
+
let timer;
|
|
41
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
42
|
+
timer = setTimeout(() => {
|
|
43
|
+
expired = true;
|
|
44
|
+
reject(new ClaustrumUnreachableError());
|
|
45
|
+
}, limit);
|
|
46
|
+
timer.unref?.();
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
const client = await Promise.race([connecting, deadline]);
|
|
50
|
+
return Object.assign(client, { insula: insulaUsageTransport(options) });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// A connection that arrives after the deadline still owns a socket.
|
|
54
|
+
void connecting.then((client) => client.close(), () => { });
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
if (!expired && timer)
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
}
|
|
28
61
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type QuotaCommitTimer } from '../quota/commit-interval.js';
|
|
1
2
|
import { type QuotaObservation } from '../quota/index.js';
|
|
2
3
|
import { type QuotaReceipt, type VaultRosterFile } from './roster.js';
|
|
3
4
|
export type VaultQuotaPublication = {
|
|
@@ -8,11 +9,17 @@ export type VaultQuotaPublication = {
|
|
|
8
9
|
};
|
|
9
10
|
type Listener = (publication: VaultQuotaPublication) => void;
|
|
10
11
|
/** Retain the shared writer; the last consumer's close drains it. */
|
|
11
|
-
export declare function retainVaultQuotaWriter(path: string, listener: Listener): () => Promise<void>;
|
|
12
|
+
export declare function retainVaultQuotaWriter(path: string, listener: Listener, clock?: QuotaCommitTimer): () => Promise<void>;
|
|
12
13
|
/** Enqueue without filesystem work; each quota field retains its own receipt fence. */
|
|
13
14
|
export declare function enqueueVaultQuota(path: string, publication: VaultQuotaPublication): Promise<boolean>;
|
|
14
15
|
/** Overlay uncommitted observations when a consumer reloads the durable roster. */
|
|
15
16
|
export declare function pendingVaultQuota(path: string, roster: VaultRosterFile | undefined): VaultRosterFile | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Whether this process holds quota readings for `path` that no commit of the
|
|
19
|
+
* durable roster has written yet, so a reader of the file alone is not seeing
|
|
20
|
+
* everything this process would publish.
|
|
21
|
+
*/
|
|
22
|
+
export declare function hasPendingVaultQuota(path: string): boolean;
|
|
16
23
|
/** Wait at most timeoutMs for detached publication; shutdown loss is acceptable. */
|
|
17
24
|
export declare function drainVaultQuota(path: string, timeoutMs?: number): Promise<void>;
|
|
18
25
|
export {};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { createLogger } from '../logger/index.js';
|
|
3
|
+
import { QUOTA_COMMIT_INTERVAL_MS, quotaCommitTimer, } from '../quota/commit-interval.js';
|
|
3
4
|
import { isQuotaObservation } from '../quota/index.js';
|
|
5
|
+
import { quotaFields } from '../quota/pending.js';
|
|
4
6
|
import { applyVaultQuota, mutateVaultRoster, } from './roster.js';
|
|
5
7
|
// Every consumer of the same file shares one mailbox and one lock waiter.
|
|
6
8
|
const writers = new Map();
|
|
7
9
|
const logger = createLogger('claustrum');
|
|
8
10
|
const COALESCE_MS = 100;
|
|
9
|
-
function writerFor(path) {
|
|
11
|
+
function writerFor(path, clock = quotaCommitTimer) {
|
|
10
12
|
const key = resolve(path);
|
|
11
13
|
let writer = writers.get(key);
|
|
12
14
|
if (!writer) {
|
|
@@ -16,6 +18,7 @@ function writerFor(path) {
|
|
|
16
18
|
listeners: new Set(),
|
|
17
19
|
wake: new Set(),
|
|
18
20
|
abort: new AbortController(),
|
|
21
|
+
clock,
|
|
19
22
|
failures: 0,
|
|
20
23
|
draining: false,
|
|
21
24
|
order: 0,
|
|
@@ -25,8 +28,8 @@ function writerFor(path) {
|
|
|
25
28
|
return writer;
|
|
26
29
|
}
|
|
27
30
|
/** Retain the shared writer; the last consumer's close drains it. */
|
|
28
|
-
export function retainVaultQuotaWriter(path, listener) {
|
|
29
|
-
const writer = writerFor(path);
|
|
31
|
+
export function retainVaultQuotaWriter(path, listener, clock) {
|
|
32
|
+
const writer = writerFor(path, clock);
|
|
30
33
|
writer.listeners.add(listener);
|
|
31
34
|
let released;
|
|
32
35
|
return () => (released ??= (() => {
|
|
@@ -34,65 +37,14 @@ export function retainVaultQuotaWriter(path, listener) {
|
|
|
34
37
|
return writer.listeners.size ? Promise.resolve() : drainVaultQuota(path);
|
|
35
38
|
})());
|
|
36
39
|
}
|
|
37
|
-
//
|
|
38
|
-
// window (scope and label), each covered-but-unread window, the budget, each
|
|
39
|
-
// counter and the credits record. The mailbox keeps the newest of each part on
|
|
40
|
-
// its own, so a later observation that carries only some parts can't erase an
|
|
41
|
-
// older but still newest reading of another part, which is how
|
|
42
|
-
// mergeQuotaObservation treats them on disk. Each key also carries the
|
|
43
|
-
// receipt's identity, so the identity fence still applies to every part.
|
|
40
|
+
// Each field retains the receipt fence as well as its independent timestamp.
|
|
44
41
|
function fields(publication) {
|
|
45
|
-
|
|
46
|
-
const { observation } = publication;
|
|
47
|
-
const witness = JSON.stringify([
|
|
42
|
+
return quotaFields(publication, JSON.stringify([
|
|
48
43
|
publication.routeId,
|
|
49
44
|
publication.receipt?.credentialId,
|
|
50
45
|
publication.receipt?.accountIdentitySource,
|
|
51
46
|
publication.receipt?.expectedAccountIdentity,
|
|
52
|
-
]);
|
|
53
|
-
const add = (key, at, part) => result.set(`${witness}:${key}`, {
|
|
54
|
-
at,
|
|
55
|
-
order: 0,
|
|
56
|
-
publication: { ...publication, observation: part },
|
|
57
|
-
});
|
|
58
|
-
const keyFor = (scope, label) => JSON.stringify([scope ?? 'all', label]);
|
|
59
|
-
const read = new Set();
|
|
60
|
-
for (const reading of observation.readings ?? []) {
|
|
61
|
-
const key = keyFor(reading.scope, reading.label);
|
|
62
|
-
read.add(key);
|
|
63
|
-
add(key, observation.checkedAt, {
|
|
64
|
-
checkedAt: observation.checkedAt,
|
|
65
|
-
readings: [{ ...reading }],
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
for (const pair of observation.coverage ?? []) {
|
|
69
|
-
const key = keyFor(pair.scope, pair.label);
|
|
70
|
-
if (!read.has(key))
|
|
71
|
-
add(key, observation.checkedAt, {
|
|
72
|
-
checkedAt: observation.checkedAt,
|
|
73
|
-
coverage: [{ ...pair }],
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
if (observation.budget)
|
|
77
|
-
add('budget', observation.checkedAt, {
|
|
78
|
-
checkedAt: observation.checkedAt,
|
|
79
|
-
budget: { ...observation.budget },
|
|
80
|
-
});
|
|
81
|
-
for (const [name, counter] of Object.entries(observation.counters ?? {})) {
|
|
82
|
-
add(`counter:${name}`, counter.checkedAt, {
|
|
83
|
-
checkedAt: observation.checkedAt,
|
|
84
|
-
counters: { [name]: { ...counter } },
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
if (observation.credits)
|
|
88
|
-
add('credits', observation.credits.checkedAt, {
|
|
89
|
-
checkedAt: observation.checkedAt,
|
|
90
|
-
credits: { ...observation.credits },
|
|
91
|
-
});
|
|
92
|
-
// Even an empty observation can bind an account proved by its receipt.
|
|
93
|
-
if (!result.size)
|
|
94
|
-
add('empty', observation.checkedAt, { checkedAt: observation.checkedAt });
|
|
95
|
-
return result;
|
|
47
|
+
]));
|
|
96
48
|
}
|
|
97
49
|
/** Enqueue without filesystem work; each quota field retains its own receipt fence. */
|
|
98
50
|
export function enqueueVaultQuota(path, publication) {
|
|
@@ -143,37 +95,57 @@ export function pendingVaultQuota(path, roster) {
|
|
|
143
95
|
}
|
|
144
96
|
return roster;
|
|
145
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Whether this process holds quota readings for `path` that no commit of the
|
|
100
|
+
* durable roster has written yet, so a reader of the file alone is not seeing
|
|
101
|
+
* everything this process would publish.
|
|
102
|
+
*/
|
|
103
|
+
export function hasPendingVaultQuota(path) {
|
|
104
|
+
return !!writers.get(resolve(path))?.pending.size;
|
|
105
|
+
}
|
|
146
106
|
function schedule(writer, delay) {
|
|
147
107
|
if (writer.running ||
|
|
148
108
|
writer.timer ||
|
|
149
109
|
!writer.pending.size ||
|
|
150
110
|
writer.abort.signal.aborted)
|
|
151
111
|
return;
|
|
152
|
-
|
|
112
|
+
const remaining = writer.draining
|
|
113
|
+
? 0
|
|
114
|
+
: (writer.committedAt ?? -Infinity) +
|
|
115
|
+
QUOTA_COMMIT_INTERVAL_MS -
|
|
116
|
+
writer.clock.now();
|
|
117
|
+
writer.timer = writer.clock.schedule(Math.max(delay, remaining), () => {
|
|
153
118
|
writer.timer = undefined;
|
|
154
|
-
flush(writer);
|
|
155
|
-
}
|
|
119
|
+
return flush(writer);
|
|
120
|
+
});
|
|
156
121
|
if (!writer.draining)
|
|
157
122
|
writer.timer.unref?.();
|
|
158
123
|
}
|
|
124
|
+
function cancelTimer(writer) {
|
|
125
|
+
writer.timer?.cancel();
|
|
126
|
+
writer.timer = undefined;
|
|
127
|
+
}
|
|
159
128
|
function flush(writer) {
|
|
160
129
|
if (writer.running || writer.abort.signal.aborted)
|
|
161
|
-
return;
|
|
130
|
+
return writer.running ?? Promise.resolve();
|
|
162
131
|
const batch = new Map([...writer.pending].map(([account, pending]) => [
|
|
163
132
|
account,
|
|
164
133
|
new Map(pending),
|
|
165
134
|
]));
|
|
166
135
|
writer.running = (async () => {
|
|
167
136
|
try {
|
|
168
|
-
await mutateVaultRoster(writer.path, (current) => {
|
|
137
|
+
const committed = await mutateVaultRoster(writer.path, (current) => {
|
|
169
138
|
let next = current;
|
|
170
139
|
for (const field of orderedFields(batch)) {
|
|
171
140
|
if (field.priorReading)
|
|
172
141
|
next = applyVaultQuota(next, field.priorReading).roster;
|
|
173
142
|
next = applyVaultQuota(next, field.publication).roster;
|
|
174
143
|
}
|
|
175
|
-
|
|
144
|
+
const changed = JSON.stringify(next) !== JSON.stringify(current);
|
|
145
|
+
return { ...(changed && { next }), result: changed };
|
|
176
146
|
}, { signal: writer.abort.signal });
|
|
147
|
+
if (committed)
|
|
148
|
+
writer.committedAt = writer.clock.now();
|
|
177
149
|
for (const [account, fields] of batch) {
|
|
178
150
|
const pending = writer.pending.get(account);
|
|
179
151
|
for (const [key, field] of fields) {
|
|
@@ -205,6 +177,7 @@ function flush(writer) {
|
|
|
205
177
|
schedule(writer, backoff);
|
|
206
178
|
}
|
|
207
179
|
})();
|
|
180
|
+
return writer.running;
|
|
208
181
|
}
|
|
209
182
|
/** Wait at most timeoutMs for detached publication; shutdown loss is acceptable. */
|
|
210
183
|
export async function drainVaultQuota(path, timeoutMs = 1_000) {
|
|
@@ -213,8 +186,7 @@ export async function drainVaultQuota(path, timeoutMs = 1_000) {
|
|
|
213
186
|
if (!writer)
|
|
214
187
|
return;
|
|
215
188
|
writer.draining = true;
|
|
216
|
-
|
|
217
|
-
writer.timer = undefined;
|
|
189
|
+
cancelTimer(writer);
|
|
218
190
|
let deadline;
|
|
219
191
|
let wake;
|
|
220
192
|
const drained = new Promise((done) => {
|
|
@@ -244,7 +216,7 @@ export async function drainVaultQuota(path, timeoutMs = 1_000) {
|
|
|
244
216
|
// must not discard its observations or stop its retries.
|
|
245
217
|
if (!writer.listeners.size) {
|
|
246
218
|
writer.abort.abort();
|
|
247
|
-
|
|
219
|
+
cancelTimer(writer);
|
|
248
220
|
writer.pending.clear();
|
|
249
221
|
if (writers.get(key) === writer)
|
|
250
222
|
writers.delete(key);
|
|
@@ -62,7 +62,25 @@ export interface ProjectionOptions {
|
|
|
62
62
|
routePrefix?: string;
|
|
63
63
|
mapAccount?: AccountMapper;
|
|
64
64
|
now?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Preferences carried over from another roster file, used only when there
|
|
67
|
+
* is no previous roster: its declines become this roster's declines, and
|
|
68
|
+
* its account order sorts the rows this inventory produces. No row of the
|
|
69
|
+
* seed is ever projected, so a seed cannot make an account routable.
|
|
70
|
+
*/
|
|
71
|
+
seed?: VaultRosterSeed;
|
|
65
72
|
}
|
|
73
|
+
/** The preferences one roster file can pass to a first projection elsewhere. */
|
|
74
|
+
export interface VaultRosterSeed {
|
|
75
|
+
readonly declined: readonly DeclinedAccount[];
|
|
76
|
+
/** Accounts in the seed roster's order, matched by account, then by id. */
|
|
77
|
+
readonly order: readonly {
|
|
78
|
+
readonly credentialId: string;
|
|
79
|
+
readonly accountIdentity?: string;
|
|
80
|
+
}[];
|
|
81
|
+
}
|
|
82
|
+
/** Declines and account order of `roster`, and nothing else of it. */
|
|
83
|
+
export declare function vaultRosterSeed(roster: VaultRosterFile): VaultRosterSeed;
|
|
66
84
|
export declare const DEFAULT_ROUTE_PREFIX = "vault:";
|
|
67
85
|
/**
|
|
68
86
|
* Project the vault's list onto the previous roster. Pure: callers serialize
|
|
@@ -87,6 +105,12 @@ export declare function projectVaultRoster(previous: VaultRosterFile | undefined
|
|
|
87
105
|
* reaches admission.
|
|
88
106
|
*/
|
|
89
107
|
export declare function vaultRoutingRows(roster: VaultRosterFile | undefined): RoutingRow[];
|
|
108
|
+
/**
|
|
109
|
+
* `roster` as `readVaultRoster` decodes it from the file that holds it, so a
|
|
110
|
+
* committed roster and a later read of its file compare equal. Package
|
|
111
|
+
* internal: not exported from `/claustrum`.
|
|
112
|
+
*/
|
|
113
|
+
export declare function decodedVaultRoster(roster: VaultRosterFile): VaultRosterFile;
|
|
90
114
|
export declare function readVaultRoster(path: string): Promise<VaultRosterFile | undefined>;
|
|
91
115
|
/** The ownership assertion available to a roster mutation callback. */
|
|
92
116
|
export interface VaultRosterLockContext {
|
|
@@ -119,6 +143,37 @@ export declare function mutateVaultRoster<T>(path: string, change: (current: Vau
|
|
|
119
143
|
export declare function declineVaultRoute(path: string, routeId: string): Promise<undefined>;
|
|
120
144
|
/** Lift the user's decline for a vault account and every alias of it. */
|
|
121
145
|
export declare function acceptVaultRoute(path: string, routeId: string): Promise<undefined>;
|
|
146
|
+
/** Options of `reorderVaultRoster`. */
|
|
147
|
+
export interface ReorderVaultRosterOptions {
|
|
148
|
+
/** Aborts the wait for the roster's write lock. */
|
|
149
|
+
signal?: AbortSignal;
|
|
150
|
+
}
|
|
151
|
+
export interface ReorderVaultRosterResult {
|
|
152
|
+
/** The route id order now on disk. */
|
|
153
|
+
ids: string[];
|
|
154
|
+
/** `unchanged` when the roster was already in this order; nothing was written. */
|
|
155
|
+
outcome: 'reordered' | 'unchanged';
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Set the order vault accounts are tried in. Routing reads that order from
|
|
159
|
+
* the roster's row order (`vaultRoutingRows`), and a later discovery keeps
|
|
160
|
+
* each account at its position, so the order lasts until the next reorder.
|
|
161
|
+
* The order is this host's routing preference: the vault has no order of its
|
|
162
|
+
* own, and this never contacts it.
|
|
163
|
+
*
|
|
164
|
+
* `routeIds` must name every roster route id exactly once; anything else
|
|
165
|
+
* (an unknown, missing or repeated id) refuses with `invalid-order` and
|
|
166
|
+
* writes nothing. It runs under the roster's write lock, like every other
|
|
167
|
+
* roster write, so a quota reading or decline committed meanwhile is kept.
|
|
168
|
+
* Each row is kept as the same object, so only the positions change: every
|
|
169
|
+
* row's fields and the rest of the file stay as they were. An order equal to
|
|
170
|
+
* the current one writes nothing. A host with no roster file yet has no
|
|
171
|
+
* routes, so only the empty order is accepted.
|
|
172
|
+
*
|
|
173
|
+
* A `ClaustrumConsumer` in the same process does not see the new order until
|
|
174
|
+
* it re-reads the file: call `consumer.reorder` instead to update both.
|
|
175
|
+
*/
|
|
176
|
+
export declare function reorderVaultRoster(path: string, routeIds: readonly string[], options?: ReorderVaultRosterOptions): Promise<ReorderVaultRosterResult>;
|
|
122
177
|
/**
|
|
123
178
|
* The receipt fields a quota or profile observation must carry to say which
|
|
124
179
|
* send (credential and account) it came from. Pass the
|
|
@@ -159,6 +214,16 @@ export declare function applyVaultQuota(current: VaultRosterFile | undefined, in
|
|
|
159
214
|
kept: boolean;
|
|
160
215
|
};
|
|
161
216
|
export declare function forgetPendingVaultDiscovery(path: string): void;
|
|
217
|
+
/**
|
|
218
|
+
* Whether `error` from a roster commit at `path` means another writer had the
|
|
219
|
+
* roster at that moment, not that the commit itself failed: the discovery
|
|
220
|
+
* lease was busy before any roster existed, the write lock stayed busy, or a
|
|
221
|
+
* lock this commit held was lost. On Windows the same lock steps can reject
|
|
222
|
+
* with a PrivateFileError instead (a lock file that could not be read or
|
|
223
|
+
* replaced while another process held it), so a PrivateFileError naming one
|
|
224
|
+
* of this roster's lock files counts the same on every OS.
|
|
225
|
+
*/
|
|
226
|
+
export declare function isVaultRosterLockRefusal(path: string, error: unknown): boolean;
|
|
162
227
|
/**
|
|
163
228
|
* Discover and commit the roster. The lease covers the list call and the
|
|
164
229
|
* commit, not just the write: an opaque view cannot tell a delayed old reply
|
|
@@ -166,7 +231,8 @@ export declare function forgetPendingVaultDiscovery(path: string): void;
|
|
|
166
231
|
* failed list, or custody switching off mid-run, never writes anything; a
|
|
167
232
|
* peer holding the lease means its last committed roster is served instead.
|
|
168
233
|
* A write-lock timeout keeps the discovered inventory for the next poll,
|
|
169
|
-
* unless a peer has superseded its base view in the meantime.
|
|
234
|
+
* unless a peer has superseded its base view in the meantime. A list that
|
|
235
|
+
* would write the file the roster already holds takes no write lock at all.
|
|
170
236
|
*/
|
|
171
237
|
export declare function refreshVaultRoster(options: {
|
|
172
238
|
path: string;
|
|
@@ -175,6 +241,19 @@ export declare function refreshVaultRoster(options: {
|
|
|
175
241
|
signal?: AbortSignal;
|
|
176
242
|
projection?: ProjectionOptions | (() => ProjectionOptions);
|
|
177
243
|
onInventory?: (inventory: VaultInventory) => void;
|
|
244
|
+
/**
|
|
245
|
+
* For a caller that listed before taking this path's discovery lease (the
|
|
246
|
+
* discovery hub lists once for several rosters): the view this roster file
|
|
247
|
+
* held before that list. `custody.discover` then returns that list. It is
|
|
248
|
+
* held to the rule a kept discovery is held to below: it is projected only
|
|
249
|
+
* while the file still holds this view or the list's own view. Otherwise
|
|
250
|
+
* another process committed since the list, and the file is returned
|
|
251
|
+
* unchanged. No discovery is kept for a later call, because such a caller
|
|
252
|
+
* lists again.
|
|
253
|
+
*/
|
|
254
|
+
listedOver?: {
|
|
255
|
+
readonly view: string | undefined;
|
|
256
|
+
};
|
|
178
257
|
}): Promise<VaultRosterFile | undefined>;
|
|
179
258
|
/**
|
|
180
259
|
* A host-owned main account as last verified from the vault: the plugin's
|