okengine 0.11.0 → 0.11.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/package.json +1 -1
- package/site/content/docs/elements/ai.mdx +22 -1
- package/site/content/docs/elements/store.mdx +3 -1
- package/site/content/docs/elements/vault.mdx +19 -11
- package/site/content/docs/get-started/installation.mdx +7 -1
- package/site/content/docs/recipes/llama-cpp.mdx +10 -9
- package/site/content/docs/reference/cli.md +6 -2
- package/site/content/docs/reference/environment-variables.mdx +9 -9
- package/src/cli/ai-setup/ai-setup.test.ts +3 -1
- package/src/cli/ai-setup/apply.ts +61 -1
- package/src/cli/ask-seed.test.ts +4 -3
- package/src/cli/ask-seed.ts +5 -6
- package/src/cli/client-add.test.ts +2 -1
- package/src/cli/dev.test.ts +116 -0
- package/src/cli/dev.ts +107 -18
- package/src/cli/project-state.test.ts +50 -0
- package/src/cli/project-state.ts +123 -0
- package/src/cli/vault-cmd.test.ts +47 -18
- package/src/cli/vault-cmd.ts +2 -1
- package/src/compiler/extract.ts +12 -1
- package/src/console/server/console.test.ts +3 -1
- package/src/console/server/operator-db.test.ts +48 -17
- package/src/console/server/operator-db.ts +5 -1
- package/src/docker/derive.ts +24 -3
- package/src/docker/docker.test.ts +4 -2
- package/src/docker/index.ts +1 -0
- package/src/docker/recipes/index.ts +1 -0
- package/src/docker/recipes/llama-cpp.ts +20 -4
- package/src/drivers/ai-openai-compatible.ts +15 -3
- package/src/drivers/vault-builtin.test.ts +50 -42
- package/src/elements/ai/declare.ts +73 -3
- package/src/elements/ai/errors.test.ts +35 -0
- package/src/elements/ai/errors.ts +139 -0
- package/src/elements/ai/eval.ts +26 -1
- package/src/elements/ai/runtime.ts +140 -80
- package/src/elements/ai/tools.test.ts +1 -1
- package/src/elements/ai.test.ts +99 -2
- package/src/elements/ai.ts +11 -1
- package/src/elements/gate/config.ts +13 -3
- package/src/elements/gate/declare.ts +1 -1
- package/src/elements/gate/strategies.ts +2 -12
- package/src/elements/index.ts +2 -0
- package/src/elements/store/index-boot.test.ts +23 -6
- package/src/elements/store/resource.test.ts +38 -19
- package/src/elements/store/sql-session.test.ts +55 -58
- package/src/elements/vault/builtin-adapter.test.ts +115 -58
- package/src/elements/vault/builtin-adapter.ts +241 -47
- package/src/elements/vault/chaos-child.ts +424 -0
- package/src/elements/vault/chaos.test.ts +651 -0
- package/src/elements/vault/resilience.ts +6 -1
- package/src/elements/vault/security-checklist.test.ts +10 -8
- package/src/elements/vault/storage.ts +130 -27
- package/src/elements/vault/test-helpers.ts +368 -0
- package/src/elements/vault.ts +6 -0
- package/src/index.ts +2 -0
- package/src/kernel/app-auth.ts +98 -0
- package/src/kernel/app.ts +120 -78
- package/src/kernel/auto-registry.test.ts +52 -1
- package/src/kernel/boot.test.ts +4 -18
- package/src/kernel/element-registries.ts +19 -4
- package/src/kernel/errors.ts +3 -3
- package/src/kernel/fx.test.ts +25 -0
- package/src/kernel/fx.ts +4 -1
- package/src/manifest/types.ts +4 -0
- package/src/release/build-lib.ts +3 -0
- package/src/shared/lazy-src.ts +79 -0
- package/src/test/create-test-app.ts +16 -11
- package/src/test/reset-element-registries.ts +17 -9
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault-builtin chaos child — real OS process for adversarial proofs.
|
|
3
|
+
*
|
|
4
|
+
* Args:
|
|
5
|
+
* rotate-mid-rewrap <sqlUrl> <markerPath> <statePath> <secretCount> <batchSize> <killAfter>
|
|
6
|
+
* resume-rewrap <sqlUrl> <statePath> <resultPath>
|
|
7
|
+
* backup-partial <sqlUrl> <statePath> <bundlePath> <fraction> <markerPath>
|
|
8
|
+
* audit-tamper <sqlUrl> <resultPath>
|
|
9
|
+
* rotate-race <sqlUrl> <masterKey> <instanceId> <resultPath> <secretCount>
|
|
10
|
+
* read-loop <sqlUrl> <masterKey> <path> <readsPath> <stopPath> <durationMs>
|
|
11
|
+
* set-race <sqlUrl> <masterKey> <instanceId> <pathPrefix> <count> <donePath>
|
|
12
|
+
*
|
|
13
|
+
* Modes:
|
|
14
|
+
* rotate-mid-rewrap — seed secrets, start rotateMaster, hang after N DEK
|
|
15
|
+
* UPDATEs (parent SIGKILLs mid-rewrap)
|
|
16
|
+
* resume-rewrap — cold-resume continueRotateMaster until remaining=0
|
|
17
|
+
* backup-partial — exportBackup then write only a fraction of the blob
|
|
18
|
+
* audit-tamper — raw SQL UPDATE on oke_vault_audit (bypass adapter)
|
|
19
|
+
* rotate-race — call rotateMaster once; write ok/error JSON
|
|
20
|
+
* read-loop — get(path) until stopPath appears or duration elapses
|
|
21
|
+
* set-race — write `count` distinct paths; append to donePath
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { connectPglite } from "../../drivers/pglite.ts";
|
|
25
|
+
import { connectPostgres } from "../../drivers/postgres.ts";
|
|
26
|
+
import type { SqlConnection } from "../../drivers/types.ts";
|
|
27
|
+
import {
|
|
28
|
+
createBuiltinVaultAdapter,
|
|
29
|
+
sqlConnectionAsExec,
|
|
30
|
+
type BuiltinVaultAdapter,
|
|
31
|
+
} from "./builtin-adapter.ts";
|
|
32
|
+
import { generateMasterKey, masterKeyToBase64 } from "./crypto.ts";
|
|
33
|
+
import type { SqlExec } from "./storage.ts";
|
|
34
|
+
|
|
35
|
+
const mode = process.argv[2];
|
|
36
|
+
|
|
37
|
+
if (!mode) {
|
|
38
|
+
console.error(
|
|
39
|
+
"usage: chaos-child <rotate-mid-rewrap|resume-rewrap|backup-partial|audit-tamper|rotate-race|read-loop|set-race> …",
|
|
40
|
+
);
|
|
41
|
+
process.exit(2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Persisted across SIGKILL so the survivor can resume. */
|
|
45
|
+
interface RotateState {
|
|
46
|
+
readonly oldMasterKey: string;
|
|
47
|
+
readonly newMasterKey: string;
|
|
48
|
+
readonly secretCount: number;
|
|
49
|
+
readonly killAfter: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Open the SQL backend the parent handed us (Postgres URL or PGlite datadir).
|
|
54
|
+
*
|
|
55
|
+
* @param sqlUrl - `postgres://…` or on-disk PGlite path
|
|
56
|
+
*/
|
|
57
|
+
async function openSql(sqlUrl: string): Promise<SqlConnection> {
|
|
58
|
+
if (/^postgres(ql)?:\/\//.test(sqlUrl)) {
|
|
59
|
+
return connectPostgres({ url: sqlUrl });
|
|
60
|
+
}
|
|
61
|
+
return connectPglite({ url: sqlUrl });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Wrap `db.execute` so the Nth `oke_vault_keys` rewrap UPDATE hangs.
|
|
66
|
+
*
|
|
67
|
+
* @param db - Underlying exec
|
|
68
|
+
* @param killAfter - Hang after this many DEK rewrap UPDATEs
|
|
69
|
+
* @param onReady - Called once before the hang (write marker / state)
|
|
70
|
+
*/
|
|
71
|
+
function hangAfterRewrapUpdates(
|
|
72
|
+
db: SqlExec,
|
|
73
|
+
killAfter: number,
|
|
74
|
+
onReady: () => Promise<void>,
|
|
75
|
+
): SqlExec {
|
|
76
|
+
let updates = 0;
|
|
77
|
+
let armed = false;
|
|
78
|
+
const wrapped: SqlExec = {
|
|
79
|
+
query: <T>(sql: string, params?: unknown[]) => db.query<T>(sql, params),
|
|
80
|
+
execute: async (sql: string, params?: unknown[]) => {
|
|
81
|
+
await db.execute(sql, params);
|
|
82
|
+
if (!/UPDATE\s+oke_vault_keys/i.test(sql) || !/kek_version/i.test(sql)) return;
|
|
83
|
+
updates += 1;
|
|
84
|
+
if (updates === killAfter && !armed) {
|
|
85
|
+
armed = true;
|
|
86
|
+
await onReady();
|
|
87
|
+
await Bun.sleep(120_000);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
if (db.begin) {
|
|
92
|
+
wrapped.begin = <T>(fn: (tx: SqlExec) => Promise<T>) => db.begin!(fn);
|
|
93
|
+
}
|
|
94
|
+
return wrapped;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Seed `secretCount` secrets under a fresh vault; return adapter + keys.
|
|
99
|
+
*
|
|
100
|
+
* @param db - SQL surface
|
|
101
|
+
* @param secretCount - How many DEKs to create
|
|
102
|
+
* @param batchSize - Rewrap batch size
|
|
103
|
+
*/
|
|
104
|
+
async function seedVault(
|
|
105
|
+
db: SqlExec,
|
|
106
|
+
secretCount: number,
|
|
107
|
+
batchSize: number,
|
|
108
|
+
rotateLeaseMs = 150,
|
|
109
|
+
): Promise<{ adapter: BuiltinVaultAdapter; oldMasterKey: string }> {
|
|
110
|
+
const adapter = createBuiltinVaultAdapter({
|
|
111
|
+
db,
|
|
112
|
+
kekRewrapBatchSize: batchSize,
|
|
113
|
+
rotateLeaseMs,
|
|
114
|
+
});
|
|
115
|
+
const init = await adapter.initialize();
|
|
116
|
+
await adapter.unseal(init.masterKey);
|
|
117
|
+
for (let i = 0; i < secretCount; i += 1) {
|
|
118
|
+
await adapter.set(`chaos/secret-${i}`, `value-${i}`);
|
|
119
|
+
}
|
|
120
|
+
return { adapter, oldMasterKey: init.masterKey };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (mode === "rotate-mid-rewrap") {
|
|
124
|
+
const sqlUrl = process.argv[3];
|
|
125
|
+
const markerPath = process.argv[4];
|
|
126
|
+
const statePath = process.argv[5];
|
|
127
|
+
const secretCount = Number(process.argv[6] ?? "6");
|
|
128
|
+
const batchSize = Number(process.argv[7] ?? "10");
|
|
129
|
+
const killAfter = Number(process.argv[8] ?? "2");
|
|
130
|
+
if (!sqlUrl || !markerPath || !statePath) {
|
|
131
|
+
console.error(
|
|
132
|
+
"usage: rotate-mid-rewrap <sqlUrl> <markerPath> <statePath> <secretCount> <batchSize> <killAfter>",
|
|
133
|
+
);
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const conn = await openSql(sqlUrl);
|
|
138
|
+
const base = sqlConnectionAsExec(conn);
|
|
139
|
+
const next = generateMasterKey();
|
|
140
|
+
const nextB64 = masterKeyToBase64(next);
|
|
141
|
+
let oldMasterKey = "";
|
|
142
|
+
|
|
143
|
+
const db = hangAfterRewrapUpdates(base, killAfter, async () => {
|
|
144
|
+
const state: RotateState = {
|
|
145
|
+
oldMasterKey,
|
|
146
|
+
newMasterKey: nextB64,
|
|
147
|
+
secretCount,
|
|
148
|
+
killAfter,
|
|
149
|
+
};
|
|
150
|
+
await Bun.write(statePath, JSON.stringify(state));
|
|
151
|
+
await Bun.write(markerPath, JSON.stringify({ readyAt: Date.now(), killAfter, secretCount }));
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const seeded = await seedVault(db, secretCount, batchSize);
|
|
155
|
+
oldMasterKey = seeded.oldMasterKey;
|
|
156
|
+
// rotateMaster runs until the interceptor hangs mid-batch.
|
|
157
|
+
await seeded.adapter.rotateMaster(next);
|
|
158
|
+
console.error("rotate-mid-rewrap: rotateMaster returned (should have hung)");
|
|
159
|
+
process.exit(3);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (mode === "resume-rewrap") {
|
|
163
|
+
const sqlUrl = process.argv[3];
|
|
164
|
+
const statePath = process.argv[4];
|
|
165
|
+
const resultPath = process.argv[5];
|
|
166
|
+
if (!sqlUrl || !statePath || !resultPath) {
|
|
167
|
+
console.error("usage: resume-rewrap <sqlUrl> <statePath> <resultPath>");
|
|
168
|
+
process.exit(2);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const state = (await Bun.file(statePath).json()) as RotateState;
|
|
172
|
+
const conn = await openSql(sqlUrl);
|
|
173
|
+
try {
|
|
174
|
+
const adapter = createBuiltinVaultAdapter({
|
|
175
|
+
db: sqlConnectionAsExec(conn),
|
|
176
|
+
kekRewrapBatchSize: 2,
|
|
177
|
+
rotateLeaseMs: 150,
|
|
178
|
+
});
|
|
179
|
+
await adapter.unseal(state.oldMasterKey);
|
|
180
|
+
const statusBefore = await adapter.status();
|
|
181
|
+
|
|
182
|
+
// Lazy reclaim: wait out the dead holder's lease (Clock/Signal physics).
|
|
183
|
+
let progress: { remaining: number; kekVersion: number } | undefined;
|
|
184
|
+
const deadline = Date.now() + 5_000;
|
|
185
|
+
while (Date.now() < deadline) {
|
|
186
|
+
try {
|
|
187
|
+
progress = await adapter.continueRotateMaster(state.newMasterKey);
|
|
188
|
+
break;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
191
|
+
if (!message.includes("lease held")) throw error;
|
|
192
|
+
await Bun.sleep(40);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!progress) {
|
|
196
|
+
console.error("resume-rewrap: timed out waiting for rotate lease reclaim");
|
|
197
|
+
process.exit(3);
|
|
198
|
+
}
|
|
199
|
+
while (progress.remaining > 0) {
|
|
200
|
+
progress = await adapter.continueRotateMaster(state.newMasterKey);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const values: string[] = [];
|
|
204
|
+
const kekVersions: number[] = [];
|
|
205
|
+
for (let i = 0; i < state.secretCount; i += 1) {
|
|
206
|
+
const secret = await adapter.get(`chaos/secret-${i}`);
|
|
207
|
+
values.push(secret?.value ?? "<missing>");
|
|
208
|
+
kekVersions.push(secret?.kekVersion ?? -1);
|
|
209
|
+
}
|
|
210
|
+
const statusAfter = await adapter.status();
|
|
211
|
+
await Bun.write(
|
|
212
|
+
resultPath,
|
|
213
|
+
JSON.stringify({
|
|
214
|
+
statusBefore,
|
|
215
|
+
statusAfter,
|
|
216
|
+
values,
|
|
217
|
+
kekVersions,
|
|
218
|
+
remaining: progress.remaining,
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
process.exit(0);
|
|
222
|
+
} finally {
|
|
223
|
+
await conn.close();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (mode === "backup-partial") {
|
|
228
|
+
const sqlUrl = process.argv[3];
|
|
229
|
+
const statePath = process.argv[4];
|
|
230
|
+
const bundlePath = process.argv[5];
|
|
231
|
+
const fraction = Number(process.argv[6] ?? "0.4");
|
|
232
|
+
const markerPath = process.argv[7];
|
|
233
|
+
if (!sqlUrl || !statePath || !bundlePath || !markerPath) {
|
|
234
|
+
console.error(
|
|
235
|
+
"usage: backup-partial <sqlUrl> <statePath> <bundlePath> <fraction> <markerPath>",
|
|
236
|
+
);
|
|
237
|
+
process.exit(2);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const conn = await openSql(sqlUrl);
|
|
241
|
+
try {
|
|
242
|
+
const { adapter, oldMasterKey } = await seedVault(sqlConnectionAsExec(conn), 4, 100);
|
|
243
|
+
await Bun.write(statePath, JSON.stringify({ oldMasterKey }));
|
|
244
|
+
const blob = await adapter.exportBackup();
|
|
245
|
+
const keep = Math.max(1, Math.floor(blob.byteLength * fraction));
|
|
246
|
+
// Simulate crash mid-Bun.write: durable partial file with magic header.
|
|
247
|
+
await Bun.write(bundlePath, blob.subarray(0, keep));
|
|
248
|
+
await Bun.write(
|
|
249
|
+
markerPath,
|
|
250
|
+
JSON.stringify({
|
|
251
|
+
fullBytes: blob.byteLength,
|
|
252
|
+
writtenBytes: keep,
|
|
253
|
+
startsWithMagic: new TextDecoder()
|
|
254
|
+
.decode(blob.subarray(0, 20))
|
|
255
|
+
.startsWith("oke-vault-backup"),
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
// Hang so the parent can SIGKILL after the partial file is on disk.
|
|
259
|
+
await Bun.sleep(120_000);
|
|
260
|
+
} finally {
|
|
261
|
+
await conn.close();
|
|
262
|
+
}
|
|
263
|
+
process.exit(3);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (mode === "audit-tamper") {
|
|
267
|
+
const sqlUrl = process.argv[3];
|
|
268
|
+
const resultPath = process.argv[4];
|
|
269
|
+
if (!sqlUrl || !resultPath) {
|
|
270
|
+
console.error("usage: audit-tamper <sqlUrl> <resultPath>");
|
|
271
|
+
process.exit(2);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const conn = await openSql(sqlUrl);
|
|
275
|
+
try {
|
|
276
|
+
// Attacker path: raw driver SQL, no BuiltinVaultAdapter involved.
|
|
277
|
+
const rows = await conn.query(
|
|
278
|
+
`SELECT id FROM oke_vault_audit ORDER BY seq ASC LIMIT 5 OFFSET 1`,
|
|
279
|
+
);
|
|
280
|
+
const victim = (rows[0] as { id?: string } | undefined)?.id;
|
|
281
|
+
if (!victim) {
|
|
282
|
+
await Bun.write(resultPath, JSON.stringify({ ok: false, error: "no-victim-row" }));
|
|
283
|
+
process.exit(3);
|
|
284
|
+
}
|
|
285
|
+
await conn.exec(`UPDATE oke_vault_audit SET action = 'delete' WHERE id = $1`, [victim]);
|
|
286
|
+
await Bun.write(resultPath, JSON.stringify({ ok: true, victim }));
|
|
287
|
+
process.exit(0);
|
|
288
|
+
} finally {
|
|
289
|
+
await conn.close();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (mode === "rotate-race") {
|
|
294
|
+
const sqlUrl = process.argv[3];
|
|
295
|
+
const masterKey = process.argv[4];
|
|
296
|
+
const instanceId = process.argv[5];
|
|
297
|
+
const resultPath = process.argv[6];
|
|
298
|
+
const secretCount = Number(process.argv[7] ?? "8");
|
|
299
|
+
if (!sqlUrl || !masterKey || !instanceId || !resultPath) {
|
|
300
|
+
console.error(
|
|
301
|
+
"usage: rotate-race <sqlUrl> <masterKey> <instanceId> <resultPath> [secretCount]",
|
|
302
|
+
);
|
|
303
|
+
process.exit(2);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const conn = await openSql(sqlUrl);
|
|
307
|
+
try {
|
|
308
|
+
const adapter = createBuiltinVaultAdapter({
|
|
309
|
+
db: sqlConnectionAsExec(conn),
|
|
310
|
+
kekRewrapBatchSize: 2,
|
|
311
|
+
});
|
|
312
|
+
await adapter.unseal(masterKey);
|
|
313
|
+
// Ensure enough DEKs exist (idempotent if the seeder already wrote them).
|
|
314
|
+
for (let i = 0; i < secretCount; i += 1) {
|
|
315
|
+
const existing = await adapter.get(`chaos/race-${i}`);
|
|
316
|
+
if (!existing) await adapter.set(`chaos/race-${i}`, `race-${i}`);
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const progress = await adapter.rotateMaster();
|
|
320
|
+
const line = `${JSON.stringify({
|
|
321
|
+
instanceId,
|
|
322
|
+
ok: true,
|
|
323
|
+
remaining: progress.remaining,
|
|
324
|
+
kekVersion: progress.kekVersion,
|
|
325
|
+
masterKey: progress.masterKey,
|
|
326
|
+
at: Date.now(),
|
|
327
|
+
})}\n`;
|
|
328
|
+
const prev = (await Bun.file(resultPath).exists()) ? await Bun.file(resultPath).text() : "";
|
|
329
|
+
await Bun.write(resultPath, prev + line);
|
|
330
|
+
process.exit(0);
|
|
331
|
+
} catch (error) {
|
|
332
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
333
|
+
const line = `${JSON.stringify({
|
|
334
|
+
instanceId,
|
|
335
|
+
ok: false,
|
|
336
|
+
error: message,
|
|
337
|
+
at: Date.now(),
|
|
338
|
+
})}\n`;
|
|
339
|
+
const prev = (await Bun.file(resultPath).exists()) ? await Bun.file(resultPath).text() : "";
|
|
340
|
+
await Bun.write(resultPath, prev + line);
|
|
341
|
+
process.exit(0);
|
|
342
|
+
}
|
|
343
|
+
} finally {
|
|
344
|
+
await conn.close();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (mode === "read-loop") {
|
|
349
|
+
const sqlUrl = process.argv[3];
|
|
350
|
+
const masterKey = process.argv[4];
|
|
351
|
+
const path = process.argv[5];
|
|
352
|
+
const readsPath = process.argv[6];
|
|
353
|
+
const stopPath = process.argv[7];
|
|
354
|
+
const durationMs = Number(process.argv[8] ?? "8000");
|
|
355
|
+
if (!sqlUrl || !masterKey || !path || !readsPath || !stopPath) {
|
|
356
|
+
console.error(
|
|
357
|
+
"usage: read-loop <sqlUrl> <masterKey> <path> <readsPath> <stopPath> [durationMs]",
|
|
358
|
+
);
|
|
359
|
+
process.exit(2);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const conn = await openSql(sqlUrl);
|
|
363
|
+
try {
|
|
364
|
+
const adapter = createBuiltinVaultAdapter({ db: sqlConnectionAsExec(conn) });
|
|
365
|
+
await adapter.unseal(masterKey);
|
|
366
|
+
const deadline = Date.now() + durationMs;
|
|
367
|
+
while (Date.now() < deadline) {
|
|
368
|
+
if (await Bun.file(stopPath).exists()) break;
|
|
369
|
+
try {
|
|
370
|
+
const secret = await adapter.get(path);
|
|
371
|
+
const line = `${JSON.stringify({
|
|
372
|
+
ok: true,
|
|
373
|
+
value: secret?.value ?? null,
|
|
374
|
+
kekVersion: secret?.kekVersion ?? null,
|
|
375
|
+
at: Date.now(),
|
|
376
|
+
})}\n`;
|
|
377
|
+
const prev = (await Bun.file(readsPath).exists()) ? await Bun.file(readsPath).text() : "";
|
|
378
|
+
await Bun.write(readsPath, prev + line);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
381
|
+
const line = `${JSON.stringify({ ok: false, error: message, at: Date.now() })}\n`;
|
|
382
|
+
const prev = (await Bun.file(readsPath).exists()) ? await Bun.file(readsPath).text() : "";
|
|
383
|
+
await Bun.write(readsPath, prev + line);
|
|
384
|
+
}
|
|
385
|
+
await Bun.sleep(5);
|
|
386
|
+
}
|
|
387
|
+
process.exit(0);
|
|
388
|
+
} finally {
|
|
389
|
+
await conn.close();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (mode === "set-race") {
|
|
394
|
+
const sqlUrl = process.argv[3];
|
|
395
|
+
const masterKey = process.argv[4];
|
|
396
|
+
const instanceId = process.argv[5];
|
|
397
|
+
const pathPrefix = process.argv[6];
|
|
398
|
+
const count = Number(process.argv[7] ?? "10");
|
|
399
|
+
const donePath = process.argv[8];
|
|
400
|
+
if (!sqlUrl || !masterKey || !instanceId || !pathPrefix || !donePath) {
|
|
401
|
+
console.error(
|
|
402
|
+
"usage: set-race <sqlUrl> <masterKey> <instanceId> <pathPrefix> <count> <donePath>",
|
|
403
|
+
);
|
|
404
|
+
process.exit(2);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const conn = await openSql(sqlUrl);
|
|
408
|
+
try {
|
|
409
|
+
const adapter = createBuiltinVaultAdapter({ db: sqlConnectionAsExec(conn) });
|
|
410
|
+
await adapter.unseal(masterKey);
|
|
411
|
+
for (let i = 0; i < count; i += 1) {
|
|
412
|
+
await adapter.set(`${pathPrefix}/${instanceId}-${i}`, `${instanceId}-value-${i}`);
|
|
413
|
+
}
|
|
414
|
+
const line = `${JSON.stringify({ instanceId, count, at: Date.now() })}\n`;
|
|
415
|
+
const prev = (await Bun.file(donePath).exists()) ? await Bun.file(donePath).text() : "";
|
|
416
|
+
await Bun.write(donePath, prev + line);
|
|
417
|
+
process.exit(0);
|
|
418
|
+
} finally {
|
|
419
|
+
await conn.close();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
console.error(`unknown mode: ${mode}`);
|
|
424
|
+
process.exit(2);
|