@stacksjs/ts-cloud 0.7.108 → 0.7.110

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.
Files changed (135) hide show
  1. package/dist/alerts/delivery.d.ts +10 -0
  2. package/dist/alerts/model.d.ts +1 -1
  3. package/dist/api/client.d.ts +53 -0
  4. package/dist/api/handler.d.ts +10 -0
  5. package/dist/api/service.d.ts +1 -0
  6. package/dist/aws/index.js +10 -10
  7. package/dist/aws/lambda.d.ts +19 -0
  8. package/dist/bin/cli.js +1520 -1204
  9. package/dist/bin/dashboard-server.js +935 -621
  10. package/dist/chunk-19bs955w.js +12203 -0
  11. package/dist/{chunk-fmt40tpw.js → chunk-53jatzaw.js} +2 -2
  12. package/dist/{chunk-ef2dwfrg.js → chunk-8kh577qh.js} +5 -5
  13. package/dist/{chunk-s9c2x5jv.js → chunk-9v2pwxv9.js} +2 -2
  14. package/dist/chunk-aa2dkn7b.js +205 -0
  15. package/dist/{chunk-33sy72r8.js → chunk-d3vx2ekv.js} +71 -25
  16. package/dist/chunk-d48vtdj4.js +433 -0
  17. package/dist/{chunk-c6db5m63.js → chunk-t2s3gdq6.js} +253 -8
  18. package/dist/chunk-va2yd85b.js +200 -0
  19. package/dist/{chunk-3g4r09dd.js → chunk-wmbep3b5.js} +1 -1
  20. package/dist/{chunk-eg8ck25v.js → chunk-wmz8x3cy.js} +6935 -15274
  21. package/dist/{chunk-ybcz6sxc.js → chunk-x6yzgv7y.js} +33 -0
  22. package/dist/control-plane/types.d.ts +1 -1
  23. package/dist/deploy/dashboard-protection.test.d.ts +1 -0
  24. package/dist/deploy/dashboard-spend.test.d.ts +1 -0
  25. package/dist/deploy/index.js +11 -8
  26. package/dist/deploy/local-dashboard-server.d.ts +2 -0
  27. package/dist/drivers/index.js +4 -2
  28. package/dist/drivers/shared/protection.d.ts +43 -0
  29. package/dist/drivers/shared/protection.test.d.ts +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.js +364 -102
  32. package/dist/protection/controls.d.ts +127 -0
  33. package/dist/protection/controls.test.d.ts +1 -0
  34. package/dist/protection/ddos.d.ts +130 -0
  35. package/dist/protection/ddos.test.d.ts +1 -0
  36. package/dist/protection/index.d.ts +11 -0
  37. package/dist/protection/index.js +89 -0
  38. package/dist/protection/ratelimit.d.ts +154 -0
  39. package/dist/protection/ratelimit.test.d.ts +1 -0
  40. package/dist/protection/waf.d.ts +143 -0
  41. package/dist/protection/waf.test.d.ts +1 -0
  42. package/dist/spend/anomaly-config.d.ts +130 -0
  43. package/dist/spend/anomaly-config.test.d.ts +1 -0
  44. package/dist/spend/anomaly.d.ts +114 -0
  45. package/dist/spend/anomaly.test.d.ts +1 -0
  46. package/dist/spend/api.d.ts +42 -0
  47. package/dist/spend/api.test.d.ts +1 -0
  48. package/dist/spend/appliers.d.ts +126 -0
  49. package/dist/spend/docs.test.d.ts +1 -0
  50. package/dist/spend/enforcement.d.ts +111 -0
  51. package/dist/spend/enforcement.integration.test.d.ts +1 -0
  52. package/dist/spend/evaluator.d.ts +70 -0
  53. package/dist/spend/evaluator.test.d.ts +1 -0
  54. package/dist/spend/focus.d.ts +93 -0
  55. package/dist/spend/focus.test.d.ts +1 -0
  56. package/dist/spend/gate.d.ts +96 -0
  57. package/dist/spend/index.d.ts +20 -0
  58. package/dist/spend/index.js +173 -0
  59. package/dist/spend/lease.d.ts +54 -0
  60. package/dist/spend/lease.test.d.ts +1 -0
  61. package/dist/spend/meter.d.ts +70 -0
  62. package/dist/spend/meter.test.d.ts +1 -0
  63. package/dist/spend/model.d.ts +228 -0
  64. package/dist/spend/notifications.d.ts +58 -0
  65. package/dist/spend/pricing.d.ts +109 -0
  66. package/dist/spend/pricing.test.d.ts +1 -0
  67. package/dist/spend/projection.d.ts +36 -0
  68. package/dist/spend/runner.d.ts +130 -0
  69. package/dist/spend/service.d.ts +129 -0
  70. package/dist/spend/signals.d.ts +131 -0
  71. package/dist/spend/signals.test.d.ts +1 -0
  72. package/dist/spend/sms-sdk.test.d.ts +1 -0
  73. package/dist/spend/spend.integration.test.d.ts +1 -0
  74. package/dist/spend/store.d.ts +166 -0
  75. package/dist/spend/transports.d.ts +169 -0
  76. package/dist/spend/transports.test.d.ts +1 -0
  77. package/dist/spend/window.d.ts +52 -0
  78. package/dist/spend/window.test.d.ts +1 -0
  79. package/dist/ui/404.html +1 -0
  80. package/dist/ui/access-denied.html +1 -1
  81. package/dist/ui/account/automation.html +3 -3
  82. package/dist/ui/account/security.html +2 -2
  83. package/dist/ui/applications/compose.html +4 -4
  84. package/dist/ui/applications/new.html +2 -2
  85. package/dist/ui/data/backups.html +4 -4
  86. package/dist/ui/data/services.html +4 -4
  87. package/dist/ui/data/volumes.html +4 -4
  88. package/dist/ui/index.html +4 -4
  89. package/dist/ui/infrastructure/topology.html +4 -4
  90. package/dist/ui/integrations.html +2 -2
  91. package/dist/ui/operations/alerts.html +4 -4
  92. package/dist/ui/operations/configuration.html +4 -4
  93. package/dist/ui/operations/jobs.html +3 -3
  94. package/dist/ui/operations/maintenance.html +4 -4
  95. package/dist/ui/operations/observability.html +4 -4
  96. package/dist/ui/operations/previews.html +4 -4
  97. package/dist/ui/operations/queue.html +3 -3
  98. package/dist/ui/operations/regions.html +4 -4
  99. package/dist/ui/operations/releases.html +4 -4
  100. package/dist/ui/operations/spend.html +1634 -0
  101. package/dist/ui/operations/workloads.html +4 -4
  102. package/dist/ui/security.html +2 -2
  103. package/dist/ui/server/actions.html +4 -4
  104. package/dist/ui/server/activity.html +2 -2
  105. package/dist/ui/server/capacity.html +4 -4
  106. package/dist/ui/server/database.html +4 -4
  107. package/dist/ui/server/deployments.html +4 -4
  108. package/dist/ui/server/diagnostics.html +2 -2
  109. package/dist/ui/server/firewall.html +100 -161
  110. package/dist/ui/server/fleet.html +4 -4
  111. package/dist/ui/server/logs.html +4 -4
  112. package/dist/ui/server/metrics.html +4 -4
  113. package/dist/ui/server/security.html +2 -2
  114. package/dist/ui/server/services.html +2 -2
  115. package/dist/ui/server/sites.html +4 -4
  116. package/dist/ui/server/ssh-keys.html +4 -4
  117. package/dist/ui/server/team.html +4 -4
  118. package/dist/ui/server/terminal.html +2 -2
  119. package/dist/ui/serverless/alarms.html +4 -4
  120. package/dist/ui/serverless/assets.html +2 -2
  121. package/dist/ui/serverless/cost.html +2 -2
  122. package/dist/ui/serverless/data.html +4 -4
  123. package/dist/ui/serverless/deployments.html +1 -1
  124. package/dist/ui/serverless/firewall.html +2 -2
  125. package/dist/ui/serverless/functions.html +4 -4
  126. package/dist/ui/serverless/logs.html +4 -4
  127. package/dist/ui/serverless/metrics.html +2 -2
  128. package/dist/ui/serverless/queues.html +4 -4
  129. package/dist/ui/serverless/secrets.html +4 -4
  130. package/dist/ui/serverless/traces.html +4 -4
  131. package/dist/ui/serverless.html +3 -3
  132. package/dist/ui-src/pages/operations/spend.stx +225 -0
  133. package/dist/ui-src/pages/partials/nav.stx +2 -0
  134. package/dist/ui-src/pages/server/firewall.stx +89 -0
  135. package/package.json +11 -3
@@ -0,0 +1,93 @@
1
+ /**
2
+ * FOCUS export: cost data in the shape FinOps tools already read.
3
+ *
4
+ * FOCUS (FinOps Open Cost and Usage Specification) is the interchange format
5
+ * Vantage, CloudZero, and the rest ingest. Emitting it matters more than it
6
+ * looks: without it, every team wanting cost observability writes a bespoke
7
+ * transformer against our column names, and that transformer breaks the first
8
+ * time we add a meter.
9
+ *
10
+ * Two deliberate choices:
11
+ *
12
+ * - **Newline-delimited JSON**, not an array. A year of hourly rollups is
13
+ * large, and a consumer should be able to stream it without holding the
14
+ * whole export in memory - which also means we can generate it without
15
+ * doing so either.
16
+ * - **Daily granularity by default.** FOCUS consumers expect a billing
17
+ * period grain, and an hourly export is ~24x the rows for detail nobody
18
+ * reconciles against an invoice. Hourly stays available for our own API.
19
+ */
20
+ import type { UsageRollup } from './model';
21
+ /** The specification version these rows conform to. */
22
+ export declare const FOCUS_VERSION = "1.3";
23
+ /**
24
+ * A FOCUS billing record.
25
+ *
26
+ * Column names are the specification's, not ours - that is the whole point.
27
+ * Only the columns we can populate honestly are emitted; inventing a
28
+ * `ContractedCost` we do not know would be worse than omitting it.
29
+ */
30
+ export interface FocusRecord {
31
+ /** Specification version, so a consumer can branch on it. */
32
+ FocusVersion: string;
33
+ /** Inclusive start of the charge period, ISO-8601. */
34
+ ChargePeriodStart: string;
35
+ /** Exclusive end. */
36
+ ChargePeriodEnd: string;
37
+ BillingPeriodStart: string;
38
+ BillingPeriodEnd: string;
39
+ BillingCurrency: string;
40
+ /** What the customer is billed. We report our estimate. */
41
+ BilledCost: number;
42
+ /** Amortized cost. Identical to billed here: we model no commitments. */
43
+ EffectiveCost: number;
44
+ ListCost: number;
45
+ ChargeCategory: 'Usage';
46
+ ChargeDescription: string;
47
+ /** `AWS`, `Hetzner`, or whatever the meter was attributed to. */
48
+ ProviderName: string;
49
+ PublisherName: string;
50
+ InvoiceIssuerName: string;
51
+ ServiceName: string;
52
+ ServiceCategory: string;
53
+ /** Our meter key, so a consumer can map back to what we measured. */
54
+ SkuId: string;
55
+ PricingUnit: string;
56
+ PricingQuantity: number;
57
+ ConsumedQuantity: number;
58
+ ConsumedUnit: string;
59
+ RegionId?: string;
60
+ ResourceId?: string;
61
+ /** The project and environment, so cost splits the way teams are organized. */
62
+ SubAccountId?: string;
63
+ SubAccountName?: string;
64
+ Tags: Record<string, string>;
65
+ }
66
+ export interface FocusExportOptions {
67
+ /** Billing period the rows belong to. */
68
+ billingPeriodStart: string;
69
+ billingPeriodEnd: string;
70
+ currency?: string;
71
+ /** `daily` (the FOCUS norm) or `hourly` (our native grain). */
72
+ granularity?: 'hourly' | 'daily';
73
+ /** Names for the sub-account column, keyed by project id. */
74
+ projectNames?: Record<string, string>;
75
+ }
76
+ /**
77
+ * Convert rollups into FOCUS records.
78
+ *
79
+ * Rows are aggregated to the requested grain first. Emitting one record per
80
+ * rollup at daily granularity would produce 24 rows per day per meter that a
81
+ * consumer then has to sum, which defeats the point of choosing a grain.
82
+ */
83
+ export declare function toFocusRecords(rollups: readonly UsageRollup[], options: FocusExportOptions): FocusRecord[];
84
+ /** Serialize to newline-delimited JSON, one record per line. */
85
+ export declare function toFocusJsonl(records: readonly FocusRecord[]): string;
86
+ /**
87
+ * Stream the export.
88
+ *
89
+ * A year at hourly grain is hundreds of thousands of rows; building one string
90
+ * would hold the whole export in memory on both ends. The chunking is by
91
+ * record count rather than bytes because a consumer reads lines, not bytes.
92
+ */
93
+ export declare function streamFocusJsonl(records: readonly FocusRecord[], chunkSize?: number): Generator<string>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The spend gate: what "capped" actually means to the rest of the platform.
3
+ *
4
+ * The naive way to apply a cap is for each enforcement action to reach into
5
+ * the subsystem it affects and switch something off. That fails in three ways
6
+ * this design avoids:
7
+ *
8
+ * 1. **It is not crash-safe.** A cap applied by mutating in-memory state
9
+ * quietly lifts itself the next time the control plane restarts, which is
10
+ * exactly when a runaway workload is least supervised.
11
+ * 2. **It couples every action to a driver.** `block_builds` would need SSH
12
+ * to a box to mean anything, so it could not be tested, and a driver
13
+ * outage would read as "not capped".
14
+ * 3. **It has no single answer.** With state spread across five subsystems,
15
+ * "is this project capped?" has five answers that drift.
16
+ *
17
+ * Instead, enforcement writes one durable record - the gate - into the control
18
+ * plane, and the subsystems that spend money *read* it before acting. The gate
19
+ * is the source of truth; a driver call is an additional, best-effort effect
20
+ * on top of it. So a driver that fails leaves the cap in force rather than
21
+ * silently open, and lifting a cap is a delete rather than an undo.
22
+ */
23
+ import type { ControlPlaneStore } from '../control-plane';
24
+ import type { EnforcementAction } from './model';
25
+ /** Operations the gate can refuse. */
26
+ export type GatedOperation = 'build' | 'deploy' | 'function_invoke' | 'request';
27
+ /** Control-plane setting key holding every live gate entry. */
28
+ export declare const SPEND_GATE_SETTING = "spend.gates";
29
+ export interface SpendGateEntry {
30
+ budgetId: string;
31
+ action: EnforcementAction;
32
+ organizationId: string;
33
+ projectId?: string;
34
+ environmentId?: string;
35
+ reason: string;
36
+ /** A simulated entry is recorded and reported but never refuses anything. */
37
+ simulated: boolean;
38
+ appliedAt: string;
39
+ }
40
+ export interface GateVerdict {
41
+ allowed: boolean;
42
+ action?: EnforcementAction;
43
+ reason?: string;
44
+ budgetId?: string;
45
+ }
46
+ type Scope = {
47
+ organizationId: string;
48
+ projectId?: string;
49
+ environmentId?: string;
50
+ };
51
+ export declare class SpendGate {
52
+ private readonly controlPlane;
53
+ private readonly options;
54
+ constructor(controlPlane: ControlPlaneStore, options?: {
55
+ now?: () => Date;
56
+ });
57
+ private read;
58
+ private write;
59
+ /** Entries governing a scope - equal or broader. Most disruptive first. */
60
+ list(scope?: Scope): SpendGateEntry[];
61
+ /**
62
+ * Entries under a scope - equal or narrower.
63
+ *
64
+ * What a dashboard and a reconcile pass want: everything an organization has
65
+ * in force, including its projects' own caps.
66
+ */
67
+ listUnder(scope: Scope): SpendGateEntry[];
68
+ /** Record an action as in force. Idempotent on (budget, action). */
69
+ open(entry: Omit<SpendGateEntry, 'appliedAt'> & {
70
+ appliedAt?: string;
71
+ }): SpendGateEntry;
72
+ /** Lift an action. Returns false when it was not in force. */
73
+ close(budgetId: string, action: EnforcementAction): boolean;
74
+ /** Lift every action for a budget. Used when a budget is deleted or disabled. */
75
+ closeBudget(budgetId: string): number;
76
+ /**
77
+ * The question every spending caller should ask.
78
+ *
79
+ * Fails open for an operation the gate has no rule about: a cap must never
80
+ * block something it was not designed to reason about.
81
+ */
82
+ check(operation: GatedOperation, scope: Scope): GateVerdict;
83
+ /** The strongest action in force for a scope, ignoring simulated entries. */
84
+ strongest(scope: Scope): EnforcementAction | undefined;
85
+ }
86
+ /** Raised when a spend cap refuses an operation. Carries a 402 for the API. */
87
+ export declare class SpendCapError extends Error {
88
+ readonly operation: GatedOperation;
89
+ readonly verdict: GateVerdict;
90
+ readonly code = "spend_cap_exceeded";
91
+ readonly status = 402;
92
+ constructor(operation: GatedOperation, verdict: GateVerdict);
93
+ }
94
+ /** Throw unless the gate allows the operation. The one-liner for a call site. */
95
+ export declare function assertSpendAllows(gate: SpendGate, operation: GatedOperation, scope: Scope): void;
96
+ export {};
@@ -0,0 +1,20 @@
1
+ export * from './model';
2
+ export * from './pricing';
3
+ export * from './window';
4
+ export * from './meter';
5
+ export * from './store';
6
+ export * from './projection';
7
+ export * from './evaluator';
8
+ export * from './enforcement';
9
+ export * from './anomaly';
10
+ export * from './anomaly-config';
11
+ export * from './signals';
12
+ export * from './focus';
13
+ export * from './service';
14
+ export * from './gate';
15
+ export * from './appliers';
16
+ export * from './notifications';
17
+ export * from './lease';
18
+ export * from './runner';
19
+ export * from './transports';
20
+ export * from './api';
@@ -0,0 +1,173 @@
1
+ import {
2
+ AnomalyConfigStore,
3
+ AwsSpendTransport,
4
+ ComputeSpendTransport,
5
+ CounterTracker,
6
+ DEFAULT_METER_MAPPINGS,
7
+ DEFAULT_PRICE_BOOK,
8
+ DEFAULT_THRESHOLDS,
9
+ DETECTABLE_SIGNALS,
10
+ ENFORCEMENT_ACTIONS,
11
+ ENFORCEMENT_DESCRIPTIONS,
12
+ ENFORCEMENT_SEVERITY,
13
+ FOCUS_VERSION,
14
+ METERS,
15
+ METER_KEYS,
16
+ MICRO_CENTS,
17
+ MIN_PROJECTION_CONFIDENCE,
18
+ RecordingSpendTransport,
19
+ SENSITIVITY_THRESHOLDS,
20
+ SPEND_CYCLE_SECONDS,
21
+ SPEND_GATE_SETTING,
22
+ SPEND_LEASE_SETTING,
23
+ SignalSource,
24
+ SpendCapError,
25
+ SpendGate,
26
+ SpendLoopLease,
27
+ SpendNotificationRouter,
28
+ SpendRunner,
29
+ SpendService,
30
+ SpendStore,
31
+ URGENT_SPEND_EVENTS,
32
+ USER_VISIBLE_ACTIONS,
33
+ aggregateDeltas,
34
+ anomalyOptionsForSignal,
35
+ assertSpendAllows,
36
+ budgetWindow,
37
+ compositeSpendTransport,
38
+ createEnforcementHandlers,
39
+ detectAnomalies,
40
+ detectLatestAnomaly,
41
+ evaluateBudget,
42
+ ewma,
43
+ findMapping,
44
+ findPriceEntry,
45
+ formatCents,
46
+ formatTimeToExhaustion,
47
+ governingLimitCents,
48
+ handleSpendRequest,
49
+ hourBucket,
50
+ hourBucketsBetween,
51
+ isOperationAllowed,
52
+ isValidTimeZone,
53
+ lookbackHoursForSignal,
54
+ median,
55
+ medianAbsoluteDeviation,
56
+ mergeDecisions,
57
+ mergePriceBooks,
58
+ meterTelemetry,
59
+ optionsForSignal,
60
+ percentOfLimit,
61
+ planEnforcement,
62
+ previousBudgetWindow,
63
+ priceUsage,
64
+ priceUsageLines,
65
+ projectSpend,
66
+ robustZScore,
67
+ roundCents,
68
+ routeMatches,
69
+ runEnforcement,
70
+ scopeKey,
71
+ signalDefinition,
72
+ spendNotificationText,
73
+ spendOpenApiPaths,
74
+ startSpendLoop,
75
+ streamFocusJsonl,
76
+ strongestActiveAction,
77
+ tieredMicroCents,
78
+ toFocusJsonl,
79
+ toFocusRecords,
80
+ usageDeltaKey,
81
+ utcInstantOfLocal,
82
+ zeroFill,
83
+ zoneOffsetMs,
84
+ zonedParts
85
+ } from "../chunk-19bs955w.js";
86
+ import"../chunk-aa2dkn7b.js";
87
+ import"../chunk-t2s3gdq6.js";
88
+ import"../chunk-v0bahtg2.js";
89
+ export {
90
+ zonedParts,
91
+ zoneOffsetMs,
92
+ zeroFill,
93
+ utcInstantOfLocal,
94
+ usageDeltaKey,
95
+ toFocusRecords,
96
+ toFocusJsonl,
97
+ tieredMicroCents,
98
+ strongestActiveAction,
99
+ streamFocusJsonl,
100
+ startSpendLoop,
101
+ spendOpenApiPaths,
102
+ spendNotificationText,
103
+ signalDefinition,
104
+ scopeKey,
105
+ runEnforcement,
106
+ routeMatches,
107
+ roundCents,
108
+ robustZScore,
109
+ projectSpend,
110
+ priceUsageLines,
111
+ priceUsage,
112
+ previousBudgetWindow,
113
+ planEnforcement,
114
+ percentOfLimit,
115
+ optionsForSignal,
116
+ meterTelemetry,
117
+ mergePriceBooks,
118
+ mergeDecisions,
119
+ medianAbsoluteDeviation,
120
+ median,
121
+ lookbackHoursForSignal,
122
+ isValidTimeZone,
123
+ isOperationAllowed,
124
+ hourBucketsBetween,
125
+ hourBucket,
126
+ handleSpendRequest,
127
+ governingLimitCents,
128
+ formatTimeToExhaustion,
129
+ formatCents,
130
+ findPriceEntry,
131
+ findMapping,
132
+ ewma,
133
+ evaluateBudget,
134
+ detectLatestAnomaly,
135
+ detectAnomalies,
136
+ createEnforcementHandlers,
137
+ compositeSpendTransport,
138
+ budgetWindow,
139
+ assertSpendAllows,
140
+ anomalyOptionsForSignal,
141
+ aggregateDeltas,
142
+ USER_VISIBLE_ACTIONS,
143
+ URGENT_SPEND_EVENTS,
144
+ SpendStore,
145
+ SpendService,
146
+ SpendRunner,
147
+ SpendNotificationRouter,
148
+ SpendLoopLease,
149
+ SpendGate,
150
+ SpendCapError,
151
+ SignalSource,
152
+ SPEND_LEASE_SETTING,
153
+ SPEND_GATE_SETTING,
154
+ SPEND_CYCLE_SECONDS,
155
+ SENSITIVITY_THRESHOLDS,
156
+ RecordingSpendTransport,
157
+ MIN_PROJECTION_CONFIDENCE,
158
+ MICRO_CENTS,
159
+ METER_KEYS,
160
+ METERS,
161
+ FOCUS_VERSION,
162
+ ENFORCEMENT_SEVERITY,
163
+ ENFORCEMENT_DESCRIPTIONS,
164
+ ENFORCEMENT_ACTIONS,
165
+ DETECTABLE_SIGNALS,
166
+ DEFAULT_THRESHOLDS,
167
+ DEFAULT_PRICE_BOOK,
168
+ DEFAULT_METER_MAPPINGS,
169
+ CounterTracker,
170
+ ComputeSpendTransport,
171
+ AwsSpendTransport,
172
+ AnomalyConfigStore
173
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A lease, so only one process runs the spend loop at a time.
3
+ *
4
+ * The loop is started in more than one place by design - the dashboard server
5
+ * runs it, and `cloud spend:work` runs it standalone - so two of them
6
+ * overlapping is a configuration people will actually reach.
7
+ *
8
+ * Most of the damage is already prevented downstream: the gate is idempotent on
9
+ * (budget, action), enforcement records have a unique index on live rows, and
10
+ * notification deliveries dedupe on their idempotency key. What a lease adds is
11
+ * avoiding the *work*: two processes evaluating every budget every minute
12
+ * doubles the database traffic and the provider calls that enforcement makes,
13
+ * and provider calls are rate-limited and occasionally billed.
14
+ *
15
+ * It is an advisory lease with a TTL, not a distributed lock. A process that
16
+ * dies holding one blocks the loop for at most `ttlSeconds`, which is the right
17
+ * trade: a stalled cap for a minute is better than a lock nobody can clear.
18
+ */
19
+ import type { ControlPlaneStore } from '../control-plane';
20
+ export declare const SPEND_LEASE_SETTING = "spend.loop.lease";
21
+ export interface SpendLeaseState {
22
+ owner: string;
23
+ acquiredAt: string;
24
+ expiresAt: string;
25
+ }
26
+ export interface SpendLeaseOptions {
27
+ /** Identifies the holder in the record. A hostname and pid is ideal. */
28
+ owner?: string;
29
+ /** How long a held lease stays valid without renewal. */
30
+ ttlSeconds?: number;
31
+ now?: () => Date;
32
+ }
33
+ export declare class SpendLoopLease {
34
+ private readonly controlPlane;
35
+ readonly owner: string;
36
+ private readonly ttlMs;
37
+ constructor(controlPlane: ControlPlaneStore, options?: SpendLeaseOptions);
38
+ private readonly nowFn;
39
+ private read;
40
+ /** The current holder, or undefined when the lease is free or expired. */
41
+ current(): SpendLeaseState | undefined;
42
+ /**
43
+ * Take or renew the lease.
44
+ *
45
+ * Read and write happen inside one transaction, because two processes doing
46
+ * read-then-write outside one would both see it free and both take it -
47
+ * which is the exact race the lease exists to prevent.
48
+ */
49
+ acquire(): boolean;
50
+ /** Give the lease up. Only the holder may, so a late release cannot steal it. */
51
+ release(): boolean;
52
+ /** True when this process currently holds it. */
53
+ held(): boolean;
54
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ import type { TelemetryRecord } from '../telemetry';
2
+ import type { MeterKey, UsageDelta } from './model';
3
+ /** A rule mapping a telemetry signal onto a meter. */
4
+ export interface MeterMapping {
5
+ /** Telemetry `name` to match, or a prefix when it ends with `*`. */
6
+ match: string;
7
+ meter: MeterKey;
8
+ /** Which field carries the quantity. `value` is the default. */
9
+ field?: 'value' | 'bytesOut' | 'bytesIn' | 'durationMs' | 'count';
10
+ /** Multiply the raw field by this to reach the meter's unit. */
11
+ scale?: number;
12
+ /**
13
+ * The signal is a monotonically increasing counter and must be differenced.
14
+ * Anything an agent reports as a running period total needs this.
15
+ */
16
+ cumulative?: boolean;
17
+ }
18
+ /**
19
+ * Default mappings against the signal names the platform already emits.
20
+ *
21
+ * `request.count` and `traffic.requests` both exist because the edge collector
22
+ * and the dashboard's own instrumentation grew up separately; mapping both is
23
+ * cheaper and less brittle than renaming a signal that operators' dashboards
24
+ * already query.
25
+ */
26
+ export declare const DEFAULT_METER_MAPPINGS: readonly MeterMapping[];
27
+ export declare function findMapping(name: string, mappings?: readonly MeterMapping[]): MeterMapping | undefined;
28
+ /** Stable id for a delta, so replaying the same record is a no-op in the store. */
29
+ export declare function usageDeltaKey(delta: UsageDelta, sourceId: string): string;
30
+ /**
31
+ * Difference monotonic counters into deltas.
32
+ *
33
+ * `/proc/net/dev` style counters reset on reboot and wrap on 32-bit interfaces.
34
+ * A naive subtraction turns either into a hugely negative delta - or, worse,
35
+ * a caller who clamps at zero silently loses the usage. The rule used here: a
36
+ * value lower than the last one means the counter restarted, so the new value
37
+ * *is* the delta. That over-counts by at most one scrape interval and never
38
+ * under-counts, which is the right direction for something guarding a budget.
39
+ */
40
+ export declare class CounterTracker {
41
+ private readonly resetToleranceRatio;
42
+ private readonly last;
43
+ constructor(resetToleranceRatio?: number);
44
+ /** Returns the increment to bill, or undefined when there is nothing new. */
45
+ delta(key: string, value: number, at?: Date | string): number | undefined;
46
+ /** Drop tracked counters not seen since `before`, so a retired host stops holding memory. */
47
+ prune(before: Date): number;
48
+ get size(): number;
49
+ }
50
+ export interface MeteredDelta extends UsageDelta {
51
+ /** Deterministic idempotency key. */
52
+ key: string;
53
+ }
54
+ export interface MeterOptions {
55
+ mappings?: readonly MeterMapping[];
56
+ /** Shared across calls so cumulative counters difference correctly over time. */
57
+ counters?: CounterTracker;
58
+ /** Fallback when a record carries no provider attribute. */
59
+ defaultProvider?: string;
60
+ }
61
+ /**
62
+ * Convert telemetry records into usage deltas.
63
+ *
64
+ * Records that map to no meter are skipped silently - the telemetry stream
65
+ * carries far more than billing cares about, and warning on each would drown
66
+ * the log.
67
+ */
68
+ export declare function meterTelemetry(organizationId: string, records: readonly TelemetryRecord[], options?: MeterOptions): MeteredDelta[];
69
+ /** Collapse deltas that share a scope, meter, and hour. Fewer rows, same total. */
70
+ export declare function aggregateDeltas(deltas: readonly UsageDelta[]): UsageDelta[];
@@ -0,0 +1 @@
1
+ export {};