negotium 0.2.0 → 0.2.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.
@@ -8,7 +8,7 @@ import {
8
8
  getTopic,
9
9
  getTopicByNameForUser,
10
10
  isAgentKind,
11
- } from "@negotium/core";
11
+ } from "../src/index.ts";
12
12
  import { z } from "zod";
13
13
  import {
14
14
  CRON_CONTEXT_RETAIN_TURNS,
@@ -15,7 +15,7 @@ import { homedir } from "node:os";
15
15
  import { basename, dirname, join, resolve } from "node:path";
16
16
  import { sanitizeTopicName } from "#security/sanitize";
17
17
  import { Database } from "#storage/sqlite";
18
- import { decryptVaultValue, encryptVaultValue } from "#storage/vault-crypto";
18
+ import { decryptVaultValueWithKey, encryptVaultValueWithKey } from "#storage/vault-crypto-core";
19
19
 
20
20
  export const CANONICAL_LOCAL_USER_ID = "local";
21
21
  export const SINGLE_USER_MIGRATION_ID = "negotium-0.2.0-single-user";
@@ -40,7 +40,9 @@ interface MoveOperation {
40
40
  source: string;
41
41
  staged: string;
42
42
  destination?: string;
43
- state: "pending" | "staged" | "placed";
43
+ destinationStaged?: string;
44
+ collision?: "keep-destination" | "replace-destination" | "merge-jsonl" | "merge-tasks";
45
+ state: "pending" | "staged" | "source-staged" | "destination-staged" | "placed";
44
46
  }
45
47
 
46
48
  interface MigrationJournal {
@@ -70,7 +72,9 @@ function assertNoActiveNode(stateDir: string): void {
70
72
  const infoPath = join(stateDir, runtimeName, "node-daemon.json");
71
73
  if (!existsSync(infoPath)) continue;
72
74
  try {
73
- const value = JSON.parse(readFileSync(infoPath, "utf8")) as { pid?: number };
75
+ const value = JSON.parse(readFileSync(infoPath, "utf8")) as {
76
+ pid?: number;
77
+ };
74
78
  if (typeof value.pid === "number" && processIsAlive(value.pid)) {
75
79
  throw new Error(`Refusing migration while Negotium pid ${value.pid} is active.`);
76
80
  }
@@ -121,17 +125,60 @@ function addDirectoryContents(
121
125
  }
122
126
  }
123
127
 
124
- function assertNoCollisions(operations: MoveOperation[]): void {
128
+ function pathWithin(path: string, root: string): boolean {
129
+ return path === root || path.startsWith(`${root}/`);
130
+ }
131
+
132
+ function collisionPolicy(
133
+ operation: MoveOperation,
134
+ stateDir: string,
135
+ ): NonNullable<MoveOperation["collision"]> | undefined {
136
+ const destination = operation.destination;
137
+ if (!destination || !existsSync(destination)) return undefined;
138
+ if (pathWithin(destination, join(stateDir, "runtime"))) return "keep-destination";
139
+ if (pathWithin(destination, join(stateDir, "binaries"))) return "keep-destination";
140
+ if (pathWithin(destination, join(stateDir, "secrets"))) return "replace-destination";
141
+ if (destination === join(stateDir, "data", "vault", "vault.db")) {
142
+ return "replace-destination";
143
+ }
144
+ if (pathWithin(destination, join(stateDir, "browser", "profiles"))) {
145
+ return "replace-destination";
146
+ }
147
+ if (
148
+ pathWithin(destination, join(stateDir, "data", "conversations")) &&
149
+ destination.endsWith(".jsonl")
150
+ ) {
151
+ return "merge-jsonl";
152
+ }
153
+ if (pathWithin(destination, join(stateDir, "data", "tasks")) && destination.endsWith(".json")) {
154
+ return "merge-tasks";
155
+ }
156
+ if (
157
+ statSync(operation.source).isFile() &&
158
+ statSync(destination).isFile() &&
159
+ readFileSync(operation.source).equals(readFileSync(destination))
160
+ ) {
161
+ return "keep-destination";
162
+ }
163
+ throw new Error(`Migration collision at ${destination}.`);
164
+ }
165
+
166
+ function configureCollisions(
167
+ operations: MoveOperation[],
168
+ stateDir: string,
169
+ stagingRoot: string,
170
+ ): void {
125
171
  const destinations = new Set<string>();
126
172
  for (const operation of operations) {
127
173
  if (!operation.destination) continue;
128
- if (existsSync(operation.destination)) {
129
- throw new Error(`Migration collision at ${operation.destination}.`);
130
- }
131
174
  if (destinations.has(operation.destination)) {
132
175
  throw new Error(`Multiple legacy paths target ${operation.destination}.`);
133
176
  }
134
177
  destinations.add(operation.destination);
178
+ operation.collision = collisionPolicy(operation, stateDir);
179
+ if (operation.collision && operation.collision !== "keep-destination") {
180
+ operation.destinationStaged = join(stagingRoot, randomUUID());
181
+ }
135
182
  }
136
183
  }
137
184
 
@@ -141,16 +188,115 @@ function writeJournal(path: string, journal: MigrationJournal): void {
141
188
 
142
189
  function rollbackFilesystem(journal: MigrationJournal, journalPath: string): void {
143
190
  for (const operation of [...journal.operations].reverse()) {
144
- const current = operation.state === "placed" ? operation.destination : operation.staged;
145
- if (!current || !existsSync(current) || existsSync(operation.source)) continue;
146
- mkdirSync(dirname(operation.source), { recursive: true });
147
- renameSync(current, operation.source);
191
+ if (operation.state === "placed") {
192
+ if (operation.collision === "keep-destination" || !operation.destination) {
193
+ if (existsSync(operation.staged) && !existsSync(operation.source)) {
194
+ mkdirSync(dirname(operation.source), { recursive: true });
195
+ renameSync(operation.staged, operation.source);
196
+ }
197
+ } else if (operation.destination && existsSync(operation.destination)) {
198
+ if (operation.collision === "merge-jsonl" || operation.collision === "merge-tasks") {
199
+ rmSync(operation.destination, { recursive: true, force: true });
200
+ if (existsSync(operation.staged) && !existsSync(operation.source)) {
201
+ mkdirSync(dirname(operation.source), { recursive: true });
202
+ renameSync(operation.staged, operation.source);
203
+ }
204
+ } else if (!existsSync(operation.source)) {
205
+ mkdirSync(dirname(operation.source), { recursive: true });
206
+ renameSync(operation.destination, operation.source);
207
+ }
208
+ }
209
+ } else if (existsSync(operation.staged) && !existsSync(operation.source)) {
210
+ mkdirSync(dirname(operation.source), { recursive: true });
211
+ renameSync(operation.staged, operation.source);
212
+ }
213
+ if (
214
+ operation.destination &&
215
+ operation.destinationStaged &&
216
+ existsSync(operation.destinationStaged) &&
217
+ !existsSync(operation.destination)
218
+ ) {
219
+ mkdirSync(dirname(operation.destination), { recursive: true });
220
+ renameSync(operation.destinationStaged, operation.destination);
221
+ }
148
222
  operation.state = "pending";
149
223
  }
150
224
  journal.phase = "filesystem";
151
225
  writeJournal(journalPath, journal);
152
226
  }
153
227
 
228
+ function mergedJsonl(source: string, destination: string): string {
229
+ const lines = new Set<string>();
230
+ for (const path of [source, destination]) {
231
+ for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
232
+ if (line) lines.add(line);
233
+ }
234
+ }
235
+ return lines.size > 0 ? `${[...lines].join("\n")}\n` : "";
236
+ }
237
+
238
+ interface TaskFileShape {
239
+ version: 1;
240
+ tasks: Array<{ id: string; blockedBy?: string[]; [key: string]: unknown }>;
241
+ }
242
+
243
+ function readTaskFile(path: string): TaskFileShape {
244
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as Partial<TaskFileShape>;
245
+ if (parsed.version !== 1 || !Array.isArray(parsed.tasks)) {
246
+ throw new Error(`Invalid task file during migration: ${path}`);
247
+ }
248
+ return parsed as TaskFileShape;
249
+ }
250
+
251
+ function mergedTasks(source: string, destination: string): string {
252
+ const legacy = readTaskFile(source).tasks;
253
+ const current = readTaskFile(destination).tasks;
254
+ const merged = legacy.map((task) => ({ ...task }));
255
+ const usedIds = new Set(merged.map((task) => String(task.id)));
256
+ let nextId = Math.max(0, ...[...usedIds].map(Number).filter(Number.isInteger)) + 1;
257
+ const remapped = new Map<string, string>();
258
+
259
+ for (const task of current) {
260
+ const originalId = String(task.id);
261
+ const exact = merged.find(
262
+ (candidate) =>
263
+ candidate.id === originalId && JSON.stringify(candidate) === JSON.stringify(task),
264
+ );
265
+ if (exact) {
266
+ remapped.set(originalId, originalId);
267
+ continue;
268
+ }
269
+ const id = usedIds.has(originalId) ? String(nextId++) : originalId;
270
+ usedIds.add(id);
271
+ remapped.set(originalId, id);
272
+ merged.push({ ...task, id });
273
+ }
274
+ for (const task of merged.slice(legacy.length)) {
275
+ if (task.blockedBy) task.blockedBy = task.blockedBy.map((id) => remapped.get(id) ?? id);
276
+ }
277
+ return `${JSON.stringify({ version: 1, tasks: merged }, null, 2)}\n`;
278
+ }
279
+
280
+ function placeOperation(operation: MoveOperation): void {
281
+ if (!operation.destination || operation.collision === "keep-destination") return;
282
+ mkdirSync(dirname(operation.destination), { recursive: true });
283
+ if (operation.collision === "merge-jsonl") {
284
+ writeFileSync(
285
+ operation.destination,
286
+ mergedJsonl(operation.staged, operation.destinationStaged!),
287
+ );
288
+ return;
289
+ }
290
+ if (operation.collision === "merge-tasks") {
291
+ writeFileSync(
292
+ operation.destination,
293
+ mergedTasks(operation.staged, operation.destinationStaged!),
294
+ );
295
+ return;
296
+ }
297
+ renameSync(operation.staged, operation.destination);
298
+ }
299
+
154
300
  function tablesWithColumn(database: InstanceType<typeof Database>, columnName: string): string[] {
155
301
  const tables = database
156
302
  .query<{ name: string }, []>(
@@ -239,11 +385,11 @@ function migrateDatabase(
239
385
  )
240
386
  .all(source);
241
387
  for (const row of rows) {
242
- const plaintext = decryptVaultValue(source, row.key, row.value, masterKey).value;
388
+ const plaintext = decryptVaultValueWithKey(source, row.key, row.value, masterKey).value;
243
389
  database
244
390
  .query("UPDATE vault SET value = ? WHERE user_id = ? AND key = ?")
245
391
  .run(
246
- encryptVaultValue(CANONICAL_LOCAL_USER_ID, row.key, plaintext, masterKey),
392
+ encryptVaultValueWithKey(CANONICAL_LOCAL_USER_ID, row.key, plaintext, masterKey),
247
393
  source,
248
394
  row.key,
249
395
  );
@@ -512,7 +658,7 @@ export function migrateSingleUserState(
512
658
  state: "pending",
513
659
  });
514
660
  }
515
- assertNoCollisions(operations);
661
+ configureCollisions(operations, stateDir, stagingRoot);
516
662
  journal = {
517
663
  id: SINGLE_USER_MIGRATION_ID,
518
664
  sourcePrincipal: source,
@@ -529,30 +675,57 @@ export function migrateSingleUserState(
529
675
  try {
530
676
  for (const operation of journal.operations) {
531
677
  // Reconcile a crash between rename(2) and the following journal write.
678
+ if (operation.state === "staged") operation.state = "source-staged";
679
+ if (operation.state === "pending" && !existsSync(operation.source)) {
680
+ if (existsSync(operation.staged)) operation.state = "source-staged";
681
+ else if (operation.destination && existsSync(operation.destination)) {
682
+ operation.state = "placed";
683
+ }
684
+ }
685
+ if (operation.state === "pending") {
686
+ mkdirSync(dirname(operation.staged), { recursive: true });
687
+ renameSync(operation.source, operation.staged);
688
+ operation.state = "source-staged";
689
+ writeJournal(journalPath, journal);
690
+ }
532
691
  if (
533
- operation.state === "pending" &&
534
- !existsSync(operation.source) &&
535
- existsSync(operation.staged)
692
+ operation.state === "source-staged" &&
693
+ operation.collision === "replace-destination" &&
694
+ operation.destination &&
695
+ operation.destinationStaged &&
696
+ !existsSync(operation.staged) &&
697
+ existsSync(operation.destination) &&
698
+ existsSync(operation.destinationStaged)
536
699
  ) {
537
- operation.state = "staged";
700
+ operation.state = "placed";
701
+ writeJournal(journalPath, journal);
538
702
  }
539
703
  if (
540
- operation.state === "staged" &&
704
+ operation.state === "destination-staged" &&
705
+ operation.collision !== "merge-jsonl" &&
706
+ operation.collision !== "merge-tasks" &&
707
+ operation.collision !== "keep-destination" &&
541
708
  operation.destination &&
542
709
  !existsSync(operation.staged) &&
543
710
  existsSync(operation.destination)
544
711
  ) {
545
712
  operation.state = "placed";
713
+ writeJournal(journalPath, journal);
546
714
  }
547
- if (operation.state === "pending") {
548
- mkdirSync(dirname(operation.staged), { recursive: true });
549
- renameSync(operation.source, operation.staged);
550
- operation.state = "staged";
715
+ if (operation.state === "source-staged") {
716
+ if (operation.destination && operation.destinationStaged) {
717
+ if (existsSync(operation.destination) && !existsSync(operation.destinationStaged)) {
718
+ renameSync(operation.destination, operation.destinationStaged);
719
+ }
720
+ if (!existsSync(operation.destinationStaged)) {
721
+ throw new Error(`Missing collision backup for ${operation.destination}.`);
722
+ }
723
+ }
724
+ operation.state = "destination-staged";
551
725
  writeJournal(journalPath, journal);
552
726
  }
553
- if (operation.state === "staged" && operation.destination) {
554
- mkdirSync(dirname(operation.destination), { recursive: true });
555
- renameSync(operation.staged, operation.destination);
727
+ if (operation.state === "destination-staged") {
728
+ placeOperation(operation);
556
729
  operation.state = "placed";
557
730
  writeJournal(journalPath, journal);
558
731
  }
@@ -598,10 +771,21 @@ export function migrateSingleUserState(
598
771
  migratedTables,
599
772
  };
600
773
  for (const store of ["conversations", "tasks", "users"]) {
601
- rmSync(join(stateDir, "data", store, source), { recursive: true, force: true });
774
+ rmSync(join(stateDir, "data", store, source), {
775
+ recursive: true,
776
+ force: true,
777
+ });
602
778
  }
603
- rmSync(join(stateDir, "workspace", "browser-profiles"), { recursive: true, force: true });
604
- writeFileSync(markerPath, `${JSON.stringify(marker, null, 2)}\n`, { mode: 0o600, flag: "wx" });
779
+ rmSync(join(stateDir, "run"), { recursive: true, force: true });
780
+ rmSync(join(stateDir, "bin"), { recursive: true, force: true });
781
+ rmSync(join(stateDir, "workspace", "browser-profiles"), {
782
+ recursive: true,
783
+ force: true,
784
+ });
785
+ writeFileSync(markerPath, `${JSON.stringify(marker, null, 2)}\n`, {
786
+ mode: 0o600,
787
+ flag: "wx",
788
+ });
605
789
  journal.phase = "complete";
606
790
  rmSync(stagingRoot, { recursive: true, force: true });
607
791
  return {
@@ -0,0 +1,65 @@
1
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
2
+
3
+ const ENVELOPE_PREFIX = "otium-vault:v1:";
4
+ const IV_BYTES = 12;
5
+ const KEY_BYTES = 32;
6
+
7
+ function encryptionKey(masterKey: string): Buffer {
8
+ return createHash("sha256")
9
+ .update("otium-vault-value-v1\0", "utf8")
10
+ .update(masterKey, "utf8")
11
+ .digest()
12
+ .subarray(0, KEY_BYTES);
13
+ }
14
+
15
+ function aad(userId: string, key: string): Buffer {
16
+ return Buffer.from(`${userId}\0${key.toUpperCase()}`, "utf8");
17
+ }
18
+
19
+ export function isEncryptedVaultValue(value: string): boolean {
20
+ return value.startsWith(ENVELOPE_PREFIX);
21
+ }
22
+
23
+ export function encryptVaultValueWithKey(
24
+ userId: string,
25
+ key: string,
26
+ value: string,
27
+ masterKey: string,
28
+ ): string {
29
+ const iv = randomBytes(IV_BYTES);
30
+ const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
31
+ cipher.setAAD(aad(userId, key));
32
+ const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
33
+ const tag = cipher.getAuthTag();
34
+ return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
35
+ }
36
+
37
+ export function decryptVaultValueWithKey(
38
+ userId: string,
39
+ key: string,
40
+ storedValue: string,
41
+ masterKey: string,
42
+ ): { value: string; legacyPlaintext: boolean } {
43
+ if (!isEncryptedVaultValue(storedValue)) {
44
+ return { value: storedValue, legacyPlaintext: true };
45
+ }
46
+
47
+ const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
48
+ const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
49
+ if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
50
+ throw new Error("Invalid encrypted vault value");
51
+ }
52
+
53
+ const iv = Buffer.from(ivPart, "base64url");
54
+ const ciphertext = Buffer.from(ciphertextPart, "base64url");
55
+ const tag = Buffer.from(tagPart, "base64url");
56
+ if (iv.length !== IV_BYTES || tag.length !== 16) {
57
+ throw new Error("Invalid encrypted vault value");
58
+ }
59
+
60
+ const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
61
+ decipher.setAAD(aad(userId, key));
62
+ decipher.setAuthTag(tag);
63
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
64
+ return { value: plaintext.toString("utf8"), legacyPlaintext: false };
65
+ }
@@ -1,25 +1,11 @@
1
- import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
2
1
  import { VAULT_MASTER_KEY } from "#platform/config";
2
+ import {
3
+ decryptVaultValueWithKey,
4
+ encryptVaultValueWithKey,
5
+ isEncryptedVaultValue,
6
+ } from "#storage/vault-crypto-core";
3
7
 
4
- const ENVELOPE_PREFIX = "otium-vault:v1:";
5
- const IV_BYTES = 12;
6
- const KEY_BYTES = 32;
7
-
8
- function encryptionKey(masterKey = VAULT_MASTER_KEY): Buffer {
9
- return createHash("sha256")
10
- .update("otium-vault-value-v1\0", "utf8")
11
- .update(masterKey, "utf8")
12
- .digest()
13
- .subarray(0, KEY_BYTES);
14
- }
15
-
16
- function aad(userId: string, key: string): Buffer {
17
- return Buffer.from(`${userId}\0${key.toUpperCase()}`, "utf8");
18
- }
19
-
20
- export function isEncryptedVaultValue(value: string): boolean {
21
- return value.startsWith(ENVELOPE_PREFIX);
22
- }
8
+ export { isEncryptedVaultValue };
23
9
 
24
10
  /** Encrypt one vault row. The user/key binding prevents ciphertext row swapping. */
25
11
  export function encryptVaultValue(
@@ -28,12 +14,7 @@ export function encryptVaultValue(
28
14
  value: string,
29
15
  masterKey = VAULT_MASTER_KEY,
30
16
  ): string {
31
- const iv = randomBytes(IV_BYTES);
32
- const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
33
- cipher.setAAD(aad(userId, key));
34
- const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
35
- const tag = cipher.getAuthTag();
36
- return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
17
+ return encryptVaultValueWithKey(userId, key, value, masterKey);
37
18
  }
38
19
 
39
20
  /**
@@ -46,26 +27,5 @@ export function decryptVaultValue(
46
27
  storedValue: string,
47
28
  masterKey = VAULT_MASTER_KEY,
48
29
  ): { value: string; legacyPlaintext: boolean } {
49
- if (!isEncryptedVaultValue(storedValue)) {
50
- return { value: storedValue, legacyPlaintext: true };
51
- }
52
-
53
- const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
54
- const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
55
- if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
56
- throw new Error("Invalid encrypted vault value");
57
- }
58
-
59
- const iv = Buffer.from(ivPart, "base64url");
60
- const ciphertext = Buffer.from(ciphertextPart, "base64url");
61
- const tag = Buffer.from(tagPart, "base64url");
62
- if (iv.length !== IV_BYTES || tag.length !== 16) {
63
- throw new Error("Invalid encrypted vault value");
64
- }
65
-
66
- const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
67
- decipher.setAAD(aad(userId, key));
68
- decipher.setAuthTag(tag);
69
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
70
- return { value: plaintext.toString("utf8"), legacyPlaintext: false };
30
+ return decryptVaultValueWithKey(userId, key, storedValue, masterKey);
71
31
  }
@@ -1 +1 @@
1
- export const NEGOTIUM_VERSION = "0.2.0";
1
+ export const NEGOTIUM_VERSION = "0.2.2";
@@ -0,0 +1,6 @@
1
+ export declare function isEncryptedVaultValue(value: string): boolean;
2
+ export declare function encryptVaultValueWithKey(userId: string, key: string, value: string, masterKey: string): string;
3
+ export declare function decryptVaultValueWithKey(userId: string, key: string, storedValue: string, masterKey: string): {
4
+ value: string;
5
+ legacyPlaintext: boolean;
6
+ };
@@ -1,4 +1,5 @@
1
- export declare function isEncryptedVaultValue(value: string): boolean;
1
+ import { isEncryptedVaultValue } from "./vault-crypto-core";
2
+ export { isEncryptedVaultValue };
2
3
  /** Encrypt one vault row. The user/key binding prevents ciphertext row swapping. */
3
4
  export declare function encryptVaultValue(userId: string, key: string, value: string, masterKey?: string): string;
4
5
  /**
@@ -1 +1 @@
1
- export declare const NEGOTIUM_VERSION = "0.2.0";
1
+ export declare const NEGOTIUM_VERSION = "0.2.2";
package/dist/vault.js CHANGED
@@ -329,12 +329,12 @@ if (isBun) {
329
329
  Database = NodeDatabase;
330
330
  }
331
331
 
332
- // ../../packages/core/src/storage/vault-crypto.ts
332
+ // ../../packages/core/src/storage/vault-crypto-core.ts
333
333
  import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes2 } from "crypto";
334
334
  var ENVELOPE_PREFIX = "otium-vault:v1:";
335
335
  var IV_BYTES = 12;
336
336
  var KEY_BYTES = 32;
337
- function encryptionKey(masterKey = VAULT_MASTER_KEY) {
337
+ function encryptionKey(masterKey) {
338
338
  return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
339
339
  }
340
340
  function aad(userId, key) {
@@ -343,7 +343,7 @@ function aad(userId, key) {
343
343
  function isEncryptedVaultValue(value) {
344
344
  return value.startsWith(ENVELOPE_PREFIX);
345
345
  }
346
- function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
346
+ function encryptVaultValueWithKey(userId, key, value, masterKey) {
347
347
  const iv = randomBytes2(IV_BYTES);
348
348
  const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
349
349
  cipher.setAAD(aad(userId, key));
@@ -351,7 +351,7 @@ function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
351
351
  const tag = cipher.getAuthTag();
352
352
  return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
353
353
  }
354
- function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
354
+ function decryptVaultValueWithKey(userId, key, storedValue, masterKey) {
355
355
  if (!isEncryptedVaultValue(storedValue)) {
356
356
  return { value: storedValue, legacyPlaintext: true };
357
357
  }
@@ -373,6 +373,14 @@ function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KE
373
373
  return { value: plaintext.toString("utf8"), legacyPlaintext: false };
374
374
  }
375
375
 
376
+ // ../../packages/core/src/storage/vault-crypto.ts
377
+ function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
378
+ return encryptVaultValueWithKey(userId, key, value, masterKey);
379
+ }
380
+ function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
381
+ return decryptVaultValueWithKey(userId, key, storedValue, masterKey);
382
+ }
383
+
376
384
  // ../../packages/core/src/storage/vault.ts
377
385
  var vaultDb;
378
386
  var vaultMasterKey = VAULT_MASTER_KEY;
@@ -591,4 +599,4 @@ export {
591
599
  VAULT_DESCRIPTION_MAX_LENGTH
592
600
  };
593
601
 
594
- //# debugId=FB4F08028E1EE42864756E2164756E21
602
+ //# debugId=D6C5CDDC478A1CCC64756E2164756E21