instar 1.3.782 → 1.3.784

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 (77) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +41 -15
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/MachineIdentity.d.ts +13 -0
  5. package/dist/core/MachineIdentity.d.ts.map +1 -1
  6. package/dist/core/MachineIdentity.js +31 -0
  7. package/dist/core/MachineIdentity.js.map +1 -1
  8. package/dist/core/MeteredSpendGate.d.ts +127 -0
  9. package/dist/core/MeteredSpendGate.d.ts.map +1 -0
  10. package/dist/core/MeteredSpendGate.js +248 -0
  11. package/dist/core/MeteredSpendGate.js.map +1 -0
  12. package/dist/core/MeteredSpendLedger.d.ts +154 -0
  13. package/dist/core/MeteredSpendLedger.d.ts.map +1 -0
  14. package/dist/core/MeteredSpendLedger.js +434 -0
  15. package/dist/core/MeteredSpendLedger.js.map +1 -0
  16. package/dist/core/PinAttemptStore.d.ts +35 -0
  17. package/dist/core/PinAttemptStore.d.ts.map +1 -0
  18. package/dist/core/PinAttemptStore.js +85 -0
  19. package/dist/core/PinAttemptStore.js.map +1 -0
  20. package/dist/core/PostUpdateMigrator.d.ts +7 -0
  21. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  22. package/dist/core/PostUpdateMigrator.js +24 -0
  23. package/dist/core/PostUpdateMigrator.js.map +1 -1
  24. package/dist/core/RenderedPlanStore.d.ts +61 -0
  25. package/dist/core/RenderedPlanStore.d.ts.map +1 -0
  26. package/dist/core/RenderedPlanStore.js +92 -0
  27. package/dist/core/RenderedPlanStore.js.map +1 -0
  28. package/dist/core/RoutingSpendCapsStore.d.ts +113 -0
  29. package/dist/core/RoutingSpendCapsStore.d.ts.map +1 -0
  30. package/dist/core/RoutingSpendCapsStore.js +241 -0
  31. package/dist/core/RoutingSpendCapsStore.js.map +1 -0
  32. package/dist/core/SpendAlertDispatcher.d.ts +94 -0
  33. package/dist/core/SpendAlertDispatcher.d.ts.map +1 -0
  34. package/dist/core/SpendAlertDispatcher.js +168 -0
  35. package/dist/core/SpendAlertDispatcher.js.map +1 -0
  36. package/dist/core/SpendAlertEmitters.d.ts +118 -0
  37. package/dist/core/SpendAlertEmitters.d.ts.map +1 -0
  38. package/dist/core/SpendAlertEmitters.js +220 -0
  39. package/dist/core/SpendAlertEmitters.js.map +1 -0
  40. package/dist/core/SpendAlertResolver.d.ts +89 -0
  41. package/dist/core/SpendAlertResolver.d.ts.map +1 -0
  42. package/dist/core/SpendAlertResolver.js +175 -0
  43. package/dist/core/SpendAlertResolver.js.map +1 -0
  44. package/dist/core/TelegramSpendTopicChannel.d.ts +50 -0
  45. package/dist/core/TelegramSpendTopicChannel.d.ts.map +1 -0
  46. package/dist/core/TelegramSpendTopicChannel.js +106 -0
  47. package/dist/core/TelegramSpendTopicChannel.js.map +1 -0
  48. package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
  49. package/dist/core/WriteDomainRegistry.js +17 -0
  50. package/dist/core/WriteDomainRegistry.js.map +1 -1
  51. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  52. package/dist/core/devGatedFeatures.js +9 -0
  53. package/dist/core/devGatedFeatures.js.map +1 -1
  54. package/dist/core/routingSpendView.d.ts +10 -2
  55. package/dist/core/routingSpendView.d.ts.map +1 -1
  56. package/dist/core/routingSpendView.js +18 -10
  57. package/dist/core/routingSpendView.js.map +1 -1
  58. package/dist/core/types.d.ts +45 -3
  59. package/dist/core/types.d.ts.map +1 -1
  60. package/dist/core/types.js.map +1 -1
  61. package/dist/server/AgentServer.d.ts +25 -0
  62. package/dist/server/AgentServer.d.ts.map +1 -1
  63. package/dist/server/AgentServer.js +304 -0
  64. package/dist/server/AgentServer.js.map +1 -1
  65. package/dist/server/routes.d.ts +10 -0
  66. package/dist/server/routes.d.ts.map +1 -1
  67. package/dist/server/routes.js +196 -4
  68. package/dist/server/routes.js.map +1 -1
  69. package/dist/testing/selfActionRegistry.d.ts.map +1 -1
  70. package/dist/testing/selfActionRegistry.js +206 -0
  71. package/dist/testing/selfActionRegistry.js.map +1 -1
  72. package/package.json +1 -1
  73. package/src/data/builtin-manifest.json +64 -64
  74. package/upgrades/1.3.783.md +66 -0
  75. package/upgrades/1.3.784.md +59 -0
  76. package/upgrades/side-effects/routing-spend-increment-b.md +44 -0
  77. package/upgrades/side-effects/routing-spend-increment-c.md +44 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * MeteredSpendLedger — Layer 3 of the Routing Control Room
3
+ * (docs/specs/routing-control-room-spend-alerts.md, Increment B).
4
+ *
5
+ * The AUTHORITATIVE money truth: an append-only, booking-priced ledger of every
6
+ * metered-door reservation/settlement, plus a maintained O(1) committed total the
7
+ * fail-closed gate reads. This ledger — NOT `feature_metrics`, NOT the Layer-1c
8
+ * provider-report store — is the ONLY rebuild source for committed spend
9
+ * (FD-3/FD-9): rebuilding from Layer 0 joined to current prices is FORBIDDEN at
10
+ * the gate (a downward `corrects` would re-open capped headroom).
11
+ *
12
+ * Write discipline (adopted from DriftSpendLedger, upgraded per spec C4-2):
13
+ * - Rows are APPEND-ONLY JSONL, fsync'd, written FIRST; the totals file is a
14
+ * regenerable CACHE of the fold, rewritten atomically (tmp+rename) SECOND.
15
+ * A crash between the two leaves totals STALE-LOW by at most one booking; the
16
+ * next gate read runs a cheap high-water check (file size) and re-folds.
17
+ * - Writes are FAIL-CLOSED and NON-SWALLOWING: a booking that cannot be durably
18
+ * persisted throws, and the caller must refuse the metered call.
19
+ * - Boot: fold all rows, REWRITE the totals cache from row truth (torn totals
20
+ * rename / totals-without-append are always corrected; the append-first
21
+ * ordering makes totals-without-append impossible, asserted in tests).
22
+ * - A torn trailing append (partial last line) is the malformed-row-skip case.
23
+ *
24
+ * Reserve/settle lifecycle (A-B2/A2-1):
25
+ * - reserve(): books the worst-case estimate (cached tokens as FULL input,
26
+ * FD-19; output at the REQUIRED max-tokens ceiling). Outstanding reserves are
27
+ * INSIDE the committed total (concurrent reservations see each other).
28
+ * - settle(): idempotent terminal `reserved → settled`; books ACTUAL cost.
29
+ * A settle after expiry books the actual cost as a fresh ABSOLUTE row
30
+ * (expiry-aware settle) — the late-settle race can never under-count.
31
+ * - expire(): the reserve-expiry sweep (takes the per-key mutex) expires only
32
+ * reserves older than the TTL still in `reserved` state.
33
+ * - First terminal transition wins; the loser is a no-op.
34
+ *
35
+ * Concurrency: metered calls funnel through the single server process, so the
36
+ * booking critical sections are guarded by an in-process async mutex per keyRef
37
+ * (held only for the booking, RELEASED during the LLM round-trip) plus a
38
+ * proper-lockfile advisory lock for defence against a second process.
39
+ */
40
+ /** Default reserve TTL — pinned comfortably above the metered-call latency ceiling (spec: 15 min vs a 5-min call ceiling). */
41
+ export declare const DEFAULT_RESERVE_TTL_MS: number;
42
+ export type MeteredBookingKind = 'reserve' | 'settle' | 'expire';
43
+ /** One append-only booking row. NEVER mutated; corrections are new rows. */
44
+ export interface MeteredBookingRow {
45
+ ts: string;
46
+ keyRef: string;
47
+ door: string;
48
+ modelId: string;
49
+ kind: MeteredBookingKind;
50
+ /** The per-call id (=== meteredCallId) minted at reserve time. */
51
+ reserveId: string;
52
+ /** Reserve: worst-case estimate. Settle: ACTUAL booked cost. Expire: 0. */
53
+ costUsd: number;
54
+ /** The metered-lease epoch the booking was made under (A-B4). */
55
+ leaseEpoch: number;
56
+ /** Set on a settle that arrived after its reserve was expired (books absolute). */
57
+ lateAfterExpire?: boolean;
58
+ }
59
+ /** The maintained O(1) running totals (a regenerable cache of the fold). */
60
+ export interface MeteredCommittedTotals {
61
+ keyRef: string;
62
+ committedLifetimeUsd: number;
63
+ committedDayUsd: number;
64
+ /** UTC day (YYYY-MM-DD) the day total belongs to; a new day resets it. */
65
+ dayEpoch: string;
66
+ updatedAt: string;
67
+ }
68
+ export interface ReserveRequest {
69
+ keyRef: string;
70
+ door: string;
71
+ modelId: string;
72
+ /** Worst-case reserve in USD (computed by the gate at canonical BASE price). */
73
+ reserveUsd: number;
74
+ leaseEpoch: number;
75
+ /**
76
+ * When set, the cap comparison happens ATOMICALLY inside the per-key booking
77
+ * critical section (`committed + reserve > cap` strict `>`, both caps) — the
78
+ * only ordering under which two concurrent reservations are guaranteed to see
79
+ * each other (spec Layer 3). Refusal throws CapExceededError; nothing books.
80
+ */
81
+ admitOnlyUnderCaps?: {
82
+ lifetimeCapUsd: number;
83
+ dailyCapUsd: number;
84
+ };
85
+ }
86
+ /** Thrown by reserve() when admitOnlyUnderCaps would be breached — nothing was booked. */
87
+ export declare class CapExceededError extends Error {
88
+ capKind: 'lifetime' | 'daily';
89
+ committedUsd: number;
90
+ reserveUsd: number;
91
+ capUsd: number;
92
+ constructor(capKind: 'lifetime' | 'daily', committedUsd: number, reserveUsd: number, capUsd: number);
93
+ }
94
+ export interface ReserveHandle {
95
+ reserveId: string;
96
+ /** Committed lifetime total AFTER this reservation (outstanding reserves included). */
97
+ committedLifetimeUsd: number;
98
+ committedDayUsd: number;
99
+ }
100
+ export declare class MeteredLedgerWriteError extends Error {
101
+ cause?: unknown | undefined;
102
+ constructor(msg: string, cause?: unknown | undefined);
103
+ }
104
+ export interface MeteredSpendLedgerOptions {
105
+ /** The agent's `.instar/` dir; rows/totals live under `state/`, the lock under `local/`. */
106
+ stateDir: string;
107
+ reserveTtlMs?: number;
108
+ now?: () => number;
109
+ }
110
+ export declare class MeteredSpendLedger {
111
+ private readonly rowsPath;
112
+ private readonly totalsPath;
113
+ private readonly lockTarget;
114
+ private readonly ttlMs;
115
+ private readonly now;
116
+ /** In-memory fold state, authoritative in-process. Rebuilt on construction. */
117
+ private reserves;
118
+ private totals;
119
+ /** Byte length of the rows file as of our last append/fold (the high-water mark). */
120
+ private rowsBytes;
121
+ /** In-process per-key mutex tail. */
122
+ private mutexTail;
123
+ constructor(opts: MeteredSpendLedgerOptions);
124
+ /** O(1) committed totals for a key, day-rolled to NOW. High-water-checked against external appends. */
125
+ committed(keyRef: string): MeteredCommittedTotals;
126
+ /** Reserve worst-case cost. FAIL-CLOSED: any persistence failure throws. */
127
+ reserve(req: ReserveRequest): Promise<ReserveHandle>;
128
+ /**
129
+ * Terminal settle at ACTUAL cost. Idempotent: a second settle is a no-op.
130
+ * A settle after expiry books the actual as a fresh ABSOLUTE row (late-settle).
131
+ */
132
+ settle(keyRef: string, reserveId: string, actualUsd: number): Promise<void>;
133
+ /** Reserve-expiry sweep (Layer 3 — takes the per-key mutex). Expires only TTL-stale `reserved` states. */
134
+ sweepExpired(): Promise<number>;
135
+ /** All totals (for the caps view). Day-rolled to now. */
136
+ allCommitted(): MeteredCommittedTotals[];
137
+ /** Apply one row to the in-memory fold. */
138
+ private applyRow;
139
+ /** Contribution of one reserve state to committed totals. */
140
+ private contribution;
141
+ private recomputeTotalsForKey;
142
+ /** Day-rolled view of a key's totals (a new UTC day resets the day figure). */
143
+ private currentTotals;
144
+ /** Full refold from row truth (the fold is canon; the totals file is a cache). */
145
+ private refoldFromRows;
146
+ /** Cheap external-append detection: rows-file byte length vs our high-water mark. */
147
+ private checkHighWater;
148
+ /** Append one row, fsync'd, under the advisory file lock. NON-SWALLOWING. */
149
+ private appendDurable;
150
+ /** Atomic (tmp+rename) rewrite of the totals cache. Best-effort — rows are canon. */
151
+ private rewriteTotalsCache;
152
+ private withKeyMutex;
153
+ }
154
+ //# sourceMappingURL=MeteredSpendLedger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeteredSpendLedger.d.ts","sourceRoot":"","sources":["../../src/core/MeteredSpendLedger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,434 @@
1
+ /**
2
+ * MeteredSpendLedger — Layer 3 of the Routing Control Room
3
+ * (docs/specs/routing-control-room-spend-alerts.md, Increment B).
4
+ *
5
+ * The AUTHORITATIVE money truth: an append-only, booking-priced ledger of every
6
+ * metered-door reservation/settlement, plus a maintained O(1) committed total the
7
+ * fail-closed gate reads. This ledger — NOT `feature_metrics`, NOT the Layer-1c
8
+ * provider-report store — is the ONLY rebuild source for committed spend
9
+ * (FD-3/FD-9): rebuilding from Layer 0 joined to current prices is FORBIDDEN at
10
+ * the gate (a downward `corrects` would re-open capped headroom).
11
+ *
12
+ * Write discipline (adopted from DriftSpendLedger, upgraded per spec C4-2):
13
+ * - Rows are APPEND-ONLY JSONL, fsync'd, written FIRST; the totals file is a
14
+ * regenerable CACHE of the fold, rewritten atomically (tmp+rename) SECOND.
15
+ * A crash between the two leaves totals STALE-LOW by at most one booking; the
16
+ * next gate read runs a cheap high-water check (file size) and re-folds.
17
+ * - Writes are FAIL-CLOSED and NON-SWALLOWING: a booking that cannot be durably
18
+ * persisted throws, and the caller must refuse the metered call.
19
+ * - Boot: fold all rows, REWRITE the totals cache from row truth (torn totals
20
+ * rename / totals-without-append are always corrected; the append-first
21
+ * ordering makes totals-without-append impossible, asserted in tests).
22
+ * - A torn trailing append (partial last line) is the malformed-row-skip case.
23
+ *
24
+ * Reserve/settle lifecycle (A-B2/A2-1):
25
+ * - reserve(): books the worst-case estimate (cached tokens as FULL input,
26
+ * FD-19; output at the REQUIRED max-tokens ceiling). Outstanding reserves are
27
+ * INSIDE the committed total (concurrent reservations see each other).
28
+ * - settle(): idempotent terminal `reserved → settled`; books ACTUAL cost.
29
+ * A settle after expiry books the actual cost as a fresh ABSOLUTE row
30
+ * (expiry-aware settle) — the late-settle race can never under-count.
31
+ * - expire(): the reserve-expiry sweep (takes the per-key mutex) expires only
32
+ * reserves older than the TTL still in `reserved` state.
33
+ * - First terminal transition wins; the loser is a no-op.
34
+ *
35
+ * Concurrency: metered calls funnel through the single server process, so the
36
+ * booking critical sections are guarded by an in-process async mutex per keyRef
37
+ * (held only for the booking, RELEASED during the LLM round-trip) plus a
38
+ * proper-lockfile advisory lock for defence against a second process.
39
+ */
40
+ import fs from 'node:fs';
41
+ import path from 'node:path';
42
+ import crypto from 'node:crypto';
43
+ import lockfile from 'proper-lockfile';
44
+ const LOCK_OPTIONS = {
45
+ retries: { retries: 50, factor: 1.2, minTimeout: 50, maxTimeout: 500 },
46
+ stale: 10_000,
47
+ realpath: false,
48
+ };
49
+ /** Default reserve TTL — pinned comfortably above the metered-call latency ceiling (spec: 15 min vs a 5-min call ceiling). */
50
+ export const DEFAULT_RESERVE_TTL_MS = 15 * 60 * 1000;
51
+ /** Thrown by reserve() when admitOnlyUnderCaps would be breached — nothing was booked. */
52
+ export class CapExceededError extends Error {
53
+ capKind;
54
+ committedUsd;
55
+ reserveUsd;
56
+ capUsd;
57
+ constructor(capKind, committedUsd, reserveUsd, capUsd) {
58
+ super(`${capKind}: committed ${committedUsd.toFixed(4)} + reserve ${reserveUsd.toFixed(4)} > cap ${capUsd.toFixed(2)}`);
59
+ this.capKind = capKind;
60
+ this.committedUsd = committedUsd;
61
+ this.reserveUsd = reserveUsd;
62
+ this.capUsd = capUsd;
63
+ this.name = 'CapExceededError';
64
+ }
65
+ }
66
+ export class MeteredLedgerWriteError extends Error {
67
+ cause;
68
+ constructor(msg, cause) {
69
+ super(`metered-spend ledger write failed (fail-closed): ${msg}`);
70
+ this.cause = cause;
71
+ this.name = 'MeteredLedgerWriteError';
72
+ }
73
+ }
74
+ function utcDay(ms) {
75
+ return new Date(ms).toISOString().slice(0, 10);
76
+ }
77
+ function round6(n) {
78
+ return Math.round(n * 1e6) / 1e6;
79
+ }
80
+ export class MeteredSpendLedger {
81
+ rowsPath;
82
+ totalsPath;
83
+ lockTarget;
84
+ ttlMs;
85
+ now;
86
+ /** In-memory fold state, authoritative in-process. Rebuilt on construction. */
87
+ reserves = new Map();
88
+ totals = new Map();
89
+ /** Byte length of the rows file as of our last append/fold (the high-water mark). */
90
+ rowsBytes = 0;
91
+ /** In-process per-key mutex tail. */
92
+ mutexTail = new Map();
93
+ constructor(opts) {
94
+ const stateSub = path.join(opts.stateDir, 'state');
95
+ const localSub = path.join(opts.stateDir, 'local');
96
+ fs.mkdirSync(stateSub, { recursive: true });
97
+ fs.mkdirSync(localSub, { recursive: true });
98
+ this.rowsPath = path.join(stateSub, 'metered-spend-ledger.jsonl');
99
+ this.totalsPath = path.join(stateSub, 'metered-spend-totals.json');
100
+ this.lockTarget = path.join(localSub, 'metered-spend.lock');
101
+ if (!fs.existsSync(this.lockTarget))
102
+ fs.writeFileSync(this.lockTarget, '');
103
+ this.ttlMs = opts.reserveTtlMs ?? DEFAULT_RESERVE_TTL_MS;
104
+ this.now = opts.now ?? (() => Date.now());
105
+ // Boot: the fold is canon — rebuild from rows and REWRITE the totals cache.
106
+ this.refoldFromRows();
107
+ this.rewriteTotalsCache();
108
+ }
109
+ // ── Public API ─────────────────────────────────────────────────────
110
+ /** O(1) committed totals for a key, day-rolled to NOW. High-water-checked against external appends. */
111
+ committed(keyRef) {
112
+ this.checkHighWater();
113
+ return this.currentTotals(keyRef);
114
+ }
115
+ /** Reserve worst-case cost. FAIL-CLOSED: any persistence failure throws. */
116
+ async reserve(req) {
117
+ if (!Number.isFinite(req.reserveUsd) || req.reserveUsd < 0) {
118
+ throw new MeteredLedgerWriteError(`invalid reserveUsd ${req.reserveUsd}`);
119
+ }
120
+ return this.withKeyMutex(req.keyRef, async () => {
121
+ this.checkHighWater();
122
+ if (req.admitOnlyUnderCaps) {
123
+ // ATOMIC check-and-reserve: this runs inside the per-key mutex, so every
124
+ // outstanding concurrent reservation is already in the committed total.
125
+ const t = this.currentTotals(req.keyRef);
126
+ if (t.committedLifetimeUsd + req.reserveUsd > req.admitOnlyUnderCaps.lifetimeCapUsd) {
127
+ throw new CapExceededError('lifetime', t.committedLifetimeUsd, req.reserveUsd, req.admitOnlyUnderCaps.lifetimeCapUsd);
128
+ }
129
+ if (t.committedDayUsd + req.reserveUsd > req.admitOnlyUnderCaps.dailyCapUsd) {
130
+ throw new CapExceededError('daily', t.committedDayUsd, req.reserveUsd, req.admitOnlyUnderCaps.dailyCapUsd);
131
+ }
132
+ }
133
+ const nowMs = this.now();
134
+ const reserveId = crypto.randomBytes(12).toString('hex');
135
+ const row = {
136
+ ts: new Date(nowMs).toISOString(),
137
+ keyRef: req.keyRef,
138
+ door: req.door,
139
+ modelId: req.modelId,
140
+ kind: 'reserve',
141
+ reserveId,
142
+ costUsd: round6(req.reserveUsd),
143
+ leaseEpoch: req.leaseEpoch,
144
+ };
145
+ await this.appendDurable(row);
146
+ this.applyRow(row);
147
+ this.rewriteTotalsCache();
148
+ const t = this.currentTotals(req.keyRef);
149
+ return { reserveId, committedLifetimeUsd: t.committedLifetimeUsd, committedDayUsd: t.committedDayUsd };
150
+ });
151
+ }
152
+ /**
153
+ * Terminal settle at ACTUAL cost. Idempotent: a second settle is a no-op.
154
+ * A settle after expiry books the actual as a fresh ABSOLUTE row (late-settle).
155
+ */
156
+ async settle(keyRef, reserveId, actualUsd) {
157
+ if (!Number.isFinite(actualUsd) || actualUsd < 0) {
158
+ throw new MeteredLedgerWriteError(`invalid actualUsd ${actualUsd}`);
159
+ }
160
+ return this.withKeyMutex(keyRef, async () => {
161
+ const st = this.reserves.get(reserveId);
162
+ if (st && st.state === 'settled')
163
+ return; // first terminal wins — no-op
164
+ const lateAfterExpire = st?.state === 'expired';
165
+ const row = {
166
+ ts: new Date(this.now()).toISOString(),
167
+ keyRef,
168
+ door: st?.door ?? 'unknown',
169
+ modelId: st?.modelId ?? 'unknown',
170
+ kind: 'settle',
171
+ reserveId,
172
+ costUsd: round6(actualUsd),
173
+ leaseEpoch: 0,
174
+ ...(lateAfterExpire ? { lateAfterExpire: true } : {}),
175
+ };
176
+ await this.appendDurable(row);
177
+ this.applyRow(row);
178
+ this.rewriteTotalsCache();
179
+ });
180
+ }
181
+ /** Reserve-expiry sweep (Layer 3 — takes the per-key mutex). Expires only TTL-stale `reserved` states. */
182
+ async sweepExpired() {
183
+ const nowMs = this.now();
184
+ let expired = 0;
185
+ // Group stale reserves by key so each key's bookings stay serialized.
186
+ const byKey = new Map();
187
+ for (const [id, st] of this.reserves) {
188
+ if (st.state !== 'reserved')
189
+ continue;
190
+ if (nowMs - st.reservedAtMs <= this.ttlMs)
191
+ continue;
192
+ const arr = byKey.get(st.keyRef) ?? [];
193
+ arr.push({ id, st });
194
+ byKey.set(st.keyRef, arr);
195
+ }
196
+ for (const [keyRef, stales] of byKey) {
197
+ await this.withKeyMutex(keyRef, async () => {
198
+ for (const { id, st } of stales) {
199
+ if (st.state !== 'reserved')
200
+ continue; // settled while we waited — first terminal wins
201
+ const row = {
202
+ ts: new Date(this.now()).toISOString(),
203
+ keyRef,
204
+ door: st.door,
205
+ modelId: st.modelId,
206
+ kind: 'expire',
207
+ reserveId: id,
208
+ costUsd: 0,
209
+ leaseEpoch: 0,
210
+ };
211
+ await this.appendDurable(row);
212
+ this.applyRow(row);
213
+ expired++;
214
+ }
215
+ this.rewriteTotalsCache();
216
+ });
217
+ }
218
+ return expired;
219
+ }
220
+ /** All totals (for the caps view). Day-rolled to now. */
221
+ allCommitted() {
222
+ this.checkHighWater();
223
+ const keys = new Set();
224
+ for (const st of this.reserves.values())
225
+ keys.add(st.keyRef);
226
+ for (const k of this.totals.keys())
227
+ keys.add(k);
228
+ return [...keys].map((k) => this.currentTotals(k));
229
+ }
230
+ // ── Fold internals ─────────────────────────────────────────────────
231
+ /** Apply one row to the in-memory fold. */
232
+ applyRow(row, opts) {
233
+ const day = utcDay(Date.parse(row.ts));
234
+ if (row.kind === 'reserve') {
235
+ this.reserves.set(row.reserveId, {
236
+ keyRef: row.keyRef,
237
+ door: row.door,
238
+ modelId: row.modelId,
239
+ reserveUsd: row.costUsd,
240
+ day,
241
+ reservedAtMs: Date.parse(row.ts),
242
+ state: 'reserved',
243
+ });
244
+ }
245
+ else if (row.kind === 'settle') {
246
+ const st = this.reserves.get(row.reserveId);
247
+ if (st) {
248
+ if (st.state === 'settled')
249
+ return; // idempotent
250
+ st.state = 'settled';
251
+ st.settledUsd = row.costUsd;
252
+ // A late settle books on the SETTLE's own day when the reserve day already
253
+ // contributed nothing (it was expired); otherwise the contribution keeps
254
+ // the reserve's day.
255
+ if (row.lateAfterExpire)
256
+ st.day = day;
257
+ }
258
+ else {
259
+ // Settle with no known reserve (e.g. rows pruned): book absolute on its own day.
260
+ this.reserves.set(row.reserveId, {
261
+ keyRef: row.keyRef,
262
+ door: row.door,
263
+ modelId: row.modelId,
264
+ reserveUsd: 0,
265
+ day,
266
+ reservedAtMs: Date.parse(row.ts),
267
+ state: 'settled',
268
+ settledUsd: row.costUsd,
269
+ });
270
+ }
271
+ }
272
+ else if (row.kind === 'expire') {
273
+ const st = this.reserves.get(row.reserveId);
274
+ if (st && st.state === 'reserved')
275
+ st.state = 'expired';
276
+ }
277
+ if (!opts?.skipTotals)
278
+ this.recomputeTotalsForKey(row.keyRef);
279
+ }
280
+ /** Contribution of one reserve state to committed totals. */
281
+ contribution(st) {
282
+ if (st.state === 'settled')
283
+ return st.settledUsd ?? 0;
284
+ if (st.state === 'expired')
285
+ return 0;
286
+ return st.reserveUsd; // outstanding reserve — inside the committed total
287
+ }
288
+ recomputeTotalsForKey(keyRef) {
289
+ const today = utcDay(this.now());
290
+ let lifetime = 0;
291
+ let dayUsd = 0;
292
+ for (const st of this.reserves.values()) {
293
+ if (st.keyRef !== keyRef)
294
+ continue;
295
+ const c = this.contribution(st);
296
+ lifetime += c;
297
+ if (st.day === today)
298
+ dayUsd += c;
299
+ }
300
+ this.totals.set(keyRef, {
301
+ keyRef,
302
+ committedLifetimeUsd: round6(lifetime),
303
+ committedDayUsd: round6(dayUsd),
304
+ dayEpoch: today,
305
+ updatedAt: new Date(this.now()).toISOString(),
306
+ });
307
+ }
308
+ /** Day-rolled view of a key's totals (a new UTC day resets the day figure). */
309
+ currentTotals(keyRef) {
310
+ const today = utcDay(this.now());
311
+ const t = this.totals.get(keyRef);
312
+ if (!t) {
313
+ return { keyRef, committedLifetimeUsd: 0, committedDayUsd: 0, dayEpoch: today, updatedAt: new Date(this.now()).toISOString() };
314
+ }
315
+ if (t.dayEpoch !== today) {
316
+ this.recomputeTotalsForKey(keyRef);
317
+ return this.totals.get(keyRef);
318
+ }
319
+ return t;
320
+ }
321
+ /** Full refold from row truth (the fold is canon; the totals file is a cache). */
322
+ refoldFromRows() {
323
+ this.reserves = new Map();
324
+ this.totals = new Map();
325
+ let raw = '';
326
+ try {
327
+ raw = fs.readFileSync(this.rowsPath, 'utf-8');
328
+ this.rowsBytes = Buffer.byteLength(raw);
329
+ }
330
+ catch {
331
+ // @silent-fallback-ok: no rows file yet — a fresh ledger folds to zero.
332
+ this.rowsBytes = 0;
333
+ return;
334
+ }
335
+ const keys = new Set();
336
+ for (const line of raw.split('\n')) {
337
+ if (!line.trim())
338
+ continue;
339
+ let row;
340
+ try {
341
+ row = JSON.parse(line);
342
+ }
343
+ catch {
344
+ // Malformed-row-skip: a torn trailing append is expected crash residue.
345
+ continue;
346
+ }
347
+ if (typeof row?.reserveId !== 'string' ||
348
+ typeof row?.keyRef !== 'string' ||
349
+ typeof row?.costUsd !== 'number' ||
350
+ !Number.isFinite(row.costUsd) ||
351
+ row.costUsd < 0 ||
352
+ !['reserve', 'settle', 'expire'].includes(row?.kind)) {
353
+ continue;
354
+ }
355
+ // Inline apply without per-row totals recompute (batched below).
356
+ keys.add(row.keyRef);
357
+ this.applyRow(row, { skipTotals: true });
358
+ }
359
+ for (const k of keys)
360
+ this.recomputeTotalsForKey(k);
361
+ }
362
+ /** Cheap external-append detection: rows-file byte length vs our high-water mark. */
363
+ checkHighWater() {
364
+ let size = 0;
365
+ try {
366
+ size = fs.statSync(this.rowsPath).size;
367
+ }
368
+ catch {
369
+ // @silent-fallback-ok: rows file absent — nothing appended externally.
370
+ size = 0;
371
+ }
372
+ if (size !== this.rowsBytes) {
373
+ this.refoldFromRows();
374
+ this.rewriteTotalsCache();
375
+ }
376
+ }
377
+ // ── Durable write internals ────────────────────────────────────────
378
+ /** Append one row, fsync'd, under the advisory file lock. NON-SWALLOWING. */
379
+ async appendDurable(row) {
380
+ let release = null;
381
+ try {
382
+ release = await lockfile.lock(this.lockTarget, LOCK_OPTIONS);
383
+ const line = JSON.stringify(row) + '\n';
384
+ const fd = fs.openSync(this.rowsPath, 'a', 0o600);
385
+ try {
386
+ fs.writeSync(fd, line);
387
+ fs.fsyncSync(fd);
388
+ }
389
+ finally {
390
+ fs.closeSync(fd);
391
+ }
392
+ this.rowsBytes += Buffer.byteLength(line);
393
+ }
394
+ catch (err) {
395
+ throw new MeteredLedgerWriteError(String(err), err);
396
+ }
397
+ finally {
398
+ if (release)
399
+ await release().catch(() => { });
400
+ }
401
+ }
402
+ /** Atomic (tmp+rename) rewrite of the totals cache. Best-effort — rows are canon. */
403
+ rewriteTotalsCache() {
404
+ try {
405
+ const file = { rowsBytes: this.rowsBytes, totals: Object.fromEntries(this.totals) };
406
+ const tmp = this.totalsPath + '.tmp';
407
+ fs.writeFileSync(tmp, JSON.stringify(file, null, 2), { mode: 0o600 });
408
+ fs.renameSync(tmp, this.totalsPath);
409
+ }
410
+ catch {
411
+ // @silent-fallback-ok: the totals file is a regenerable CACHE of the fold —
412
+ // a failed cache write never blocks a booking (rows are already durable) and
413
+ // is corrected by the next boot-time refold.
414
+ }
415
+ }
416
+ // ── In-process per-key mutex ───────────────────────────────────────
417
+ withKeyMutex(keyRef, fn) {
418
+ const tail = this.mutexTail.get(keyRef) ?? Promise.resolve();
419
+ let done;
420
+ const next = new Promise((r) => (done = r));
421
+ this.mutexTail.set(keyRef, next);
422
+ return tail.then(async () => {
423
+ try {
424
+ return await fn();
425
+ }
426
+ finally {
427
+ done();
428
+ if (this.mutexTail.get(keyRef) === next)
429
+ this.mutexTail.delete(keyRef);
430
+ }
431
+ });
432
+ }
433
+ }
434
+ //# sourceMappingURL=MeteredSpendLedger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeteredSpendLedger.js","sourceRoot":"","sources":["../../src/core/MeteredSpendLedger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * PinAttemptStore — durable write-through behind the PIN routes' per-IP
3
+ * brute-force lockout (docs/specs/routing-control-room-spend-alerts.md,
4
+ * Surface 2, S2-1 hardening).
5
+ *
6
+ * `checkMandatePin`'s attempt counter was an in-memory Map, so a server restart
7
+ * reset the lockout. This store persists the counters to
8
+ * `<stateDir>/state/pin-attempts.json` (atomic tmp+rename) and is shared by the
9
+ * PIN routes. Semantics unchanged: N attempts per window per IP; the window
10
+ * expiry clears the entry. Express `trust proxy` is off fleet-wide, so req.ip
11
+ * already ignores X-Forwarded-For.
12
+ */
13
+ export declare class PinAttemptStore {
14
+ private readonly filePath;
15
+ private entries;
16
+ private readonly maxAttempts;
17
+ private readonly windowMs;
18
+ private readonly now;
19
+ constructor(opts: {
20
+ stateDir: string;
21
+ maxAttempts?: number;
22
+ windowMs?: number;
23
+ now?: () => number;
24
+ });
25
+ /** True when the IP is currently locked out. */
26
+ blocked(ip: string): boolean;
27
+ /** Record a FAILED attempt; returns attempts remaining (≥ 0). */
28
+ recordFailure(ip: string): number;
29
+ /** Clear an IP's counter on a successful PIN entry. */
30
+ recordSuccess(ip: string): void;
31
+ private current;
32
+ private load;
33
+ private persist;
34
+ }
35
+ //# sourceMappingURL=PinAttemptStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PinAttemptStore.d.ts","sourceRoot":"","sources":["../../src/core/PinAttemptStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;KAAE;IAUnG,gDAAgD;IAChD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAK5B,iEAAiE;IACjE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM;IAWjC,uDAAuD;IACvD,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI/B,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,IAAI;IAYZ,OAAO,CAAC,OAAO;CAUhB"}