@forgezero/runtime 0.1.1 → 0.1.3
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 +27 -4
- package/dist/audit.js +262 -23
- package/dist/identity.d.ts +25 -0
- package/dist/identity.js +99 -3
- package/dist/jobs.d.ts +3 -2
- package/dist/jobs.js +288 -13
- package/dist/pipeline.d.ts +11 -37
- package/dist/pipeline.js +0 -27
- package/dist/queue.d.ts +17 -3
- package/dist/queue.js +86 -19
- package/dist/snp.d.ts +7 -6
- package/dist/snp.js +6 -1
- package/package.json +6 -9
- package/dist/finance/binance.d.ts +0 -27
- package/dist/finance/binance.js +0 -452
- package/dist/serial.d.ts +0 -54
- package/dist/serial.js +0 -40
package/dist/queue.js
CHANGED
|
@@ -14,6 +14,15 @@ class QueueStoppedError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
class QueueKeyStoppedError extends Error {
|
|
18
|
+
key;
|
|
19
|
+
constructor(key) {
|
|
20
|
+
super(`queue: key ${key} is stopped`);
|
|
21
|
+
this.key = key;
|
|
22
|
+
this.name = "QueueKeyStoppedError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
class TaskCancelledError extends Error {
|
|
18
27
|
constructor() {
|
|
19
28
|
super("queue: task cancelled");
|
|
@@ -25,12 +34,19 @@ var DEFAULT_RETRY = {
|
|
|
25
34
|
backoffMs: (attempt) => Math.min(30000, 2 ** attempt * 100)
|
|
26
35
|
};
|
|
27
36
|
function createQueue(options = {}) {
|
|
28
|
-
const width =
|
|
37
|
+
const width = options.width ?? 8;
|
|
29
38
|
const retry = { ...DEFAULT_RETRY, ...options.retry };
|
|
39
|
+
if (!Number.isSafeInteger(width) || width < 1) {
|
|
40
|
+
throw new RangeError("queue: width must be a positive integer");
|
|
41
|
+
}
|
|
42
|
+
if (!Number.isSafeInteger(retry.attempts) || retry.attempts < 1) {
|
|
43
|
+
throw new RangeError("queue: retry attempts must be a positive integer");
|
|
44
|
+
}
|
|
30
45
|
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
31
46
|
const lanes = new Map;
|
|
32
47
|
const running = new Set;
|
|
33
48
|
const paused = new Set;
|
|
49
|
+
const stoppedKeys = new Set;
|
|
34
50
|
let sequence = 0;
|
|
35
51
|
let globallyPaused = false;
|
|
36
52
|
let accepting = true;
|
|
@@ -48,8 +64,10 @@ function createQueue(options = {}) {
|
|
|
48
64
|
idle.shift()();
|
|
49
65
|
};
|
|
50
66
|
function pump() {
|
|
51
|
-
if (globallyPaused || aborted)
|
|
67
|
+
if (globallyPaused || aborted) {
|
|
68
|
+
announceIdle();
|
|
52
69
|
return;
|
|
70
|
+
}
|
|
53
71
|
for (const [key, lane] of lanes) {
|
|
54
72
|
if (running.size >= width)
|
|
55
73
|
break;
|
|
@@ -62,18 +80,21 @@ function createQueue(options = {}) {
|
|
|
62
80
|
async function execute(key) {
|
|
63
81
|
running.add(key);
|
|
64
82
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!entry || globallyPaused || paused.has(key) || aborted)
|
|
69
|
-
break;
|
|
83
|
+
const lane = lanes.get(key);
|
|
84
|
+
const entry = lane?.[0];
|
|
85
|
+
if (entry && !globallyPaused && !paused.has(key) && !aborted) {
|
|
70
86
|
lane.shift();
|
|
71
87
|
await attempt(entry);
|
|
72
88
|
}
|
|
73
89
|
} finally {
|
|
74
90
|
running.delete(key);
|
|
75
|
-
|
|
91
|
+
const remaining = lanes.get(key);
|
|
92
|
+
if (remaining?.length === 0)
|
|
76
93
|
lanes.delete(key);
|
|
94
|
+
else if (remaining) {
|
|
95
|
+
lanes.delete(key);
|
|
96
|
+
lanes.set(key, remaining);
|
|
97
|
+
}
|
|
77
98
|
pump();
|
|
78
99
|
}
|
|
79
100
|
}
|
|
@@ -95,15 +116,21 @@ function createQueue(options = {}) {
|
|
|
95
116
|
entry.reject(cause);
|
|
96
117
|
return;
|
|
97
118
|
}
|
|
98
|
-
|
|
119
|
+
const delay = retry.backoffMs(entry.attempt);
|
|
120
|
+
if (!Number.isFinite(delay) || delay < 0) {
|
|
121
|
+
failed += 1;
|
|
122
|
+
entry.reject(new RangeError("queue: retry backoff must be a non-negative finite number"));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await sleep(delay);
|
|
99
126
|
}
|
|
100
127
|
}
|
|
101
128
|
}
|
|
102
129
|
return {
|
|
103
|
-
run(key, handler) {
|
|
130
|
+
run(key, handler, ...args) {
|
|
104
131
|
const id = `q_${++sequence}`;
|
|
105
|
-
if (!accepting) {
|
|
106
|
-
const refused = Promise.reject(new QueueStoppedError);
|
|
132
|
+
if (!accepting || stoppedKeys.has(key)) {
|
|
133
|
+
const refused = Promise.reject(accepting ? new QueueKeyStoppedError(key) : new QueueStoppedError);
|
|
107
134
|
refused.catch(() => {
|
|
108
135
|
return;
|
|
109
136
|
});
|
|
@@ -118,7 +145,7 @@ function createQueue(options = {}) {
|
|
|
118
145
|
const entry = {
|
|
119
146
|
id,
|
|
120
147
|
key,
|
|
121
|
-
run: async () => handler(),
|
|
148
|
+
run: async () => handler(...args),
|
|
122
149
|
resolve,
|
|
123
150
|
reject,
|
|
124
151
|
attempt: 0,
|
|
@@ -142,6 +169,7 @@ function createQueue(options = {}) {
|
|
|
142
169
|
entry.reject(new TaskCancelledError);
|
|
143
170
|
if (lane.length === 0 && !running.has(key))
|
|
144
171
|
lanes.delete(key);
|
|
172
|
+
announceIdle();
|
|
145
173
|
return true;
|
|
146
174
|
}
|
|
147
175
|
return false;
|
|
@@ -153,6 +181,28 @@ function createQueue(options = {}) {
|
|
|
153
181
|
paused.delete(key);
|
|
154
182
|
pump();
|
|
155
183
|
},
|
|
184
|
+
stopKey(key) {
|
|
185
|
+
stoppedKeys.add(key);
|
|
186
|
+
paused.delete(key);
|
|
187
|
+
const lane = lanes.get(key);
|
|
188
|
+
if (!lane)
|
|
189
|
+
return 0;
|
|
190
|
+
let removed = 0;
|
|
191
|
+
for (const entry of lane.splice(0)) {
|
|
192
|
+
removed += 1;
|
|
193
|
+
entry.cancelled = true;
|
|
194
|
+
entry.reject(new QueueKeyStoppedError(key));
|
|
195
|
+
}
|
|
196
|
+
if (!running.has(key))
|
|
197
|
+
lanes.delete(key);
|
|
198
|
+
announceIdle();
|
|
199
|
+
return removed;
|
|
200
|
+
},
|
|
201
|
+
startKey(key) {
|
|
202
|
+
const changed = stoppedKeys.delete(key);
|
|
203
|
+
pump();
|
|
204
|
+
return changed;
|
|
205
|
+
},
|
|
156
206
|
pause() {
|
|
157
207
|
globallyPaused = true;
|
|
158
208
|
},
|
|
@@ -170,6 +220,7 @@ function createQueue(options = {}) {
|
|
|
170
220
|
keys: lanes.size,
|
|
171
221
|
paused: globallyPaused,
|
|
172
222
|
pausedKeys: [...paused],
|
|
223
|
+
stoppedKeys: [...stoppedKeys],
|
|
173
224
|
completed,
|
|
174
225
|
failed
|
|
175
226
|
};
|
|
@@ -181,15 +232,25 @@ function createQueue(options = {}) {
|
|
|
181
232
|
return new Promise((resolve) => idle.push(resolve));
|
|
182
233
|
},
|
|
183
234
|
async stop(deadlineMs = 30000) {
|
|
235
|
+
if (!Number.isSafeInteger(deadlineMs) || deadlineMs < 0) {
|
|
236
|
+
throw new RangeError("queue: stop deadline must be a non-negative integer");
|
|
237
|
+
}
|
|
184
238
|
accepting = false;
|
|
185
239
|
const before = { completed, failed };
|
|
240
|
+
globallyPaused = false;
|
|
241
|
+
paused.clear();
|
|
242
|
+
pump();
|
|
186
243
|
let timedOut = false;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
244
|
+
let deadlineHandle;
|
|
245
|
+
const deadline = new Promise((resolve) => {
|
|
246
|
+
deadlineHandle = setTimeout(() => {
|
|
190
247
|
timedOut = true;
|
|
191
|
-
|
|
192
|
-
|
|
248
|
+
resolve();
|
|
249
|
+
}, deadlineMs);
|
|
250
|
+
});
|
|
251
|
+
await Promise.race([this.whenIdle(), deadline]);
|
|
252
|
+
if (!timedOut && deadlineHandle !== undefined)
|
|
253
|
+
clearTimeout(deadlineHandle);
|
|
193
254
|
if (timedOut)
|
|
194
255
|
aborted = true;
|
|
195
256
|
let abandoned = 0;
|
|
@@ -199,6 +260,11 @@ function createQueue(options = {}) {
|
|
|
199
260
|
entry.reject(new QueueStoppedError);
|
|
200
261
|
}
|
|
201
262
|
abandoned += running.size;
|
|
263
|
+
for (const [key, lane] of lanes) {
|
|
264
|
+
if (lane.length === 0 && !running.has(key))
|
|
265
|
+
lanes.delete(key);
|
|
266
|
+
}
|
|
267
|
+
announceIdle();
|
|
202
268
|
return {
|
|
203
269
|
completed: completed - before.completed,
|
|
204
270
|
failed: failed - before.failed,
|
|
@@ -211,5 +277,6 @@ function createQueue(options = {}) {
|
|
|
211
277
|
export {
|
|
212
278
|
createQueue,
|
|
213
279
|
TaskCancelledError,
|
|
214
|
-
QueueStoppedError
|
|
280
|
+
QueueStoppedError,
|
|
281
|
+
QueueKeyStoppedError
|
|
215
282
|
};
|
package/dist/snp.d.ts
CHANGED
|
@@ -17,10 +17,9 @@
|
|
|
17
17
|
* ## What this does NOT do
|
|
18
18
|
*
|
|
19
19
|
* It does not verify the signature. Chaining a report to AMD's root needs the
|
|
20
|
-
* VCEK certificate for that specific chip at that specific TCB
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* would produce `verified: true` for a report nobody signed.
|
|
20
|
+
* VCEK certificate for that specific chip at that specific TCB and is performed
|
|
21
|
+
* by the API's pinned KDS verifier. Keeping network trust out of this parser
|
|
22
|
+
* preserves a total byte-to-fields function.
|
|
24
23
|
*
|
|
25
24
|
* So `parseSnpReport` returns the signature bytes and says nothing about them.
|
|
26
25
|
* The caller supplies a verifier, which is the same shape every other trust
|
|
@@ -29,8 +28,8 @@
|
|
|
29
28
|
/** The structure is exactly this long. Anything else is not a report. */
|
|
30
29
|
export declare const REPORT_BYTES = 1184;
|
|
31
30
|
export declare class SnpError extends Error {
|
|
32
|
-
readonly code: 'BAD_LENGTH' | 'BAD_VERSION' | 'BAD_VMPL';
|
|
33
|
-
constructor(code: 'BAD_LENGTH' | 'BAD_VERSION' | 'BAD_VMPL', message: string);
|
|
31
|
+
readonly code: 'BAD_LENGTH' | 'BAD_VERSION' | 'BAD_VMPL' | 'BAD_SIGNATURE_ALGORITHM';
|
|
32
|
+
constructor(code: 'BAD_LENGTH' | 'BAD_VERSION' | 'BAD_VMPL' | 'BAD_SIGNATURE_ALGORITHM', message: string);
|
|
34
33
|
}
|
|
35
34
|
/**
|
|
36
35
|
* Guest policy bits.
|
|
@@ -61,6 +60,8 @@ export interface SnpReport {
|
|
|
61
60
|
guestSvn: number;
|
|
62
61
|
policy: GuestPolicy;
|
|
63
62
|
vmpl: number;
|
|
63
|
+
/** 1 is ECDSA P-384 with SHA-384. */
|
|
64
|
+
signatureAlgorithm: number;
|
|
64
65
|
/** 48 bytes of hex. What the guest actually booted. */
|
|
65
66
|
measurement: string;
|
|
66
67
|
/** 64 bytes of hex. Whatever the guest asked the PSP to bind in — our nonce. */
|
package/dist/snp.js
CHANGED
|
@@ -69,19 +69,24 @@ function parseSnpReport(bytes) {
|
|
|
69
69
|
}
|
|
70
70
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
71
71
|
const version = view.getUint32(OFFSET.version, true);
|
|
72
|
-
if (version !== 2 && version !== 3) {
|
|
72
|
+
if (version !== 2 && version !== 3 && version !== 5) {
|
|
73
73
|
throw new SnpError("BAD_VERSION", `Report version ${version} is not one this parser understands.`);
|
|
74
74
|
}
|
|
75
75
|
const vmpl = view.getUint32(OFFSET.vmpl, true);
|
|
76
76
|
if (vmpl > 3) {
|
|
77
77
|
throw new SnpError("BAD_VMPL", `VMPL ${vmpl} is outside the defined range.`);
|
|
78
78
|
}
|
|
79
|
+
const signatureAlgorithm = view.getUint32(OFFSET.signatureAlgo, true);
|
|
80
|
+
if (signatureAlgorithm !== 1) {
|
|
81
|
+
throw new SnpError("BAD_SIGNATURE_ALGORITHM", `Signature algorithm ${signatureAlgorithm} is not ECDSA P-384 with SHA-384.`);
|
|
82
|
+
}
|
|
79
83
|
const slice = (offset, length) => hex(bytes.subarray(offset, offset + length));
|
|
80
84
|
return {
|
|
81
85
|
version,
|
|
82
86
|
guestSvn: view.getUint32(OFFSET.guestSvn, true),
|
|
83
87
|
policy: readPolicy(view, OFFSET.policy),
|
|
84
88
|
vmpl,
|
|
89
|
+
signatureAlgorithm,
|
|
85
90
|
measurement: slice(OFFSET.measurement, 48),
|
|
86
91
|
reportData: slice(OFFSET.reportData, 64),
|
|
87
92
|
hostData: slice(OFFSET.hostData, 32),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
|
|
3
3
|
"name": "@forgezero/runtime",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -59,10 +59,6 @@
|
|
|
59
59
|
"types": "./dist/otpauth.d.ts",
|
|
60
60
|
"default": "./dist/otpauth.js"
|
|
61
61
|
},
|
|
62
|
-
"./serial": {
|
|
63
|
-
"types": "./dist/serial.d.ts",
|
|
64
|
-
"default": "./dist/serial.js"
|
|
65
|
-
},
|
|
66
62
|
"./pipeline": {
|
|
67
63
|
"types": "./dist/pipeline.d.ts",
|
|
68
64
|
"default": "./dist/pipeline.js"
|
|
@@ -182,7 +178,8 @@
|
|
|
182
178
|
},
|
|
183
179
|
"scripts": {
|
|
184
180
|
"check": "tsc --noEmit",
|
|
185
|
-
"
|
|
181
|
+
"prebuild": "rm -rf dist",
|
|
182
|
+
"build": "bun build src/jobs.ts src/queue.ts src/outbox.ts src/audit.ts src/backup.ts src/notify.ts src/notify-templates.ts src/calendar.ts src/compliance.ts src/pipeline.ts src/totp.ts src/otpauth.ts src/identity.ts src/slip10.ts src/openssh.ts src/ssh-cert.ts src/importers.ts src/snp.ts src/passkey.ts src/custody-crypto.ts src/custody-share.ts src/phrase.ts src/ssh-agent.ts src/schema.ts src/schema-typebox.ts src/finance/discounts.ts src/finance/money.ts src/finance/storage.ts src/finance/custody.ts src/finance/tax.ts src/finance/derive.ts src/finance/venues.ts src/finance/ledger.ts src/finance/rates.ts src/finance/transfers.ts src/finance/chain.ts src/finance/chain-addresses.ts src/finance/chain-deposits.ts src/finance/chain-withdrawals.ts src/finance/chain-reconcile.ts src/finance/market.ts src/finance/commission.ts --root src --outdir dist --target browser --format esm --packages external && tsc --emitDeclarationOnly --declaration --noEmit false --outDir dist",
|
|
186
183
|
"prepublishOnly": "bun run check && bun run build"
|
|
187
184
|
},
|
|
188
185
|
"dependencies": {
|
|
@@ -241,13 +238,13 @@
|
|
|
241
238
|
"working-days"
|
|
242
239
|
],
|
|
243
240
|
"license": "MIT",
|
|
244
|
-
"homepage": "https://forgezero.net/docs/runtime",
|
|
241
|
+
"homepage": "https://www.forgezero.net/docs/runtime",
|
|
245
242
|
"repository": {
|
|
246
243
|
"type": "git",
|
|
247
|
-
"url": "git+https://github.com/
|
|
244
|
+
"url": "git+https://github.com/forgezero-net/packages.git",
|
|
248
245
|
"directory": "packages/runtime"
|
|
249
246
|
},
|
|
250
|
-
"bugs": "https://github.com/
|
|
247
|
+
"bugs": "https://github.com/forgezero-net/packages/issues",
|
|
251
248
|
"sideEffects": false,
|
|
252
249
|
"files": [
|
|
253
250
|
"dist",
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { VenueError, type VenueAdapter, type MarketType, type OrderStatus } from './venues';
|
|
2
|
-
export interface BinanceCredentials {
|
|
3
|
-
apiKey: string;
|
|
4
|
-
apiSecret: string;
|
|
5
|
-
}
|
|
6
|
-
export interface BinanceOptions {
|
|
7
|
-
credentials: BinanceCredentials;
|
|
8
|
-
/** Override for testnet, or for a test. */
|
|
9
|
-
hosts?: Partial<Record<MarketType, string>>;
|
|
10
|
-
fetch?: typeof globalThis.fetch;
|
|
11
|
-
/**
|
|
12
|
-
* How far a request may be delayed before Binance refuses it.
|
|
13
|
-
*
|
|
14
|
-
* 5s rather than the 60s maximum. A signed order that arrives a minute late
|
|
15
|
-
* is an order placed into a market that has moved, and accepting it is worse
|
|
16
|
-
* than being told to retry.
|
|
17
|
-
*/
|
|
18
|
-
recvWindowMs?: number;
|
|
19
|
-
now?: () => number;
|
|
20
|
-
}
|
|
21
|
-
/** Binance spells `BTC/USDT` as `BTCUSDT`. Denormalised here and nowhere else. */
|
|
22
|
-
export declare const binanceSymbol: (symbol: string) => string;
|
|
23
|
-
/** Binance statuses → ours. An unknown one is `rejected`, never silently `accepted`. */
|
|
24
|
-
export declare function toOrderStatus(status: string): OrderStatus;
|
|
25
|
-
export declare function createBinanceAdapter(options: BinanceOptions): VenueAdapter;
|
|
26
|
-
/** Turn a Binance error into something that names the actual cause. */
|
|
27
|
-
export declare function readBinanceError(error: unknown): VenueError | undefined;
|