@openlimiter/core 1.3.5 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acquire/cadence.d.ts +5 -3
- package/dist/acquire/cadence.d.ts.map +1 -1
- package/dist/acquire/cadence.js +14 -9
- package/dist/acquire/cadence.js.map +1 -1
- package/dist/acquire/coordination.d.ts.map +1 -1
- package/dist/acquire/coordination.js +3 -4
- package/dist/acquire/coordination.js.map +1 -1
- package/dist/acquire/credentials.d.ts +5 -2
- package/dist/acquire/credentials.d.ts.map +1 -1
- package/dist/acquire/credentials.js +45 -11
- package/dist/acquire/credentials.js.map +1 -1
- package/dist/acquire/cursor.d.ts +8 -0
- package/dist/acquire/cursor.d.ts.map +1 -0
- package/dist/acquire/cursor.js +60 -0
- package/dist/acquire/cursor.js.map +1 -0
- package/dist/acquire/identity.d.ts +16 -2
- package/dist/acquire/identity.d.ts.map +1 -1
- package/dist/acquire/identity.js +64 -1
- package/dist/acquire/identity.js.map +1 -1
- package/dist/acquire/index.d.ts +1 -0
- package/dist/acquire/index.d.ts.map +1 -1
- package/dist/acquire/index.js +1 -0
- package/dist/acquire/index.js.map +1 -1
- package/dist/acquire/providers.d.ts +1 -0
- package/dist/acquire/providers.d.ts.map +1 -1
- package/dist/acquire/providers.js +10 -1
- package/dist/acquire/providers.js.map +1 -1
- package/dist/acquire/runner.d.ts +7 -14
- package/dist/acquire/runner.d.ts.map +1 -1
- package/dist/acquire/runner.js +151 -9
- package/dist/acquire/runner.js.map +1 -1
- package/dist/acquire/transport.d.ts +7 -1
- package/dist/acquire/transport.d.ts.map +1 -1
- package/dist/acquire/transport.js +30 -4
- package/dist/acquire/transport.js.map +1 -1
- package/dist/activity/contract.d.ts +108 -0
- package/dist/activity/contract.d.ts.map +1 -0
- package/dist/activity/contract.js +281 -0
- package/dist/activity/contract.js.map +1 -0
- package/dist/activity/spool.d.ts +20 -0
- package/dist/activity/spool.d.ts.map +1 -0
- package/dist/activity/spool.js +305 -0
- package/dist/activity/spool.js.map +1 -0
- package/dist/cache.d.ts +41 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +134 -4
- package/dist/cache.js.map +1 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +6 -0
- package/dist/collection.js.map +1 -1
- package/dist/connection-state.d.ts +15 -0
- package/dist/connection-state.d.ts.map +1 -1
- package/dist/connection-state.js +18 -0
- package/dist/connection-state.js.map +1 -1
- package/dist/contracts/notify.d.ts +47 -0
- package/dist/contracts/notify.d.ts.map +1 -0
- package/dist/contracts/notify.js +83 -0
- package/dist/contracts/notify.js.map +1 -0
- package/dist/contracts/surfaces.d.ts +61 -0
- package/dist/contracts/surfaces.d.ts.map +1 -0
- package/dist/contracts/surfaces.js +73 -0
- package/dist/contracts/surfaces.js.map +1 -0
- package/dist/contracts/wire.d.ts +108 -0
- package/dist/contracts/wire.d.ts.map +1 -0
- package/dist/contracts/wire.js +202 -0
- package/dist/contracts/wire.js.map +1 -0
- package/dist/data-rules.d.ts +30 -0
- package/dist/data-rules.d.ts.map +1 -0
- package/dist/data-rules.js +57 -0
- package/dist/data-rules.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/normalizer.d.ts.map +1 -1
- package/dist/normalizer.js +42 -7
- package/dist/normalizer.js.map +1 -1
- package/dist/types.d.ts +44 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -1
- package/package.json +16 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { chmodSync, closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readSync, realpathSync, renameSync, rmdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isActivityEvent, MAX_ACTIVITY_EVENT_AGE_MS, MAX_ACTIVITY_EVENT_BYTES, MAX_ACTIVITY_SPOOL_BYTES, readActivityEvent } from "./contract.js";
|
|
5
|
+
const EVENT_NAME = /^(\d{16})-([a-f0-9-]{36})\.json$/u;
|
|
6
|
+
const TEMP_NAME = /^\.[a-f0-9-]{36}\.tmp$/u;
|
|
7
|
+
const MAX_ENTRIES = 65_536;
|
|
8
|
+
/* A healthy writer holds the lock for milliseconds; a hook killed mid write
|
|
9
|
+
(Ctrl+C in the agent) strands it, so recover well before the event age. */
|
|
10
|
+
const STALE_WRITER_LOCK_MS = 10_000;
|
|
11
|
+
function code(error) {
|
|
12
|
+
return error.code;
|
|
13
|
+
}
|
|
14
|
+
function checkpoint(options) {
|
|
15
|
+
options.signal?.throwIfAborted();
|
|
16
|
+
if (options.deadline !== undefined && performance.now() >= options.deadline)
|
|
17
|
+
throw new Error("activity: deadline");
|
|
18
|
+
}
|
|
19
|
+
/** The root is already canonical. Refuse links in the owned subtree. */
|
|
20
|
+
function directories(target, create) {
|
|
21
|
+
if (!path.isAbsolute(target))
|
|
22
|
+
throw new Error("activity: absolute state root required");
|
|
23
|
+
let current = path.dirname(target);
|
|
24
|
+
const root = lstatSync(current);
|
|
25
|
+
if (!root.isDirectory() || root.isSymbolicLink())
|
|
26
|
+
throw new Error("activity: unsafe directory");
|
|
27
|
+
let created = false;
|
|
28
|
+
for (const segment of target.slice(current.length).split(path.sep).filter(Boolean)) {
|
|
29
|
+
if (segment === "." || segment === "..")
|
|
30
|
+
throw new Error("activity: path escape");
|
|
31
|
+
current = path.join(current, segment);
|
|
32
|
+
let info;
|
|
33
|
+
try {
|
|
34
|
+
info = lstatSync(current);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (!create || code(error) !== "ENOENT")
|
|
38
|
+
throw error;
|
|
39
|
+
try {
|
|
40
|
+
mkdirSync(current, { mode: 0o700 });
|
|
41
|
+
if (current === target)
|
|
42
|
+
created = true;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (code(error) !== "EEXIST")
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
info = lstatSync(current);
|
|
49
|
+
}
|
|
50
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
51
|
+
throw new Error("activity: unsafe directory");
|
|
52
|
+
}
|
|
53
|
+
return created;
|
|
54
|
+
}
|
|
55
|
+
function spoolPath(root, create = false) {
|
|
56
|
+
if (!path.isAbsolute(root) || root.split(/[\\/]/u).includes(".."))
|
|
57
|
+
throw new Error("activity: path escape");
|
|
58
|
+
if (create)
|
|
59
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
60
|
+
// System roots may contain aliases (macOS /var, Windows junctions). Resolve
|
|
61
|
+
// only the supplied state root, never activity or any of its entries.
|
|
62
|
+
return path.join(realpathSync(root), "activity");
|
|
63
|
+
}
|
|
64
|
+
function same(left, right) {
|
|
65
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
66
|
+
}
|
|
67
|
+
function regular(info) {
|
|
68
|
+
return info.isFile() && !info.isSymbolicLink() && info.nlink === 1 &&
|
|
69
|
+
(process.platform === "win32" ||
|
|
70
|
+
(info.uid === process.getuid?.() && (info.mode & 0o077) === 0));
|
|
71
|
+
}
|
|
72
|
+
function readFile(file, info, now) {
|
|
73
|
+
if (!regular(info) || info.size > MAX_ACTIVITY_EVENT_BYTES)
|
|
74
|
+
throw new Error("activity: unsafe file");
|
|
75
|
+
const fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
|
|
76
|
+
try {
|
|
77
|
+
const opened = fstatSync(fd);
|
|
78
|
+
if (!same(info, opened) || !regular(opened) || opened.size > MAX_ACTIVITY_EVENT_BYTES)
|
|
79
|
+
throw new Error("activity: changed file");
|
|
80
|
+
const bytes = Buffer.alloc(MAX_ACTIVITY_EVENT_BYTES + 1);
|
|
81
|
+
let length = 0;
|
|
82
|
+
while (length < bytes.length) {
|
|
83
|
+
const count = readSync(fd, bytes, length, bytes.length - length, null);
|
|
84
|
+
if (count === 0)
|
|
85
|
+
break;
|
|
86
|
+
length += count;
|
|
87
|
+
}
|
|
88
|
+
if (length > MAX_ACTIVITY_EVENT_BYTES)
|
|
89
|
+
throw new Error("activity: oversized file");
|
|
90
|
+
const serialized = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length));
|
|
91
|
+
const event = readActivityEvent(serialized, now, "desktop");
|
|
92
|
+
if (event.source === "hook" && !isActivityEvent(event, now, "hook"))
|
|
93
|
+
throw new Error("activity: invalid hook process");
|
|
94
|
+
return event;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
closeSync(fd);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function prepareActivitySpool(options) {
|
|
101
|
+
checkpoint(options);
|
|
102
|
+
const directory = spoolPath(options.directory, true);
|
|
103
|
+
const created = directories(directory, true);
|
|
104
|
+
const before = lstatSync(directory);
|
|
105
|
+
if (process.platform === "win32") {
|
|
106
|
+
const stamp = path.join(directory, ".acl-verified");
|
|
107
|
+
let verified = false;
|
|
108
|
+
try {
|
|
109
|
+
const info = lstatSync(stamp);
|
|
110
|
+
verified = regular(info) && info.size <= MAX_ACTIVITY_EVENT_BYTES &&
|
|
111
|
+
(options.now ?? Date.now()) - info.mtimeMs <= MAX_ACTIVITY_EVENT_AGE_MS;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (code(error) !== "ENOENT")
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
if (created || !verified) {
|
|
118
|
+
if (!options.protectWindowsDirectory)
|
|
119
|
+
throw new Error("activity: Windows ACL helper required");
|
|
120
|
+
await options.protectWindowsDirectory(directory);
|
|
121
|
+
checkpoint(options);
|
|
122
|
+
directories(directory, false);
|
|
123
|
+
if (!same(before, lstatSync(directory)))
|
|
124
|
+
throw new Error("activity: changed directory");
|
|
125
|
+
const temporary = path.join(directory, "." + randomUUID() + ".tmp");
|
|
126
|
+
try {
|
|
127
|
+
const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
|
|
128
|
+
try {
|
|
129
|
+
writeFileSync(fd, "");
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
closeSync(fd);
|
|
133
|
+
}
|
|
134
|
+
// Replace only the stamp itself. Never traverse a planted directory or link.
|
|
135
|
+
try {
|
|
136
|
+
const info = lstatSync(stamp);
|
|
137
|
+
if (info.isDirectory() && !info.isSymbolicLink())
|
|
138
|
+
rmdirSync(stamp);
|
|
139
|
+
else if (!regular(info))
|
|
140
|
+
unlinkSync(stamp);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (code(error) !== "ENOENT")
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
renameSync(temporary, stamp);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
try {
|
|
150
|
+
unlinkSync(temporary);
|
|
151
|
+
}
|
|
152
|
+
catch { /* Published or failed stamp. */ }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
if (before.uid !== process.getuid?.())
|
|
158
|
+
throw new Error("activity: wrong directory owner");
|
|
159
|
+
chmodSync(directory, 0o700);
|
|
160
|
+
}
|
|
161
|
+
checkpoint(options);
|
|
162
|
+
directories(directory, false);
|
|
163
|
+
if (!same(before, lstatSync(directory)))
|
|
164
|
+
throw new Error("activity: changed directory");
|
|
165
|
+
return directory;
|
|
166
|
+
}
|
|
167
|
+
/** A global spool lock serializes size accounting and sequence allocation. */
|
|
168
|
+
export async function writeActivityEvent(event, options) {
|
|
169
|
+
const now = options.now ?? Date.now();
|
|
170
|
+
if (!isActivityEvent(event, now, event.source === "hook" ? "hook" : "desktop"))
|
|
171
|
+
throw new Error("activity: invalid event");
|
|
172
|
+
const directory = await prepareActivitySpool(options);
|
|
173
|
+
const lock = path.join(directory, ".writer");
|
|
174
|
+
const deadline = performance.now() + 40;
|
|
175
|
+
while (true) {
|
|
176
|
+
checkpoint(options);
|
|
177
|
+
try {
|
|
178
|
+
mkdirSync(lock, { mode: 0o700 });
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
if (code(error) !== "EEXIST" || performance.now() >= deadline)
|
|
183
|
+
throw error;
|
|
184
|
+
const info = lstatSync(lock);
|
|
185
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
186
|
+
throw new Error("activity: unsafe lock");
|
|
187
|
+
// Only an empty abandoned lock can be recovered. Never recurse into it.
|
|
188
|
+
if (now - info.mtimeMs > STALE_WRITER_LOCK_MS) {
|
|
189
|
+
rmdirSync(lock);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
await new Promise((resolve) => setTimeout(resolve, 2));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let temporary;
|
|
196
|
+
try {
|
|
197
|
+
directories(directory, false);
|
|
198
|
+
let total = 0;
|
|
199
|
+
let sequence = Math.max(event.sequence, now * 1_000);
|
|
200
|
+
const entries = readdirSync(directory);
|
|
201
|
+
if (entries.length > MAX_ENTRIES)
|
|
202
|
+
throw new Error("activity: too many entries");
|
|
203
|
+
for (const name of entries) {
|
|
204
|
+
checkpoint(options);
|
|
205
|
+
if (name === ".writer")
|
|
206
|
+
continue;
|
|
207
|
+
const file = path.join(directory, name);
|
|
208
|
+
const info = lstatSync(file);
|
|
209
|
+
if (!regular(info))
|
|
210
|
+
throw new Error("activity: unsafe spool entry");
|
|
211
|
+
const match = EVENT_NAME.exec(name);
|
|
212
|
+
const owned = match !== null || TEMP_NAME.test(name);
|
|
213
|
+
if (owned && now - info.mtimeMs > MAX_ACTIVITY_EVENT_AGE_MS) {
|
|
214
|
+
unlinkSync(file);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (info.size > MAX_ACTIVITY_EVENT_BYTES)
|
|
218
|
+
throw new Error("activity: oversized spool entry");
|
|
219
|
+
/* Names and times only: parsing every record here made each write O(n)
|
|
220
|
+
reads and dropped hooks once a busy day filled the spool. A record
|
|
221
|
+
whose observedAt is stale is refused by the reader and removed by
|
|
222
|
+
the age cleanup above. */
|
|
223
|
+
total += info.size;
|
|
224
|
+
if (match !== null)
|
|
225
|
+
sequence = Math.max(sequence, Number(match[1]) + 1);
|
|
226
|
+
}
|
|
227
|
+
const stored = { ...event, sequence };
|
|
228
|
+
if (!isActivityEvent(stored, now, event.source === "hook" ? "hook" : "desktop"))
|
|
229
|
+
throw new Error("activity: invalid sequence");
|
|
230
|
+
const bytes = Buffer.from(JSON.stringify(stored));
|
|
231
|
+
if (bytes.length > MAX_ACTIVITY_EVENT_BYTES || total + bytes.length > MAX_ACTIVITY_SPOOL_BYTES)
|
|
232
|
+
throw new Error("activity: spool full");
|
|
233
|
+
const id = randomUUID();
|
|
234
|
+
temporary = path.join(directory, "." + id + ".tmp");
|
|
235
|
+
const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
|
|
236
|
+
try {
|
|
237
|
+
writeFileSync(fd, bytes);
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
closeSync(fd);
|
|
241
|
+
}
|
|
242
|
+
checkpoint(options);
|
|
243
|
+
directories(directory, false);
|
|
244
|
+
renameSync(temporary, path.join(directory, String(sequence).padStart(16, "0") + "-" + id + ".json"));
|
|
245
|
+
temporary = undefined;
|
|
246
|
+
return stored;
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
if (temporary !== undefined) {
|
|
250
|
+
try {
|
|
251
|
+
unlinkSync(temporary);
|
|
252
|
+
}
|
|
253
|
+
catch { /* Failed writes never publish. */ }
|
|
254
|
+
}
|
|
255
|
+
rmdirSync(lock);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/** Read only. Invalid, stale, partial and hostile records are counted and skipped. */
|
|
259
|
+
export function readActivitySpool(directory, now = Date.now()) {
|
|
260
|
+
const events = [];
|
|
261
|
+
let skipped = 0;
|
|
262
|
+
try {
|
|
263
|
+
const spool = spoolPath(directory);
|
|
264
|
+
directories(spool, false);
|
|
265
|
+
const info = lstatSync(spool);
|
|
266
|
+
if (process.platform !== "win32" && (info.uid !== process.getuid?.() || (info.mode & 0o077) !== 0))
|
|
267
|
+
throw new Error("activity: unsafe directory");
|
|
268
|
+
const names = readdirSync(spool);
|
|
269
|
+
let total = 0;
|
|
270
|
+
for (const [index, name] of names.entries()) {
|
|
271
|
+
if (name === ".acl-verified" && process.platform === "win32") {
|
|
272
|
+
const stamp = lstatSync(path.join(spool, name));
|
|
273
|
+
if (!regular(stamp) || stamp.size > MAX_ACTIVITY_EVENT_BYTES)
|
|
274
|
+
skipped += 1;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (name === ".writer") {
|
|
278
|
+
const lock = lstatSync(path.join(spool, name));
|
|
279
|
+
if (!lock.isDirectory() || lock.isSymbolicLink())
|
|
280
|
+
skipped += 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
if (index >= MAX_ENTRIES || !EVENT_NAME.test(name))
|
|
285
|
+
throw new Error("activity: unexpected name");
|
|
286
|
+
const file = path.join(spool, name);
|
|
287
|
+
const info = lstatSync(file);
|
|
288
|
+
total += info.size;
|
|
289
|
+
if (total > MAX_ACTIVITY_SPOOL_BYTES)
|
|
290
|
+
throw new Error("activity: read cap");
|
|
291
|
+
events.push(readFile(file, info, now));
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
skipped += 1;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (code(error) !== "ENOENT")
|
|
300
|
+
skipped += 1;
|
|
301
|
+
}
|
|
302
|
+
events.sort((left, right) => left.sequence - right.sequence || left.observedAt.localeCompare(right.observedAt) || left.eventId.localeCompare(right.eventId));
|
|
303
|
+
return { events, skipped };
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=spool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spool.js","sourceRoot":"","sources":["../../src/activity/spool.ts"],"names":[],"mappings":"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"}
|
package/dist/cache.d.ts
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
|
+
export { freshnessPolicy } from "./data-rules.js";
|
|
1
2
|
import { type CacheState, type CacheSuppression, type CollectionReport } from "./collection.js";
|
|
2
|
-
import type { Snapshot } from "./types.js";
|
|
3
|
+
import type { ProviderCode, Snapshot } from "./types.js";
|
|
3
4
|
export declare const CACHE_FILE_NAME = "openlimiter-cache.json";
|
|
5
|
+
export declare const MAX_POLICY_TIMESTAMP = 253402300799999;
|
|
6
|
+
/** Pure policy shared with the desktop contract. All instants are UTC. */
|
|
7
|
+
export declare function retryPolicy(input: {
|
|
8
|
+
attemptCount: number;
|
|
9
|
+
retryAfter: string | null;
|
|
10
|
+
now: string;
|
|
11
|
+
jitterSeconds: number;
|
|
12
|
+
layer: string;
|
|
13
|
+
}): {
|
|
14
|
+
localDelaySeconds: number;
|
|
15
|
+
serverDeadline: string | null;
|
|
16
|
+
nextAllowedAt: string;
|
|
17
|
+
blockedUntil: string | null;
|
|
18
|
+
};
|
|
19
|
+
export declare function leasePolicy(input: {
|
|
20
|
+
now: string;
|
|
21
|
+
requester: string;
|
|
22
|
+
owner: string | null;
|
|
23
|
+
expiresAt: string | null;
|
|
24
|
+
leaseSeconds: number;
|
|
25
|
+
}): {
|
|
26
|
+
acquired: boolean;
|
|
27
|
+
takeover: boolean;
|
|
28
|
+
owner: string | null;
|
|
29
|
+
expiresAt: string | null;
|
|
30
|
+
};
|
|
31
|
+
export declare function withPolicyFreshness(snapshot: Snapshot): Snapshot;
|
|
32
|
+
export declare function recordAcquisitionAvailability(provider: ProviderCode, availability: "expired_credentials" | "access_denied" | "rate_limited", now: string, retryAt: string | undefined, directory?: string, accountId?: string): Promise<void>;
|
|
33
|
+
export interface MachineLease {
|
|
34
|
+
readonly attempts: number;
|
|
35
|
+
stillOwned(now?: number): Promise<boolean>;
|
|
36
|
+
complete(nextAllowedAt: number, attempts: number, refusalRevision?: string): Promise<void>;
|
|
37
|
+
release(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/** Provider scope deliberately also excludes aliases of the same account. */
|
|
40
|
+
export declare function acquireMachineLease(provider: string, directory?: string, now?: number, credentialRevision?: string): Promise<MachineLease | null>;
|
|
4
41
|
export declare const CACHE_LOCK_NAME = "openlimiter.lock";
|
|
5
42
|
/** Largest JSON document this package will read into memory. */
|
|
6
43
|
export declare const MAX_JSON_FILE_BYTES = 1048576;
|
|
@@ -122,7 +159,7 @@ export interface CacheMergeResult {
|
|
|
122
159
|
* unchanged result skips the write entirely, which keeps a statusline that
|
|
123
160
|
* renders on every keystroke from churning the disk.
|
|
124
161
|
*/
|
|
125
|
-
export declare function mergeSnapshotCache(incoming: readonly Snapshot[], directory?: string): Promise<CacheMergeResult>;
|
|
162
|
+
export declare function mergeSnapshotCache(incoming: readonly Snapshot[], directory?: string, now?: number): Promise<CacheMergeResult>;
|
|
126
163
|
/**
|
|
127
164
|
* Fold rows this machine just acquired into the cache, without ever losing a
|
|
128
165
|
* newer row somebody else wrote.
|
|
@@ -166,4 +203,6 @@ export declare function applyCollectionReportToCache(report: CollectionReport, d
|
|
|
166
203
|
state: CacheState;
|
|
167
204
|
written: boolean;
|
|
168
205
|
}>;
|
|
206
|
+
/** Startup maintenance uses the same lock as all writers and never repairs unreadable state. */
|
|
207
|
+
export declare function pruneSnapshotCache(directory?: string, now?: number): Promise<number>;
|
|
169
208
|
//# sourceMappingURL=cache.d.ts.map
|
package/dist/cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
package/dist/cache.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { freshnessPolicy, retainSnapshots } from "./data-rules.js";
|
|
2
|
+
export { freshnessPolicy } from "./data-rules.js";
|
|
1
3
|
import { randomUUID } from "node:crypto";
|
|
2
4
|
import { constants } from "node:fs";
|
|
3
5
|
import { chmod, lstat, mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
@@ -7,6 +9,118 @@ import { applyCollectionReport, readSuppressions, snapshotBelongsTo, visibleSnap
|
|
|
7
9
|
import { MAX_CACHE_ENTRIES, mergeSnapshots, snapshotIdentity } from "./merge.js";
|
|
8
10
|
import { canonicalJson, normalizeMeter, normalizeMeters } from "./normalizer.js";
|
|
9
11
|
export const CACHE_FILE_NAME = "openlimiter-cache.json";
|
|
12
|
+
export const MAX_POLICY_TIMESTAMP = 253_402_300_799_999;
|
|
13
|
+
/** Pure policy shared with the desktop contract. All instants are UTC. */
|
|
14
|
+
export function retryPolicy(input) {
|
|
15
|
+
const now = Date.parse(input.now);
|
|
16
|
+
const raw = input.retryAfter?.trim();
|
|
17
|
+
const server = raw === undefined ? NaN : /^\d+$/u.test(raw)
|
|
18
|
+
? Math.min(MAX_POLICY_TIMESTAMP, now + Number(raw) * 1000)
|
|
19
|
+
: /^[A-Za-z]{3}, /u.test(raw) ? Date.parse(raw) : NaN;
|
|
20
|
+
const localDelaySeconds = Math.min(60 * 2 ** Math.min(input.attemptCount, 4), 900) + Math.max(0, input.jitterSeconds);
|
|
21
|
+
const ceiling = now + (input.layer === "collector" ? 86400 : 604800) * 1000;
|
|
22
|
+
const cappedServer = Math.min(server, ceiling);
|
|
23
|
+
const serverDeadline = Number.isFinite(server) ? new Date(cappedServer).toISOString() : null;
|
|
24
|
+
return {
|
|
25
|
+
localDelaySeconds, serverDeadline,
|
|
26
|
+
nextAllowedAt: new Date(Math.max(now + localDelaySeconds * 1000, Number.isFinite(server) ? cappedServer : 0)).toISOString(),
|
|
27
|
+
blockedUntil: server > ceiling ? serverDeadline : null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function leasePolicy(input) {
|
|
31
|
+
const acquired = input.owner === null || input.expiresAt === null || Date.parse(input.expiresAt) <= Date.parse(input.now);
|
|
32
|
+
return {
|
|
33
|
+
acquired, takeover: acquired && input.owner !== null && input.owner !== input.requester,
|
|
34
|
+
owner: acquired ? input.requester : input.owner,
|
|
35
|
+
expiresAt: acquired ? new Date(Date.parse(input.now) + input.leaseSeconds * 1000).toISOString() : input.expiresAt
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function withPolicyFreshness(snapshot) {
|
|
39
|
+
if (!["native_payload", "documented_api", "internal_payload", "local_file"].includes(snapshot.source))
|
|
40
|
+
return snapshot;
|
|
41
|
+
return { ...snapshot, expiresAt: freshnessPolicy({ ...snapshot, sourceClass: snapshot.source, now: snapshot.observedAt }).expiresAt };
|
|
42
|
+
}
|
|
43
|
+
export async function recordAcquisitionAvailability(provider, availability, now, retryAt, directory = resolveStateDirectory(), accountId) {
|
|
44
|
+
await withCacheLock(directory, async () => {
|
|
45
|
+
const state = await readCacheState(directory);
|
|
46
|
+
if (!state.ok && state.reason !== "missing")
|
|
47
|
+
throw new Error("Unreadable snapshot cache");
|
|
48
|
+
const rows = retainSnapshots(state.ok ? state.state.snapshots : [], Date.parse(now));
|
|
49
|
+
const matches = (row) => row.provider === provider && row.accountId === accountId;
|
|
50
|
+
const matching = rows.filter(matches);
|
|
51
|
+
if (matching.some(row => row.observedAt > now))
|
|
52
|
+
return;
|
|
53
|
+
const seed = {
|
|
54
|
+
provider, ...(accountId ? { accountId } : {}), meter: "ACQUISITION", value: 0, unit: "PERCENT", window: { kind: "unknown" }, resetAt: null,
|
|
55
|
+
source: "internal_payload", precision: "exact", observedAt: now, writer: "cli",
|
|
56
|
+
expiresAt: freshnessPolicy({ sourceClass: "internal_payload", observedAt: now, now }).expiresAt,
|
|
57
|
+
labels: { credentialOrigin: "official-local-tool", dataInterfaceStatus: "internal-endpoint", automationRisk: "high", verification: "UNVERIFIED" }
|
|
58
|
+
};
|
|
59
|
+
const snapshots = [...rows.filter((row) => !matches(row)), ...(matching.length ? matching : [seed]).map((row) => {
|
|
60
|
+
const { retryAt: _oldRetry, ...rest } = row;
|
|
61
|
+
return { ...rest, availability, ...(availability === "rate_limited" && retryAt ? { retryAt } : {}) };
|
|
62
|
+
})];
|
|
63
|
+
await replaceCache(directory, snapshots, state.ok ? state.state.suppressions : []);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Provider scope deliberately also excludes aliases of the same account. */
|
|
67
|
+
export async function acquireMachineLease(provider, directory = resolveStateDirectory(), now = Date.now(), credentialRevision) {
|
|
68
|
+
const named = provider.toLowerCase().replaceAll("_", "-");
|
|
69
|
+
const slug = named === "gemini-cli" || named === "antigravity" ? "code-assist" : named;
|
|
70
|
+
if (!/^[a-z][a-z-]{0,31}$/u.test(slug))
|
|
71
|
+
throw new Error("Invalid provider");
|
|
72
|
+
const file = path.join(directory, `acquisition-${slug}.json`);
|
|
73
|
+
const read = async () => {
|
|
74
|
+
const result = await readJsonFileSafely(file);
|
|
75
|
+
if (!result.ok) {
|
|
76
|
+
if (result.reason !== "missing")
|
|
77
|
+
throw new Error("Unreadable acquisition policy");
|
|
78
|
+
return { owner: null, expiresAt: 0, token: "", nextAllowedAt: 0, attempts: 0 };
|
|
79
|
+
}
|
|
80
|
+
const value = result.value;
|
|
81
|
+
if (!isRecord(value) || !Number.isSafeInteger(value.expiresAt) || !Number.isSafeInteger(value.nextAllowedAt) || !Number.isSafeInteger(value.attempts) || typeof value.token !== "string" || (value.owner !== null && value.owner !== "desktop" && value.owner !== "cli"))
|
|
82
|
+
throw new Error("Invalid acquisition policy");
|
|
83
|
+
return value;
|
|
84
|
+
};
|
|
85
|
+
const state = await withCacheLock(directory, async () => {
|
|
86
|
+
const current = await read();
|
|
87
|
+
const decision = leasePolicy({ now: new Date(now).toISOString(), requester: "cli", owner: current.owner, expiresAt: new Date(current.expiresAt).toISOString(), leaseSeconds: 60 });
|
|
88
|
+
const changedRefusal = typeof current.refusalRevision === "string" && credentialRevision !== undefined && current.refusalRevision !== credentialRevision;
|
|
89
|
+
if (!decision.acquired || (now < current.nextAllowedAt && !changedRefusal))
|
|
90
|
+
return null;
|
|
91
|
+
const next = { ...current, lastAccount: null, owner: "cli", expiresAt: now + 60_000, token: randomUUID() };
|
|
92
|
+
await writeFileAtomically(file, canonicalJson(next));
|
|
93
|
+
return next;
|
|
94
|
+
});
|
|
95
|
+
if (state === null)
|
|
96
|
+
return null;
|
|
97
|
+
const mutate = async (action) => withCacheLock(directory, async () => {
|
|
98
|
+
const current = await read();
|
|
99
|
+
if (current.token !== state.token)
|
|
100
|
+
throw new Error("Acquisition lease lost");
|
|
101
|
+
action(current);
|
|
102
|
+
await writeFileAtomically(file, canonicalJson(current));
|
|
103
|
+
});
|
|
104
|
+
const started = Date.now();
|
|
105
|
+
const heartbeat = setInterval(() => {
|
|
106
|
+
void mutate((current) => {
|
|
107
|
+
const at = now + Date.now() - started;
|
|
108
|
+
if (current.owner !== "cli" || at >= current.expiresAt)
|
|
109
|
+
throw new Error("Acquisition lease expired");
|
|
110
|
+
current.expiresAt = at + 60_000;
|
|
111
|
+
}).catch(() => clearInterval(heartbeat));
|
|
112
|
+
}, 20_000);
|
|
113
|
+
heartbeat.unref();
|
|
114
|
+
return {
|
|
115
|
+
attempts: state.attempts,
|
|
116
|
+
stillOwned: async (at = Date.now()) => { const current = await read(); return current.token === state.token && current.owner === "cli" && at < current.expiresAt; },
|
|
117
|
+
complete: async (nextAllowedAt, attempts, refusalRevision) => mutate((current) => { current.nextAllowedAt = nextAllowedAt; current.attempts = attempts; current.refusalRevision = refusalRevision ?? null; }),
|
|
118
|
+
release: async () => {
|
|
119
|
+
clearInterval(heartbeat);
|
|
120
|
+
await mutate((current) => { current.owner = null; current.expiresAt = 0; }).catch(() => undefined);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
10
124
|
export const CACHE_LOCK_NAME = "openlimiter.lock";
|
|
11
125
|
/** Largest JSON document this package will read into memory. */
|
|
12
126
|
export const MAX_JSON_FILE_BYTES = 1_048_576;
|
|
@@ -477,14 +591,14 @@ export async function writeSnapshotCache(snapshots, directory = resolveStateDire
|
|
|
477
591
|
* unchanged result skips the write entirely, which keeps a statusline that
|
|
478
592
|
* renders on every keystroke from churning the disk.
|
|
479
593
|
*/
|
|
480
|
-
export async function mergeSnapshotCache(incoming, directory = resolveStateDirectory()) {
|
|
594
|
+
export async function mergeSnapshotCache(incoming, directory = resolveStateDirectory(), now = Date.now()) {
|
|
481
595
|
rejectOutOfBounds(incoming);
|
|
482
596
|
return await withCacheLock(directory, async () => {
|
|
483
597
|
const cached = await readCacheState(directory);
|
|
484
598
|
const state = cached.ok
|
|
485
599
|
? cached.state
|
|
486
600
|
: { snapshots: [], suppressions: [] };
|
|
487
|
-
const merged = mergeSnapshots(state.snapshots, incoming);
|
|
601
|
+
const merged = retainSnapshots(mergeSnapshots(state.snapshots, incoming), now);
|
|
488
602
|
if (canonicalJson(merged) === canonicalJson(state.snapshots)) {
|
|
489
603
|
return { merged, written: false };
|
|
490
604
|
}
|
|
@@ -519,7 +633,7 @@ export async function mergeAcquiredSnapshots(report, directory = resolveStateDir
|
|
|
519
633
|
return await withCacheLock(directory, async () => {
|
|
520
634
|
const cached = await readCacheState(directory);
|
|
521
635
|
const before = cached.ok
|
|
522
|
-
? cached.state
|
|
636
|
+
? { ...cached.state, snapshots: cached.state.snapshots.filter((row) => !(row.provider === report.provider && row.accountId === report.accountId && row.meter === "ACQUISITION" && row.availability !== undefined && row.observedAt <= report.observedAt)) }
|
|
523
637
|
: { snapshots: [], suppressions: [] };
|
|
524
638
|
const held = new Map(before.snapshots.map((snapshot) => [snapshotIdentity(snapshot), snapshot]));
|
|
525
639
|
const taken = [];
|
|
@@ -563,7 +677,7 @@ export async function mergeAcquiredSnapshots(report, directory = resolveStateDir
|
|
|
563
677
|
await replaceCache(directory, after.snapshots, after.suppressions);
|
|
564
678
|
return { written: true, taken: taken.length, deferred };
|
|
565
679
|
}
|
|
566
|
-
const merged = mergeSnapshots(before.snapshots, taken);
|
|
680
|
+
const merged = retainSnapshots(mergeSnapshots(before.snapshots, taken), Date.parse(report.observedAt));
|
|
567
681
|
if (canonicalJson(merged) === canonicalJson(before.snapshots)) {
|
|
568
682
|
return { written: false, taken: taken.length, deferred };
|
|
569
683
|
}
|
|
@@ -600,4 +714,20 @@ export async function applyCollectionReportToCache(report, directory = resolveSt
|
|
|
600
714
|
return { state: after, written: true };
|
|
601
715
|
});
|
|
602
716
|
}
|
|
717
|
+
/** Startup maintenance uses the same lock as all writers and never repairs unreadable state. */
|
|
718
|
+
export async function pruneSnapshotCache(directory = resolveStateDirectory(), now = Date.now()) {
|
|
719
|
+
const existing = await readCacheState(directory);
|
|
720
|
+
if (!existing.ok)
|
|
721
|
+
return 0;
|
|
722
|
+
return withCacheLock(directory, async () => {
|
|
723
|
+
const cached = await readCacheState(directory);
|
|
724
|
+
if (!cached.ok)
|
|
725
|
+
return 0;
|
|
726
|
+
const snapshots = retainSnapshots(cached.state.snapshots, now);
|
|
727
|
+
const count = cached.state.snapshots.length - snapshots.length;
|
|
728
|
+
if (count)
|
|
729
|
+
await replaceCache(directory, snapshots, cached.state.suppressions);
|
|
730
|
+
return count;
|
|
731
|
+
});
|
|
732
|
+
}
|
|
603
733
|
//# sourceMappingURL=cache.js.map
|