network-ai 3.7.0 → 3.8.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/README.md +2 -2
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/consistency.d.ts +194 -0
- package/dist/lib/consistency.d.ts.map +1 -0
- package/dist/lib/consistency.js +274 -0
- package/dist/lib/consistency.js.map +1 -0
- package/dist/lib/federated-budget.d.ts +205 -0
- package/dist/lib/federated-budget.d.ts.map +1 -0
- package/dist/lib/federated-budget.js +275 -0
- package/dist/lib/federated-budget.js.map +1 -0
- package/dist/run-tests.d.ts +9 -0
- package/dist/run-tests.d.ts.map +1 -0
- package/dist/run-tests.js +76 -0
- package/dist/run-tests.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Configurable Consistency Levels for Blackboard Backends
|
|
4
|
+
*
|
|
5
|
+
* Wraps any `BlackboardBackend` with one of three consistency guarantees:
|
|
6
|
+
*
|
|
7
|
+
* - `'eventual'` (default) — writes return immediately; async replication
|
|
8
|
+
* happens in the background. Highest throughput.
|
|
9
|
+
* - `'session'` — read-your-writes guarantee. Every write made
|
|
10
|
+
* by this instance is immediately visible to
|
|
11
|
+
* subsequent reads, even before it propagates to
|
|
12
|
+
* other nodes. Tracked in a local session cache.
|
|
13
|
+
* - `'strong'` — writes are flushed to the backend before the
|
|
14
|
+
* async `writeAsync()` resolves. For backends
|
|
15
|
+
* that implement `FlushableBackend` (e.g.
|
|
16
|
+
* `RedisBackend`, `CrdtBackend`) this guarantees
|
|
17
|
+
* durability before the caller continues.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { MemoryBackend } from './blackboard-backend';
|
|
22
|
+
* import { ConsistentBackend } from './consistency';
|
|
23
|
+
*
|
|
24
|
+
* const backend = new ConsistentBackend(new MemoryBackend(), 'session');
|
|
25
|
+
* backend.write('k', 'v', 'agent-1');
|
|
26
|
+
* backend.read('k'); // always returns 'v' in this session
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Integration with `getBlackboard()`:
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const board = orchestrator.getBlackboard('live', {
|
|
32
|
+
* backend: new RedisBackend(client),
|
|
33
|
+
* consistency: 'strong',
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @module Consistency
|
|
38
|
+
* @version 1.0.0
|
|
39
|
+
* @license MIT
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.ConsistentBackend = void 0;
|
|
43
|
+
exports.isFlushable = isFlushable;
|
|
44
|
+
/**
|
|
45
|
+
* Type guard — returns `true` if `backend` implements `FlushableBackend`.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* if (isFlushable(backend)) {
|
|
50
|
+
* await backend.flush();
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
function isFlushable(backend) {
|
|
55
|
+
return typeof backend.flush === 'function';
|
|
56
|
+
}
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// CONSISTENT BACKEND
|
|
59
|
+
// ============================================================================
|
|
60
|
+
/**
|
|
61
|
+
* A `BlackboardBackend` wrapper that adds configurable consistency semantics.
|
|
62
|
+
*
|
|
63
|
+
* Fully satisfies the synchronous `BlackboardBackend` interface so it can be
|
|
64
|
+
* used anywhere a plain backend is accepted. For `'strong'` consistency, use
|
|
65
|
+
* `writeAsync()` to await durability confirmation.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // Session consistency — read-your-writes
|
|
70
|
+
* const backend = new ConsistentBackend(new MemoryBackend(), 'session');
|
|
71
|
+
* backend.write('task', 'pending', 'agent-1');
|
|
72
|
+
* backend.read('task'); // → 'pending' (guaranteed, even before replication)
|
|
73
|
+
*
|
|
74
|
+
* // Strong consistency with Redis
|
|
75
|
+
* const backend = new ConsistentBackend(new RedisBackend(client), 'strong');
|
|
76
|
+
* await backend.writeAsync('result', data, 'agent-1');
|
|
77
|
+
* // Redis has confirmed the write
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
class ConsistentBackend {
|
|
81
|
+
_backend;
|
|
82
|
+
_level;
|
|
83
|
+
/**
|
|
84
|
+
* Session write cache.
|
|
85
|
+
*
|
|
86
|
+
* - `BlackboardEntry` → written in this session (overrides backend reads)
|
|
87
|
+
* - `null` → deleted in this session (hides backend reads)
|
|
88
|
+
*
|
|
89
|
+
* Only populated for `'session'` consistency.
|
|
90
|
+
*/
|
|
91
|
+
_session = new Map();
|
|
92
|
+
/**
|
|
93
|
+
* Create a new `ConsistentBackend`.
|
|
94
|
+
*
|
|
95
|
+
* @param backend The underlying storage backend to wrap.
|
|
96
|
+
* @param level Consistency level. Defaults to `'eventual'`.
|
|
97
|
+
*/
|
|
98
|
+
constructor(backend, level = 'eventual') {
|
|
99
|
+
this._backend = backend;
|
|
100
|
+
this._level = level;
|
|
101
|
+
}
|
|
102
|
+
// --------------------------------------------------------------------------
|
|
103
|
+
// BlackboardBackend interface
|
|
104
|
+
// --------------------------------------------------------------------------
|
|
105
|
+
/**
|
|
106
|
+
* Read an entry.
|
|
107
|
+
*
|
|
108
|
+
* - `eventual` / `strong`: delegates directly to the underlying backend.
|
|
109
|
+
* - `session`: returns the session-cached entry if present; falls back to
|
|
110
|
+
* the backend. Returns `null` for session-deleted keys even if the backend
|
|
111
|
+
* still has them.
|
|
112
|
+
*/
|
|
113
|
+
read(key) {
|
|
114
|
+
if (this._level === 'session') {
|
|
115
|
+
const cached = this._session.get(key);
|
|
116
|
+
if (cached === undefined) {
|
|
117
|
+
// Not in session cache — fall through to backend
|
|
118
|
+
}
|
|
119
|
+
else if (cached === null) {
|
|
120
|
+
// Session-deleted tombstone
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Live session entry — check expiry
|
|
125
|
+
if (this._isExpired(cached)) {
|
|
126
|
+
this._session.delete(key);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return cached;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return this._backend.read(key);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Write a value synchronously.
|
|
136
|
+
*
|
|
137
|
+
* For all consistency levels this writes immediately to the underlying
|
|
138
|
+
* backend. For `'session'` the result is also cached locally so subsequent
|
|
139
|
+
* `read()` calls see it without waiting for replication.
|
|
140
|
+
*
|
|
141
|
+
* For `'strong'` consistency, prefer `writeAsync()` to await durability.
|
|
142
|
+
*/
|
|
143
|
+
write(key, value, sourceAgent, ttl) {
|
|
144
|
+
const entry = this._backend.write(key, value, sourceAgent, ttl);
|
|
145
|
+
if (this._level === 'session') {
|
|
146
|
+
this._session.set(key, entry);
|
|
147
|
+
}
|
|
148
|
+
return entry;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Delete an entry.
|
|
152
|
+
*
|
|
153
|
+
* For `'session'` consistency, also records a session-tombstone so the key
|
|
154
|
+
* appears deleted to subsequent `read()` calls in this session.
|
|
155
|
+
*/
|
|
156
|
+
delete(key) {
|
|
157
|
+
const result = this._backend.delete(key);
|
|
158
|
+
if (this._level === 'session') {
|
|
159
|
+
this._session.set(key, null); // tombstone
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Return all non-expired, non-deleted keys.
|
|
165
|
+
*
|
|
166
|
+
* For `'session'` consistency, session-written keys are included even if
|
|
167
|
+
* not yet visible in the backend; session-deleted keys are excluded.
|
|
168
|
+
*/
|
|
169
|
+
listKeys() {
|
|
170
|
+
if (this._level !== 'session') {
|
|
171
|
+
return this._backend.listKeys();
|
|
172
|
+
}
|
|
173
|
+
// Start with backend keys, then overlay session mutations
|
|
174
|
+
const backendKeys = new Set(this._backend.listKeys());
|
|
175
|
+
// Add session-written keys (not yet in backend, or overriding backend)
|
|
176
|
+
for (const [key, entry] of this._session.entries()) {
|
|
177
|
+
if (entry === null) {
|
|
178
|
+
// Session delete — remove from result
|
|
179
|
+
backendKeys.delete(key);
|
|
180
|
+
}
|
|
181
|
+
else if (!this._isExpired(entry)) {
|
|
182
|
+
backendKeys.add(key);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return Array.from(backendKeys);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Return a snapshot of all non-expired, non-deleted entries.
|
|
189
|
+
*
|
|
190
|
+
* For `'session'` consistency, session writes overlay the backend snapshot.
|
|
191
|
+
*/
|
|
192
|
+
getSnapshot() {
|
|
193
|
+
if (this._level !== 'session') {
|
|
194
|
+
return this._backend.getSnapshot();
|
|
195
|
+
}
|
|
196
|
+
// Start from backend snapshot, apply session overlay
|
|
197
|
+
const result = { ...this._backend.getSnapshot() };
|
|
198
|
+
for (const [key, entry] of this._session.entries()) {
|
|
199
|
+
if (entry === null) {
|
|
200
|
+
// Session delete
|
|
201
|
+
delete result[key];
|
|
202
|
+
}
|
|
203
|
+
else if (!this._isExpired(entry)) {
|
|
204
|
+
result[key] = entry;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
// --------------------------------------------------------------------------
|
|
210
|
+
// Extended consistency API
|
|
211
|
+
// --------------------------------------------------------------------------
|
|
212
|
+
/**
|
|
213
|
+
* Write a value and, for `'strong'` consistency, await durability
|
|
214
|
+
* confirmation from the backend.
|
|
215
|
+
*
|
|
216
|
+
* - `eventual` / `session`: behaves identically to `write()` but returns
|
|
217
|
+
* a `Promise` for API uniformity.
|
|
218
|
+
* - `strong` + `FlushableBackend`: calls `backend.flush()` after the write
|
|
219
|
+
* and resolves only after the flush completes.
|
|
220
|
+
* - `strong` + non-flushable backend: writes synchronously and resolves
|
|
221
|
+
* immediately (backend writes are already synchronous/durable).
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const entry = await backend.writeAsync('checkpoint', data, 'agent-1');
|
|
226
|
+
* // Under 'strong' + RedisBackend: Redis has persisted the entry by here
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
async writeAsync(key, value, sourceAgent, ttl) {
|
|
230
|
+
const entry = this.write(key, value, sourceAgent, ttl);
|
|
231
|
+
if (this._level === 'strong' && isFlushable(this._backend)) {
|
|
232
|
+
await this._backend.flush();
|
|
233
|
+
}
|
|
234
|
+
return entry;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The configured consistency level.
|
|
238
|
+
*/
|
|
239
|
+
get consistencyLevel() {
|
|
240
|
+
return this._level;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The underlying backend being wrapped.
|
|
244
|
+
*/
|
|
245
|
+
get backend() {
|
|
246
|
+
return this._backend;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Number of entries currently in the session cache (live + tombstones).
|
|
250
|
+
* Always `0` for `'eventual'` and `'strong'` levels.
|
|
251
|
+
*/
|
|
252
|
+
get sessionSize() {
|
|
253
|
+
return this._session.size;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Clear the session write cache without modifying the underlying backend.
|
|
257
|
+
*
|
|
258
|
+
* After calling this, `read()` will reflect the backend state directly.
|
|
259
|
+
* Only meaningful for `'session'` consistency.
|
|
260
|
+
*/
|
|
261
|
+
clearSession() {
|
|
262
|
+
this._session.clear();
|
|
263
|
+
}
|
|
264
|
+
// --------------------------------------------------------------------------
|
|
265
|
+
// Private helpers
|
|
266
|
+
// --------------------------------------------------------------------------
|
|
267
|
+
_isExpired(entry) {
|
|
268
|
+
if (!entry.ttl)
|
|
269
|
+
return false;
|
|
270
|
+
return Date.now() > new Date(entry.timestamp).getTime() + entry.ttl * 1000;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
exports.ConsistentBackend = ConsistentBackend;
|
|
274
|
+
//# sourceMappingURL=consistency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consistency.js","sourceRoot":"","sources":["../../lib/consistency.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federated Budget Tracking
|
|
3
|
+
*
|
|
4
|
+
* Tracks token spending across distributed agent swarms. Each `FederatedBudget`
|
|
5
|
+
* instance enforces a global ceiling shared among all agents that call `spend()`.
|
|
6
|
+
*
|
|
7
|
+
* When an optional `BlackboardBackend` is supplied, the budget state is written
|
|
8
|
+
* to the blackboard after every mutation. Wiring a `CrdtBackend` or `RedisBackend`
|
|
9
|
+
* as the underlying backend therefore gives automatic cross-node synchronization
|
|
10
|
+
* with no extra configuration.
|
|
11
|
+
*
|
|
12
|
+
* Architecture:
|
|
13
|
+
* - In-memory `spent` map keyed by `agentId` holds per-agent cumulative totals.
|
|
14
|
+
* - `spend()` is synchronous and enforces both the global ceiling and an optional
|
|
15
|
+
* per-agent ceiling in a single check.
|
|
16
|
+
* - A `blackboard` backend (if supplied) stores a JSON snapshot under `budgetKey`
|
|
17
|
+
* after every `spend()` / `reset()` / `setCeiling()` call so distributed nodes
|
|
18
|
+
* can read the latest state.
|
|
19
|
+
* - `loadFromBlackboard()` deserializes a previously saved snapshot so a
|
|
20
|
+
* restarted node can recover its prior accumulated spend.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { FederatedBudget } from 'network-ai';
|
|
25
|
+
*
|
|
26
|
+
* const budget = new FederatedBudget({ ceiling: 10_000 });
|
|
27
|
+
*
|
|
28
|
+
* budget.spend('agent-1', 3000); // { allowed: true, remaining: 7000 }
|
|
29
|
+
* budget.spend('agent-2', 8000); // { allowed: false, remaining: 7000 }
|
|
30
|
+
* budget.remaining(); // 7000
|
|
31
|
+
* budget.getSpendLog(); // { 'agent-1': 3000 }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example With blackboard persistence
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { CrdtBackend } from 'network-ai';
|
|
37
|
+
* import { FederatedBudget } from 'network-ai';
|
|
38
|
+
*
|
|
39
|
+
* const node = new CrdtBackend('node-a');
|
|
40
|
+
* const budget = new FederatedBudget({ ceiling: 50_000, blackboard: node });
|
|
41
|
+
*
|
|
42
|
+
* budget.spend('agent-1', 1000);
|
|
43
|
+
* // State is now stored in node under 'federated-budget'
|
|
44
|
+
* // Sync node to other CrdtBackend nodes to propagate the spend.
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @module FederatedBudget
|
|
48
|
+
* @version 1.0.0
|
|
49
|
+
* @license MIT
|
|
50
|
+
*/
|
|
51
|
+
import type { BlackboardBackend } from './blackboard-backend';
|
|
52
|
+
/**
|
|
53
|
+
* Result returned by {@link FederatedBudget.spend}.
|
|
54
|
+
*/
|
|
55
|
+
export interface SpendResult {
|
|
56
|
+
/** Whether the spend was allowed (i.e. did not breach any ceiling). */
|
|
57
|
+
allowed: boolean;
|
|
58
|
+
/** Remaining tokens in the global pool after this call. */
|
|
59
|
+
remaining: number;
|
|
60
|
+
/** Reason the spend was denied, if `allowed` is `false`. */
|
|
61
|
+
deniedReason?: 'global_ceiling' | 'per_agent_ceiling';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A single entry in the spend log returned by {@link FederatedBudget.getSpendLog}.
|
|
65
|
+
*/
|
|
66
|
+
export interface SpendLogEntry {
|
|
67
|
+
/** Agent that made the spend. */
|
|
68
|
+
agentId: string;
|
|
69
|
+
/** Number of tokens spent in this transaction. */
|
|
70
|
+
tokens: number;
|
|
71
|
+
/** ISO timestamp of the spend. */
|
|
72
|
+
timestamp: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Construction options for {@link FederatedBudget}.
|
|
76
|
+
*/
|
|
77
|
+
export interface FederatedBudgetOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Global token ceiling shared across all agents.
|
|
80
|
+
* No single call to `spend()` may push the cumulative total above this value.
|
|
81
|
+
* Must be a positive integer.
|
|
82
|
+
*/
|
|
83
|
+
ceiling: number;
|
|
84
|
+
/**
|
|
85
|
+
* Optional per-agent ceiling.
|
|
86
|
+
* When set, each individual agent is also capped at this many cumulative tokens,
|
|
87
|
+
* independently of the global ceiling.
|
|
88
|
+
* Must be a positive integer if provided.
|
|
89
|
+
*/
|
|
90
|
+
perAgentCeiling?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Optional blackboard backend.
|
|
93
|
+
* When provided, budget state is persisted under `budgetKey` after every
|
|
94
|
+
* mutation so distributed nodes can observe the latest spend.
|
|
95
|
+
*
|
|
96
|
+
* Pair with a `CrdtBackend` or `RedisBackend` for automatic multi-node sync.
|
|
97
|
+
*/
|
|
98
|
+
blackboard?: BlackboardBackend;
|
|
99
|
+
/**
|
|
100
|
+
* Key used to store the budget snapshot on the blackboard.
|
|
101
|
+
* Defaults to `'federated-budget'`.
|
|
102
|
+
*/
|
|
103
|
+
budgetKey?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Agent identifier used as the `agentId` when writing to the blackboard.
|
|
106
|
+
* Defaults to `'federated-budget-tracker'`.
|
|
107
|
+
*/
|
|
108
|
+
blackboardAgent?: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Federated token-budget tracker for distributed agent swarms.
|
|
112
|
+
*
|
|
113
|
+
* Enforces a shared global ceiling across all agents and optionally an
|
|
114
|
+
* individual per-agent ceiling. State can be persisted to any
|
|
115
|
+
* `BlackboardBackend` for cross-node visibility.
|
|
116
|
+
*/
|
|
117
|
+
export declare class FederatedBudget {
|
|
118
|
+
private readonly _ceiling;
|
|
119
|
+
private _dynamicCeiling;
|
|
120
|
+
private readonly _perAgentCeiling;
|
|
121
|
+
private readonly _spent;
|
|
122
|
+
private _totalSpent;
|
|
123
|
+
private readonly _log;
|
|
124
|
+
private readonly _blackboard;
|
|
125
|
+
private readonly _budgetKey;
|
|
126
|
+
private readonly _bbAgent;
|
|
127
|
+
constructor(options: FederatedBudgetOptions);
|
|
128
|
+
/**
|
|
129
|
+
* Attempt to spend `tokens` on behalf of `agentId`.
|
|
130
|
+
*
|
|
131
|
+
* The spend is allowed only when:
|
|
132
|
+
* 1. `totalSpent + tokens <= ceiling` (global ceiling not breached), AND
|
|
133
|
+
* 2. `agentSpent + tokens <= perAgentCeiling` if a per-agent ceiling is set.
|
|
134
|
+
*
|
|
135
|
+
* When allowed the internal counters are updated and the state is persisted
|
|
136
|
+
* to the blackboard (if configured).
|
|
137
|
+
*
|
|
138
|
+
* @param agentId Identifier of the spending agent. Must be a non-empty string.
|
|
139
|
+
* @param tokens Number of tokens to spend. Must be a positive integer.
|
|
140
|
+
* @returns `SpendResult` with `allowed`, `remaining`, and optional `deniedReason`.
|
|
141
|
+
*/
|
|
142
|
+
spend(agentId: string, tokens: number): SpendResult;
|
|
143
|
+
/**
|
|
144
|
+
* Remaining tokens in the global pool.
|
|
145
|
+
*/
|
|
146
|
+
remaining(): number;
|
|
147
|
+
/**
|
|
148
|
+
* Total tokens spent across all agents.
|
|
149
|
+
*/
|
|
150
|
+
getTotalSpent(): number;
|
|
151
|
+
/**
|
|
152
|
+
* Tokens spent by a specific agent. Returns `0` if the agent has no spend.
|
|
153
|
+
*/
|
|
154
|
+
getAgentSpent(agentId: string): number;
|
|
155
|
+
/**
|
|
156
|
+
* Per-agent spend totals as a plain object: `{ agentId: totalTokens }`.
|
|
157
|
+
*/
|
|
158
|
+
getSpendLog(): Record<string, number>;
|
|
159
|
+
/**
|
|
160
|
+
* Detailed transaction log — every individual `spend()` call in order.
|
|
161
|
+
*/
|
|
162
|
+
getTransactionLog(): SpendLogEntry[];
|
|
163
|
+
/**
|
|
164
|
+
* Current global ceiling (may differ from the original if `setCeiling()` was called).
|
|
165
|
+
*/
|
|
166
|
+
getCeiling(): number;
|
|
167
|
+
/**
|
|
168
|
+
* Per-agent ceiling, or `undefined` if none was configured.
|
|
169
|
+
*/
|
|
170
|
+
getPerAgentCeiling(): number | undefined;
|
|
171
|
+
/**
|
|
172
|
+
* Dynamically adjust the global ceiling.
|
|
173
|
+
*
|
|
174
|
+
* The new ceiling must be a positive finite number. It may be set below
|
|
175
|
+
* the current `totalSpent` (no previously approved spends are reversed, but
|
|
176
|
+
* future spends will be denied until tokens are freed by a `reset()`).
|
|
177
|
+
*
|
|
178
|
+
* @param ceiling New global ceiling.
|
|
179
|
+
*/
|
|
180
|
+
setCeiling(ceiling: number): void;
|
|
181
|
+
/**
|
|
182
|
+
* Reset all spend counters and clear the transaction log.
|
|
183
|
+
*
|
|
184
|
+
* The global ceiling is preserved (its current value after any `setCeiling()`
|
|
185
|
+
* calls). After a reset `remaining() === getCeiling()`.
|
|
186
|
+
*
|
|
187
|
+
* The blackboard entry (if configured) is updated with the reset state.
|
|
188
|
+
*/
|
|
189
|
+
reset(): void;
|
|
190
|
+
/**
|
|
191
|
+
* Restore budget state from the blackboard backend.
|
|
192
|
+
*
|
|
193
|
+
* Reads the entry stored under `budgetKey`, deserializes the snapshot, and
|
|
194
|
+
* replaces the current in-memory state. Useful when a node restarts and
|
|
195
|
+
* needs to recover its prior accumulated spend.
|
|
196
|
+
*
|
|
197
|
+
* No-op (returns `false`) when no blackboard is configured or no entry exists.
|
|
198
|
+
*
|
|
199
|
+
* @returns `true` if state was successfully loaded, `false` otherwise.
|
|
200
|
+
*/
|
|
201
|
+
loadFromBlackboard(): boolean;
|
|
202
|
+
/** Serialize current state to the blackboard, if one is configured. */
|
|
203
|
+
private _persist;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=federated-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"federated-budget.d.ts","sourceRoot":"","sources":["../../lib/federated-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAM9D;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,gBAAgB,GAAG,mBAAmB,CAAC;CACvD;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAC;CACnB;AAiBD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAE/B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD;;;;;;GAMG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,OAAO,EAAE,sBAAsB;IAyB3C;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW;IA6CnD;;OAEG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,aAAa,IAAI,MAAM;IAIvB;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAItC;;OAEG;IACH,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAQrC;;OAEG;IACH,iBAAiB,IAAI,aAAa,EAAE;IAIpC;;OAEG;IACH,UAAU,IAAI,MAAM;IAIpB;;OAEG;IACH,kBAAkB,IAAI,MAAM,GAAG,SAAS;IAIxC;;;;;;;;OAQG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAQjC;;;;;;;OAOG;IACH,KAAK,IAAI,IAAI;IAOb;;;;;;;;;;OAUG;IACH,kBAAkB,IAAI,OAAO;IAsB7B,uEAAuE;IACvE,OAAO,CAAC,QAAQ;CAcjB"}
|