@usagekit/core 0.1.0 → 0.3.0
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/contracts.d.ts +81 -8
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/money.d.ts +2 -0
- package/dist/money.js +2 -0
- package/dist/policy.d.ts +9 -1
- package/dist/policy.js +22 -0
- package/dist/quantity.d.ts +2 -0
- package/dist/quantity.js +6 -0
- package/package.json +1 -1
package/dist/contracts.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ export type Scope = {
|
|
|
37
37
|
accessCredential?: AccessCredential;
|
|
38
38
|
/** Which provider secret version was used. Rotation identity, not caller identity. */
|
|
39
39
|
providerCredentialVersion?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Connection labels snapshotted at reservation: 1..16 unique entries, each 1..64 characters
|
|
42
|
+
* matching [a-z0-9][a-z0-9_.:-]*, sorted before storage. Attribution only: excluded from
|
|
43
|
+
* reserve identity, so a replay keeps the first reservation's tags.
|
|
44
|
+
*/
|
|
45
|
+
tags?: readonly string[];
|
|
40
46
|
};
|
|
41
47
|
export type FundingSource = "byok" | "platform";
|
|
42
48
|
export type Surface = "app" | "programmatic";
|
|
@@ -62,6 +68,7 @@ export type BudgetWindow = {
|
|
|
62
68
|
* Each variant names one constraint in a namespace.
|
|
63
69
|
* A platform_pool bounds spend shared by many principals and applies to operations
|
|
64
70
|
* naming that pool in ReserveInput. It is not the platform principal's own budget.
|
|
71
|
+
* A tag bounds every operation whose scope.tags contains it, across principals.
|
|
65
72
|
* A wallet is not a budget: the host ledger owns balances, holds and charges,
|
|
66
73
|
* coordinated through the host credit bridge. A wallet-level monthly cap may still
|
|
67
74
|
* be a principal Budget because the cap is a constraint, not a balance.
|
|
@@ -86,16 +93,49 @@ export type BudgetScope = {
|
|
|
86
93
|
kind: "platform_pool";
|
|
87
94
|
namespace: string;
|
|
88
95
|
poolId: string;
|
|
96
|
+
} | {
|
|
97
|
+
kind: "tag";
|
|
98
|
+
namespace: string;
|
|
99
|
+
tag: string;
|
|
89
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* surface selects the traffic a budget bounds: "any" matches every operation, a Surface
|
|
103
|
+
* matches operations whose source belongs to that surface group (see sourcesOf), and a
|
|
104
|
+
* Source matches exactly that source. Hosts set surface and source at authentication.
|
|
105
|
+
*/
|
|
90
106
|
export type Budget = {
|
|
91
107
|
id: string;
|
|
92
108
|
version: number;
|
|
93
109
|
scope: BudgetScope;
|
|
94
|
-
surface: Surface | "any";
|
|
110
|
+
surface: Surface | Source | "any";
|
|
95
111
|
unit: string;
|
|
96
112
|
limit: Quantity | null;
|
|
97
113
|
window: BudgetWindow;
|
|
98
|
-
|
|
114
|
+
/** block denies past limit. allow admits with a warning; wire and stored rows may still say the deprecated alias for allow. */
|
|
115
|
+
onExceed: "block" | "allow";
|
|
116
|
+
/** Only with onExceed allow: same unit as limit and greater than it. Exceeding it denies admission. */
|
|
117
|
+
hardLimit?: Quantity;
|
|
118
|
+
/** At most eight thresholds in ascending order; each crossing is reported once per epoch. */
|
|
119
|
+
alerts?: readonly BudgetAlert[];
|
|
120
|
+
};
|
|
121
|
+
/** percent is an integer 1..100 of limit; a Quantity threshold is in the budget unit. */
|
|
122
|
+
export type BudgetAlert = {
|
|
123
|
+
at: {
|
|
124
|
+
percent: number;
|
|
125
|
+
} | Quantity;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Reported by the command whose admitted or settled figures first reach the threshold.
|
|
129
|
+
* The store records each crossing atomically per budget id, epoch and threshold, so a
|
|
130
|
+
* replay returns the stored crossings and no later command reports the same one again.
|
|
131
|
+
*/
|
|
132
|
+
export type BudgetAlertCrossed = {
|
|
133
|
+
budgetId: string;
|
|
134
|
+
budgetVersion: number;
|
|
135
|
+
epoch: string;
|
|
136
|
+
at: BudgetAlert["at"];
|
|
137
|
+
used: Quantity;
|
|
138
|
+
reserved: Quantity;
|
|
99
139
|
};
|
|
100
140
|
export type LifecycleState = "reserved" | "dispatch_intended" | "pending" | "settled" | "released";
|
|
101
141
|
export type Certainty = "measured" | "estimated" | "unknown";
|
|
@@ -138,14 +178,14 @@ export type Receipt = {
|
|
|
138
178
|
* Store selects all applying budgets atomically inside reserve; Meter's pre-resolution
|
|
139
179
|
* is advisory and never sufficient for admission.
|
|
140
180
|
* Match namespace and the bound: principal, group, connection, accessCredential by
|
|
141
|
-
* kind and id, or poolId listed in platformPools. Match surface or
|
|
181
|
+
* kind and id, or poolId listed in platformPools. Match "any", the surface or the source.
|
|
142
182
|
* Each applicable budget's unit must appear in estimate. A missing bounded unit is
|
|
143
183
|
* a Meter validation error, not permission to bypass the bound. Store re-checks this
|
|
144
184
|
* against current budgets. Units without budgets are recorded without a bound.
|
|
145
185
|
* Use the current budget version at reservation time; snapshot its resolved epoch
|
|
146
186
|
* and version into budgetEpochs. Check every applying budget in one atomic command.
|
|
147
|
-
* Return the first exceeded bound in deterministic host-policy order; default
|
|
148
|
-
* platform_pool, principal, group, connection, access_credential.
|
|
187
|
+
* Return the first exceeded bound in deterministic host-policy order; the default
|
|
188
|
+
* is defaultBudgetOrder: platform_pool, principal, group, tag, connection, access_credential.
|
|
149
189
|
* A guest session is actor, not budget owner. The platform or a dedicated guest
|
|
150
190
|
* principal owns the operation; its pool and host-enforced session limit both apply.
|
|
151
191
|
*/
|
|
@@ -205,11 +245,13 @@ export type ReserveResult = ValidationFailure | {
|
|
|
205
245
|
replayed: false;
|
|
206
246
|
operation: Operation;
|
|
207
247
|
warnings: readonly AllowanceExceeded[];
|
|
248
|
+
alerts: readonly BudgetAlertCrossed[];
|
|
208
249
|
} | {
|
|
209
250
|
outcome: "reserved";
|
|
210
251
|
replayed: true;
|
|
211
252
|
operation: Operation;
|
|
212
253
|
warnings: readonly AllowanceExceeded[];
|
|
254
|
+
alerts: readonly BudgetAlertCrossed[];
|
|
213
255
|
} | {
|
|
214
256
|
outcome: "exceeded";
|
|
215
257
|
exceeded: AllowanceExceeded;
|
|
@@ -341,6 +383,7 @@ export type SettleResult = ValidationFailure | {
|
|
|
341
383
|
outcome: "settled";
|
|
342
384
|
replayed: boolean;
|
|
343
385
|
operation: Operation;
|
|
386
|
+
alerts: readonly BudgetAlertCrossed[];
|
|
344
387
|
} | {
|
|
345
388
|
outcome: "rejected";
|
|
346
389
|
reason: "not_holder" | "lease_expired" | "version_conflict" | "invalid_state" | "receipt_conflict";
|
|
@@ -390,14 +433,18 @@ export type AccessContext = {
|
|
|
390
433
|
canReadBillingDetail: boolean;
|
|
391
434
|
canManageBudgets: boolean;
|
|
392
435
|
};
|
|
393
|
-
/**
|
|
436
|
+
/**
|
|
437
|
+
* groupBy: "principal" is allowed only for group or namespace scope.
|
|
438
|
+
* "platform_pool" and "tag" explode one row per pool or tag of an operation, with an
|
|
439
|
+
* empty dimension when the operation has none; those rows are not additive across tags.
|
|
440
|
+
*/
|
|
394
441
|
export type UsageQuery = {
|
|
395
442
|
scope: UsageScope;
|
|
396
443
|
from: string;
|
|
397
444
|
to: string;
|
|
398
445
|
connection?: string;
|
|
399
446
|
units: readonly string[];
|
|
400
|
-
groupBy: readonly ("principal" | "provider" | "operation" | "surface" | "source" | "connection" | "day" | "access_credential" | "platform_pool")[];
|
|
447
|
+
groupBy: readonly ("principal" | "provider" | "operation" | "surface" | "source" | "connection" | "day" | "access_credential" | "platform_pool" | "funding_source" | "tag")[];
|
|
401
448
|
cursor?: string;
|
|
402
449
|
limit?: number;
|
|
403
450
|
};
|
|
@@ -415,6 +462,27 @@ export type UsagePage = {
|
|
|
415
462
|
watermark: string;
|
|
416
463
|
nextCursor?: string;
|
|
417
464
|
};
|
|
465
|
+
/**
|
|
466
|
+
* Operations whose createdAt lies in [from, to), in one lifecycle state of states (all when
|
|
467
|
+
* omitted), ordered by createdAt then operationId. Same scope and access rules as usage.
|
|
468
|
+
* Pagination mirrors usage: the first page fixes membership and order at its watermark; later
|
|
469
|
+
* pages take the cursor with the unchanged query and return each operation as currently stored.
|
|
470
|
+
*/
|
|
471
|
+
export type OperationsQuery = {
|
|
472
|
+
scope: UsageScope;
|
|
473
|
+
from: string;
|
|
474
|
+
to: string;
|
|
475
|
+
states?: readonly LifecycleState[];
|
|
476
|
+
connection?: string;
|
|
477
|
+
cursor?: string;
|
|
478
|
+
limit?: number;
|
|
479
|
+
};
|
|
480
|
+
export type OperationsPage = {
|
|
481
|
+
operations: readonly Operation[];
|
|
482
|
+
asOf: string;
|
|
483
|
+
watermark: string;
|
|
484
|
+
nextCursor?: string;
|
|
485
|
+
};
|
|
418
486
|
/** Budgets defined for exactly this scope, regardless of any operation. */
|
|
419
487
|
export type DefinedBudgetsQuery = {
|
|
420
488
|
scope: BudgetScope;
|
|
@@ -428,6 +496,8 @@ export type DefinedBudgetsQuery = {
|
|
|
428
496
|
export type ApplicableBudgetsQuery = {
|
|
429
497
|
scope: Scope;
|
|
430
498
|
surface: Surface;
|
|
499
|
+
/** Include budgets bound to exactly this source. Omitted lists surface and any budgets only. */
|
|
500
|
+
source?: Source;
|
|
431
501
|
units: readonly string[];
|
|
432
502
|
platformPools?: readonly string[];
|
|
433
503
|
};
|
|
@@ -451,10 +521,12 @@ export type ReadResult<T> = ValidationFailure | {
|
|
|
451
521
|
} | {
|
|
452
522
|
outcome: "forbidden";
|
|
453
523
|
};
|
|
454
|
-
/** Data-only budget denial. A P1 implementation may wrap it in a runtime error class. */
|
|
524
|
+
/** Data-only budget denial or warning. A P1 implementation may wrap it in a runtime error class. */
|
|
455
525
|
export interface AllowanceExceeded {
|
|
456
526
|
readonly code: "allowance_exceeded";
|
|
457
527
|
readonly budget: Budget;
|
|
528
|
+
/** Which bound was crossed: limit warns on allow budgets and denies on block budgets; hardLimit always denies. */
|
|
529
|
+
readonly boundary: "limit" | "hardLimit";
|
|
458
530
|
readonly used: Quantity;
|
|
459
531
|
readonly reserved: Quantity;
|
|
460
532
|
readonly resetsAt: string | null;
|
|
@@ -487,6 +559,7 @@ export interface Meter {
|
|
|
487
559
|
releaseUndispatched(input: ReleaseInput): Promise<ReleaseResult>;
|
|
488
560
|
getOperation(access: AccessContext, input: OperationRef): Promise<ReadResult<Operation | null>>;
|
|
489
561
|
usage(access: AccessContext, query: UsageQuery): Promise<ReadResult<UsagePage>>;
|
|
562
|
+
listOperations(access: AccessContext, query: OperationsQuery): Promise<ReadResult<OperationsPage>>;
|
|
490
563
|
definedBudgets(access: AccessContext, query: DefinedBudgetsQuery): Promise<ReadResult<readonly Budget[]>>;
|
|
491
564
|
applicableBudgets(access: AccessContext, query: ApplicableBudgetsQuery): Promise<ReadResult<readonly BudgetStatus[]>>;
|
|
492
565
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export type { Money, Quantity, AccessCredential, Scope, FundingSource, Surface, Source, BudgetWindow, BudgetScope, Budget, LifecycleState, Certainty, Measurement, Cost, Receipt, ReserveInput, ReserveResult, Operation, OperationRef, OperationCommand, Lease, DispatchGrant, DispatchIntentInput, LeaseRenewalInput, LeaseRenewal, RecoveryClaimInput, RecoveryClaim, Authority, SettleInput, ReleaseInput, CorrectionInput, SettleResult, ReleaseResult, UsageScope, AccessContext, UsageQuery, UsageRow, UsagePage, DefinedBudgetsQuery, ApplicableBudgetsQuery, BudgetStatus, ReadResult, AllowanceExceeded, Meter, } from "./contracts.js";
|
|
1
|
+
export type { Money, Quantity, AccessCredential, Scope, FundingSource, Surface, Source, BudgetWindow, BudgetScope, Budget, BudgetAlert, BudgetAlertCrossed, LifecycleState, Certainty, Measurement, Cost, Receipt, ReserveInput, ReserveResult, Operation, OperationRef, OperationCommand, Lease, DispatchGrant, DispatchIntentInput, LeaseRenewalInput, LeaseRenewal, RecoveryClaimInput, RecoveryClaim, Authority, SettleInput, ReleaseInput, CorrectionInput, SettleResult, ReleaseResult, UsageScope, AccessContext, UsageQuery, UsageRow, UsagePage, OperationsQuery, OperationsPage, DefinedBudgetsQuery, ApplicableBudgetsQuery, BudgetStatus, ReadResult, AllowanceExceeded, Meter, } from "./contracts.js";
|
|
2
2
|
export { resolveWindow } from "./windows.js";
|
|
3
3
|
export type { ResolvedWindow } from "./windows.js";
|
|
4
|
-
export { fromDecimalString, toDecimalString, add as addMoney, sub as subtractMoney, compare as compareMoney, } from "./money.js";
|
|
5
|
-
export { normalizeScale, add as addQuantity, compare as compareQuantity } from "./quantity.js";
|
|
4
|
+
export { fromDecimalString, toDecimalString, formatMoney, add as addMoney, sub as subtractMoney, compare as compareMoney, } from "./money.js";
|
|
5
|
+
export { normalizeScale, add as addQuantity, compare as compareQuantity, format as formatQuantity, } from "./quantity.js";
|
|
6
6
|
export type { ValidationFailure, AdmissionPolicy, BudgetOwner } from "./contracts.js";
|
|
7
|
-
export { defaultBudgetOrder } from "./policy.js";
|
|
7
|
+
export { defaultBudgetOrder, sourcesOf, surfaceMatches, tagPattern, normalizeTags, } from "./policy.js";
|
|
8
8
|
export type { ExpireReservationsInput, ExpireReservationsResult } from "./contracts.js";
|
|
9
9
|
export { decodeMeterJson } from "./wire.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { resolveWindow } from "./windows.js";
|
|
2
|
-
export { fromDecimalString, toDecimalString, add as addMoney, sub as subtractMoney, compare as compareMoney, } from "./money.js";
|
|
3
|
-
export { normalizeScale, add as addQuantity, compare as compareQuantity } from "./quantity.js";
|
|
4
|
-
export { defaultBudgetOrder } from "./policy.js";
|
|
2
|
+
export { fromDecimalString, toDecimalString, formatMoney, add as addMoney, sub as subtractMoney, compare as compareMoney, } from "./money.js";
|
|
3
|
+
export { normalizeScale, add as addQuantity, compare as compareQuantity, format as formatQuantity, } from "./quantity.js";
|
|
4
|
+
export { defaultBudgetOrder, sourcesOf, surfaceMatches, tagPattern, normalizeTags, } from "./policy.js";
|
|
5
5
|
export { decodeMeterJson } from "./wire.js";
|
package/dist/money.d.ts
CHANGED
|
@@ -6,3 +6,5 @@ export declare function toDecimalString(m: Money): string;
|
|
|
6
6
|
export declare const add: (a: Money, b: Money) => Money;
|
|
7
7
|
export declare const sub: (a: Money, b: Money) => Money;
|
|
8
8
|
export declare const compare: (a: Money, b: Money) => -1 | 0 | 1;
|
|
9
|
+
/** Exact cents text with four fractional digits; a named alias of toDecimalString for views. */
|
|
10
|
+
export declare const formatMoney: (m: Money) => string;
|
package/dist/money.js
CHANGED
|
@@ -15,3 +15,5 @@ export function toDecimalString(m) {
|
|
|
15
15
|
export const add = (a, b) => ({ currency: "USD", units: a.units + b.units });
|
|
16
16
|
export const sub = (a, b) => ({ currency: "USD", units: a.units - b.units });
|
|
17
17
|
export const compare = (a, b) => a.units < b.units ? -1 : a.units > b.units ? 1 : 0;
|
|
18
|
+
/** Exact cents text with four fractional digits; a named alias of toDecimalString for views. */
|
|
19
|
+
export const formatMoney = toDecimalString;
|
package/dist/policy.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
import type { BudgetScope } from "./contracts.js";
|
|
1
|
+
import type { BudgetScope, Source, Surface } from "./contracts.js";
|
|
2
|
+
/** Single source of the default admission order; hosts may permute it, never shorten it. */
|
|
2
3
|
export declare const defaultBudgetOrder: readonly BudgetScope["kind"][];
|
|
4
|
+
export declare const tagPattern: RegExp;
|
|
5
|
+
/** Sorted unique tags, or null when the list breaks the 1..16 count, format or uniqueness rules. */
|
|
6
|
+
export declare function normalizeTags(tags: readonly string[]): string[] | null;
|
|
7
|
+
/** Sources a surface budget covers. Every Source belongs to exactly one Surface. */
|
|
8
|
+
export declare const sourcesOf: (surface: Surface) => readonly Source[];
|
|
9
|
+
/** True when a budget surface value bounds an operation with this surface and source. */
|
|
10
|
+
export declare const surfaceMatches: (budgetSurface: Surface | Source | "any", surface: Surface, source?: Source) => boolean;
|
package/dist/policy.js
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
|
+
/** Single source of the default admission order; hosts may permute it, never shorten it. */
|
|
1
2
|
export const defaultBudgetOrder = Object.freeze([
|
|
2
3
|
"platform_pool",
|
|
3
4
|
"principal",
|
|
4
5
|
"group",
|
|
6
|
+
"tag",
|
|
5
7
|
"connection",
|
|
6
8
|
"access_credential",
|
|
7
9
|
]);
|
|
10
|
+
export const tagPattern = /^[a-z0-9][a-z0-9_.:-]{0,63}$/;
|
|
11
|
+
/** Sorted unique tags, or null when the list breaks the 1..16 count, format or uniqueness rules. */
|
|
12
|
+
export function normalizeTags(tags) {
|
|
13
|
+
if (!Array.isArray(tags) || tags.length < 1 || tags.length > 16)
|
|
14
|
+
return null;
|
|
15
|
+
if (tags.some((t) => typeof t !== "string" || !tagPattern.test(t)))
|
|
16
|
+
return null;
|
|
17
|
+
const sorted = [...tags].sort();
|
|
18
|
+
return sorted.some((t, n) => n > 0 && sorted[n - 1] === t) ? null : sorted;
|
|
19
|
+
}
|
|
20
|
+
const surfaceSources = Object.freeze({
|
|
21
|
+
app: Object.freeze(["app", "worker"]),
|
|
22
|
+
programmatic: Object.freeze(["api", "sdk", "cli", "mcp", "proxy"]),
|
|
23
|
+
});
|
|
24
|
+
/** Sources a surface budget covers. Every Source belongs to exactly one Surface. */
|
|
25
|
+
export const sourcesOf = (surface) => surfaceSources[surface];
|
|
26
|
+
/** True when a budget surface value bounds an operation with this surface and source. */
|
|
27
|
+
export const surfaceMatches = (budgetSurface, surface, source) => budgetSurface === "any" ||
|
|
28
|
+
budgetSurface === surface ||
|
|
29
|
+
(source !== undefined && budgetSurface === source);
|
package/dist/quantity.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ import type { Quantity } from "./contracts.js";
|
|
|
2
2
|
export declare function normalizeScale(q: Quantity, scale: number): Quantity;
|
|
3
3
|
export declare function add(a: Quantity, b: Quantity): Quantity;
|
|
4
4
|
export declare function compare(a: Quantity, b: Quantity): -1 | 0 | 1;
|
|
5
|
+
/** Exact decimal text of value * 10^-scale, trailing fractional zeros trimmed, no trailing dot. */
|
|
6
|
+
export declare function format(q: Quantity): string;
|
package/dist/quantity.js
CHANGED
|
@@ -17,3 +17,9 @@ export function compare(a, b) {
|
|
|
17
17
|
const [x, y] = pair(a, b);
|
|
18
18
|
return x.value < y.value ? -1 : x.value > y.value ? 1 : 0;
|
|
19
19
|
}
|
|
20
|
+
/** Exact decimal text of value * 10^-scale, trailing fractional zeros trimmed, no trailing dot. */
|
|
21
|
+
export function format(q) {
|
|
22
|
+
const negative = q.value < 0n, digits = (negative ? -q.value : q.value).toString().padStart(q.scale + 1, "0"), whole = digits.slice(0, digits.length - q.scale), fraction = digits.slice(digits.length - q.scale).replace(/0+$/, "");
|
|
23
|
+
const text = fraction ? `${whole}.${fraction}` : whole;
|
|
24
|
+
return negative && text !== "0" ? `-${text}` : text;
|
|
25
|
+
}
|