@centerforagenticai/pi-multi-account 0.1.1

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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +29 -0
  3. package/README.md +999 -0
  4. package/config/models/pi-multi-account.v1.json +32 -0
  5. package/config/subscription-plans.v1.json +122 -0
  6. package/package.json +76 -0
  7. package/packages/pi-anthropic-oauth/LICENSE +21 -0
  8. package/packages/pi-anthropic-oauth/package.json +54 -0
  9. package/packages/pi-anthropic-oauth/src/auth.ts +396 -0
  10. package/packages/pi-anthropic-oauth/src/context.ts +116 -0
  11. package/packages/pi-anthropic-oauth/src/convert.ts +303 -0
  12. package/packages/pi-anthropic-oauth/src/index.ts +37 -0
  13. package/packages/pi-anthropic-oauth/src/prompt.ts +137 -0
  14. package/packages/pi-anthropic-oauth/src/stream.ts +476 -0
  15. package/packages/pi-antigravity/LICENSE +21 -0
  16. package/packages/pi-antigravity/package.json +77 -0
  17. package/packages/pi-antigravity/src/auth/index.ts +14 -0
  18. package/packages/pi-antigravity/src/auth/oauth.ts +442 -0
  19. package/packages/pi-antigravity/src/client/client.ts +561 -0
  20. package/packages/pi-antigravity/src/client/index.ts +1 -0
  21. package/packages/pi-antigravity/src/context.ts +110 -0
  22. package/packages/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
  23. package/packages/pi-antigravity/src/diagnostics/index.ts +1 -0
  24. package/packages/pi-antigravity/src/image/image.ts +336 -0
  25. package/packages/pi-antigravity/src/image/index.ts +1 -0
  26. package/packages/pi-antigravity/src/index.ts +280 -0
  27. package/packages/pi-antigravity/src/models/discovery.ts +154 -0
  28. package/packages/pi-antigravity/src/models/grouping.ts +424 -0
  29. package/packages/pi-antigravity/src/models/index.ts +3 -0
  30. package/packages/pi-antigravity/src/models/models.ts +500 -0
  31. package/packages/pi-antigravity/src/stream/index.ts +1 -0
  32. package/packages/pi-antigravity/src/stream/stream.ts +1478 -0
  33. package/packages/pi-antigravity/src/types/enums.ts +42 -0
  34. package/packages/pi-antigravity/src/types/index.ts +2 -0
  35. package/packages/pi-antigravity/src/types/types.ts +292 -0
  36. package/packages/pi-antigravity/src/usage/index.ts +1 -0
  37. package/packages/pi-antigravity/src/usage/usage.ts +416 -0
  38. package/packages/pi-antigravity/src/utils/http.ts +91 -0
  39. package/packages/pi-antigravity/src/utils/index.ts +3 -0
  40. package/packages/pi-antigravity/src/utils/security.ts +73 -0
  41. package/packages/pi-antigravity/src/utils/util.ts +132 -0
  42. package/scripts/multi-account.mjs +44 -0
  43. package/src/account-labels.ts +223 -0
  44. package/src/account-plan-assignment.ts +340 -0
  45. package/src/account-rate-history.ts +372 -0
  46. package/src/anthropic-adaptive-stream.ts +531 -0
  47. package/src/anthropic-alias-stream.ts +140 -0
  48. package/src/anthropic-context-compat.ts +80 -0
  49. package/src/api-pricing.ts +579 -0
  50. package/src/bounded-file-lines.ts +97 -0
  51. package/src/catalog-rebinding.ts +177 -0
  52. package/src/catalog-registration-probe.ts +111 -0
  53. package/src/codex-adapter.ts +345 -0
  54. package/src/codex-model-defaults.ts +785 -0
  55. package/src/command-completions.ts +404 -0
  56. package/src/commands.ts +2000 -0
  57. package/src/compaction.ts +14 -0
  58. package/src/config.ts +1317 -0
  59. package/src/continuation.ts +569 -0
  60. package/src/cooldowns.ts +110 -0
  61. package/src/cost-digest-store.ts +332 -0
  62. package/src/cost-digest.ts +1044 -0
  63. package/src/cost-history.ts +251 -0
  64. package/src/cost-period-closer.ts +160 -0
  65. package/src/cost-report-json.ts +318 -0
  66. package/src/cost-report-reader.ts +368 -0
  67. package/src/cost-report-render.ts +207 -0
  68. package/src/cost-report.ts +1104 -0
  69. package/src/coverage-attestation.ts +397 -0
  70. package/src/credential-lifecycle.ts +169 -0
  71. package/src/credential-refresh.ts +248 -0
  72. package/src/declaration-notice-marker.ts +238 -0
  73. package/src/diagnostic-store.ts +276 -0
  74. package/src/diagnostics.ts +309 -0
  75. package/src/discovery.ts +471 -0
  76. package/src/duration.ts +13 -0
  77. package/src/error-classification.ts +256 -0
  78. package/src/fuzzy.ts +15 -0
  79. package/src/group-policy.ts +81 -0
  80. package/src/history-store.ts +897 -0
  81. package/src/index.ts +5572 -0
  82. package/src/lifecycle.ts +378 -0
  83. package/src/logical-dispatch.ts +279 -0
  84. package/src/logical-model-selector.ts +254 -0
  85. package/src/logical-model-switcher.ts +430 -0
  86. package/src/logical-provider-attribution.ts +544 -0
  87. package/src/logical-provider.ts +1237 -0
  88. package/src/logical-route-indicator.ts +215 -0
  89. package/src/machine-lease.ts +445 -0
  90. package/src/model-support.ts +66 -0
  91. package/src/models-declaration.ts +1091 -0
  92. package/src/openai-adapter.ts +117 -0
  93. package/src/openrouter-budget.ts +304 -0
  94. package/src/openrouter-fallback.ts +146 -0
  95. package/src/period-boundaries.ts +376 -0
  96. package/src/pi-anthropic-oauth.d.ts +6 -0
  97. package/src/preflight.ts +253 -0
  98. package/src/pricing-cache.ts +235 -0
  99. package/src/project-identity.ts +100 -0
  100. package/src/provider-registration.ts +942 -0
  101. package/src/rate-formula.ts +163 -0
  102. package/src/recovery-engine.ts +853 -0
  103. package/src/recovery-output.ts +837 -0
  104. package/src/recovery-plan.ts +239 -0
  105. package/src/report-range.ts +203 -0
  106. package/src/route-resolver.ts +789 -0
  107. package/src/routing-config-transaction.ts +232 -0
  108. package/src/routing.ts +1163 -0
  109. package/src/runtime-state.ts +630 -0
  110. package/src/session-account-groups.ts +284 -0
  111. package/src/session-restore.ts +287 -0
  112. package/src/shared-usage.ts +1392 -0
  113. package/src/standalone-cli.ts +720 -0
  114. package/src/status-view.ts +578 -0
  115. package/src/subscription-plan-catalog.ts +346 -0
  116. package/src/tier-model-resolver.ts +46 -0
  117. package/src/upstream-anthropic.ts +315 -0
  118. package/src/upstream-antigravity.ts +327 -0
  119. package/src/usage-fetch.ts +1634 -0
  120. package/src/usage.ts +1026 -0
  121. package/src/vendor.ts +87 -0
  122. package/src/warmer.ts +231 -0
  123. package/src/watchdog.ts +219 -0
  124. package/src/window-history.ts +270 -0
@@ -0,0 +1,251 @@
1
+ import type { AssistantMessage } from "@earendil-works/pi-ai";
2
+ import { randomUUID } from "node:crypto";
3
+ import { isProjectKey, projectKeyForCwd } from "./project-identity.js";
4
+ import { appendHistory, type HistoryLockExhaustion, type HistoryRecordEnvelope } from "./history-store.js";
5
+ import { isCanonicalManagedProviderId } from "./runtime-state.js";
6
+
7
+ /**
8
+ * Cost record status.
9
+ *
10
+ * REQ-COST-INCOMPLETE: mutually exclusive status assigned in priority order.
11
+ * Priority: complete > unpriced > partial
12
+ */
13
+ export type CostStatus = "complete" | "unpriced" | "partial";
14
+
15
+ /**
16
+ * Cost record payload appended to cost-history.ndjson.
17
+ *
18
+ * REQ-COST-RECORD: immutable snapshot of one observed response's cost and usage.
19
+ * REQ-OBSERVER-BOUNDED: observer id is bounded, non-identifying, process-scoped.
20
+ */
21
+ export interface CostRecordPayload {
22
+ readonly canonicalAccountId: string;
23
+ readonly projectKey: string;
24
+ readonly requestedModel: string;
25
+ readonly responseModel: string | undefined;
26
+ readonly observerId: string;
27
+ readonly status: CostStatus;
28
+ readonly schemaProvenance: string;
29
+ readonly buildProvenance: string;
30
+ readonly tokens: {
31
+ readonly input: number;
32
+ readonly output: number;
33
+ readonly cacheRead: number;
34
+ readonly cacheWrite: number;
35
+ readonly cacheWrite1h: number | undefined;
36
+ readonly total: number;
37
+ };
38
+ readonly cost: {
39
+ readonly input: number;
40
+ readonly output: number;
41
+ readonly cacheRead: number;
42
+ readonly cacheWrite: number;
43
+ readonly total: number;
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Bounded, non-identifying observer id.
49
+ *
50
+ * REQ-OBSERVER-BOUNDED: process-scoped uuid, max 36 bytes. Contains no hostname,
51
+ * username, path, or human-identifying token.
52
+ */
53
+ let processObserverId: string | undefined;
54
+ function getObserverId(): string {
55
+ if (!processObserverId) {
56
+ processObserverId = randomUUID();
57
+ }
58
+ return processObserverId;
59
+ }
60
+
61
+ /**
62
+ * Classify cost status.
63
+ *
64
+ * REQ-COST-INCOMPLETE: mutually exclusive status assigned in priority order.
65
+ * Priority: complete > unpriced > partial.
66
+ *
67
+ * Discriminator is TERMINATION, not the cost value. Do NOT branch on
68
+ * `cost.total === 0` (or any other cost-value predicate) as a signal of
69
+ * completeness. A genuinely-free successful response (e.g. a fully-cache-read
70
+ * turn) with every cost component equal to 0 is a REAL, correctly-priced
71
+ * observation and MUST classify `complete`; excluding it would understate
72
+ * usage in the REQ-COST-INCOMPLETE principal aggregate. The
73
+ * "initialized zero-cost object" carve-out for `unpriced` applies to
74
+ * INTERRUPTED responses, which by definition do not carry a terminal
75
+ * stopReason and therefore fall past the `complete` branch on stopReason
76
+ * alone — zero-ness of the payload is not the signal, the abnormal
77
+ * termination is.
78
+ *
79
+ * 1. `complete` — stopReason is `stop`, `length`, or `toolUse` AND every
80
+ * required token and cost field is finite and non-negative. Zero-valued
81
+ * but finite/non-negative cost components ARE valid; a free turn
82
+ * classifies here.
83
+ * 2. else `unpriced` — abnormal termination with no trustworthy computed
84
+ * cost: an interrupted stream whose payload is only pi's untouched
85
+ * zero-cost object, OR any response whose cost fields are missing,
86
+ * non-finite, or negative.
87
+ * 3. else `partial` — `error`/`aborted` response, or a stream interrupted
88
+ * before a successful terminal message, that retains trustworthy
89
+ * computed cost or usage data.
90
+ *
91
+ * Known residual gap (spec REQ-COST-RECORD, accepted): a synthesised
92
+ * terminal stopReason attached upstream to a genuinely-interrupted stream
93
+ * will classify `complete` under this rule. The spec accepts that side of
94
+ * the trade because stopReason is the completeness signal the emitter
95
+ * provides; do not try to fix it here.
96
+ */
97
+ export function classifyCostStatus(message: AssistantMessage): CostStatus {
98
+ const { stopReason, usage } = message;
99
+
100
+ const hasValidTokens =
101
+ Number.isFinite(usage.input) &&
102
+ usage.input >= 0 &&
103
+ Number.isFinite(usage.output) &&
104
+ usage.output >= 0 &&
105
+ Number.isFinite(usage.cacheRead) &&
106
+ usage.cacheRead >= 0 &&
107
+ Number.isFinite(usage.cacheWrite) &&
108
+ usage.cacheWrite >= 0 &&
109
+ Number.isFinite(usage.totalTokens) &&
110
+ usage.totalTokens >= 0;
111
+
112
+ const hasValidCost =
113
+ Number.isFinite(usage.cost.input) &&
114
+ usage.cost.input >= 0 &&
115
+ Number.isFinite(usage.cost.output) &&
116
+ usage.cost.output >= 0 &&
117
+ Number.isFinite(usage.cost.cacheRead) &&
118
+ usage.cost.cacheRead >= 0 &&
119
+ Number.isFinite(usage.cost.cacheWrite) &&
120
+ usage.cost.cacheWrite >= 0 &&
121
+ Number.isFinite(usage.cost.total) &&
122
+ usage.cost.total >= 0;
123
+
124
+ const isTerminalStop =
125
+ stopReason === "stop" ||
126
+ stopReason === "length" ||
127
+ stopReason === "toolUse";
128
+
129
+ // Priority 1: complete — terminal stopReason with all fields finite
130
+ // and non-negative. Zero is finite and non-negative: a fully cache-read
131
+ // turn that legitimately cost $0.00 classifies here.
132
+ if (isTerminalStop && hasValidTokens && hasValidCost) {
133
+ return "complete";
134
+ }
135
+
136
+ // Priority 2: unpriced — no trustworthy computed cost. Two disjoint
137
+ // cases: (a) cost fields are outright invalid (non-finite or negative);
138
+ // (b) abnormal termination whose payload has no cost or usage signal
139
+ // at all (the untouched zero-cost object from an interrupted stream,
140
+ // with no tokens observed either).
141
+ const hasNoUsageSignal =
142
+ usage.input === 0 &&
143
+ usage.output === 0 &&
144
+ usage.cacheRead === 0 &&
145
+ usage.cacheWrite === 0 &&
146
+ usage.totalTokens === 0 &&
147
+ usage.cost.input === 0 &&
148
+ usage.cost.output === 0 &&
149
+ usage.cost.cacheRead === 0 &&
150
+ usage.cost.cacheWrite === 0 &&
151
+ usage.cost.total === 0;
152
+
153
+ if (!hasValidCost || hasNoUsageSignal) {
154
+ return "unpriced";
155
+ }
156
+
157
+ // Priority 3: partial — abnormal termination retaining trustworthy
158
+ // cost or usage data.
159
+ return "partial";
160
+ }
161
+
162
+ /**
163
+ * Append a cost record to cost-history.ndjson.
164
+ *
165
+ * REQ-COST-RECORD: one immutable record per observed response.
166
+ * REQ-COST-NO-REPRICING: retains pi's computed cost verbatim, never recomputes.
167
+ *
168
+ * Fail-soft: returns false on append failure, caller should log and continue.
169
+ * NEVER throws into the host.
170
+ */
171
+ export async function appendCostRecord(
172
+ message: AssistantMessage,
173
+ canonicalAccountId: string,
174
+ options?: {
175
+ readonly costHistoryPath?: string;
176
+ readonly lockPath?: string;
177
+ readonly expectedBase?: string;
178
+ readonly now?: () => number;
179
+ readonly projectKey?: string;
180
+ readonly retryBudget?: {
181
+ readonly maxRetries?: number;
182
+ readonly initialDelayMs?: number;
183
+ readonly maxDelayMs?: number;
184
+ };
185
+ readonly onLockExhausted?: (details: HistoryLockExhaustion) => void;
186
+ },
187
+ ): Promise<boolean> {
188
+ try {
189
+ const rawStatus = classifyCostStatus(message);
190
+ // REQ-COST-NO-CONFLATION: Antigravity has no owning-vendor-api tier
191
+ // (`providerTypeFor` always returns "subscription" for `google-antigravity`),
192
+ // so the model `cost` rates registered from the reviewed upstream catalog
193
+ // (`src/upstream-antigravity.ts`, e.g. Google's own `geminiFlashCost`-style
194
+ // figures) are never a genuine per-token charge pi actually billed -- they
195
+ // are the same public-rate-style estimate this repository already keeps
196
+ // distinct from retained cost for every other family via `api-pricing.ts`.
197
+ // Whatever `message.usage.cost` computed from that catalog rate must never
198
+ // be recorded as a retained subscription-provider cost fact. Forcing
199
+ // "unpriced" here is what makes `cost-digest.ts#parseObservation` floor
200
+ // `retainedCostUsd` to 0 for this record downstream, while the tokens
201
+ // still enter the ledger so usage is not understated.
202
+ const status: CostStatus = isCanonicalManagedProviderId(
203
+ canonicalAccountId,
204
+ "google-antigravity",
205
+ )
206
+ ? "unpriced"
207
+ : rawStatus;
208
+ const projectKey = options?.projectKey ?? projectKeyForCwd();
209
+ if (!isProjectKey(projectKey)) return false;
210
+
211
+ const payload: CostRecordPayload = {
212
+ canonicalAccountId,
213
+ projectKey,
214
+ requestedModel: message.model,
215
+ responseModel: message.responseModel,
216
+ observerId: getObserverId(),
217
+ status,
218
+ schemaProvenance: "pi-multi-account@0.0.1",
219
+ buildProvenance: `node@${process.version}`,
220
+ tokens: {
221
+ input: message.usage.input,
222
+ output: message.usage.output,
223
+ cacheRead: message.usage.cacheRead,
224
+ cacheWrite: message.usage.cacheWrite,
225
+ cacheWrite1h: message.usage.cacheWrite1h,
226
+ total: message.usage.totalTokens,
227
+ },
228
+ cost: {
229
+ input: message.usage.cost.input,
230
+ output: message.usage.cost.output,
231
+ cacheRead: message.usage.cost.cacheRead,
232
+ cacheWrite: message.usage.cost.cacheWrite,
233
+ total: message.usage.cost.total,
234
+ },
235
+ };
236
+
237
+ const envelope: HistoryRecordEnvelope = {
238
+ schemaVersion: 1,
239
+ recordType: "cost-delta",
240
+ stableId: randomUUID(),
241
+ observedAtMs: message.timestamp,
242
+ recordedAtMs: Date.now(),
243
+ payload,
244
+ };
245
+
246
+ return await appendHistory(envelope, options);
247
+ } catch {
248
+ // Fail-soft: never throw into the host
249
+ return false;
250
+ }
251
+ }
@@ -0,0 +1,160 @@
1
+ import {
2
+ planClosedCostDigestsFromHistory,
3
+ type CostDigestRow,
4
+ } from "./cost-digest.js";
5
+ import {
6
+ CostDigestStore,
7
+ defaultCostDigestPaths,
8
+ } from "./cost-digest-store.js";
9
+ import { iterateHistory, type HistoryLogRecord } from "./history-store.js";
10
+ import {
11
+ acquireMachineLease,
12
+ type MachineLeaseHandle,
13
+ } from "./machine-lease.js";
14
+ import { getPeriodBounds } from "./period-boundaries.js";
15
+ import {
16
+ defaultPricingCachePaths,
17
+ OpenRouterPricingCache,
18
+ type PricingCacheResult,
19
+ } from "./pricing-cache.js";
20
+
21
+ export const COST_PERIOD_CLOSER_LEASE_TTL_MS = 60_000;
22
+
23
+ export interface CostPeriodCloserStore {
24
+ read(): readonly CostDigestRow[];
25
+ appendImmutable(
26
+ rows: readonly CostDigestRow[],
27
+ lease: MachineLeaseHandle,
28
+ ): number;
29
+ }
30
+
31
+ export type CostPeriodCloseResult =
32
+ | { readonly status: "closed" | "no-op"; readonly appendedRows: number }
33
+ | { readonly status: "lease-held"; readonly appendedRows: 0 }
34
+ | { readonly status: "failed"; readonly appendedRows: number }
35
+ | {
36
+ readonly status: "skipped";
37
+ readonly reason: "already-attempted-this-day";
38
+ };
39
+
40
+ interface CostPeriodCloserOptions {
41
+ readonly store: CostPeriodCloserStore;
42
+ readonly pricingCache: {
43
+ refreshIfNeeded(): Promise<PricingCacheResult>;
44
+ };
45
+ readonly readCostHistory: () => Iterable<HistoryLogRecord>;
46
+ readonly acquireLease: () => MachineLeaseHandle | undefined;
47
+ readonly now: () => number;
48
+ }
49
+
50
+ function validTimestamp(value: number): boolean {
51
+ return Number.isFinite(value) && value >= 0;
52
+ }
53
+
54
+ export class CostPeriodCloser {
55
+ readonly #store: CostPeriodCloserStore;
56
+ readonly #pricingCache: CostPeriodCloserOptions["pricingCache"];
57
+ readonly #readCostHistory: CostPeriodCloserOptions["readCostHistory"];
58
+ readonly #acquireLease: CostPeriodCloserOptions["acquireLease"];
59
+ readonly #now: CostPeriodCloserOptions["now"];
60
+ #attemptedDayStartMs: number | undefined;
61
+
62
+ constructor(options: CostPeriodCloserOptions) {
63
+ this.#store = options.store;
64
+ this.#pricingCache = options.pricingCache;
65
+ this.#readCostHistory = options.readCostHistory;
66
+ this.#acquireLease = options.acquireLease;
67
+ this.#now = options.now;
68
+ }
69
+
70
+ async closeCompletedPeriods(): Promise<CostPeriodCloseResult> {
71
+ let lease: MachineLeaseHandle | undefined;
72
+ let appendedRows = 0;
73
+ try {
74
+ const nowMs = this.#now();
75
+ if (!validTimestamp(nowMs)) return { status: "failed", appendedRows };
76
+ lease = this.#acquireLease();
77
+ if (lease === undefined) {
78
+ return { status: "lease-held", appendedRows: 0 };
79
+ }
80
+
81
+ const pricing = await this.#pricingCache.refreshIfNeeded();
82
+ const existingRows = this.#store.read();
83
+ const plannedRows = planClosedCostDigestsFromHistory({
84
+ readRecords: this.#readCostHistory,
85
+ existingRows,
86
+ pricing,
87
+ nowMs,
88
+ closedAtMs: nowMs,
89
+ });
90
+ if (plannedRows.length === 0) {
91
+ return { status: "no-op", appendedRows: 0 };
92
+ }
93
+
94
+ appendedRows = this.#store.appendImmutable(plannedRows, lease);
95
+ const persistedIds = new Set(this.#store.read().map((row) => row.id));
96
+ if (!plannedRows.every((row) => persistedIds.has(row.id))) {
97
+ return { status: "failed", appendedRows };
98
+ }
99
+ return { status: "closed", appendedRows };
100
+ } catch {
101
+ return { status: "failed", appendedRows };
102
+ } finally {
103
+ lease?.release();
104
+ }
105
+ }
106
+
107
+ async closeBeforeRender(): Promise<CostPeriodCloseResult> {
108
+ return this.closeCompletedPeriods();
109
+ }
110
+
111
+ async closeAfterObservation(
112
+ observedAtMs: number,
113
+ ): Promise<CostPeriodCloseResult> {
114
+ if (!validTimestamp(observedAtMs)) {
115
+ return { status: "failed", appendedRows: 0 };
116
+ }
117
+ const dayStartMs = getPeriodBounds(observedAtMs, "day").startMs;
118
+ if (
119
+ this.#attemptedDayStartMs !== undefined &&
120
+ dayStartMs <= this.#attemptedDayStartMs
121
+ ) {
122
+ return {
123
+ status: "skipped",
124
+ reason: "already-attempted-this-day",
125
+ };
126
+ }
127
+ this.#attemptedDayStartMs = dayStartMs;
128
+ const result = await this.closeCompletedPeriods();
129
+ if (result.status === "failed" && this.#attemptedDayStartMs === dayStartMs) {
130
+ this.#attemptedDayStartMs = undefined;
131
+ }
132
+ return result;
133
+ }
134
+ }
135
+
136
+ export function createDefaultCostPeriodCloser(options?: {
137
+ readonly now?: () => number;
138
+ }): CostPeriodCloser {
139
+ const now = options?.now ?? Date.now;
140
+ const digestPaths = defaultCostDigestPaths();
141
+ const pricingPaths = defaultPricingCachePaths();
142
+ return new CostPeriodCloser({
143
+ store: new CostDigestStore({ path: digestPaths.path }),
144
+ pricingCache: new OpenRouterPricingCache({
145
+ cachePath: pricingPaths.cachePath,
146
+ lockPath: pricingPaths.lockPath,
147
+ now,
148
+ }),
149
+ readCostHistory: () => iterateHistory("cost-delta"),
150
+ acquireLease: () =>
151
+ acquireMachineLease({
152
+ lockPath: digestPaths.lockPath,
153
+ ttlMs: COST_PERIOD_CLOSER_LEASE_TTL_MS,
154
+ now,
155
+ // A wedged digest lock would otherwise stop every period closure.
156
+ reclaimMalformed: true,
157
+ }),
158
+ now,
159
+ });
160
+ }