@promptowl/contextnest-community 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  reject,
17
17
  safePublishDocument,
18
18
  submitForReview
19
- } from "./chunk-IWA2UDAT.js";
19
+ } from "./chunk-MZGFKBOK.js";
20
20
  import {
21
21
  checkConflict,
22
22
  createVersion,
@@ -25,7 +25,7 @@ import {
25
25
  getDisplayStatus,
26
26
  getVersions,
27
27
  setApprovedVersion
28
- } from "./chunk-HIH7I232.js";
28
+ } from "./chunk-T5L4LYU4.js";
29
29
  import {
30
30
  AppError,
31
31
  ConflictError,
@@ -65,9 +65,11 @@ import {
65
65
  listStewards,
66
66
  loadAccessConfig,
67
67
  nestAllowsSelfApprove,
68
+ nestStorageRoot,
68
69
  permissionLevel,
69
70
  removeSteward,
70
71
  renameNest,
72
+ resolveNestPath,
71
73
  resolveNestPermission,
72
74
  resolveStewardsForNode,
73
75
  resolveStewardsWithFallback,
@@ -82,13 +84,18 @@ import {
82
84
  updateSteward,
83
85
  upsertEnvVar,
84
86
  validateLicense
85
- } from "./chunk-EMOE53KX.js";
87
+ } from "./chunk-2TUMMVBG.js";
86
88
  import {
87
- ANON_EMAIL,
88
- ANON_USER_ID,
89
89
  config,
90
- getDb
91
- } from "./chunk-RMU3LOPH.js";
90
+ getDb,
91
+ initDb,
92
+ insertOrIgnore,
93
+ nowExpr
94
+ } from "./chunk-7V33Z6CS.js";
95
+ import {
96
+ ANON_EMAIL,
97
+ ANON_USER_ID
98
+ } from "./chunk-SLTQACJW.js";
92
99
 
93
100
  // src/index.ts
94
101
  import { serve } from "@hono/node-server";
@@ -116,37 +123,40 @@ function newSessionId() {
116
123
  function expiryIso(ttlSeconds = SESSION_TTL_SECS) {
117
124
  return new Date(Date.now() + ttlSeconds * 1e3).toISOString();
118
125
  }
119
- function createSession(userId, userAgent) {
126
+ async function createSession(userId, userAgent) {
120
127
  const db = getDb();
121
128
  const id = newSessionId();
122
- db.prepare(
129
+ await db.run(
123
130
  `INSERT INTO sessions (id, user_id, expires_at, user_agent)
124
- VALUES (?, ?, ?, ?)`
125
- ).run(id, userId, expiryIso(), userAgent || null);
131
+ VALUES (?, ?, ?, ?)`,
132
+ [id, userId, expiryIso(), userAgent || null]
133
+ );
126
134
  return id;
127
135
  }
128
- function resolveSession(id) {
136
+ async function resolveSession(id) {
129
137
  const db = getDb();
130
- const row = db.prepare(
131
- `SELECT user_id, expires_at FROM sessions WHERE id = ?`
132
- ).get(id);
138
+ const row = await db.get(
139
+ `SELECT user_id, expires_at FROM sessions WHERE id = ?`,
140
+ [id]
141
+ );
133
142
  if (!row) return null;
134
143
  if (new Date(row.expires_at).getTime() <= Date.now()) {
135
- db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
144
+ await db.run("DELETE FROM sessions WHERE id = ?", [id]);
136
145
  return null;
137
146
  }
138
- db.prepare(
139
- "UPDATE sessions SET last_seen_at = datetime('now') WHERE id = ?"
140
- ).run(id);
147
+ await db.run(
148
+ `UPDATE sessions SET last_seen_at = ${nowExpr(db)} WHERE id = ?`,
149
+ [id]
150
+ );
141
151
  return row.user_id;
142
152
  }
143
- function deleteSession(id) {
153
+ async function deleteSession(id) {
144
154
  const db = getDb();
145
- db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
155
+ await db.run("DELETE FROM sessions WHERE id = ?", [id]);
146
156
  }
147
- function deleteAllSessionsForUser(userId) {
157
+ async function deleteAllSessionsForUser(userId) {
148
158
  const db = getDb();
149
- db.prepare("DELETE FROM sessions WHERE user_id = ?").run(userId);
159
+ await db.run("DELETE FROM sessions WHERE user_id = ?", [userId]);
150
160
  }
151
161
  function getSessionIdFromRequest(c) {
152
162
  const cookieHeader = c.req.header("Cookie");
@@ -190,7 +200,7 @@ var authMiddleware = createMiddleware(async (c, next) => {
190
200
  const db = getDb();
191
201
  const sessionId = getSessionIdFromRequest(c);
192
202
  if (sessionId) {
193
- const userId = resolveSession(sessionId);
203
+ const userId = await resolveSession(sessionId);
194
204
  if (userId) {
195
205
  c.set("userId", userId);
196
206
  c.set("nestScope", null);
@@ -200,11 +210,15 @@ var authMiddleware = createMiddleware(async (c, next) => {
200
210
  const key = parseBearerToken(c.req.header("Authorization"));
201
211
  if (key) {
202
212
  const keyHash = hashApiKey(key);
203
- const record = db.prepare("SELECT user_id, nest_id FROM api_keys WHERE key_hash = ?").get(keyHash);
213
+ const record = await db.get(
214
+ "SELECT user_id, nest_id FROM api_keys WHERE key_hash = ?",
215
+ [keyHash]
216
+ );
204
217
  if (record) {
205
- db.prepare(
206
- "UPDATE api_keys SET last_used_at = datetime('now') WHERE key_hash = ?"
207
- ).run(keyHash);
218
+ await db.run(
219
+ `UPDATE api_keys SET last_used_at = ${nowExpr(db)} WHERE key_hash = ?`,
220
+ [keyHash]
221
+ );
208
222
  c.set("userId", record.user_id);
209
223
  c.set("nestScope", record.nest_id);
210
224
  return next();
@@ -327,27 +341,30 @@ function clientIp(c) {
327
341
  }
328
342
  return "unknown";
329
343
  }
330
- function resolveCallerUserId(c) {
344
+ async function resolveCallerUserId(c) {
331
345
  const sessionId = getSessionIdFromRequest(c);
332
346
  if (sessionId) {
333
- const uid = resolveSession(sessionId);
347
+ const uid = await resolveSession(sessionId);
334
348
  if (uid) return uid;
335
349
  }
336
350
  const key = parseBearerToken(c.req.header("Authorization"));
337
351
  if (key) {
338
352
  const db = getDb();
339
- const row = db.prepare("SELECT user_id FROM api_keys WHERE key_hash = ?").get(hashApiKey(key));
353
+ const row = await db.get(
354
+ "SELECT user_id FROM api_keys WHERE key_hash = ?",
355
+ [hashApiKey(key)]
356
+ );
340
357
  if (row) return row.user_id;
341
358
  }
342
359
  return null;
343
360
  }
344
- function deviceGateBlocked(c) {
361
+ async function deviceGateBlocked(c) {
345
362
  const gate = config.PROMPTOWL_SIGN_IN_GATE;
346
363
  if (gate === "open") return null;
347
364
  const error = "PromptOwl sign-in is restricted on this server. Use email and password, or contact your admin.";
348
365
  if (gate === "disabled") return { error, gate };
349
- const callerId = resolveCallerUserId(c);
350
- if (callerId && !isLicenseAdminUserId(callerId)) return { error, gate };
366
+ const callerId = await resolveCallerUserId(c);
367
+ if (callerId && !await isLicenseAdminUserId(callerId)) return { error, gate };
351
368
  return null;
352
369
  }
353
370
  function setSessionCookie(c, sessionId) {
@@ -376,13 +393,17 @@ async function provisionPromptowlUser(c, rawEmail, rawName) {
376
393
  }
377
394
  const db = getDb();
378
395
  const meEmail = normalizeEmail(rawEmail);
379
- let user = db.prepare("SELECT id, email, name FROM users WHERE LOWER(email) = ?").get(meEmail);
396
+ let user = await db.get(
397
+ "SELECT id, email, name FROM users WHERE LOWER(email) = ?",
398
+ [meEmail]
399
+ );
380
400
  if (!user) {
381
401
  const userId = uuid();
382
402
  const placeholderHash = await hashPassword(uuid());
383
- db.prepare(
384
- "INSERT INTO users (id, email, name, password_hash) VALUES (?, ?, ?, ?)"
385
- ).run(userId, meEmail, rawName || null, placeholderHash);
403
+ await db.run(
404
+ "INSERT INTO users (id, email, name, password_hash) VALUES (?, ?, ?, ?)",
405
+ [userId, meEmail, rawName || null, placeholderHash]
406
+ );
386
407
  user = { id: userId, email: meEmail, name: rawName || null };
387
408
  trackEvent("user.register", {
388
409
  userId,
@@ -406,7 +427,7 @@ async function provisionPromptowlUser(c, rawEmail, rawName) {
406
427
  license_owner_email: lic.ownerEmail
407
428
  };
408
429
  }
409
- const sessionId = createSession(user.id, c.req.header("User-Agent"));
430
+ const sessionId = await createSession(user.id, c.req.header("User-Agent"));
410
431
  setSessionCookie(c, sessionId);
411
432
  return { ok: true, user, isAdmin, claimBlocked };
412
433
  }
@@ -423,25 +444,30 @@ authRoutes.post("/register", async (c) => {
423
444
  return c.json({ error: "Too many registration attempts, try again later" }, 429);
424
445
  }
425
446
  const db = getDb();
426
- const existing = db.prepare("SELECT id, is_invited FROM users WHERE LOWER(email) = ?").get(email);
447
+ const existing = await db.get(
448
+ "SELECT id, is_invited FROM users WHERE LOWER(email) = ?",
449
+ [email]
450
+ );
427
451
  let userId;
428
452
  const passwordHash = await hashPassword(body.password);
429
453
  if (existing && existing.is_invited === 1) {
430
454
  userId = existing.id;
431
- db.prepare(
432
- "UPDATE users SET password_hash = ?, name = COALESCE(?, name), is_invited = 0 WHERE id = ?"
433
- ).run(passwordHash, body.name || null, userId);
455
+ await db.run(
456
+ "UPDATE users SET password_hash = ?, name = COALESCE(?, name), is_invited = 0 WHERE id = ?",
457
+ [passwordHash, body.name || null, userId]
458
+ );
434
459
  trackEvent("user.register", { userId, email, claimed: true });
435
460
  } else if (existing) {
436
461
  throw new ValidationError("Email already registered");
437
462
  } else {
438
463
  userId = uuid();
439
- db.prepare(
440
- "INSERT INTO users (id, email, name, password_hash) VALUES (?, ?, ?, ?)"
441
- ).run(userId, email, body.name || null, passwordHash);
464
+ await db.run(
465
+ "INSERT INTO users (id, email, name, password_hash) VALUES (?, ?, ?, ?)",
466
+ [userId, email, body.name || null, passwordHash]
467
+ );
442
468
  trackEvent("user.register", { userId, email });
443
469
  }
444
- const sessionId = createSession(userId, c.req.header("User-Agent"));
470
+ const sessionId = await createSession(userId, c.req.header("User-Agent"));
445
471
  setSessionCookie(c, sessionId);
446
472
  return c.json(
447
473
  {
@@ -469,9 +495,10 @@ authRoutes.post("/login", async (c) => {
469
495
  return c.json({ error: "Too many login attempts, try again later" }, 429);
470
496
  }
471
497
  const db = getDb();
472
- const user = db.prepare(
473
- "SELECT id, email, name, password_hash, is_admin FROM users WHERE LOWER(email) = ?"
474
- ).get(emailLower);
498
+ const user = await db.get(
499
+ "SELECT id, email, name, password_hash, is_admin FROM users WHERE LOWER(email) = ?",
500
+ [emailLower]
501
+ );
475
502
  const check = user ? await verifyPassword(body.password, user.password_hash) : { ok: false, needsRehash: false };
476
503
  if (!user || !check.ok) {
477
504
  if (hasIp) recordFailure(ipKey, LOGIN_LIMIT);
@@ -485,15 +512,15 @@ authRoutes.post("/login", async (c) => {
485
512
  if (check.needsRehash) {
486
513
  try {
487
514
  const newHash = await hashPassword(body.password);
488
- db.prepare("UPDATE users SET password_hash = ? WHERE id = ?").run(
515
+ await db.run("UPDATE users SET password_hash = ? WHERE id = ?", [
489
516
  newHash,
490
517
  user.id
491
- );
518
+ ]);
492
519
  } catch {
493
520
  }
494
521
  }
495
522
  trackEvent("user.login", { userId: user.id });
496
- const sessionId = createSession(user.id, c.req.header("User-Agent"));
523
+ const sessionId = await createSession(user.id, c.req.header("User-Agent"));
497
524
  setSessionCookie(c, sessionId);
498
525
  return c.json({
499
526
  user: {
@@ -506,7 +533,7 @@ authRoutes.post("/login", async (c) => {
506
533
  });
507
534
  authRoutes.post("/logout", async (c) => {
508
535
  const sessionId = getSessionIdFromRequest(c);
509
- if (sessionId) deleteSession(sessionId);
536
+ if (sessionId) await deleteSession(sessionId);
510
537
  clearSessionCookie(c);
511
538
  return c.json({ ok: true });
512
539
  });
@@ -516,7 +543,10 @@ authRoutes.post("/keys", authMiddleware, async (c) => {
516
543
  );
517
544
  const db = getDb();
518
545
  const userId = c.get("userId");
519
- const existing = db.prepare("SELECT key_prefix FROM api_keys WHERE user_id = ?").get(userId);
546
+ const existing = await db.get(
547
+ "SELECT key_prefix FROM api_keys WHERE user_id = ?",
548
+ [userId]
549
+ );
520
550
  if (existing) {
521
551
  return c.json(
522
552
  {
@@ -528,15 +558,16 @@ authRoutes.post("/keys", authMiddleware, async (c) => {
528
558
  }
529
559
  const apiKey = generateApiKey();
530
560
  const keyId = uuid();
531
- db.prepare(
532
- "INSERT INTO api_keys (id, user_id, key_hash, key_prefix, nest_id, label) VALUES (?, ?, ?, ?, ?, ?)"
533
- ).run(
534
- keyId,
535
- userId,
536
- hashApiKey(apiKey),
537
- getKeyPrefix(apiKey),
538
- body.nest_id || null,
539
- body.label || null
561
+ await db.run(
562
+ "INSERT INTO api_keys (id, user_id, key_hash, key_prefix, nest_id, label) VALUES (?, ?, ?, ?, ?, ?)",
563
+ [
564
+ keyId,
565
+ userId,
566
+ hashApiKey(apiKey),
567
+ getKeyPrefix(apiKey),
568
+ body.nest_id || null,
569
+ body.label || null
570
+ ]
540
571
  );
541
572
  return c.json(
542
573
  { api_key: apiKey, key_prefix: getKeyPrefix(apiKey) },
@@ -551,39 +582,47 @@ authRoutes.post("/keys/rotate", authMiddleware, async (c) => {
551
582
  const userId = c.get("userId");
552
583
  const apiKey = generateApiKey();
553
584
  const keyId = uuid();
554
- const prior = db.prepare("SELECT label, nest_id FROM api_keys WHERE user_id = ?").get(userId);
555
- db.transaction(() => {
556
- db.prepare("DELETE FROM api_keys WHERE user_id = ?").run(userId);
557
- db.prepare(
558
- "INSERT INTO api_keys (id, user_id, key_hash, key_prefix, nest_id, label) VALUES (?, ?, ?, ?, ?, ?)"
559
- ).run(
560
- keyId,
561
- userId,
562
- hashApiKey(apiKey),
563
- getKeyPrefix(apiKey),
564
- body.nest_id ?? prior?.nest_id ?? null,
565
- body.label ?? prior?.label ?? null
585
+ const prior = await db.get(
586
+ "SELECT label, nest_id FROM api_keys WHERE user_id = ?",
587
+ [userId]
588
+ );
589
+ await db.transaction(async (tx) => {
590
+ await tx.run("DELETE FROM api_keys WHERE user_id = ?", [userId]);
591
+ await tx.run(
592
+ "INSERT INTO api_keys (id, user_id, key_hash, key_prefix, nest_id, label) VALUES (?, ?, ?, ?, ?, ?)",
593
+ [
594
+ keyId,
595
+ userId,
596
+ hashApiKey(apiKey),
597
+ getKeyPrefix(apiKey),
598
+ body.nest_id ?? prior?.nest_id ?? null,
599
+ body.label ?? prior?.label ?? null
600
+ ]
566
601
  );
567
- })();
602
+ });
568
603
  return c.json({ api_key: apiKey, key_prefix: getKeyPrefix(apiKey) });
569
604
  });
570
605
  authRoutes.get("/keys", authMiddleware, async (c) => {
571
606
  const db = getDb();
572
- const keys = db.prepare(
573
- "SELECT id, key_prefix, nest_id, label, created_at, last_used_at FROM api_keys WHERE user_id = ?"
574
- ).all(c.get("userId"));
607
+ const keys = await db.all(
608
+ "SELECT id, key_prefix, nest_id, label, created_at, last_used_at FROM api_keys WHERE user_id = ?",
609
+ [c.get("userId")]
610
+ );
575
611
  return c.json({ keys });
576
612
  });
577
613
  authRoutes.delete("/keys/:keyId", authMiddleware, async (c) => {
578
614
  const db = getDb();
579
- const result = db.prepare("DELETE FROM api_keys WHERE id = ? AND user_id = ?").run(c.req.param("keyId"), c.get("userId"));
615
+ const result = await db.run(
616
+ "DELETE FROM api_keys WHERE id = ? AND user_id = ?",
617
+ [c.req.param("keyId"), c.get("userId")]
618
+ );
580
619
  if (result.changes === 0) {
581
620
  return c.json({ error: "Key not found" }, 404);
582
621
  }
583
622
  return c.json({ deleted: true });
584
623
  });
585
624
  authRoutes.post("/device", async (c) => {
586
- const blocked = deviceGateBlocked(c);
625
+ const blocked = await deviceGateBlocked(c);
587
626
  if (blocked) return c.json(blocked, 403);
588
627
  if (!tryConsume(`device:ip:${clientIp(c)}`, DEVICE_LIMIT)) {
589
628
  return c.json({ error: "Too many device auth attempts, try again later" }, 429);
@@ -606,7 +645,7 @@ authRoutes.post("/device", async (c) => {
606
645
  return c.json(data);
607
646
  });
608
647
  authRoutes.get("/device/poll", async (c) => {
609
- const blocked = deviceGateBlocked(c);
648
+ const blocked = await deviceGateBlocked(c);
610
649
  if (blocked) return c.json(blocked, 403);
611
650
  const code = c.req.query("code");
612
651
  const clientSecret = c.req.query("client_secret");
@@ -689,12 +728,13 @@ authRoutes.get("/sso", async (c) => {
689
728
  }
690
729
  if (!claims.jti) return ssoError("invalid_ticket");
691
730
  const db = getDb();
692
- db.prepare("DELETE FROM sso_used_jti WHERE expires_at < datetime('now')").run();
731
+ await db.run(`DELETE FROM sso_used_jti WHERE expires_at < ${nowExpr(db)}`);
693
732
  const expiresAtIso = new Date((claims.exp ?? 0) * 1e3).toISOString();
694
733
  try {
695
- db.prepare(
696
- "INSERT INTO sso_used_jti (jti, expires_at) VALUES (?, ?)"
697
- ).run(claims.jti, expiresAtIso);
734
+ await db.run(
735
+ "INSERT INTO sso_used_jti (jti, expires_at) VALUES (?, ?)",
736
+ [claims.jti, expiresAtIso]
737
+ );
698
738
  } catch (err) {
699
739
  if (err?.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
700
740
  return ssoError("ticket_used");
@@ -706,7 +746,7 @@ authRoutes.get("/sso", async (c) => {
706
746
  try {
707
747
  result = await provisionPromptowlUser(c, sub, claims.name);
708
748
  } catch (err) {
709
- db.prepare("DELETE FROM sso_used_jti WHERE jti = ?").run(claims.jti);
749
+ await db.run("DELETE FROM sso_used_jti WHERE jti = ?", [claims.jti]);
710
750
  console.error("[sso] provisioning failed; released jti for retry:", err);
711
751
  return ssoError("service_error");
712
752
  }
@@ -719,13 +759,19 @@ authRoutes.get("/admin-status", async (c) => {
719
759
  const ownerEmail = lic?.valid ? lic.ownerEmail : null;
720
760
  let admin = null;
721
761
  if (ownerEmail) {
722
- const ownerRow = db.prepare("SELECT name FROM users WHERE LOWER(email) = LOWER(?) LIMIT 1").get(ownerEmail);
762
+ const ownerRow = await db.get(
763
+ "SELECT name FROM users WHERE LOWER(email) = LOWER(?) LIMIT 1",
764
+ [ownerEmail]
765
+ );
723
766
  admin = { email: ownerEmail, name: ownerRow?.name ?? null };
724
767
  }
725
- const callerId = resolveCallerUserId(c);
768
+ const callerId = await resolveCallerUserId(c);
726
769
  let me = null;
727
770
  if (callerId) {
728
- const row = db.prepare("SELECT email, name FROM users WHERE id = ?").get(callerId);
771
+ const row = await db.get(
772
+ "SELECT email, name FROM users WHERE id = ?",
773
+ [callerId]
774
+ );
729
775
  if (row) {
730
776
  me = {
731
777
  email: row.email,
@@ -748,7 +794,10 @@ authRoutes.post("/password", authMiddleware, async (c) => {
748
794
  assertValidPassword(body.next, "new password");
749
795
  const db = getDb();
750
796
  const userId = c.get("userId");
751
- const user = db.prepare("SELECT password_hash FROM users WHERE id = ?").get(userId);
797
+ const user = await db.get(
798
+ "SELECT password_hash FROM users WHERE id = ?",
799
+ [userId]
800
+ );
752
801
  if (!user) throw new ValidationError("User not found");
753
802
  const check = await verifyPassword(body.current, user.password_hash);
754
803
  if (!check.ok) {
@@ -761,20 +810,20 @@ authRoutes.post("/password", authMiddleware, async (c) => {
761
810
  );
762
811
  }
763
812
  const newHash = await hashPassword(body.next);
764
- db.prepare("UPDATE users SET password_hash = ? WHERE id = ?").run(
813
+ await db.run("UPDATE users SET password_hash = ? WHERE id = ?", [
765
814
  newHash,
766
815
  userId
767
- );
768
- deleteAllSessionsForUser(userId);
816
+ ]);
817
+ await deleteAllSessionsForUser(userId);
769
818
  clearSessionCookie(c);
770
819
  return c.json({ ok: true });
771
820
  });
772
821
  authRoutes.post("/admin/reset-password/:userId", async (c) => {
773
- const callerId = resolveCallerUserId(c);
822
+ const callerId = await resolveCallerUserId(c);
774
823
  if (!callerId) {
775
824
  return c.json({ error: "Authentication required." }, 401);
776
825
  }
777
- if (!isLicenseAdminUserId(callerId)) {
826
+ if (!await isLicenseAdminUserId(callerId)) {
778
827
  return c.json(
779
828
  { error: "Only the license-admin user can reset passwords." },
780
829
  403
@@ -782,7 +831,10 @@ authRoutes.post("/admin/reset-password/:userId", async (c) => {
782
831
  }
783
832
  const targetId = c.req.param("userId");
784
833
  const db = getDb();
785
- const target = db.prepare("SELECT id, email FROM users WHERE id = ?").get(targetId);
834
+ const target = await db.get(
835
+ "SELECT id, email FROM users WHERE id = ?",
836
+ [targetId]
837
+ );
786
838
  if (!target) return c.json({ error: "User not found" }, 404);
787
839
  let supplied;
788
840
  try {
@@ -796,14 +848,14 @@ authRoutes.post("/admin/reset-password/:userId", async (c) => {
796
848
  const generated = supplied ? null : uuid().replace(/-/g, "").slice(0, 16);
797
849
  const newPassword = supplied ?? generated;
798
850
  const newHash = await hashPassword(newPassword);
799
- db.transaction(() => {
800
- db.prepare("UPDATE users SET password_hash = ? WHERE id = ?").run(
851
+ await db.transaction(async (tx) => {
852
+ await tx.run("UPDATE users SET password_hash = ? WHERE id = ?", [
801
853
  newHash,
802
854
  target.id
803
- );
804
- db.prepare("DELETE FROM api_keys WHERE user_id = ?").run(target.id);
805
- })();
806
- deleteAllSessionsForUser(target.id);
855
+ ]);
856
+ await tx.run("DELETE FROM api_keys WHERE user_id = ?", [target.id]);
857
+ });
858
+ await deleteAllSessionsForUser(target.id);
807
859
  trackEvent("admin.reset_password", { adminId: callerId, userId: target.id });
808
860
  return c.json({
809
861
  ok: true,
@@ -814,11 +866,11 @@ authRoutes.post("/admin/reset-password/:userId", async (c) => {
814
866
  });
815
867
  });
816
868
  authRoutes.delete("/users/:userId", async (c) => {
817
- const callerId = resolveCallerUserId(c);
869
+ const callerId = await resolveCallerUserId(c);
818
870
  if (!callerId) {
819
871
  return c.json({ error: "Authentication required." }, 401);
820
872
  }
821
- if (!isLicenseAdminUserId(callerId)) {
873
+ if (!await isLicenseAdminUserId(callerId)) {
822
874
  return c.json(
823
875
  { error: "Only the license-admin user can remove users." },
824
876
  403
@@ -829,9 +881,15 @@ authRoutes.delete("/users/:userId", async (c) => {
829
881
  return c.json({ error: "You can't remove your own admin account." }, 400);
830
882
  }
831
883
  const db = getDb();
832
- const target = db.prepare("SELECT id, email FROM users WHERE id = ?").get(targetId);
884
+ const target = await db.get(
885
+ "SELECT id, email FROM users WHERE id = ?",
886
+ [targetId]
887
+ );
833
888
  if (!target) return c.json({ error: "User not found" }, 404);
834
- const ownedNests = db.prepare("SELECT COUNT(*) AS c FROM nests WHERE user_id = ?").get(target.id).c;
889
+ const ownedNests = (await db.get(
890
+ "SELECT COUNT(*) AS c FROM nests WHERE user_id = ?",
891
+ [target.id]
892
+ )).c;
835
893
  if (ownedNests > 0) {
836
894
  return c.json(
837
895
  {
@@ -841,17 +899,18 @@ authRoutes.delete("/users/:userId", async (c) => {
841
899
  409
842
900
  );
843
901
  }
844
- db.transaction(() => {
845
- db.prepare(
846
- "DELETE FROM stewards WHERE user_id = ? OR lower(user_email) = lower(?)"
847
- ).run(target.id, target.email);
848
- db.prepare("DELETE FROM nest_collaborators WHERE user_id = ?").run(
849
- target.id
902
+ await db.transaction(async (tx) => {
903
+ await tx.run(
904
+ "DELETE FROM stewards WHERE user_id = ? OR lower(user_email) = lower(?)",
905
+ [target.id, target.email]
850
906
  );
851
- db.prepare("DELETE FROM api_keys WHERE user_id = ?").run(target.id);
852
- db.prepare("DELETE FROM users WHERE id = ?").run(target.id);
853
- })();
854
- deleteAllSessionsForUser(target.id);
907
+ await tx.run("DELETE FROM nest_collaborators WHERE user_id = ?", [
908
+ target.id
909
+ ]);
910
+ await tx.run("DELETE FROM api_keys WHERE user_id = ?", [target.id]);
911
+ await tx.run("DELETE FROM users WHERE id = ?", [target.id]);
912
+ });
913
+ await deleteAllSessionsForUser(target.id);
855
914
  trackEvent("admin.remove_user", { adminId: callerId, userId: target.id });
856
915
  return c.json({ ok: true, email: target.email });
857
916
  });
@@ -859,7 +918,7 @@ authRoutes.post("/invite", async (c) => {
859
918
  const body = await c.req.json();
860
919
  if (!body.email) throw new ValidationError("email is required");
861
920
  const email = normalizeEmail(body.email);
862
- const callerId = resolveCallerUserId(c);
921
+ const callerId = await resolveCallerUserId(c);
863
922
  if (!callerId) {
864
923
  return c.json(
865
924
  {
@@ -869,7 +928,7 @@ authRoutes.post("/invite", async (c) => {
869
928
  );
870
929
  }
871
930
  const db = getDb();
872
- if (!isLicenseAdminUserId(callerId)) {
931
+ if (!await isLicenseAdminUserId(callerId)) {
873
932
  return c.json(
874
933
  {
875
934
  error: "Only the license-admin user can invite teammates. Contact the admin who installed the PromptOwl license on this server to issue invitations."
@@ -877,29 +936,34 @@ authRoutes.post("/invite", async (c) => {
877
936
  403
878
937
  );
879
938
  }
880
- let user = db.prepare("SELECT id, email FROM users WHERE LOWER(email) = ?").get(email);
939
+ let user = await db.get(
940
+ "SELECT id, email FROM users WHERE LOWER(email) = ?",
941
+ [email]
942
+ );
881
943
  if (!user) {
882
944
  const userId = uuid();
883
945
  const placeholderHash = await hashPassword(uuid());
884
- db.prepare(
885
- "INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)"
886
- ).run(userId, email, null, placeholderHash);
946
+ await db.run(
947
+ "INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)",
948
+ [userId, email, null, placeholderHash]
949
+ );
887
950
  user = { id: userId, email };
888
951
  }
889
952
  const apiKey = generateApiKey();
890
953
  const keyId = uuid();
891
- db.transaction(() => {
892
- db.prepare("DELETE FROM api_keys WHERE user_id = ?").run(user.id);
893
- db.prepare(
894
- "INSERT INTO api_keys (id, user_id, key_hash, key_prefix, label) VALUES (?, ?, ?, ?, ?)"
895
- ).run(
896
- keyId,
897
- user.id,
898
- hashApiKey(apiKey),
899
- getKeyPrefix(apiKey),
900
- body.label || "teammate"
954
+ await db.transaction(async (tx) => {
955
+ await tx.run("DELETE FROM api_keys WHERE user_id = ?", [user.id]);
956
+ await tx.run(
957
+ "INSERT INTO api_keys (id, user_id, key_hash, key_prefix, label) VALUES (?, ?, ?, ?, ?)",
958
+ [
959
+ keyId,
960
+ user.id,
961
+ hashApiKey(apiKey),
962
+ getKeyPrefix(apiKey),
963
+ body.label || "teammate"
964
+ ]
901
965
  );
902
- })();
966
+ });
903
967
  trackEvent("admin.invite", { adminId: callerId, email: body.email });
904
968
  return c.json(
905
969
  {
@@ -911,7 +975,7 @@ authRoutes.post("/invite", async (c) => {
911
975
  );
912
976
  });
913
977
  authRoutes.get("/teammates", async (c) => {
914
- const callerId = resolveCallerUserId(c);
978
+ const callerId = await resolveCallerUserId(c);
915
979
  if (!callerId) {
916
980
  return c.json(
917
981
  {
@@ -921,7 +985,7 @@ authRoutes.get("/teammates", async (c) => {
921
985
  );
922
986
  }
923
987
  const db = getDb();
924
- if (!isLicenseAdminUserId(callerId)) {
988
+ if (!await isLicenseAdminUserId(callerId)) {
925
989
  return c.json(
926
990
  {
927
991
  error: "Only the license-admin user can view the teammates list. Contact the admin who installed the PromptOwl license on this server."
@@ -929,15 +993,16 @@ authRoutes.get("/teammates", async (c) => {
929
993
  403
930
994
  );
931
995
  }
932
- const teammates = db.prepare(
996
+ const teammates = await db.all(
933
997
  `SELECT u.id, u.email, u.name, u.is_invited,
934
998
  (SELECT COUNT(*) FROM api_keys WHERE user_id = u.id) as key_count,
935
999
  (SELECT MAX(last_used_at) FROM api_keys WHERE user_id = u.id) as last_active
936
1000
  FROM users u
937
1001
  WHERE u.id != ?
938
- ORDER BY u.created_at DESC`
939
- ).all(ANON_USER_ID);
940
- const pendingStewards = db.prepare(
1002
+ ORDER BY u.created_at DESC`,
1003
+ [ANON_USER_ID]
1004
+ );
1005
+ const pendingStewards = await db.all(
941
1006
  `SELECT DISTINCT s.user_email AS email
942
1007
  FROM stewards s
943
1008
  WHERE s.is_active = 1
@@ -952,7 +1017,7 @@ authRoutes.get("/teammates", async (c) => {
952
1017
  )
953
1018
  )
954
1019
  ORDER BY s.user_email`
955
- ).all();
1020
+ );
956
1021
  const enriched = teammates.map((t) => ({ ...t, is_admin: isLicenseAdminEmail(t.email) })).sort((a, b) => Number(b.is_admin) - Number(a.is_admin));
957
1022
  return c.json({
958
1023
  teammates: enriched,
@@ -970,52 +1035,70 @@ import { serializeDocument, parseDocument as parseDocument2 } from "@promptowl/c
970
1035
  function normalizeTag(raw) {
971
1036
  return raw.trim().replace(/^#+/, "").toLowerCase();
972
1037
  }
973
- function syncNodeTags(nestId, nodeId, tags) {
1038
+ async function syncNodeTags(nestId, nodeId, tags) {
974
1039
  const db = getDb();
975
1040
  const normalized = Array.from(
976
1041
  new Set(
977
1042
  tags.filter((t) => typeof t === "string").map(normalizeTag).filter(Boolean)
978
1043
  )
979
1044
  );
980
- db.transaction(() => {
981
- db.prepare(
982
- "DELETE FROM node_tag_index WHERE nest_id = ? AND node_id = ?"
983
- ).run(nestId, nodeId);
984
- const insert = db.prepare(
985
- "INSERT OR IGNORE INTO node_tag_index (nest_id, node_id, tag_name) VALUES (?, ?, ?)"
1045
+ const insertSql = insertOrIgnore(
1046
+ db,
1047
+ "INSERT INTO node_tag_index (nest_id, node_id, tag_name) VALUES (?, ?, ?)"
1048
+ );
1049
+ await db.transaction(async (tx) => {
1050
+ await tx.run(
1051
+ "DELETE FROM node_tag_index WHERE nest_id = ? AND node_id = ?",
1052
+ [nestId, nodeId]
986
1053
  );
987
1054
  for (const tag of normalized) {
988
- insert.run(nestId, nodeId, tag);
1055
+ await tx.run(insertSql, [nestId, nodeId, tag]);
989
1056
  }
990
- })();
1057
+ });
991
1058
  }
992
- function removeNodeFromTagIndex(nestId, nodeId) {
1059
+ async function removeNodeFromTagIndex(nestId, nodeId) {
993
1060
  const db = getDb();
994
- db.prepare(
995
- "DELETE FROM node_tag_index WHERE nest_id = ? AND node_id = ?"
996
- ).run(nestId, nodeId);
1061
+ await db.run(
1062
+ "DELETE FROM node_tag_index WHERE nest_id = ? AND node_id = ?",
1063
+ [nestId, nodeId]
1064
+ );
997
1065
  }
998
1066
 
999
1067
  // src/governance/access-guard.ts
1000
- function resolveCallerEmail(userId) {
1068
+ async function resolveCallerEmail(userId) {
1001
1069
  if (!userId) return "admin@localhost";
1002
1070
  const db = getDb();
1003
- const row = db.prepare("SELECT email FROM users WHERE id = ?").get(userId);
1071
+ const row = await db.get(
1072
+ "SELECT email FROM users WHERE id = ?",
1073
+ [userId]
1074
+ );
1004
1075
  return row?.email || "admin@localhost";
1005
1076
  }
1006
- function canReadNode(nestId, nodeId, userId, userEmail) {
1007
- if (isPublicReader(nestId, userId)) {
1008
- return getApprovedVersion(nestId, nodeId) !== null;
1077
+ async function canReadNode(nestId, nodeId, userId, userEmail) {
1078
+ if (await isPublicReader(nestId, userId)) {
1079
+ return await getApprovedVersion(nestId, nodeId) !== null;
1009
1080
  }
1010
- if (!isStewardshipEnabled(nestId)) return true;
1011
- return canUserAccess(nestId, nodeId, userEmail).allowed;
1081
+ if (!await isStewardshipEnabled(nestId)) return true;
1082
+ return (await canUserAccess(nestId, nodeId, userEmail)).allowed;
1012
1083
  }
1013
- function filterAccessible(nestId, userId, userEmail, nodes) {
1014
- if (isPublicReader(nestId, userId)) {
1015
- return nodes.filter((n) => getApprovedVersion(nestId, n.id) !== null);
1084
+ async function filterAccessible(nestId, userId, userEmail, nodes) {
1085
+ if (await isPublicReader(nestId, userId)) {
1086
+ const filtered = [];
1087
+ for (const n of nodes) {
1088
+ if (await getApprovedVersion(nestId, n.id) !== null) {
1089
+ filtered.push(n);
1090
+ }
1091
+ }
1092
+ return filtered;
1093
+ }
1094
+ if (!await isStewardshipEnabled(nestId)) return nodes;
1095
+ const accessible = [];
1096
+ for (const n of nodes) {
1097
+ if ((await canUserAccess(nestId, n.id, userEmail)).allowed) {
1098
+ accessible.push(n);
1099
+ }
1016
1100
  }
1017
- if (!isStewardshipEnabled(nestId)) return nodes;
1018
- return nodes.filter((n) => canUserAccess(nestId, n.id, userEmail).allowed);
1101
+ return accessible;
1019
1102
  }
1020
1103
 
1021
1104
  // src/governance/external-edit-service.ts
@@ -1039,7 +1122,7 @@ var communityRbac = {
1039
1122
  isDocOwner: () => true
1040
1123
  };
1041
1124
  function docPath(nestId, documentId) {
1042
- return join(config.DATA_ROOT, "nests", nestId, `${documentId}.md`);
1125
+ return join(resolveNestPath(nestId), `${documentId}.md`);
1043
1126
  }
1044
1127
  async function readRaw(nestId, documentId) {
1045
1128
  try {
@@ -1063,7 +1146,7 @@ async function loadChainHead(storage, documentId) {
1063
1146
  }
1064
1147
  }
1065
1148
  async function loadLatestApprovedNode(nestId, documentId) {
1066
- const { storage } = engineCache.get(nestId);
1149
+ const { storage } = await engineCache.get(nestId);
1067
1150
  const head = await loadChainHead(storage, documentId);
1068
1151
  if (!head) return null;
1069
1152
  return parseDocument(docPath(nestId, documentId), head.content, documentId);
@@ -1079,7 +1162,7 @@ async function scanDocumentForDrift(nestId, documentId, actor = "system:scanner"
1079
1162
  return res?.meta ?? null;
1080
1163
  }
1081
1164
  async function scanDocumentForDriftInternal(nestId, documentId, actor) {
1082
- const { storage } = engineCache.get(nestId);
1165
+ const { storage } = await engineCache.get(nestId);
1083
1166
  const node = await storage.readDocument(documentId).catch(() => null);
1084
1167
  if (!node) return null;
1085
1168
  const raw = await readRaw(nestId, documentId);
@@ -1106,7 +1189,7 @@ async function scanDocumentForDriftInternal(nestId, documentId, actor) {
1106
1189
  return { meta: result.meta, created: true };
1107
1190
  }
1108
1191
  async function scanNestForDrift(nestId, actor = "system:scanner") {
1109
- const { storage } = engineCache.get(nestId);
1192
+ const { storage } = await engineCache.get(nestId);
1110
1193
  const docs = await storage.discoverDocuments();
1111
1194
  const results = await Promise.all(
1112
1195
  docs.map((doc) => scanDocumentForDriftInternal(nestId, doc.id, actor))
@@ -1115,7 +1198,7 @@ async function scanNestForDrift(nestId, actor = "system:scanner") {
1115
1198
  return { scanned: docs.length, staged };
1116
1199
  }
1117
1200
  async function getPendingChange(nestId, documentId) {
1118
- const { storage } = engineCache.get(nestId);
1201
+ const { storage } = await engineCache.get(nestId);
1119
1202
  const list = await listSuggestions(storage, documentId);
1120
1203
  for (let i = list.length - 1; i >= 0; i--) {
1121
1204
  const meta = list[i];
@@ -1132,7 +1215,7 @@ async function getPendingChange(nestId, documentId) {
1132
1215
  return null;
1133
1216
  }
1134
1217
  async function listNestExternalEdits(nestId) {
1135
- const { storage } = engineCache.get(nestId);
1218
+ const { storage } = await engineCache.get(nestId);
1136
1219
  const docs = await storage.discoverDocuments();
1137
1220
  const lists = await Promise.all(
1138
1221
  docs.map(async (doc) => {
@@ -1161,7 +1244,7 @@ async function listNestExternalEdits(nestId) {
1161
1244
  return entries.sort((a, b) => b.detected_at.localeCompare(a.detected_at));
1162
1245
  }
1163
1246
  async function getExternalEditDetail(nestId, documentId, suggestionId) {
1164
- const { storage } = engineCache.get(nestId);
1247
+ const { storage } = await engineCache.get(nestId);
1165
1248
  const found = await readSuggestion(storage, documentId, suggestionId);
1166
1249
  if (!found) return null;
1167
1250
  return {
@@ -1178,7 +1261,7 @@ async function getExternalEditDetail(nestId, documentId, suggestionId) {
1178
1261
  };
1179
1262
  }
1180
1263
  async function approveExternalEdit(input) {
1181
- const { storage } = engineCache.get(input.nestId);
1264
+ const { storage } = await engineCache.get(input.nestId);
1182
1265
  let result;
1183
1266
  try {
1184
1267
  result = await approveSuggestion({
@@ -1201,8 +1284,8 @@ async function approveExternalEdit(input) {
1201
1284
  const node = await storage.readDocument(input.documentId);
1202
1285
  const versionNum = result.versionEntry.version;
1203
1286
  const tags = node.frontmatter.tags || [];
1204
- const { createVersion: createVersion2, setApprovedVersion: setApprovedVersion2 } = await import("./version-service-REGL5CUT.js");
1205
- createVersion2({
1287
+ const { createVersion: createVersion2, setApprovedVersion: setApprovedVersion2 } = await import("./version-service-MUZYTYMW.js");
1288
+ await createVersion2({
1206
1289
  nestId: input.nestId,
1207
1290
  nodeId: input.documentId,
1208
1291
  version: versionNum,
@@ -1212,7 +1295,12 @@ async function approveExternalEdit(input) {
1212
1295
  tags,
1213
1296
  changeNote: input.comment || "External edit approved"
1214
1297
  });
1215
- setApprovedVersion2(input.nestId, input.documentId, versionNum, input.actor);
1298
+ await setApprovedVersion2(
1299
+ input.nestId,
1300
+ input.documentId,
1301
+ versionNum,
1302
+ input.actor
1303
+ );
1216
1304
  } catch (err) {
1217
1305
  console.error(
1218
1306
  `[external-edit] failed to mirror approved version into node_versions for ${input.nestId}/${input.documentId}:`,
@@ -1222,7 +1310,7 @@ async function approveExternalEdit(input) {
1222
1310
  return result;
1223
1311
  }
1224
1312
  async function rejectExternalEdit(input) {
1225
- const { storage } = engineCache.get(input.nestId);
1313
+ const { storage } = await engineCache.get(input.nestId);
1226
1314
  const result = await rejectSuggestion({
1227
1315
  storage,
1228
1316
  rbac: communityRbac,
@@ -1248,7 +1336,7 @@ async function rejectExternalEdit(input) {
1248
1336
  var scannerTimer = null;
1249
1337
  async function scanAllNests() {
1250
1338
  const db = getDb();
1251
- const rows = db.prepare("SELECT id FROM nests").all();
1339
+ const rows = await db.all("SELECT id FROM nests");
1252
1340
  await Promise.all(
1253
1341
  rows.map(
1254
1342
  ({ id }) => scanNestForDrift(id).catch(
@@ -1268,9 +1356,9 @@ function startDriftScanner(intervalMs = 3e4) {
1268
1356
  }
1269
1357
 
1270
1358
  // src/nodes/service.ts
1271
- function userIdFromEmail(email) {
1359
+ async function userIdFromEmail(email) {
1272
1360
  const db = getDb();
1273
- const row = db.prepare("SELECT id FROM users WHERE LOWER(email) = LOWER(?)").get(email);
1361
+ const row = await db.get("SELECT id FROM users WHERE LOWER(email) = LOWER(?)", [email]);
1274
1362
  return row?.id ?? ANON_USER_ID;
1275
1363
  }
1276
1364
  var normalizeTag2 = (t) => t.startsWith("#") ? t : `#${t}`;
@@ -1302,7 +1390,7 @@ function toNodeResponse(node) {
1302
1390
  };
1303
1391
  }
1304
1392
  async function listNodesForCaller(nestId, userId, filters = {}) {
1305
- const { storage, versions: versionManager } = engineCache.get(nestId);
1393
+ const { storage, versions: versionManager } = await engineCache.get(nestId);
1306
1394
  let documents = await storage.discoverDocuments();
1307
1395
  if (filters.type) {
1308
1396
  documents = documents.filter((n) => n.frontmatter.type === filters.type);
@@ -1313,14 +1401,14 @@ async function listNodesForCaller(nestId, userId, filters = {}) {
1313
1401
  (n) => (n.frontmatter.tags || []).includes(tag)
1314
1402
  );
1315
1403
  }
1316
- const userEmail = resolveCallerEmail(userId);
1317
- const accessible = filterAccessible(nestId, userId, userEmail, documents);
1318
- const publicReader = isPublicReader(nestId, userId);
1404
+ const userEmail = await resolveCallerEmail(userId);
1405
+ const accessible = await filterAccessible(nestId, userId, userEmail, documents);
1406
+ const publicReader = await isPublicReader(nestId, userId);
1319
1407
  const enriched = await Promise.all(
1320
1408
  accessible.map(async (doc) => {
1321
1409
  const r = toNodeResponse(doc);
1322
1410
  if (publicReader) {
1323
- const approved = getApprovedVersion(nestId, doc.id);
1411
+ const approved = await getApprovedVersion(nestId, doc.id);
1324
1412
  if (approved != null) {
1325
1413
  try {
1326
1414
  const raw = await versionManager.reconstructVersion(doc.id, approved);
@@ -1344,7 +1432,7 @@ async function listNodesForCaller(nestId, userId, filters = {}) {
1344
1432
  r.pendingChange = pending;
1345
1433
  r.status = "external_edit_pending";
1346
1434
  } else {
1347
- r.status = getDisplayStatus(nestId, r.id);
1435
+ r.status = await getDisplayStatus(nestId, r.id);
1348
1436
  }
1349
1437
  return r;
1350
1438
  })
@@ -1352,15 +1440,15 @@ async function listNodesForCaller(nestId, userId, filters = {}) {
1352
1440
  return filters.limit ? enriched.slice(0, filters.limit) : enriched;
1353
1441
  }
1354
1442
  async function listNodesForCallerByEmail(nestId, userEmail, filters = {}) {
1355
- return listNodesForCaller(nestId, userIdFromEmail(userEmail), filters);
1443
+ return listNodesForCaller(nestId, await userIdFromEmail(userEmail), filters);
1356
1444
  }
1357
1445
  async function createNode(nestId, input, userEmail) {
1358
- const { storage, versions: versionManager } = engineCache.get(nestId);
1446
+ const { storage, versions: versionManager } = await engineCache.get(nestId);
1359
1447
  const slug = input.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1360
1448
  const id = input.id ?? `nodes/${slug}`;
1361
1449
  const now = (/* @__PURE__ */ new Date()).toISOString();
1362
1450
  const tags = (input.tags || []).map(normalizeTag2);
1363
- const hasStewards = isStewardshipEnabled(nestId);
1451
+ const hasStewards = await isStewardshipEnabled(nestId);
1364
1452
  const initialStatus = hasStewards ? "draft" : "published";
1365
1453
  const initialVersion = hasStewards ? 1 : 0;
1366
1454
  let node = {
@@ -1380,7 +1468,7 @@ async function createNode(nestId, input, userEmail) {
1380
1468
  rawContent: ""
1381
1469
  };
1382
1470
  await storage.writeDocument(id, serializeDocument(node));
1383
- syncNodeTags(nestId, id, tags);
1471
+ await syncNodeTags(nestId, id, tags);
1384
1472
  let savedVersion = 1;
1385
1473
  if (hasStewards) {
1386
1474
  try {
@@ -1388,7 +1476,7 @@ async function createNode(nestId, input, userEmail) {
1388
1476
  } catch (err) {
1389
1477
  console.error("VersionManager.createVersion failed (node create)", err);
1390
1478
  }
1391
- createVersion({
1479
+ await createVersion({
1392
1480
  nestId,
1393
1481
  nodeId: id,
1394
1482
  version: 1,
@@ -1404,7 +1492,7 @@ async function createNode(nestId, input, userEmail) {
1404
1492
  note: "Auto-published on create (no stewards configured)"
1405
1493
  });
1406
1494
  savedVersion = result.node.frontmatter.version || 1;
1407
- createVersion({
1495
+ await createVersion({
1408
1496
  nestId,
1409
1497
  nodeId: id,
1410
1498
  version: savedVersion,
@@ -1413,11 +1501,11 @@ async function createNode(nestId, input, userEmail) {
1413
1501
  status: "published",
1414
1502
  tags
1415
1503
  });
1416
- setApprovedVersion(nestId, id, savedVersion, userEmail);
1504
+ await setApprovedVersion(nestId, id, savedVersion, userEmail);
1417
1505
  node = result.node;
1418
1506
  } catch (err) {
1419
1507
  console.error("publishDocument failed (node create auto-publish)", err);
1420
- createVersion({
1508
+ await createVersion({
1421
1509
  nestId,
1422
1510
  nodeId: id,
1423
1511
  version: 1,
@@ -1428,11 +1516,11 @@ async function createNode(nestId, input, userEmail) {
1428
1516
  });
1429
1517
  }
1430
1518
  }
1431
- trackEvent("node.create", { nestId, nodeId: id });
1519
+ await trackEvent("node.create", { nestId, nodeId: id });
1432
1520
  return { node, version: savedVersion };
1433
1521
  }
1434
1522
  async function registerImportedDocuments(nestId, userEmail) {
1435
- const { storage } = engineCache.get(nestId);
1523
+ const { storage } = await engineCache.get(nestId);
1436
1524
  let docs;
1437
1525
  try {
1438
1526
  docs = await storage.discoverDocuments();
@@ -1443,7 +1531,7 @@ async function registerImportedDocuments(nestId, userEmail) {
1443
1531
  let registered = 0;
1444
1532
  for (const doc of docs) {
1445
1533
  const nodeId = doc.id;
1446
- if (getCurrentVersion(nestId, nodeId) > 0) continue;
1534
+ if (await getCurrentVersion(nestId, nodeId) > 0) continue;
1447
1535
  const rawTags = Array.isArray(doc.frontmatter?.tags) ? doc.frontmatter.tags : [];
1448
1536
  const tags = rawTags.map((t) => normalizeTag2(String(t)));
1449
1537
  const fmVersion = Number(doc.frontmatter?.version);
@@ -1459,7 +1547,7 @@ async function registerImportedDocuments(nestId, userEmail) {
1459
1547
  } catch (err) {
1460
1548
  console.error("safePublishDocument failed (import register)", nodeId, err);
1461
1549
  }
1462
- createVersion({
1550
+ await createVersion({
1463
1551
  nestId,
1464
1552
  nodeId,
1465
1553
  version,
@@ -1469,15 +1557,15 @@ async function registerImportedDocuments(nestId, userEmail) {
1469
1557
  changeNote: "Imported from existing folder",
1470
1558
  tags
1471
1559
  });
1472
- setApprovedVersion(nestId, nodeId, version, userEmail);
1473
- syncNodeTags(nestId, nodeId, tags);
1560
+ await setApprovedVersion(nestId, nodeId, version, userEmail);
1561
+ await syncNodeTags(nestId, nodeId, tags);
1474
1562
  registered++;
1475
1563
  }
1476
- trackEvent("nest.import.documents", { nestId, registered });
1564
+ await trackEvent("nest.import.documents", { nestId, registered });
1477
1565
  return registered;
1478
1566
  }
1479
1567
  async function updateNode(nestId, nodeId, patch, userEmail) {
1480
- const { storage, versions: versionManager } = engineCache.get(nestId);
1568
+ const { storage, versions: versionManager } = await engineCache.get(nestId);
1481
1569
  let node;
1482
1570
  try {
1483
1571
  node = await storage.readDocument(nodeId);
@@ -1501,16 +1589,16 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
1501
1589
  if (patch.title) {
1502
1590
  node = { ...node, frontmatter: { ...node.frontmatter, title: patch.title } };
1503
1591
  }
1504
- const hasStewards = isStewardshipEnabled(nestId);
1592
+ const hasStewards = await isStewardshipEnabled(nestId);
1505
1593
  const currentTags = node.frontmatter.tags || [];
1506
- if (hasStewards && getPendingReview(nestId, nodeId)) {
1594
+ if (hasStewards && await getPendingReview(nestId, nodeId)) {
1507
1595
  throw new LockedError(
1508
1596
  "This document is awaiting steward review and is locked. Approve or reject the pending review before editing."
1509
1597
  );
1510
1598
  }
1511
1599
  let responseVersion;
1512
1600
  if (hasStewards) {
1513
- const currentVersion = getCurrentVersion(nestId, nodeId);
1601
+ const currentVersion = await getCurrentVersion(nestId, nodeId);
1514
1602
  const newVersion = currentVersion + 1;
1515
1603
  node = {
1516
1604
  ...node,
@@ -1525,13 +1613,13 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
1525
1613
  };
1526
1614
  node = { ...node, frontmatter: stripUndefined(node.frontmatter) };
1527
1615
  await storage.writeDocument(nodeId, serializeDocument(node));
1528
- syncNodeTags(nestId, nodeId, currentTags);
1616
+ await syncNodeTags(nestId, nodeId, currentTags);
1529
1617
  try {
1530
1618
  await versionManager.createVersion(node, userEmail, { note: patch.changeNote });
1531
1619
  } catch (err) {
1532
1620
  console.error("VersionManager.createVersion failed (node patch)", err);
1533
1621
  }
1534
- createVersion({
1622
+ await createVersion({
1535
1623
  nestId,
1536
1624
  nodeId,
1537
1625
  version: newVersion,
@@ -1553,7 +1641,7 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
1553
1641
  };
1554
1642
  node = { ...node, frontmatter: stripUndefined(node.frontmatter) };
1555
1643
  await storage.writeDocument(nodeId, serializeDocument(node));
1556
- syncNodeTags(nestId, nodeId, currentTags);
1644
+ await syncNodeTags(nestId, nodeId, currentTags);
1557
1645
  let publishedVersion = (node.frontmatter.version || 0) + 1;
1558
1646
  try {
1559
1647
  const result = await safePublishDocument(storage, nodeId, {
@@ -1565,7 +1653,7 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
1565
1653
  } catch (err) {
1566
1654
  console.error("publishDocument failed (node patch auto-publish)", err);
1567
1655
  }
1568
- createVersion({
1656
+ await createVersion({
1569
1657
  nestId,
1570
1658
  nodeId,
1571
1659
  version: publishedVersion,
@@ -1575,7 +1663,7 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
1575
1663
  tags: currentTags,
1576
1664
  changeNote: patch.changeNote
1577
1665
  });
1578
- setApprovedVersion(nestId, nodeId, publishedVersion, userEmail);
1666
+ await setApprovedVersion(nestId, nodeId, publishedVersion, userEmail);
1579
1667
  responseVersion = publishedVersion;
1580
1668
  }
1581
1669
  return { node, version: responseVersion };
@@ -1583,8 +1671,11 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
1583
1671
 
1584
1672
  // src/nests/unsynced-service.ts
1585
1673
  import { readdirSync, readFileSync, rmSync, statSync } from "fs";
1586
- import { join as join2, relative } from "path";
1674
+ import { join as join2, relative, resolve } from "path";
1587
1675
  var RESERVED = /* @__PURE__ */ new Set(["nests"]);
1676
+ function isNestStorageRoot(absPath) {
1677
+ return resolve(absPath) === resolve(nestStorageRoot());
1678
+ }
1588
1679
  function scanMarkdown(dir) {
1589
1680
  let count = 0;
1590
1681
  let size = 0;
@@ -1664,7 +1755,9 @@ function listUnsyncedFolders() {
1664
1755
  if (!e.isDirectory()) continue;
1665
1756
  if (e.name.startsWith(".")) continue;
1666
1757
  if (RESERVED.has(e.name)) continue;
1667
- out.push(...collectLeafFolders(e.name, join2(root, e.name)));
1758
+ const abs = join2(root, e.name);
1759
+ if (isNestStorageRoot(abs)) continue;
1760
+ out.push(...collectLeafFolders(e.name, abs));
1668
1761
  }
1669
1762
  out.sort((a, b) => a.name.localeCompare(b.name));
1670
1763
  console.log(
@@ -1718,6 +1811,9 @@ async function syncUnsyncedFolder(userId, folderName, callerEmail) {
1718
1811
  );
1719
1812
  assertSafeFolderName(folderName);
1720
1813
  const src = join2(config.DATA_ROOT, folderName);
1814
+ if (isNestStorageRoot(src)) {
1815
+ throw new ValidationError("Folder is not eligible for sync");
1816
+ }
1721
1817
  let stat;
1722
1818
  try {
1723
1819
  stat = statSync(src);
@@ -1737,7 +1833,7 @@ async function syncUnsyncedFolder(userId, folderName, callerEmail) {
1737
1833
  }
1738
1834
  const segments = folderName.split("/").filter(Boolean);
1739
1835
  const baseName = segments[segments.length - 1] || folderName;
1740
- const nestName = uniqueNestName(userId, baseName);
1836
+ const nestName = await uniqueNestName(userId, baseName);
1741
1837
  if (nestName !== baseName) {
1742
1838
  console.log(
1743
1839
  `[unsynced] name "${baseName}" already in use, using "${nestName}" instead`
@@ -1761,10 +1857,13 @@ async function syncUnsyncedFolder(userId, folderName, callerEmail) {
1761
1857
  }
1762
1858
 
1763
1859
  // src/nests/routes.ts
1764
- function effectivePermission(nestId, userId) {
1860
+ async function effectivePermission(nestId, userId) {
1765
1861
  if (config.AUTH_MODE === "open") {
1766
1862
  const db = getDb();
1767
- const nest = db.prepare("SELECT user_id FROM nests WHERE id = ?").get(nestId);
1863
+ const nest = await db.get(
1864
+ "SELECT user_id FROM nests WHERE id = ?",
1865
+ [nestId]
1866
+ );
1768
1867
  if (nest && nest.user_id === ANON_USER_ID) return "owner";
1769
1868
  }
1770
1869
  return resolveNestPermission(nestId, userId);
@@ -1772,21 +1871,19 @@ function effectivePermission(nestId, userId) {
1772
1871
  var nestRoutes = new Hono2();
1773
1872
  nestRoutes.get("/", async (c) => {
1774
1873
  const userId = c.get("userId");
1775
- const owned = listNests(userId);
1776
- const shared = listSharedNests(userId);
1777
- const publicExtras = listPublicNests(userId);
1874
+ const owned = await listNests(userId);
1875
+ const shared = await listSharedNests(userId);
1876
+ const publicExtras = await listPublicNests(userId);
1778
1877
  const db = getDb();
1779
- const ownerEmailStmt = db.prepare(
1780
- "SELECT email FROM users WHERE id = ?"
1781
- );
1782
- const callerEmail = resolveCallerEmail(userId);
1783
- const annotate = (n) => {
1784
- const permission = effectivePermission(n.id, userId);
1878
+ const ownerEmailSql = "SELECT email FROM users WHERE id = ?";
1879
+ const callerEmail = await resolveCallerEmail(userId);
1880
+ const annotate = async (n) => {
1881
+ const permission = await effectivePermission(n.id, userId);
1785
1882
  const is_owner = permission === "owner";
1786
1883
  let owner_email = null;
1787
- const roles = is_owner ? ["owner"] : resolveUserRoles(n.id, callerEmail);
1884
+ const roles = is_owner ? ["owner"] : await resolveUserRoles(n.id, callerEmail);
1788
1885
  if (!is_owner && n.user_id !== ANON_USER_ID) {
1789
- const row = ownerEmailStmt.get(n.user_id);
1886
+ const row = await db.get(ownerEmailSql, [n.user_id]);
1790
1887
  owner_email = row?.email ?? null;
1791
1888
  }
1792
1889
  return { ...n, permission, is_owner, owner_email, roles };
@@ -1796,7 +1893,7 @@ nestRoutes.get("/", async (c) => {
1796
1893
  for (const n of [...owned, ...shared, ...publicExtras]) {
1797
1894
  if (seen.has(n.id)) continue;
1798
1895
  seen.add(n.id);
1799
- out.push(annotate(n));
1896
+ out.push(await annotate(n));
1800
1897
  }
1801
1898
  return c.json({ nests: out });
1802
1899
  });
@@ -1818,20 +1915,20 @@ nestRoutes.post("/import", async (c) => {
1818
1915
  const nest = await importNest(userId, body.name, files);
1819
1916
  const documents = await registerImportedDocuments(
1820
1917
  nest.id,
1821
- resolveCallerEmail(userId)
1918
+ await resolveCallerEmail(userId)
1822
1919
  );
1823
1920
  return c.json({ nest, documents }, 201);
1824
1921
  });
1825
1922
  nestRoutes.get("/unsynced", async (c) => {
1826
1923
  const userId = c.get("userId");
1827
- if (config.AUTH_MODE !== "open" && !isLicenseAdminUserId(userId)) {
1924
+ if (config.AUTH_MODE !== "open" && !await isLicenseAdminUserId(userId)) {
1828
1925
  throw new ForbiddenError("Only the server admin can list unsynced folders");
1829
1926
  }
1830
1927
  return c.json({ folders: listUnsyncedFolders() });
1831
1928
  });
1832
1929
  nestRoutes.post("/unsynced/sync", async (c) => {
1833
1930
  const userId = c.get("userId");
1834
- if (config.AUTH_MODE !== "open" && !isLicenseAdminUserId(userId)) {
1931
+ if (config.AUTH_MODE !== "open" && !await isLicenseAdminUserId(userId)) {
1835
1932
  throw new ForbiddenError("Only the server admin can sync folders");
1836
1933
  }
1837
1934
  const body = await c.req.json();
@@ -1841,30 +1938,30 @@ nestRoutes.post("/unsynced/sync", async (c) => {
1841
1938
  const result = await syncUnsyncedFolder(
1842
1939
  userId,
1843
1940
  body.name,
1844
- resolveCallerEmail(userId)
1941
+ await resolveCallerEmail(userId)
1845
1942
  );
1846
1943
  return c.json(result, 201);
1847
1944
  });
1848
1945
  nestRoutes.get("/:nestId", async (c) => {
1849
1946
  const nestId = c.req.param("nestId");
1850
1947
  const userId = c.get("userId");
1851
- const permission = effectivePermission(nestId, userId);
1948
+ const permission = await effectivePermission(nestId, userId);
1852
1949
  if (permission === "none") {
1853
1950
  throw new NotFoundError("Nest not found");
1854
1951
  }
1855
- const email = resolveCallerEmail(userId);
1856
- let roles = resolveUserRoles(nestId, email);
1952
+ const email = await resolveCallerEmail(userId);
1953
+ let roles = await resolveUserRoles(nestId, email);
1857
1954
  if (permission === "owner" && !roles.includes("owner")) {
1858
1955
  roles = ["owner", ...roles];
1859
1956
  }
1860
- const myStewards = getStewardsForUser(nestId, email);
1861
- const nest = getNest(nestId);
1957
+ const myStewards = await getStewardsForUser(nestId, email);
1958
+ const nest = await getNest(nestId);
1862
1959
  return c.json({ nest, permission, roles, myStewards });
1863
1960
  });
1864
1961
  nestRoutes.patch("/:nestId", async (c) => {
1865
1962
  const nestId = c.req.param("nestId");
1866
1963
  const userId = c.get("userId");
1867
- const permission = effectivePermission(nestId, userId);
1964
+ const permission = await effectivePermission(nestId, userId);
1868
1965
  if (permission === "none") {
1869
1966
  throw new NotFoundError("Nest not found");
1870
1967
  }
@@ -1874,7 +1971,7 @@ nestRoutes.patch("/:nestId", async (c) => {
1874
1971
  );
1875
1972
  }
1876
1973
  const body = await c.req.json();
1877
- const nest = renameNest(nestId, {
1974
+ const nest = await renameNest(nestId, {
1878
1975
  name: body.name,
1879
1976
  description: body.description
1880
1977
  });
@@ -1883,13 +1980,13 @@ nestRoutes.patch("/:nestId", async (c) => {
1883
1980
  nestRoutes.delete("/:nestId", async (c) => {
1884
1981
  const nestId = c.req.param("nestId");
1885
1982
  const userId = c.get("userId");
1886
- const nest = getNest(nestId);
1983
+ const nest = await getNest(nestId);
1887
1984
  if (!nest) {
1888
1985
  throw new NotFoundError("Nest not found");
1889
1986
  }
1890
- const permission = effectivePermission(nestId, userId);
1987
+ const permission = await effectivePermission(nestId, userId);
1891
1988
  const isAnonOwned = nest.user_id === ANON_USER_ID;
1892
- const adminCaretaker = config.AUTH_MODE !== "open" && isAnonOwned && isLicenseAdminUserId(userId);
1989
+ const adminCaretaker = config.AUTH_MODE !== "open" && isAnonOwned && await isLicenseAdminUserId(userId);
1893
1990
  if (permission !== "owner" && !adminCaretaker) {
1894
1991
  throw new ForbiddenError(
1895
1992
  "You don't have permission to delete this nest. Only the nest owner can delete it."
@@ -1900,20 +1997,20 @@ nestRoutes.delete("/:nestId", async (c) => {
1900
1997
  });
1901
1998
  nestRoutes.get("/:nestId/settings", async (c) => {
1902
1999
  const nestId = c.req.param("nestId");
1903
- const permission = effectivePermission(nestId, c.get("userId"));
2000
+ const permission = await effectivePermission(nestId, c.get("userId"));
1904
2001
  if (permission === "none") {
1905
2002
  throw new NotFoundError("Nest not found");
1906
2003
  }
1907
2004
  return c.json({
1908
- stewardship_enabled: isStewardshipEnabled(nestId),
1909
- allow_self_approve: nestAllowsSelfApprove(nestId)
2005
+ stewardship_enabled: await isStewardshipEnabled(nestId),
2006
+ allow_self_approve: await nestAllowsSelfApprove(nestId)
1910
2007
  });
1911
2008
  });
1912
2009
  nestRoutes.patch("/:nestId/settings", async (c) => {
1913
2010
  const nestId = c.req.param("nestId");
1914
2011
  const userId = c.get("userId");
1915
- const isServerAdmin = isLicenseAdminUserId(userId);
1916
- const permission = effectivePermission(nestId, userId);
2012
+ const isServerAdmin = await isLicenseAdminUserId(userId);
2013
+ const permission = await effectivePermission(nestId, userId);
1917
2014
  if (!isServerAdmin && permission !== "owner") {
1918
2015
  return c.json(
1919
2016
  {
@@ -1926,17 +2023,17 @@ nestRoutes.patch("/:nestId/settings", async (c) => {
1926
2023
  let wiped = null;
1927
2024
  if (typeof body.stewardship_enabled === "boolean") {
1928
2025
  if (body.stewardship_enabled) {
1929
- setStewardshipEnabled(nestId, true);
2026
+ await setStewardshipEnabled(nestId, true);
1930
2027
  } else {
1931
- wiped = disableStewardshipAndWipeGovernance(nestId);
2028
+ wiped = await disableStewardshipAndWipeGovernance(nestId);
1932
2029
  }
1933
2030
  }
1934
2031
  if (typeof body.allow_self_approve === "boolean") {
1935
- setAllowSelfApprove(nestId, body.allow_self_approve);
2032
+ await setAllowSelfApprove(nestId, body.allow_self_approve);
1936
2033
  }
1937
2034
  return c.json({
1938
- stewardship_enabled: isStewardshipEnabled(nestId),
1939
- allow_self_approve: nestAllowsSelfApprove(nestId),
2035
+ stewardship_enabled: await isStewardshipEnabled(nestId),
2036
+ allow_self_approve: await nestAllowsSelfApprove(nestId),
1940
2037
  wiped
1941
2038
  });
1942
2039
  });
@@ -1961,21 +2058,28 @@ async function addCollaborator(params) {
1961
2058
  let userId = params.userId;
1962
2059
  if (!userId && params.email) {
1963
2060
  const email = normalizeEmail(params.email);
1964
- const existing = db.prepare("SELECT id FROM users WHERE LOWER(email) = ?").get(email);
2061
+ const existing = await db.get(
2062
+ "SELECT id FROM users WHERE LOWER(email) = ?",
2063
+ [email]
2064
+ );
1965
2065
  if (existing) {
1966
2066
  userId = existing.id;
1967
2067
  } else {
1968
2068
  const { hashPassword: hashPassword2 } = await import("./keys-73STFJJB.js");
1969
2069
  userId = uuid2();
1970
- db.prepare(
1971
- "INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)"
1972
- ).run(userId, email, null, await hashPassword2(uuid2()));
2070
+ await db.run(
2071
+ "INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)",
2072
+ [userId, email, null, await hashPassword2(uuid2())]
2073
+ );
1973
2074
  }
1974
2075
  }
1975
2076
  if (!userId) {
1976
2077
  throw new ValidationError("user_id or email is required");
1977
2078
  }
1978
- const ownerRow = db.prepare("SELECT user_id FROM nests WHERE id = ?").get(nestId);
2079
+ const ownerRow = await db.get(
2080
+ "SELECT user_id FROM nests WHERE id = ?",
2081
+ [nestId]
2082
+ );
1979
2083
  if (!ownerRow) {
1980
2084
  throw new ValidationError("Nest not found");
1981
2085
  }
@@ -1989,19 +2093,29 @@ async function addCollaborator(params) {
1989
2093
  if (selfByEmail || selfById) {
1990
2094
  throw new ValidationError("You can't add yourself as a collaborator.");
1991
2095
  }
1992
- const dupe = db.prepare("SELECT id FROM nest_collaborators WHERE nest_id = ? AND user_id = ?").get(nestId, userId);
2096
+ const dupe = await db.get(
2097
+ "SELECT id FROM nest_collaborators WHERE nest_id = ? AND user_id = ?",
2098
+ [nestId, userId]
2099
+ );
1993
2100
  if (dupe) {
1994
2101
  throw new ConflictError(
1995
2102
  `${params.email || "This user"} already has access to this nest. Change their permission instead of adding them again.`
1996
2103
  );
1997
2104
  }
1998
- const granterByEmail = !params.grantedByUserId && params.grantedByEmail ? db.prepare("SELECT id FROM users WHERE LOWER(email) = LOWER(?)").get(params.grantedByEmail)?.id : void 0;
2105
+ const granterByEmail = !params.grantedByUserId && params.grantedByEmail ? (await db.get(
2106
+ "SELECT id FROM users WHERE LOWER(email) = LOWER(?)",
2107
+ [params.grantedByEmail]
2108
+ ))?.id : void 0;
1999
2109
  const granterId = params.grantedByUserId || granterByEmail || ownerRow.user_id;
2000
2110
  const collabId = uuid2();
2001
- db.prepare(
2002
- "INSERT INTO nest_collaborators (id, nest_id, user_id, permission, granted_by) VALUES (?, ?, ?, ?, ?)"
2003
- ).run(collabId, nestId, userId, params.permission, granterId);
2004
- return db.prepare("SELECT * FROM nest_collaborators WHERE id = ?").get(collabId);
2111
+ await db.run(
2112
+ "INSERT INTO nest_collaborators (id, nest_id, user_id, permission, granted_by) VALUES (?, ?, ?, ?, ?)",
2113
+ [collabId, nestId, userId, params.permission, granterId]
2114
+ );
2115
+ return await db.get(
2116
+ "SELECT * FROM nest_collaborators WHERE id = ?",
2117
+ [collabId]
2118
+ );
2005
2119
  }
2006
2120
 
2007
2121
  // src/nests/sharing-routes.ts
@@ -2009,20 +2123,25 @@ var sharingRoutes = new Hono3();
2009
2123
  sharingRoutes.get("/collaborators", async (c) => {
2010
2124
  const db = getDb();
2011
2125
  const nestId = c.req.param("nestId");
2012
- const collabs = db.prepare(
2126
+ const collabs = await db.all(
2013
2127
  `SELECT nc.*, u.email FROM nest_collaborators nc
2014
2128
  LEFT JOIN users u ON nc.user_id = u.id
2015
2129
  WHERE nc.nest_id = ?
2016
- ORDER BY nc.granted_at`
2017
- ).all(nestId);
2018
- const enriched = collabs.map((collab) => {
2019
- if (!collab.email) return { ...collab, stewardRoles: [], roles: [] };
2020
- return {
2130
+ ORDER BY nc.granted_at`,
2131
+ [nestId]
2132
+ );
2133
+ const enriched = [];
2134
+ for (const collab of collabs) {
2135
+ if (!collab.email) {
2136
+ enriched.push({ ...collab, stewardRoles: [], roles: [] });
2137
+ continue;
2138
+ }
2139
+ enriched.push({
2021
2140
  ...collab,
2022
- stewardRoles: getStewardRolesForUser(nestId, collab.email),
2023
- roles: resolveUserRoles(nestId, collab.email)
2024
- };
2025
- });
2141
+ stewardRoles: await getStewardRolesForUser(nestId, collab.email),
2142
+ roles: await resolveUserRoles(nestId, collab.email)
2143
+ });
2144
+ }
2026
2145
  return c.json({ collaborators: enriched });
2027
2146
  });
2028
2147
  sharingRoutes.post("/collaborators", async (c) => {
@@ -2045,9 +2164,10 @@ sharingRoutes.patch("/collaborators/:collabId", async (c) => {
2045
2164
  throw new ValidationError("permission must be read, write, or admin");
2046
2165
  }
2047
2166
  const db = getDb();
2048
- const info = db.prepare(
2049
- "UPDATE nest_collaborators SET permission = ? WHERE id = ? AND nest_id = ?"
2050
- ).run(body.permission, c.req.param("collabId"), c.req.param("nestId"));
2167
+ const info = await db.run(
2168
+ "UPDATE nest_collaborators SET permission = ? WHERE id = ? AND nest_id = ?",
2169
+ [body.permission, c.req.param("collabId"), c.req.param("nestId")]
2170
+ );
2051
2171
  if (info.changes === 0) {
2052
2172
  throw new NotFoundError("Collaborator not found");
2053
2173
  }
@@ -2055,9 +2175,10 @@ sharingRoutes.patch("/collaborators/:collabId", async (c) => {
2055
2175
  });
2056
2176
  sharingRoutes.delete("/collaborators/:collabId", async (c) => {
2057
2177
  const db = getDb();
2058
- const info = db.prepare(
2059
- "DELETE FROM nest_collaborators WHERE id = ? AND nest_id = ?"
2060
- ).run(c.req.param("collabId"), c.req.param("nestId"));
2178
+ const info = await db.run(
2179
+ "DELETE FROM nest_collaborators WHERE id = ? AND nest_id = ?",
2180
+ [c.req.param("collabId"), c.req.param("nestId")]
2181
+ );
2061
2182
  if (info.changes === 0) {
2062
2183
  throw new NotFoundError("Collaborator not found");
2063
2184
  }
@@ -2069,10 +2190,10 @@ sharingRoutes.patch("/visibility", async (c) => {
2069
2190
  throw new ValidationError("visibility must be private or public");
2070
2191
  }
2071
2192
  const db = getDb();
2072
- db.prepare("UPDATE nests SET visibility = ? WHERE id = ?").run(
2193
+ await db.run("UPDATE nests SET visibility = ? WHERE id = ?", [
2073
2194
  body.visibility,
2074
2195
  c.req.param("nestId")
2075
- );
2196
+ ]);
2076
2197
  return c.json({ visibility: body.visibility });
2077
2198
  });
2078
2199
 
@@ -2122,7 +2243,7 @@ nodeRoutes.post("/", async (c) => {
2122
2243
  throw new ValidationError("title and content are required");
2123
2244
  }
2124
2245
  const nestId = c.req.param("nestId");
2125
- const authorEmail = getUserEmail(c);
2246
+ const authorEmail = await getUserEmail(c);
2126
2247
  const { node } = await createNode(
2127
2248
  nestId,
2128
2249
  {
@@ -2135,7 +2256,7 @@ nodeRoutes.post("/", async (c) => {
2135
2256
  },
2136
2257
  authorEmail
2137
2258
  );
2138
- const resolved = resolveStewardsForNode(nestId, node.id);
2259
+ const resolved = await resolveStewardsForNode(nestId, node.id);
2139
2260
  return c.json({
2140
2261
  node: toNodeResponse(node),
2141
2262
  stewards: resolved.length > 0 ? resolved.map((r) => ({
@@ -2148,8 +2269,8 @@ nodeRoutes.post("/", async (c) => {
2148
2269
  nodeRoutes.get("/:nodeId{.+?}/stewards", async (c) => {
2149
2270
  const nestId = c.req.param("nestId");
2150
2271
  const nodeId = c.req.param("nodeId");
2151
- const { resolveStewardsWithFallback: resolveStewardsWithFallback2 } = await import("./stewardship-service-P4M5UEJW.js");
2152
- const { stewards, fallbackToOwner, ownerEmail } = resolveStewardsWithFallback2(
2272
+ const { resolveStewardsWithFallback: resolveStewardsWithFallback2 } = await import("./stewardship-service-VIKF3VZB.js");
2273
+ const { stewards, fallbackToOwner, ownerEmail } = await resolveStewardsWithFallback2(
2153
2274
  nestId,
2154
2275
  nodeId
2155
2276
  );
@@ -2169,18 +2290,19 @@ nodeRoutes.get("/:nodeId{.+?}/stewards", async (c) => {
2169
2290
  nodeRoutes.get("/:nodeId{.+?}/versions", async (c) => {
2170
2291
  const nestId = c.req.param("nestId");
2171
2292
  const nodeId = c.req.param("nodeId");
2172
- const { getVersions: getVersions2, getApprovedVersion: getApprovedVersion2 } = await import("./version-service-REGL5CUT.js");
2173
- const allVersions = getVersions2(nestId, nodeId);
2174
- const approved = getApprovedVersion2(nestId, nodeId);
2293
+ const { getVersions: getVersions2, getApprovedVersion: getApprovedVersion2 } = await import("./version-service-MUZYTYMW.js");
2294
+ const allVersions = await getVersions2(nestId, nodeId);
2295
+ const approved = await getApprovedVersion2(nestId, nodeId);
2175
2296
  const db = getDb();
2176
- const resolutions = db.prepare(
2297
+ const resolutions = await db.all(
2177
2298
  `SELECT version, status, resolved_by, resolved_at
2178
2299
  FROM review_requests
2179
2300
  WHERE nest_id = ? AND node_id = ?
2180
2301
  AND status IN ('approved', 'rejected')
2181
2302
  AND resolved_by IS NOT NULL
2182
- ORDER BY resolved_at DESC`
2183
- ).all(nestId, nodeId);
2303
+ ORDER BY resolved_at DESC`,
2304
+ [nestId, nodeId]
2305
+ );
2184
2306
  const byVersion = /* @__PURE__ */ new Map();
2185
2307
  for (const r of resolutions) {
2186
2308
  if (!byVersion.has(r.version)) {
@@ -2200,17 +2322,17 @@ nodeRoutes.get("/:nodeId{.+?}/versions", async (c) => {
2200
2322
  nodeRoutes.get("/:nodeId{.+?}/reviews", async (c) => {
2201
2323
  const nestId = c.req.param("nestId");
2202
2324
  const nodeId = c.req.param("nodeId");
2203
- const { getReviewHistory: getReviewHistory2 } = await import("./review-service-XNXHF6Q7.js");
2204
- const history = getReviewHistory2(nestId, nodeId);
2325
+ const { getReviewHistory: getReviewHistory2 } = await import("./review-service-QJ3FBOML.js");
2326
+ const history = await getReviewHistory2(nestId, nodeId);
2205
2327
  return c.json({ reviews: history });
2206
2328
  });
2207
2329
  nodeRoutes.post("/:nodeId{.+}/revert", async (c) => {
2208
2330
  const nestId = c.req.param("nestId");
2209
2331
  const nodeId = c.req.param("nodeId");
2210
- const { versions: versionManager } = engineCache.get(nestId);
2332
+ const { versions: versionManager } = await engineCache.get(nestId);
2211
2333
  const userId = c.get("userId");
2212
- const userEmail = resolveCallerEmail(userId);
2213
- if (!canReadNode(nestId, nodeId, userId, userEmail)) {
2334
+ const userEmail = await resolveCallerEmail(userId);
2335
+ if (!await canReadNode(nestId, nodeId, userId, userEmail)) {
2214
2336
  return c.json(
2215
2337
  { error: "Access denied \u2014 no steward assignment for this node" },
2216
2338
  403
@@ -2236,16 +2358,16 @@ nodeRoutes.post("/:nodeId{.+}/revert", async (c) => {
2236
2358
  { content, changeNote: `Restored from version ${targetVersion}` },
2237
2359
  userEmail
2238
2360
  );
2239
- trackEvent("node.revert", { nestId, nodeId, targetVersion });
2361
+ await trackEvent("node.revert", { nestId, nodeId, targetVersion });
2240
2362
  return c.json({ ok: true, version, node: toNodeResponse(node) });
2241
2363
  });
2242
2364
  nodeRoutes.get("/:nodeId{.+}", async (c) => {
2243
2365
  const nestId = c.req.param("nestId");
2244
2366
  const nodeId = c.req.param("nodeId");
2245
- const { storage, versions: versionManager } = engineCache.get(nestId);
2367
+ const { storage, versions: versionManager } = await engineCache.get(nestId);
2246
2368
  const userId = c.get("userId");
2247
- const userEmail = resolveCallerEmail(userId);
2248
- if (!canReadNode(nestId, nodeId, userId, userEmail)) {
2369
+ const userEmail = await resolveCallerEmail(userId);
2370
+ if (!await canReadNode(nestId, nodeId, userId, userEmail)) {
2249
2371
  return c.json(
2250
2372
  { error: "Access denied \u2014 no steward assignment for this node" },
2251
2373
  403
@@ -2275,8 +2397,8 @@ nodeRoutes.get("/:nodeId{.+}", async (c) => {
2275
2397
  }
2276
2398
  }
2277
2399
  const response = toNodeResponse(node);
2278
- if (isPublicReader(nestId, userId)) {
2279
- const approved = getApprovedVersion(nestId, nodeId);
2400
+ if (await isPublicReader(nestId, userId)) {
2401
+ const approved = await getApprovedVersion(nestId, nodeId);
2280
2402
  if (approved != null) {
2281
2403
  try {
2282
2404
  const raw = await versionManager.reconstructVersion(
@@ -2303,9 +2425,9 @@ nodeRoutes.get("/:nodeId{.+}", async (c) => {
2303
2425
  return c.json({ node: response });
2304
2426
  }
2305
2427
  }
2306
- response.status = node.pendingChange ? "external_edit_pending" : getDisplayStatus(nestId, nodeId);
2428
+ response.status = node.pendingChange ? "external_edit_pending" : await getDisplayStatus(nestId, nodeId);
2307
2429
  if (response.status === "pending_review") {
2308
- const pending = getPendingReview(nestId, nodeId);
2430
+ const pending = await getPendingReview(nestId, nodeId);
2309
2431
  response.pendingReviewBy = pending?.requestedBy ?? null;
2310
2432
  }
2311
2433
  if (isMarkdownFormat(c)) {
@@ -2322,7 +2444,7 @@ nodeRoutes.patch("/:nodeId{.+}", async (c) => {
2322
2444
  const baseVersionHeader = c.req.header("X-Base-Version");
2323
2445
  if (baseVersionHeader) {
2324
2446
  const baseVersion = parseInt(baseVersionHeader, 10);
2325
- const conflict = checkConflict(nestId, nodeId, baseVersion);
2447
+ const conflict = await checkConflict(nestId, nodeId, baseVersion);
2326
2448
  if (conflict.conflict) {
2327
2449
  return c.json(
2328
2450
  {
@@ -2337,7 +2459,7 @@ nodeRoutes.patch("/:nodeId{.+}", async (c) => {
2337
2459
  );
2338
2460
  }
2339
2461
  }
2340
- const authorEmail = getUserEmail(c);
2462
+ const authorEmail = await getUserEmail(c);
2341
2463
  const { node, version: responseVersion } = await updateNode(
2342
2464
  nestId,
2343
2465
  nodeId,
@@ -2356,8 +2478,8 @@ nodeRoutes.patch("/:nodeId{.+}", async (c) => {
2356
2478
  nodeRoutes.delete("/:nodeId{.+}", async (c) => {
2357
2479
  const nestId = c.req.param("nestId");
2358
2480
  const nodeId = c.req.param("nodeId");
2359
- const { storage } = engineCache.get(nestId);
2360
- if (isStewardshipEnabled(nestId) && getPendingReview(nestId, nodeId)) {
2481
+ const { storage } = await engineCache.get(nestId);
2482
+ if (await isStewardshipEnabled(nestId) && await getPendingReview(nestId, nodeId)) {
2361
2483
  throw new LockedError(
2362
2484
  "This document is awaiting steward review and is locked. Approve or reject the pending review before deleting."
2363
2485
  );
@@ -2367,30 +2489,34 @@ nodeRoutes.delete("/:nodeId{.+}", async (c) => {
2367
2489
  } catch {
2368
2490
  throw new NotFoundError(`Node not found: ${nodeId}`);
2369
2491
  }
2370
- removeNodeFromTagIndex(nestId, nodeId);
2492
+ await removeNodeFromTagIndex(nestId, nodeId);
2371
2493
  const db = getDb();
2372
- db.transaction(() => {
2373
- db.prepare(
2374
- "DELETE FROM node_versions WHERE nest_id = ? AND node_id = ?"
2375
- ).run(nestId, nodeId);
2376
- db.prepare(
2377
- "DELETE FROM review_requests WHERE nest_id = ? AND node_id = ?"
2378
- ).run(nestId, nodeId);
2379
- db.prepare(
2380
- "DELETE FROM approved_versions WHERE nest_id = ? AND node_id = ?"
2381
- ).run(nestId, nodeId);
2382
- db.prepare(
2494
+ await db.transaction(async (tx) => {
2495
+ await tx.run(
2496
+ "DELETE FROM node_versions WHERE nest_id = ? AND node_id = ?",
2497
+ [nestId, nodeId]
2498
+ );
2499
+ await tx.run(
2500
+ "DELETE FROM review_requests WHERE nest_id = ? AND node_id = ?",
2501
+ [nestId, nodeId]
2502
+ );
2503
+ await tx.run(
2504
+ "DELETE FROM approved_versions WHERE nest_id = ? AND node_id = ?",
2505
+ [nestId, nodeId]
2506
+ );
2507
+ await tx.run(
2383
2508
  `DELETE FROM stewards
2384
- WHERE nest_id = ? AND scope = 'document' AND node_pattern = ?`
2385
- ).run(nestId, nodeId);
2386
- })();
2387
- trackEvent("node.delete", { nestId, nodeId });
2509
+ WHERE nest_id = ? AND scope = 'document' AND node_pattern = ?`,
2510
+ [nestId, nodeId]
2511
+ );
2512
+ });
2513
+ await trackEvent("node.delete", { nestId, nodeId });
2388
2514
  return c.json({ deleted: true });
2389
2515
  });
2390
- function getUserEmail(c) {
2516
+ async function getUserEmail(c) {
2391
2517
  const userId = c.get("userId");
2392
2518
  const db = getDb();
2393
- const user = db.prepare("SELECT email FROM users WHERE id = ?").get(userId);
2519
+ const user = await db.get("SELECT email FROM users WHERE id = ?", [userId]);
2394
2520
  return user?.email || "anonymous@localhost";
2395
2521
  }
2396
2522
 
@@ -2484,11 +2610,12 @@ function projectThreadsToMarkdown(artifactTitle, threads) {
2484
2610
  }
2485
2611
 
2486
2612
  // src/annotations/service.ts
2487
- function loadComments(threadId) {
2613
+ async function loadComments(threadId) {
2488
2614
  const db = getDb();
2489
- const rows = db.prepare(
2490
- "SELECT id, author, body, created_at FROM annotation_comments WHERE thread_id = ? ORDER BY created_at ASC, rowid ASC"
2491
- ).all(threadId);
2615
+ const rows = await db.all(
2616
+ "SELECT id, author, body, created_at FROM annotation_comments WHERE thread_id = ? ORDER BY created_at ASC, rowid ASC",
2617
+ [threadId]
2618
+ );
2492
2619
  return rows.map((r) => ({
2493
2620
  id: r.id,
2494
2621
  author: r.author,
@@ -2496,7 +2623,7 @@ function loadComments(threadId) {
2496
2623
  createdAt: r.created_at
2497
2624
  }));
2498
2625
  }
2499
- function rowToThread(row) {
2626
+ async function rowToThread(row) {
2500
2627
  let anchor = null;
2501
2628
  if (row.anchor_json) {
2502
2629
  try {
@@ -2516,19 +2643,20 @@ function rowToThread(row) {
2516
2643
  createdAt: row.created_at,
2517
2644
  resolvedBy: row.resolved_by,
2518
2645
  resolvedAt: row.resolved_at,
2519
- comments: loadComments(row.id)
2646
+ comments: await loadComments(row.id)
2520
2647
  };
2521
2648
  }
2522
- function getThreadRow(threadId) {
2523
- return getDb().prepare("SELECT * FROM annotation_threads WHERE id = ?").get(threadId);
2649
+ async function getThreadRow(threadId) {
2650
+ return await getDb().get("SELECT * FROM annotation_threads WHERE id = ?", [threadId]);
2524
2651
  }
2525
- function listThreads(nestId, nodeId) {
2526
- const rows = getDb().prepare(
2527
- "SELECT * FROM annotation_threads WHERE nest_id = ? AND node_id = ? ORDER BY created_at ASC, rowid ASC"
2528
- ).all(nestId, nodeId);
2529
- return rows.map(rowToThread);
2652
+ async function listThreads(nestId, nodeId) {
2653
+ const rows = await getDb().all(
2654
+ "SELECT * FROM annotation_threads WHERE nest_id = ? AND node_id = ? ORDER BY created_at ASC, rowid ASC",
2655
+ [nestId, nodeId]
2656
+ );
2657
+ return Promise.all(rows.map(rowToThread));
2530
2658
  }
2531
- function createThread(nestId, nodeId, input, authorEmail) {
2659
+ async function createThread(nestId, nodeId, input, authorEmail) {
2532
2660
  const body = (input.body ?? "").trim();
2533
2661
  if (!body) {
2534
2662
  throw new Error("comment body is required");
@@ -2536,61 +2664,66 @@ function createThread(nestId, nodeId, input, authorEmail) {
2536
2664
  const db = getDb();
2537
2665
  const id = uuid3();
2538
2666
  const anchor = clampAnchor(input.anchor);
2539
- const snapshot = input.snapshotVersion ?? getApprovedVersion(nestId, nodeId) ?? null;
2540
- const tx = db.transaction(() => {
2541
- db.prepare(
2667
+ const snapshot = input.snapshotVersion ?? await getApprovedVersion(nestId, nodeId) ?? null;
2668
+ await db.transaction(async (tx) => {
2669
+ await tx.run(
2542
2670
  `INSERT INTO annotation_threads
2543
2671
  (id, nest_id, node_id, snapshot_version, anchor_json, status, created_by)
2544
- VALUES (?, ?, ?, ?, ?, 'open', ?)`
2545
- ).run(
2546
- id,
2547
- nestId,
2548
- nodeId,
2549
- snapshot,
2550
- anchor ? JSON.stringify(anchor) : null,
2551
- authorEmail
2672
+ VALUES (?, ?, ?, ?, ?, 'open', ?)`,
2673
+ [
2674
+ id,
2675
+ nestId,
2676
+ nodeId,
2677
+ snapshot,
2678
+ anchor ? JSON.stringify(anchor) : null,
2679
+ authorEmail
2680
+ ]
2681
+ );
2682
+ await tx.run(
2683
+ "INSERT INTO annotation_comments (id, thread_id, author, body) VALUES (?, ?, ?, ?)",
2684
+ [uuid3(), id, authorEmail, body]
2552
2685
  );
2553
- db.prepare(
2554
- "INSERT INTO annotation_comments (id, thread_id, author, body) VALUES (?, ?, ?, ?)"
2555
- ).run(uuid3(), id, authorEmail, body);
2556
2686
  });
2557
- tx();
2558
- return rowToThread(getThreadRow(id));
2687
+ return rowToThread(await getThreadRow(id));
2559
2688
  }
2560
- function getScopedThreadRow(threadId, nestId, nodeId) {
2561
- const row = getThreadRow(threadId);
2689
+ async function getScopedThreadRow(threadId, nestId, nodeId) {
2690
+ const row = await getThreadRow(threadId);
2562
2691
  if (!row || row.nest_id !== nestId || row.node_id !== nodeId) {
2563
2692
  throw new NotFoundError(`Thread not found: ${threadId}`);
2564
2693
  }
2565
2694
  return row;
2566
2695
  }
2567
- function addComment(nestId, nodeId, threadId, authorEmail, body) {
2696
+ async function addComment(nestId, nodeId, threadId, authorEmail, body) {
2568
2697
  const trimmed = (body ?? "").trim();
2569
2698
  if (!trimmed) {
2570
2699
  throw new Error("comment body is required");
2571
2700
  }
2572
- getScopedThreadRow(threadId, nestId, nodeId);
2573
- getDb().prepare(
2574
- "INSERT INTO annotation_comments (id, thread_id, author, body) VALUES (?, ?, ?, ?)"
2575
- ).run(uuid3(), threadId, authorEmail, trimmed);
2576
- return rowToThread(getThreadRow(threadId));
2701
+ await getScopedThreadRow(threadId, nestId, nodeId);
2702
+ await getDb().run(
2703
+ "INSERT INTO annotation_comments (id, thread_id, author, body) VALUES (?, ?, ?, ?)",
2704
+ [uuid3(), threadId, authorEmail, trimmed]
2705
+ );
2706
+ return rowToThread(await getThreadRow(threadId));
2577
2707
  }
2578
- function setThreadStatus(nestId, nodeId, threadId, status, byEmail) {
2579
- getScopedThreadRow(threadId, nestId, nodeId);
2708
+ async function setThreadStatus(nestId, nodeId, threadId, status, byEmail) {
2709
+ await getScopedThreadRow(threadId, nestId, nodeId);
2710
+ const db = getDb();
2580
2711
  if (status === "resolved") {
2581
- getDb().prepare(
2582
- "UPDATE annotation_threads SET status = 'resolved', resolved_by = ?, resolved_at = datetime('now') WHERE id = ?"
2583
- ).run(byEmail, threadId);
2712
+ await db.run(
2713
+ `UPDATE annotation_threads SET status = 'resolved', resolved_by = ?, resolved_at = ${nowExpr(db)} WHERE id = ?`,
2714
+ [byEmail, threadId]
2715
+ );
2584
2716
  } else {
2585
- getDb().prepare(
2586
- "UPDATE annotation_threads SET status = 'open', resolved_by = NULL, resolved_at = NULL WHERE id = ?"
2587
- ).run(threadId);
2717
+ await db.run(
2718
+ "UPDATE annotation_threads SET status = 'open', resolved_by = NULL, resolved_at = NULL WHERE id = ?",
2719
+ [threadId]
2720
+ );
2588
2721
  }
2589
- return rowToThread(getThreadRow(threadId));
2722
+ return rowToThread(await getThreadRow(threadId));
2590
2723
  }
2591
2724
  async function readArtifact(nestId, nodeId, version) {
2592
- const { storage, versions: versionManager } = engineCache.get(nestId);
2593
- const target = version ?? getApprovedVersion(nestId, nodeId) ?? null;
2725
+ const { storage, versions: versionManager } = await engineCache.get(nestId);
2726
+ const target = version ?? await getApprovedVersion(nestId, nodeId) ?? null;
2594
2727
  let title = nodeId;
2595
2728
  let type = null;
2596
2729
  let liveHtml = "";
@@ -2627,12 +2760,12 @@ async function syncAnnotationsNode(nestId, nodeId, userEmail) {
2627
2760
  try {
2628
2761
  let title = nodeId;
2629
2762
  try {
2630
- const { storage } = engineCache.get(nestId);
2763
+ const { storage } = await engineCache.get(nestId);
2631
2764
  const node = await storage.readDocument(nodeId);
2632
2765
  title = node.frontmatter?.title || nodeId;
2633
2766
  } catch {
2634
2767
  }
2635
- const threads = listThreads(nestId, nodeId);
2768
+ const threads = await listThreads(nestId, nodeId);
2636
2769
  const derivedTitle = `${title} \u2014 Annotations`;
2637
2770
  const markdown = projectThreadsToMarkdown(title, threads);
2638
2771
  const derivedId = derivedAnnotationsId(nodeId);
@@ -2680,25 +2813,25 @@ annotationRoutes.get("/:nodeId{.+}/annotations", async (c) => {
2680
2813
  const nestId = c.req.param("nestId");
2681
2814
  const nodeId = getNodeId(c);
2682
2815
  const userId = c.get("userId");
2683
- const userEmail = resolveCallerEmail(userId);
2684
- if (!canReadNode(nestId, nodeId, userId, userEmail)) {
2816
+ const userEmail = await resolveCallerEmail(userId);
2817
+ if (!await canReadNode(nestId, nodeId, userId, userEmail)) {
2685
2818
  return c.json({ error: "Access denied" }, 403);
2686
2819
  }
2687
- return c.json({ threads: listThreads(nestId, nodeId) });
2820
+ return c.json({ threads: await listThreads(nestId, nodeId) });
2688
2821
  });
2689
2822
  annotationRoutes.post("/:nodeId{.+}/annotations", async (c) => {
2690
2823
  const nestId = c.req.param("nestId");
2691
2824
  const nodeId = getNodeId(c);
2692
2825
  const userId = c.get("userId");
2693
- const userEmail = resolveCallerEmail(userId);
2694
- if (!canReadNode(nestId, nodeId, userId, userEmail)) {
2826
+ const userEmail = await resolveCallerEmail(userId);
2827
+ if (!await canReadNode(nestId, nodeId, userId, userEmail)) {
2695
2828
  return c.json({ error: "Access denied" }, 403);
2696
2829
  }
2697
2830
  const input = await c.req.json();
2698
2831
  if (!input.body || !input.body.trim()) {
2699
2832
  throw new ValidationError("body is required");
2700
2833
  }
2701
- const thread = createThread(
2834
+ const thread = await createThread(
2702
2835
  nestId,
2703
2836
  nodeId,
2704
2837
  {
@@ -2716,15 +2849,15 @@ annotationRoutes.post("/:nodeId{.+}/annotations/:threadId/comments", async (c) =
2716
2849
  const nodeId = getNodeId(c);
2717
2850
  const threadId = c.req.param("threadId");
2718
2851
  const userId = c.get("userId");
2719
- const userEmail = resolveCallerEmail(userId);
2720
- if (!canReadNode(nestId, nodeId, userId, userEmail)) {
2852
+ const userEmail = await resolveCallerEmail(userId);
2853
+ if (!await canReadNode(nestId, nodeId, userId, userEmail)) {
2721
2854
  return c.json({ error: "Access denied" }, 403);
2722
2855
  }
2723
2856
  const input = await c.req.json();
2724
2857
  if (!input.body || !input.body.trim()) {
2725
2858
  throw new ValidationError("body is required");
2726
2859
  }
2727
- const thread = addComment(nestId, nodeId, threadId, userEmail, input.body);
2860
+ const thread = await addComment(nestId, nodeId, threadId, userEmail, input.body);
2728
2861
  await syncAnnotationsNode(nestId, nodeId, userEmail);
2729
2862
  return c.json({ thread });
2730
2863
  });
@@ -2733,11 +2866,11 @@ annotationRoutes.post("/:nodeId{.+}/annotations/:threadId/resolve", async (c) =>
2733
2866
  const nodeId = getNodeId(c);
2734
2867
  const threadId = c.req.param("threadId");
2735
2868
  const userId = c.get("userId");
2736
- const userEmail = resolveCallerEmail(userId);
2737
- if (!canReadNode(nestId, nodeId, userId, userEmail)) {
2869
+ const userEmail = await resolveCallerEmail(userId);
2870
+ if (!await canReadNode(nestId, nodeId, userId, userEmail)) {
2738
2871
  return c.json({ error: "Access denied" }, 403);
2739
2872
  }
2740
- const thread = setThreadStatus(nestId, nodeId, threadId, "resolved", userEmail);
2873
+ const thread = await setThreadStatus(nestId, nodeId, threadId, "resolved", userEmail);
2741
2874
  await syncAnnotationsNode(nestId, nodeId, userEmail);
2742
2875
  return c.json({ thread });
2743
2876
  });
@@ -2746,11 +2879,11 @@ annotationRoutes.post("/:nodeId{.+}/annotations/:threadId/reopen", async (c) =>
2746
2879
  const nodeId = getNodeId(c);
2747
2880
  const threadId = c.req.param("threadId");
2748
2881
  const userId = c.get("userId");
2749
- const userEmail = resolveCallerEmail(userId);
2750
- if (!canReadNode(nestId, nodeId, userId, userEmail)) {
2882
+ const userEmail = await resolveCallerEmail(userId);
2883
+ if (!await canReadNode(nestId, nodeId, userId, userEmail)) {
2751
2884
  return c.json({ error: "Access denied" }, 403);
2752
2885
  }
2753
- const thread = setThreadStatus(nestId, nodeId, threadId, "open", userEmail);
2886
+ const thread = await setThreadStatus(nestId, nodeId, threadId, "open", userEmail);
2754
2887
  await syncAnnotationsNode(nestId, nodeId, userEmail);
2755
2888
  return c.json({ thread });
2756
2889
  });
@@ -2758,8 +2891,8 @@ annotationRoutes.get("/:nodeId{.+}/hosted", async (c) => {
2758
2891
  const nestId = c.req.param("nestId");
2759
2892
  const nodeId = getNodeId(c);
2760
2893
  const userId = c.get("userId");
2761
- const userEmail = resolveCallerEmail(userId);
2762
- if (!canReadNode(nestId, nodeId, userId, userEmail)) {
2894
+ const userEmail = await resolveCallerEmail(userId);
2895
+ if (!await canReadNode(nestId, nodeId, userId, userEmail)) {
2763
2896
  return c.json({ error: "Access denied" }, 403);
2764
2897
  }
2765
2898
  const vRaw = c.req.query("v");
@@ -2950,15 +3083,16 @@ function tokenizePrompt(prompt) {
2950
3083
  const words = prompt.toLowerCase().split(/[^a-z0-9_-]+/).filter(Boolean).filter((w) => w.length >= 3 && !STOPWORDS.has(w));
2951
3084
  return Array.from(new Set(words));
2952
3085
  }
2953
- function compilePrompt(prompt, nestId, titles) {
3086
+ async function compilePrompt(prompt, nestId, titles) {
2954
3087
  const tokens = tokenizePrompt(prompt);
2955
3088
  if (tokens.length === 0) {
2956
3089
  return { selector: null, matchedTags: [], matchedTitles: [], unmatched: [] };
2957
3090
  }
2958
3091
  const db = getDb();
2959
- const tagRows = db.prepare(
2960
- "SELECT DISTINCT tag_name FROM node_tag_index WHERE nest_id = ?"
2961
- ).all(nestId);
3092
+ const tagRows = await db.all(
3093
+ "SELECT DISTINCT tag_name FROM node_tag_index WHERE nest_id = ?",
3094
+ [nestId]
3095
+ );
2962
3096
  const knownTags = new Set(tagRows.map((r) => r.tag_name));
2963
3097
  const matchedTags = /* @__PURE__ */ new Set();
2964
3098
  for (const token of tokens) {
@@ -2988,11 +3122,11 @@ function compilePrompt(prompt, nestId, titles) {
2988
3122
 
2989
3123
  // src/nodes/readable-body.ts
2990
3124
  async function resolveReadableBody(nestId, nodeId, userId, workingBody) {
2991
- if (!isPublicReader(nestId, userId)) return workingBody;
2992
- const approved = getApprovedVersion(nestId, nodeId);
3125
+ if (!await isPublicReader(nestId, userId)) return workingBody;
3126
+ const approved = await getApprovedVersion(nestId, nodeId);
2993
3127
  if (approved == null) return "";
2994
3128
  try {
2995
- const { versions } = engineCache.get(nestId);
3129
+ const { versions } = await engineCache.get(nestId);
2996
3130
  const raw = await versions.reconstructVersion(nodeId, approved);
2997
3131
  return bodyOnly(nodeId, raw);
2998
3132
  } catch {
@@ -3000,11 +3134,11 @@ async function resolveReadableBody(nestId, nodeId, userId, workingBody) {
3000
3134
  }
3001
3135
  }
3002
3136
  async function resolveExportBody(nestId, nodeId, workingBody) {
3003
- if (!isStewardshipEnabled(nestId)) return workingBody;
3004
- const approved = getApprovedVersion(nestId, nodeId);
3137
+ if (!await isStewardshipEnabled(nestId)) return workingBody;
3138
+ const approved = await getApprovedVersion(nestId, nodeId);
3005
3139
  if (approved == null) return null;
3006
3140
  try {
3007
- const { versions } = engineCache.get(nestId);
3141
+ const { versions } = await engineCache.get(nestId);
3008
3142
  const raw = await versions.reconstructVersion(nodeId, approved);
3009
3143
  return bodyOnly(nodeId, raw);
3010
3144
  } catch {
@@ -3027,9 +3161,9 @@ function extractWikiTargets(body) {
3027
3161
  return out;
3028
3162
  }
3029
3163
  async function buildNestGraph(nestId, userId, userEmail, canSeeIdentities) {
3030
- const { storage } = engineCache.get(nestId);
3164
+ const { storage } = await engineCache.get(nestId);
3031
3165
  const docs = await storage.discoverDocuments();
3032
- const accessible = filterAccessible(nestId, userId, userEmail, docs);
3166
+ const accessible = await filterAccessible(nestId, userId, userEmail, docs);
3033
3167
  const idSet = new Set(accessible.map((d) => d.id));
3034
3168
  const titleToId = /* @__PURE__ */ new Map();
3035
3169
  for (const d of accessible) {
@@ -3065,7 +3199,7 @@ async function buildNestGraph(nestId, userId, userEmail, canSeeIdentities) {
3065
3199
  }
3066
3200
  }
3067
3201
  const tags = [...tagCounts.entries()].map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
3068
- const exposeStewards = isStewardshipEnabled(nestId) && !isPublicReader(nestId, userId);
3202
+ const exposeStewards = await isStewardshipEnabled(nestId) && !await isPublicReader(nestId, userId);
3069
3203
  const stewardRows = exposeStewards ? (await listStewards({ nestId })).filter(
3070
3204
  // Drop document-scoped rows whose node the caller can't see.
3071
3205
  (s) => s.scope !== "document" || idSet.has(s.nodePattern || "")
@@ -3159,7 +3293,7 @@ var queryRoutes = new Hono6();
3159
3293
  queryRoutes.get("/graph", async (c) => {
3160
3294
  const nestId = c.req.param("nestId");
3161
3295
  const userId = c.get("userId");
3162
- const userEmail = resolveCallerEmail(userId);
3296
+ const userEmail = await resolveCallerEmail(userId);
3163
3297
  const canSeeIdentities = permissionLevel(c.get("nestPermission")) >= permissionLevel("write");
3164
3298
  const graph = await buildNestGraph(nestId, userId, userEmail, canSeeIdentities);
3165
3299
  return c.json(graph);
@@ -3184,7 +3318,7 @@ queryRoutes.post("/context", async (c) => {
3184
3318
  throw new ValidationError("prompt or selector is required");
3185
3319
  }
3186
3320
  const nestId = c.req.param("nestId");
3187
- const { query: queryEngine, storage } = engineCache.get(nestId);
3321
+ const { query: queryEngine, storage } = await engineCache.get(nestId);
3188
3322
  const maxTokens = Math.max(50, body.max_tokens ?? 4e3);
3189
3323
  const hops = body.hops ?? 2;
3190
3324
  const includeDrafts = body.include_drafts === true;
@@ -3194,7 +3328,7 @@ queryRoutes.post("/context", async (c) => {
3194
3328
  const allDocs = await storage.discoverDocuments();
3195
3329
  if (!selector && body.prompt) {
3196
3330
  const titles = allDocs.map((d) => d.frontmatter.title);
3197
- compileDetail = compilePrompt(body.prompt, nestId, titles);
3331
+ compileDetail = await compilePrompt(body.prompt, nestId, titles);
3198
3332
  selector = compileDetail.selector;
3199
3333
  if (compileDetail.matchedTitles.length > 0) {
3200
3334
  const matchedLower = new Set(
@@ -3228,9 +3362,9 @@ queryRoutes.post("/context", async (c) => {
3228
3362
  }
3229
3363
  }
3230
3364
  const userId = c.get("userId");
3231
- const userEmail = resolveCallerEmail(userId);
3365
+ const userEmail = await resolveCallerEmail(userId);
3232
3366
  const beforePermission = documents.length;
3233
- const accessible = filterAccessible(nestId, userId, userEmail, documents);
3367
+ const accessible = await filterAccessible(nestId, userId, userEmail, documents);
3234
3368
  const permissionFiltered = beforePermission - accessible.length;
3235
3369
  const readable = await Promise.all(
3236
3370
  accessible.map(async (doc) => ({
@@ -3289,13 +3423,13 @@ queryRoutes.post("/query", async (c) => {
3289
3423
  throw new ValidationError("query is required");
3290
3424
  }
3291
3425
  const nestId = c.req.param("nestId");
3292
- const { query: queryEngine } = engineCache.get(nestId);
3426
+ const { query: queryEngine } = await engineCache.get(nestId);
3293
3427
  const result = await queryEngine.query(body.query, {
3294
3428
  hops: body.hops ?? 2
3295
3429
  });
3296
3430
  const userId = c.get("userId");
3297
- const userEmail = resolveCallerEmail(userId);
3298
- const accessible = filterAccessible(nestId, userId, userEmail, result.documents);
3431
+ const userEmail = await resolveCallerEmail(userId);
3432
+ const accessible = await filterAccessible(nestId, userId, userEmail, result.documents);
3299
3433
  return c.json({
3300
3434
  query: body.query,
3301
3435
  count: accessible.length,
@@ -3314,7 +3448,7 @@ queryRoutes.get("/search", async (c) => {
3314
3448
  throw new ValidationError("q query parameter is required");
3315
3449
  }
3316
3450
  const nestId = c.req.param("nestId");
3317
- const { storage } = engineCache.get(nestId);
3451
+ const { storage } = await engineCache.get(nestId);
3318
3452
  const documents = await storage.discoverDocuments();
3319
3453
  const terms = q.toLowerCase().split(/\s+/).filter(Boolean);
3320
3454
  const matches = documents.filter((node) => {
@@ -3327,8 +3461,8 @@ queryRoutes.get("/search", async (c) => {
3327
3461
  return terms.every((term) => haystack.includes(term));
3328
3462
  });
3329
3463
  const userId = c.get("userId");
3330
- const userEmail = resolveCallerEmail(userId);
3331
- const accessible = filterAccessible(nestId, userId, userEmail, matches);
3464
+ const userEmail = await resolveCallerEmail(userId);
3465
+ const accessible = await filterAccessible(nestId, userId, userEmail, matches);
3332
3466
  return c.json({
3333
3467
  query: q,
3334
3468
  count: accessible.length,
@@ -3342,7 +3476,7 @@ queryRoutes.get("/search", async (c) => {
3342
3476
  });
3343
3477
  });
3344
3478
  queryRoutes.get("/overview", async (c) => {
3345
- const { storage } = engineCache.get(c.req.param("nestId"));
3479
+ const { storage } = await engineCache.get(c.req.param("nestId"));
3346
3480
  const documents = await storage.discoverDocuments();
3347
3481
  const types = {};
3348
3482
  const tags = {};
@@ -3367,7 +3501,7 @@ queryRoutes.get("/overview", async (c) => {
3367
3501
  });
3368
3502
  });
3369
3503
  queryRoutes.get("/context", async (c) => {
3370
- const { storage } = engineCache.get(c.req.param("nestId"));
3504
+ const { storage } = await engineCache.get(c.req.param("nestId"));
3371
3505
  const content = await storage.readContextMd();
3372
3506
  return c.json({ content: content || "" });
3373
3507
  });
@@ -3376,7 +3510,7 @@ queryRoutes.get("/export", async (c) => {
3376
3510
  throw new ValidationError("format=markdown is required");
3377
3511
  }
3378
3512
  const nestId = c.req.param("nestId");
3379
- const { storage, query: queryEngine } = engineCache.get(nestId);
3513
+ const { storage, query: queryEngine } = await engineCache.get(nestId);
3380
3514
  const selector = c.req.query("selector")?.trim() || null;
3381
3515
  let documents;
3382
3516
  if (selector) {
@@ -3386,9 +3520,9 @@ queryRoutes.get("/export", async (c) => {
3386
3520
  documents = await storage.discoverDocuments();
3387
3521
  }
3388
3522
  const userId = c.get("userId");
3389
- const userEmail = resolveCallerEmail(userId);
3390
- const accessible = filterAccessible(nestId, userId, userEmail, documents);
3391
- const governed = isStewardshipEnabled(nestId);
3523
+ const userEmail = await resolveCallerEmail(userId);
3524
+ const accessible = await filterAccessible(nestId, userId, userEmail, documents);
3525
+ const governed = await isStewardshipEnabled(nestId);
3392
3526
  const resolved = await Promise.all(
3393
3527
  accessible.map(async (n) => {
3394
3528
  const body = await resolveExportBody(nestId, n.id, n.body || "");
@@ -3435,7 +3569,7 @@ queryRoutes.post("/publish", async (c) => {
3435
3569
  throw new ValidationError("documents array or context_md is required");
3436
3570
  }
3437
3571
  const nestId = c.req.param("nestId");
3438
- const { storage } = engineCache.get(nestId);
3572
+ const { storage } = await engineCache.get(nestId);
3439
3573
  const created = [];
3440
3574
  if (body.context_md) {
3441
3575
  await storage.writeContextMd(body.context_md);
@@ -3467,7 +3601,7 @@ queryRoutes.post("/publish", async (c) => {
3467
3601
  };
3468
3602
  const serialized = serializeDocument2(node);
3469
3603
  await storage.writeDocument(id, serialized);
3470
- syncNodeTags(nestId, id, tags);
3604
+ await syncNodeTags(nestId, id, tags);
3471
3605
  created.push(id);
3472
3606
  }
3473
3607
  trackEvent("nest.publish", { nestId, count: created.length });
@@ -3737,8 +3871,8 @@ var TOOL_DEFINITIONS = [
3737
3871
  }
3738
3872
  ];
3739
3873
  async function resolveLlmBody(ctx, node) {
3740
- if (!isStewardshipEnabled(ctx.nestId)) return node.body || "";
3741
- const approved = getApprovedVersion(ctx.nestId, node.id);
3874
+ if (!await isStewardshipEnabled(ctx.nestId)) return node.body || "";
3875
+ const approved = await getApprovedVersion(ctx.nestId, node.id);
3742
3876
  if (approved == null) return null;
3743
3877
  try {
3744
3878
  return await ctx.versionManager.reconstructVersion(node.id, approved);
@@ -3877,7 +4011,7 @@ ${n.body || ""}`;
3877
4011
  return resolved.join("\n\n---\n\n") || "No nodes resolved.";
3878
4012
  }
3879
4013
  case "context_create": {
3880
- if (!canCreateInNest(nestId, userEmail)) {
4014
+ if (!await canCreateInNest(nestId, userEmail)) {
3881
4015
  return "You don't have permission to create documents in this nest.";
3882
4016
  }
3883
4017
  const { node } = await createNode(
@@ -3899,7 +4033,7 @@ ${n.body || ""}`;
3899
4033
  (n) => n.frontmatter.title.toLowerCase() === args.title.toLowerCase()
3900
4034
  );
3901
4035
  if (!node) return `Node not found: ${args.title}`;
3902
- const editCheck = canUserEdit(nestId, node.id, userEmail);
4036
+ const editCheck = await canUserEdit(nestId, node.id, userEmail);
3903
4037
  if (!editCheck.allowed) {
3904
4038
  return `You don't have permission to edit "${args.title}": ${editCheck.reason}`;
3905
4039
  }
@@ -3923,7 +4057,7 @@ ${n.body || ""}`;
3923
4057
  (n) => n.frontmatter.title.toLowerCase() === args.title.toLowerCase()
3924
4058
  );
3925
4059
  const nodeId = node?.id || "";
3926
- const resolved = resolveStewardsForNode(ctx.nestId, nodeId);
4060
+ const resolved = await resolveStewardsForNode(ctx.nestId, nodeId);
3927
4061
  if (resolved.length === 0) {
3928
4062
  return `No stewards configured for "${args.title}". Changes are auto-approved.`;
3929
4063
  }
@@ -3979,14 +4113,14 @@ ${list}`;
3979
4113
  (n) => n.frontmatter.title.toLowerCase() === args.title.toLowerCase()
3980
4114
  );
3981
4115
  if (!node) return `Node not found: ${args.title}`;
3982
- const submitCheck = canUserEdit(ctx.nestId, node.id, userEmail);
4116
+ const submitCheck = await canUserEdit(ctx.nestId, node.id, userEmail);
3983
4117
  if (!submitCheck.allowed) {
3984
4118
  return `You don't have permission to submit "${args.title}" for review: ${submitCheck.reason}`;
3985
4119
  }
3986
- const currentVersion = getCurrentVersion(ctx.nestId, node.id);
4120
+ const currentVersion = await getCurrentVersion(ctx.nestId, node.id);
3987
4121
  if (currentVersion === 0) return `No versions found for "${args.title}"`;
3988
4122
  try {
3989
- const request = submitForReview({
4123
+ const request = await submitForReview({
3990
4124
  nestId: ctx.nestId,
3991
4125
  nodeId: node.id,
3992
4126
  version: currentVersion,
@@ -3994,7 +4128,7 @@ ${list}`;
3994
4128
  note: args.note,
3995
4129
  priority: args.priority
3996
4130
  });
3997
- const resolved = resolveStewardsForNode(
4131
+ const resolved = await resolveStewardsForNode(
3998
4132
  ctx.nestId,
3999
4133
  node.id
4000
4134
  );
@@ -4013,7 +4147,7 @@ ${resolved.map((r) => `- ${r.steward.userEmail} (${r.source})`).join("\n")}` : "
4013
4147
  (n) => n.frontmatter.title.toLowerCase() === args.title.toLowerCase()
4014
4148
  );
4015
4149
  if (!node) return `Node not found: ${args.title}`;
4016
- const currentVersion = getCurrentVersion(ctx.nestId, node.id);
4150
+ const currentVersion = await getCurrentVersion(ctx.nestId, node.id);
4017
4151
  try {
4018
4152
  const request = await approve({
4019
4153
  nestId: ctx.nestId,
@@ -4034,9 +4168,9 @@ Note: ${args.note}` : ""}`;
4034
4168
  (n) => n.frontmatter.title.toLowerCase() === args.title.toLowerCase()
4035
4169
  );
4036
4170
  if (!node) return `Node not found: ${args.title}`;
4037
- const currentVersion = getCurrentVersion(ctx.nestId, node.id);
4171
+ const currentVersion = await getCurrentVersion(ctx.nestId, node.id);
4038
4172
  try {
4039
- const request = reject({
4173
+ const request = await reject({
4040
4174
  nestId: ctx.nestId,
4041
4175
  nodeId: node.id,
4042
4176
  version: currentVersion,
@@ -4055,8 +4189,8 @@ Reason: ${args.note}`;
4055
4189
  (n) => n.frontmatter.title.toLowerCase() === args.title.toLowerCase()
4056
4190
  );
4057
4191
  if (!node) return `Node not found: ${args.title}`;
4058
- const allVersions = getVersions(ctx.nestId, node.id);
4059
- const approved = getApprovedVersion(ctx.nestId, node.id);
4192
+ const allVersions = await getVersions(ctx.nestId, node.id);
4193
+ const approved = await getApprovedVersion(ctx.nestId, node.id);
4060
4194
  if (allVersions.length === 0) {
4061
4195
  return `No version history for "${args.title}".`;
4062
4196
  }
@@ -4092,7 +4226,7 @@ ${list}`;
4092
4226
  }
4093
4227
  }
4094
4228
  case "context_share_nest": {
4095
- const roles = resolveUserRoles(ctx.nestId, ctx.userEmail);
4229
+ const roles = await resolveUserRoles(ctx.nestId, ctx.userEmail);
4096
4230
  if (!canManageWith(roles)) {
4097
4231
  return "You don't have permission to share this nest via this tool (admin only). You can still invite from the UI with write access, or ask a nest admin.";
4098
4232
  }
@@ -4111,7 +4245,7 @@ ${list}`;
4111
4245
  }
4112
4246
  }
4113
4247
  case "context_unsynced_list": {
4114
- if (config.AUTH_MODE !== "open" && !isLicenseAdminUserId(userId)) {
4248
+ if (config.AUTH_MODE !== "open" && !await isLicenseAdminUserId(userId)) {
4115
4249
  return "You don't have permission to list unsynced folders. Server admin only.";
4116
4250
  }
4117
4251
  const folders = listUnsyncedFolders();
@@ -4124,7 +4258,7 @@ ${list}`;
4124
4258
  ${list}`;
4125
4259
  }
4126
4260
  case "context_sync_folder": {
4127
- if (config.AUTH_MODE !== "open" && !isLicenseAdminUserId(userId)) {
4261
+ if (config.AUTH_MODE !== "open" && !await isLicenseAdminUserId(userId)) {
4128
4262
  return "You don't have permission to sync folders. Server admin only.";
4129
4263
  }
4130
4264
  try {
@@ -4146,9 +4280,9 @@ ${list}`;
4146
4280
  // src/mcp/routes.ts
4147
4281
  import { z } from "zod";
4148
4282
  var mcpRoutes = new Hono7();
4149
- function getUserEmail2(userId) {
4283
+ async function getUserEmail2(userId) {
4150
4284
  const db = getDb();
4151
- const user = db.prepare("SELECT email FROM users WHERE id = ?").get(userId);
4285
+ const user = await db.get("SELECT email FROM users WHERE id = ?", [userId]);
4152
4286
  return user?.email || "anonymous@localhost";
4153
4287
  }
4154
4288
  function createMcpServerForNest(nestId, userId, userEmail) {
@@ -4156,7 +4290,6 @@ function createMcpServerForNest(nestId, userId, userEmail) {
4156
4290
  { name: `contextnest-${nestId}`, version: "1.0.0" },
4157
4291
  { capabilities: { tools: {} } }
4158
4292
  );
4159
- const engine = engineCache.get(nestId);
4160
4293
  for (const tool of TOOL_DEFINITIONS) {
4161
4294
  const props = tool.inputSchema.properties || {};
4162
4295
  const required = tool.inputSchema.required || [];
@@ -4171,6 +4304,7 @@ function createMcpServerForNest(nestId, userId, userEmail) {
4171
4304
  shape[key] = field;
4172
4305
  }
4173
4306
  server.tool(tool.name, tool.description, shape, async (args) => {
4307
+ const engine = await engineCache.get(nestId);
4174
4308
  const text = await handleToolCall(tool.name, args, {
4175
4309
  storage: engine.storage,
4176
4310
  queryEngine: engine.query,
@@ -4187,7 +4321,7 @@ function createMcpServerForNest(nestId, userId, userEmail) {
4187
4321
  mcpRoutes.all("/", async (c) => {
4188
4322
  const nestId = c.req.param("nestId");
4189
4323
  const userId = c.get("userId");
4190
- const userEmail = getUserEmail2(userId);
4324
+ const userEmail = await getUserEmail2(userId);
4191
4325
  const server = createMcpServerForNest(nestId, userId, userEmail);
4192
4326
  const transport = new WebStandardStreamableHTTPServerTransport({
4193
4327
  sessionIdGenerator: void 0,
@@ -4208,41 +4342,43 @@ import { Hono as Hono8 } from "hono";
4208
4342
 
4209
4343
  // src/governance/comment-service.ts
4210
4344
  import { v4 as uuid4 } from "uuid";
4211
- function createComment(params) {
4345
+ async function createComment(params) {
4212
4346
  const db = getDb();
4213
4347
  const body = (params.body ?? "").trim();
4214
4348
  if (!body) {
4215
4349
  throw new Error("Comment body is required");
4216
4350
  }
4217
4351
  if (params.parentId) {
4218
- const parent = db.prepare(
4219
- "SELECT id FROM comments WHERE id = ? AND nest_id = ? AND node_id = ?"
4220
- ).get(params.parentId, params.nestId, params.nodeId);
4352
+ const parent = await db.get(
4353
+ "SELECT id FROM comments WHERE id = ? AND nest_id = ? AND node_id = ?",
4354
+ [params.parentId, params.nestId, params.nodeId]
4355
+ );
4221
4356
  if (!parent) {
4222
4357
  throw new Error("Parent comment not found on this node");
4223
4358
  }
4224
4359
  }
4225
4360
  const id = uuid4();
4226
- db.prepare(
4361
+ await db.run(
4227
4362
  `INSERT INTO comments
4228
4363
  (id, nest_id, node_id, version, anchor_start, anchor_end, anchor_text,
4229
4364
  parent_id, author, body)
4230
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
4231
- ).run(
4232
- id,
4233
- params.nestId,
4234
- params.nodeId,
4235
- params.version ?? null,
4236
- params.anchor?.start ?? null,
4237
- params.anchor?.end ?? null,
4238
- params.anchor?.text ?? null,
4239
- params.parentId ?? null,
4240
- params.author,
4241
- body
4365
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4366
+ [
4367
+ id,
4368
+ params.nestId,
4369
+ params.nodeId,
4370
+ params.version ?? null,
4371
+ params.anchor?.start ?? null,
4372
+ params.anchor?.end ?? null,
4373
+ params.anchor?.text ?? null,
4374
+ params.parentId ?? null,
4375
+ params.author,
4376
+ body
4377
+ ]
4242
4378
  );
4243
- return getComment(id);
4379
+ return await getComment(id);
4244
4380
  }
4245
- function listComments(nestId, nodeId, opts = {}) {
4381
+ async function listComments(nestId, nodeId, opts = {}) {
4246
4382
  const db = getDb();
4247
4383
  const args = [nestId, nodeId];
4248
4384
  let statusClause = "";
@@ -4250,35 +4386,38 @@ function listComments(nestId, nodeId, opts = {}) {
4250
4386
  statusClause = " AND status = ?";
4251
4387
  args.push(opts.status);
4252
4388
  }
4253
- const rows = db.prepare(
4389
+ const rows = await db.all(
4254
4390
  `SELECT * FROM comments
4255
4391
  WHERE nest_id = ? AND node_id = ?${statusClause}
4256
- ORDER BY created_at ASC`
4257
- ).all(...args);
4392
+ ORDER BY created_at ASC`,
4393
+ args
4394
+ );
4258
4395
  return rows.map(rowToComment);
4259
4396
  }
4260
- function getComment(id) {
4397
+ async function getComment(id) {
4261
4398
  const db = getDb();
4262
- const row = db.prepare("SELECT * FROM comments WHERE id = ?").get(id);
4399
+ const row = await db.get("SELECT * FROM comments WHERE id = ?", [id]);
4263
4400
  return row ? rowToComment(row) : null;
4264
4401
  }
4265
- function resolveComment(params) {
4402
+ async function resolveComment(params) {
4266
4403
  const db = getDb();
4267
- const existing = db.prepare(
4268
- "SELECT id, status FROM comments WHERE id = ? AND nest_id = ? AND node_id = ?"
4269
- ).get(params.commentId, params.nestId, params.nodeId);
4404
+ const existing = await db.get(
4405
+ "SELECT id, status FROM comments WHERE id = ? AND nest_id = ? AND node_id = ?",
4406
+ [params.commentId, params.nestId, params.nodeId]
4407
+ );
4270
4408
  if (!existing) {
4271
4409
  throw new Error("Comment not found");
4272
4410
  }
4273
4411
  if (existing.status === "resolved") {
4274
4412
  throw new Error("Comment is already resolved");
4275
4413
  }
4276
- db.prepare(
4414
+ await db.run(
4277
4415
  `UPDATE comments
4278
- SET status = 'resolved', resolved_by = ?, resolved_at = datetime('now')
4279
- WHERE id = ?`
4280
- ).run(params.resolvedBy, params.commentId);
4281
- return getComment(params.commentId);
4416
+ SET status = 'resolved', resolved_by = ?, resolved_at = ${nowExpr(db)}
4417
+ WHERE id = ?`,
4418
+ [params.resolvedBy, params.commentId]
4419
+ );
4420
+ return await getComment(params.commentId);
4282
4421
  }
4283
4422
  async function getActivity(params) {
4284
4423
  const db = getDb();
@@ -4286,13 +4425,14 @@ async function getActivity(params) {
4286
4425
  const entries = [];
4287
4426
  const nodeFilter = params.nodeId ? " AND node_id = ?" : "";
4288
4427
  const baseArgs = params.nodeId ? [params.nestId, params.nodeId] : [params.nestId];
4289
- const commentRows = db.prepare(
4428
+ const commentRows = await db.all(
4290
4429
  `SELECT id, node_id, author, body, status, created_at, resolved_by, resolved_at
4291
4430
  FROM comments
4292
4431
  WHERE nest_id = ?${nodeFilter}
4293
4432
  ORDER BY COALESCE(resolved_at, created_at) DESC
4294
- LIMIT ?`
4295
- ).all(...baseArgs, limit);
4433
+ LIMIT ?`,
4434
+ [...baseArgs, limit]
4435
+ );
4296
4436
  for (const r of commentRows) {
4297
4437
  entries.push({
4298
4438
  type: "comment",
@@ -4313,13 +4453,14 @@ async function getActivity(params) {
4313
4453
  });
4314
4454
  }
4315
4455
  }
4316
- const versionRows = db.prepare(
4456
+ const versionRows = await db.all(
4317
4457
  `SELECT node_id, version, author, change_note, created_at
4318
4458
  FROM node_versions
4319
4459
  WHERE nest_id = ?${nodeFilter}
4320
4460
  ORDER BY created_at DESC
4321
- LIMIT ?`
4322
- ).all(...baseArgs, limit);
4461
+ LIMIT ?`,
4462
+ [...baseArgs, limit]
4463
+ );
4323
4464
  for (const r of versionRows) {
4324
4465
  entries.push({
4325
4466
  type: "edit",
@@ -4330,13 +4471,14 @@ async function getActivity(params) {
4330
4471
  refId: String(r.version)
4331
4472
  });
4332
4473
  }
4333
- const reviewRows = db.prepare(
4474
+ const reviewRows = await db.all(
4334
4475
  `SELECT id, node_id, requested_by, requested_at, status, resolved_by, resolved_at
4335
4476
  FROM review_requests
4336
4477
  WHERE nest_id = ?${nodeFilter}
4337
4478
  ORDER BY COALESCE(resolved_at, requested_at) DESC
4338
- LIMIT ?`
4339
- ).all(...baseArgs, limit);
4479
+ LIMIT ?`,
4480
+ [...baseArgs, limit]
4481
+ );
4340
4482
  for (const r of reviewRows) {
4341
4483
  entries.push({
4342
4484
  type: "review_requested",
@@ -4479,8 +4621,7 @@ function parseEntry(str) {
4479
4621
  return entry;
4480
4622
  }
4481
4623
  function loadStewardsConfig(nestId) {
4482
- const dataRoot = config.DATA_ROOT;
4483
- const nestPath = join3(dataRoot, "nests", nestId);
4624
+ const nestPath = resolveNestPath(nestId);
4484
4625
  const candidates = [
4485
4626
  join3(nestPath, "stewards.yaml"),
4486
4627
  join3(nestPath, "stewards.yml"),
@@ -4512,24 +4653,25 @@ governanceRoutes.get("/stewards", async (c) => {
4512
4653
  search: search || void 0
4513
4654
  });
4514
4655
  const cache = /* @__PURE__ */ new Map();
4515
- const enriched = stewards.map((s) => {
4656
+ const enriched = [];
4657
+ for (const s of stewards) {
4516
4658
  const key = s.userEmail.toLowerCase();
4517
4659
  let merged = cache.get(key);
4518
4660
  if (!merged) {
4519
4661
  merged = {
4520
- collaboratorRole: getCollaboratorRole(nestId, s.userEmail),
4521
- roles: resolveUserRoles(nestId, s.userEmail)
4662
+ collaboratorRole: await getCollaboratorRole(nestId, s.userEmail),
4663
+ roles: await resolveUserRoles(nestId, s.userEmail)
4522
4664
  };
4523
4665
  cache.set(key, merged);
4524
4666
  }
4525
- return { ...s, ...merged };
4526
- });
4667
+ enriched.push({ ...s, ...merged });
4668
+ }
4527
4669
  return c.json({ stewards: enriched });
4528
4670
  });
4529
4671
  governanceRoutes.post("/stewards", async (c) => {
4530
4672
  const nestId = c.req.param("nestId");
4531
4673
  const body = await c.req.json();
4532
- const assignedBy = getUserEmail3(c);
4674
+ const assignedBy = await getUserEmail3(c);
4533
4675
  if (!body.scope) throw new ValidationError("scope is required");
4534
4676
  if (body.scope === "folder") {
4535
4677
  throw new ValidationError(
@@ -4571,7 +4713,7 @@ governanceRoutes.patch("/stewards/:stewardId", async (c) => {
4571
4713
  if (!body.role && !body.scope) {
4572
4714
  throw new ValidationError("role or scope is required");
4573
4715
  }
4574
- const steward = updateSteward(stewardId, {
4716
+ const steward = await updateSteward(stewardId, {
4575
4717
  role: body.role,
4576
4718
  scope: body.scope,
4577
4719
  documentId: body.nodePattern,
@@ -4581,7 +4723,7 @@ governanceRoutes.patch("/stewards/:stewardId", async (c) => {
4581
4723
  });
4582
4724
  governanceRoutes.delete("/stewards/:stewardId", async (c) => {
4583
4725
  const stewardId = c.req.param("stewardId");
4584
- removeSteward(stewardId);
4726
+ await removeSteward(stewardId);
4585
4727
  return c.json({ removed: true });
4586
4728
  });
4587
4729
  governanceRoutes.post("/stewards/sync", async (c) => {
@@ -4590,7 +4732,7 @@ governanceRoutes.post("/stewards/sync", async (c) => {
4590
4732
  if (!stewardsConfig) {
4591
4733
  return c.json({ synced: 0, message: "No stewards.yaml found" });
4592
4734
  }
4593
- const count = syncFromConfig(nestId, stewardsConfig);
4735
+ const count = await syncFromConfig(nestId, stewardsConfig);
4594
4736
  return c.json({ synced: count });
4595
4737
  });
4596
4738
  governanceRoutes.get("/review-queue", async (c) => {
@@ -4604,16 +4746,17 @@ governanceRoutes.get("/review-queue", async (c) => {
4604
4746
  limit,
4605
4747
  offset
4606
4748
  });
4607
- const email = getUserEmail3(c);
4749
+ const email = await getUserEmail3(c);
4608
4750
  const canReviewCache = /* @__PURE__ */ new Map();
4609
- const requests = result.requests.map((r) => {
4751
+ const requests = [];
4752
+ for (const r of result.requests) {
4610
4753
  let canReview = canReviewCache.get(r.nodeId);
4611
4754
  if (canReview === void 0) {
4612
- canReview = canUserApprove(nestId, r.nodeId, email).allowed;
4755
+ canReview = (await canUserApprove(nestId, r.nodeId, email)).allowed;
4613
4756
  canReviewCache.set(r.nodeId, canReview);
4614
4757
  }
4615
- return { ...r, canReview };
4616
- });
4758
+ requests.push({ ...r, canReview });
4759
+ }
4617
4760
  return c.json({ ...result, requests });
4618
4761
  });
4619
4762
  governanceRoutes.get("/external-edits", async (c) => {
@@ -4627,7 +4770,7 @@ governanceRoutes.get("/external-edits", async (c) => {
4627
4770
  });
4628
4771
  governanceRoutes.post("/external-edits/scan", async (c) => {
4629
4772
  const nestId = c.req.param("nestId");
4630
- const actor = getUserEmail3(c);
4773
+ const actor = await getUserEmail3(c);
4631
4774
  const result = await scanNestForDrift(nestId, actor);
4632
4775
  return c.json(result);
4633
4776
  });
@@ -4641,7 +4784,7 @@ var governanceNodeRoutes = new Hono8();
4641
4784
  governanceNodeRoutes.get("/:nodeId{.+}/stewards", async (c) => {
4642
4785
  const nestId = c.req.param("nestId");
4643
4786
  const nodeId = c.req.param("nodeId");
4644
- const { stewards: resolved, fallbackToOwner, ownerEmail } = resolveStewardsWithFallback(nestId, nodeId);
4787
+ const { stewards: resolved, fallbackToOwner, ownerEmail } = await resolveStewardsWithFallback(nestId, nodeId);
4645
4788
  return c.json({
4646
4789
  nodeId,
4647
4790
  stewards: resolved.map((r) => ({
@@ -4658,9 +4801,9 @@ governanceNodeRoutes.get("/:nodeId{.+}/stewards", async (c) => {
4658
4801
  governanceNodeRoutes.get("/:nodeId{.+}/versions", async (c) => {
4659
4802
  const nestId = c.req.param("nestId");
4660
4803
  const nodeId = c.req.param("nodeId");
4661
- const allVersions = getVersions(nestId, nodeId);
4662
- const approved = getApprovedVersion(nestId, nodeId);
4663
- const { versions: versionManager } = engineCache.get(nestId);
4804
+ const allVersions = await getVersions(nestId, nodeId);
4805
+ const approved = await getApprovedVersion(nestId, nodeId);
4806
+ const { versions: versionManager } = await engineCache.get(nestId);
4664
4807
  const withContent = await Promise.all(
4665
4808
  allVersions.map(async (v) => {
4666
4809
  try {
@@ -4680,14 +4823,14 @@ governanceNodeRoutes.get("/:nodeId{.+}/versions", async (c) => {
4680
4823
  governanceNodeRoutes.get("/:nodeId{.+}/reviews", async (c) => {
4681
4824
  const nestId = c.req.param("nestId");
4682
4825
  const nodeId = c.req.param("nodeId");
4683
- const history = getReviewHistory(nestId, nodeId);
4826
+ const history = await getReviewHistory(nestId, nodeId);
4684
4827
  return c.json({ reviews: history });
4685
4828
  });
4686
4829
  governanceNodeRoutes.get("/:nodeId{.+}/comments", async (c) => {
4687
4830
  const nestId = c.req.param("nestId");
4688
4831
  const nodeId = c.req.param("nodeId");
4689
4832
  const status = c.req.query("status");
4690
- const list = listComments(nestId, nodeId, {
4833
+ const list = await listComments(nestId, nodeId, {
4691
4834
  status: status === "open" || status === "resolved" ? status : void 0
4692
4835
  });
4693
4836
  return c.json({ comments: list });
@@ -4696,9 +4839,9 @@ governanceNodeRoutes.post("/:nodeId{.+}/comments", async (c) => {
4696
4839
  const nestId = c.req.param("nestId");
4697
4840
  const nodeId = c.req.param("nodeId");
4698
4841
  const body = await c.req.json();
4699
- const author = getUserEmail3(c);
4842
+ const author = await getUserEmail3(c);
4700
4843
  try {
4701
- const comment = createComment({
4844
+ const comment = await createComment({
4702
4845
  nestId,
4703
4846
  nodeId,
4704
4847
  author,
@@ -4720,9 +4863,9 @@ governanceNodeRoutes.post(
4720
4863
  const nestId = c.req.param("nestId");
4721
4864
  const nodeId = c.req.param("nodeId");
4722
4865
  const commentId = c.req.param("commentId");
4723
- const resolvedBy = getUserEmail3(c);
4866
+ const resolvedBy = await getUserEmail3(c);
4724
4867
  try {
4725
- const comment = resolveComment({
4868
+ const comment = await resolveComment({
4726
4869
  nestId,
4727
4870
  nodeId,
4728
4871
  commentId,
@@ -4749,14 +4892,14 @@ governanceNodeRoutes.post("/:nodeId{.+}/submit-review", async (c) => {
4749
4892
  const nestId = c.req.param("nestId");
4750
4893
  const nodeId = c.req.param("nodeId");
4751
4894
  const body = await c.req.json();
4752
- const currentVersion = getCurrentVersion(nestId, nodeId);
4895
+ const currentVersion = await getCurrentVersion(nestId, nodeId);
4753
4896
  if (currentVersion === 0) {
4754
4897
  throw new ValidationError("Node has no versions to review");
4755
4898
  }
4756
- const userEmail = getUserEmail3(c);
4899
+ const userEmail = await getUserEmail3(c);
4757
4900
  let request;
4758
4901
  try {
4759
- request = submitForReview({
4902
+ request = await submitForReview({
4760
4903
  nestId,
4761
4904
  nodeId,
4762
4905
  version: currentVersion,
@@ -4771,7 +4914,7 @@ governanceNodeRoutes.post("/:nodeId{.+}/submit-review", async (c) => {
4771
4914
  }
4772
4915
  throw err;
4773
4916
  }
4774
- const resolved = resolveStewardsForNode(nestId, nodeId);
4917
+ const resolved = await resolveStewardsForNode(nestId, nodeId);
4775
4918
  return c.json(
4776
4919
  {
4777
4920
  review: request,
@@ -4788,13 +4931,13 @@ governanceNodeRoutes.post("/:nodeId{.+}/approve", async (c) => {
4788
4931
  const nestId = c.req.param("nestId");
4789
4932
  const nodeId = c.req.param("nodeId");
4790
4933
  const body = await c.req.json();
4791
- const userEmail = getUserEmail3(c);
4934
+ const userEmail = await getUserEmail3(c);
4792
4935
  const isAdmin = isSuperAdmin(userEmail);
4793
4936
  try {
4794
4937
  const request = await approve({
4795
4938
  nestId,
4796
4939
  nodeId,
4797
- version: getCurrentVersion(nestId, nodeId),
4940
+ version: await getCurrentVersion(nestId, nodeId),
4798
4941
  approvedBy: userEmail,
4799
4942
  note: body.note,
4800
4943
  override: body.override && isAdmin
@@ -4811,12 +4954,12 @@ governanceNodeRoutes.post("/:nodeId{.+}/reject", async (c) => {
4811
4954
  if (!body.note) {
4812
4955
  throw new ValidationError("Rejection note is required");
4813
4956
  }
4814
- const userEmail = getUserEmail3(c);
4957
+ const userEmail = await getUserEmail3(c);
4815
4958
  try {
4816
- const request = reject({
4959
+ const request = await reject({
4817
4960
  nestId,
4818
4961
  nodeId,
4819
- version: getCurrentVersion(nestId, nodeId),
4962
+ version: await getCurrentVersion(nestId, nodeId),
4820
4963
  rejectedBy: userEmail,
4821
4964
  note: body.note
4822
4965
  });
@@ -4828,20 +4971,20 @@ governanceNodeRoutes.post("/:nodeId{.+}/reject", async (c) => {
4828
4971
  governanceNodeRoutes.get("/:nodeId{.+}/can-access", async (c) => {
4829
4972
  const nestId = c.req.param("nestId");
4830
4973
  const nodeId = c.req.param("nodeId");
4831
- const userEmail = getUserEmail3(c);
4832
- return c.json(canUserAccess(nestId, nodeId, userEmail));
4974
+ const userEmail = await getUserEmail3(c);
4975
+ return c.json(await canUserAccess(nestId, nodeId, userEmail));
4833
4976
  });
4834
4977
  governanceNodeRoutes.get("/:nodeId{.+}/can-approve", async (c) => {
4835
4978
  const nestId = c.req.param("nestId");
4836
4979
  const nodeId = c.req.param("nodeId");
4837
- const userEmail = getUserEmail3(c);
4838
- return c.json(canUserApprove(nestId, nodeId, userEmail));
4980
+ const userEmail = await getUserEmail3(c);
4981
+ return c.json(await canUserApprove(nestId, nodeId, userEmail));
4839
4982
  });
4840
4983
  governanceNodeRoutes.get("/:nodeId{.+}/can-edit", async (c) => {
4841
4984
  const nestId = c.req.param("nestId");
4842
4985
  const nodeId = c.req.param("nodeId");
4843
- const userEmail = getUserEmail3(c);
4844
- return c.json(canUserEdit(nestId, nodeId, userEmail));
4986
+ const userEmail = await getUserEmail3(c);
4987
+ return c.json(await canUserEdit(nestId, nodeId, userEmail));
4845
4988
  });
4846
4989
  governanceNodeRoutes.get("/:nodeId{.+?}/external-edits", async (c) => {
4847
4990
  const nestId = c.req.param("nestId");
@@ -4873,7 +5016,7 @@ governanceNodeRoutes.post(
4873
5016
  const nodeId = c.req.param("nodeId");
4874
5017
  const suggestionId = c.req.param("suggestionId");
4875
5018
  const body = await c.req.json().catch(() => ({}));
4876
- const actor = getUserEmail3(c);
5019
+ const actor = await getUserEmail3(c);
4877
5020
  try {
4878
5021
  const result = await approveExternalEdit({
4879
5022
  nestId,
@@ -4909,7 +5052,7 @@ governanceNodeRoutes.post(
4909
5052
  if (!body.reason) {
4910
5053
  throw new ValidationError("Rejection reason is required");
4911
5054
  }
4912
- const actor = getUserEmail3(c);
5055
+ const actor = await getUserEmail3(c);
4913
5056
  try {
4914
5057
  const result = await rejectExternalEdit({
4915
5058
  nestId,
@@ -4930,31 +5073,38 @@ governanceNodeRoutes.post(
4930
5073
  governanceNodeRoutes.post("/:nodeId{.+}/cancel-review", async (c) => {
4931
5074
  const nestId = c.req.param("nestId");
4932
5075
  const nodeId = c.req.param("nodeId");
4933
- const userEmail = getUserEmail3(c);
4934
- const request = cancelReview({
5076
+ const userEmail = await getUserEmail3(c);
5077
+ const request = await cancelReview({
4935
5078
  nestId,
4936
5079
  nodeId,
4937
5080
  cancelledBy: userEmail
4938
5081
  });
4939
5082
  return c.json({ review: request });
4940
5083
  });
4941
- function getUserEmail3(c) {
5084
+ async function getUserEmail3(c) {
4942
5085
  const userId = c.get("userId");
4943
5086
  const db = getDb();
4944
- const user = db.prepare("SELECT email FROM users WHERE id = ?").get(userId);
5087
+ const user = await db.get(
5088
+ "SELECT email FROM users WHERE id = ?",
5089
+ [userId]
5090
+ );
4945
5091
  return user?.email || "anonymous@localhost";
4946
5092
  }
4947
5093
 
4948
5094
  // src/auth/anonymous.ts
4949
5095
  import bcrypt from "bcryptjs";
4950
- function ensureAnonymousUser() {
5096
+ async function ensureAnonymousUser() {
4951
5097
  const db = getDb();
4952
- const exists = db.prepare("SELECT id FROM users WHERE id = ?").get(ANON_USER_ID);
5098
+ const exists = await db.get(
5099
+ "SELECT id FROM users WHERE id = ?",
5100
+ [ANON_USER_ID]
5101
+ );
4953
5102
  if (!exists) {
4954
5103
  const placeholder = bcrypt.hashSync("anon-no-login", 4);
4955
- db.prepare(
4956
- "INSERT INTO users (id, email, name, password_hash) VALUES (?, ?, ?, ?)"
4957
- ).run(ANON_USER_ID, ANON_EMAIL, "Admin", placeholder);
5104
+ await db.run(
5105
+ "INSERT INTO users (id, email, name, password_hash) VALUES (?, ?, ?, ?)",
5106
+ [ANON_USER_ID, ANON_EMAIL, "Admin", placeholder]
5107
+ );
4958
5108
  }
4959
5109
  return ANON_USER_ID;
4960
5110
  }
@@ -4972,7 +5122,7 @@ var UI_DIR_CANDIDATES = [
4972
5122
  var UI_DIR_ABS = UI_DIR_CANDIDATES.find((p) => existsSync2(p)) || UI_DIR_CANDIDATES[0];
4973
5123
  var UI_DIR_REL = relative2(process.cwd(), UI_DIR_ABS) || ".";
4974
5124
  var openModeMiddleware = createMiddleware2(async (c, next) => {
4975
- const anonId = ensureAnonymousUser();
5125
+ const anonId = await ensureAnonymousUser();
4976
5126
  c.set("userId", anonId);
4977
5127
  c.set("nestScope", null);
4978
5128
  await next();
@@ -4989,13 +5139,13 @@ var flexAuthMiddleware = createMiddleware2(async (c, next) => {
4989
5139
  return authMiddleware(c, next);
4990
5140
  }
4991
5141
  if (config.AUTH_MODE === "open") {
4992
- const anonId = ensureAnonymousUser();
5142
+ const anonId = await ensureAnonymousUser();
4993
5143
  c.set("userId", anonId);
4994
5144
  c.set("nestScope", null);
4995
5145
  return next();
4996
5146
  }
4997
5147
  if (isPublicReadEligiblePath(c.req.method, c.req.path)) {
4998
- const anonId = ensureAnonymousUser();
5148
+ const anonId = await ensureAnonymousUser();
4999
5149
  c.set("userId", anonId);
5000
5150
  c.set("nestScope", null);
5001
5151
  return next();
@@ -5117,7 +5267,7 @@ function createApp() {
5117
5267
  }
5118
5268
  });
5119
5269
  app.use("/admin/settings", flexAuthMiddleware);
5120
- const adminSettingsAllowed = (c) => config.AUTH_MODE === "open" || isLicenseAdminUserId(c.get("userId"));
5270
+ const adminSettingsAllowed = async (c) => config.AUTH_MODE === "open" || await isLicenseAdminUserId(c.get("userId"));
5121
5271
  const currentServerSettings = () => ({
5122
5272
  promptowl_sign_in_gate: config.PROMPTOWL_SIGN_IN_GATE,
5123
5273
  logo_url: config.LOGO_URL,
@@ -5125,13 +5275,13 @@ function createApp() {
5125
5275
  public_base_url: config.PUBLIC_BASE_URL,
5126
5276
  max_body_bytes: config.MAX_BODY_BYTES
5127
5277
  });
5128
- app.get("/admin/settings", (c) => {
5129
- if (!adminSettingsAllowed(c))
5278
+ app.get("/admin/settings", async (c) => {
5279
+ if (!await adminSettingsAllowed(c))
5130
5280
  return c.json({ error: "Only the server admin can view this." }, 403);
5131
5281
  return c.json(currentServerSettings());
5132
5282
  });
5133
5283
  app.patch("/admin/settings", async (c) => {
5134
- if (!adminSettingsAllowed(c))
5284
+ if (!await adminSettingsAllowed(c))
5135
5285
  return c.json({ error: "Only the server admin can change this." }, 403);
5136
5286
  let body;
5137
5287
  try {
@@ -5187,18 +5337,22 @@ function createApp() {
5187
5337
  app.get("/stats", async (c) => {
5188
5338
  const db = getDb();
5189
5339
  const userId = c.get("userId");
5190
- const userEmail = resolveCallerEmail(userId);
5191
- const visibleNests = [...listNests(userId), ...listSharedNests(userId)];
5340
+ const userEmail = await resolveCallerEmail(userId);
5341
+ const owned = await listNests(userId);
5342
+ const sharedNests = await listSharedNests(userId);
5343
+ const visibleNests = [...owned, ...sharedNests];
5192
5344
  let documents = 0;
5193
5345
  for (const nest of visibleNests) {
5194
5346
  try {
5195
- const { storage } = engineCache.get(nest.id);
5347
+ const { storage } = await engineCache.get(nest.id);
5196
5348
  const docs = await storage.discoverDocuments();
5197
- documents += filterAccessible(nest.id, userId, userEmail, docs).length;
5349
+ documents += (await filterAccessible(nest.id, userId, userEmail, docs)).length;
5198
5350
  } catch {
5199
5351
  }
5200
5352
  }
5201
- const usersRow = db.prepare("SELECT COUNT(*) as c FROM users").get();
5353
+ const usersRow = await db.get(
5354
+ "SELECT COUNT(*) as c FROM users"
5355
+ );
5202
5356
  return c.json({
5203
5357
  nests: visibleNests.length,
5204
5358
  documents,
@@ -5252,7 +5406,7 @@ function createApp() {
5252
5406
  c.set("nestPermission", "owner");
5253
5407
  return next();
5254
5408
  }
5255
- const permission = resolveNestPermission(nestId, userId);
5409
+ const permission = await resolveNestPermission(nestId, userId);
5256
5410
  if (permission === "none") {
5257
5411
  return c.json({ error: "Nest not found" }, 404);
5258
5412
  }
@@ -5262,7 +5416,7 @@ function createApp() {
5262
5416
  const isAnnotationAction = /\/annotations$/.test(path) || /\/annotations\/[^/]+\/(comments|resolve|reopen)$/.test(path);
5263
5417
  const isCommentAction = /\/comments$/.test(path) || /\/comments\/[^/]+\/resolve$/.test(path);
5264
5418
  const isStewardRoster = path.includes("/stewards") && !path.includes("/nodes/");
5265
- if (isStewardRoster && !canManageStewards(resolveCallerEmail(userId))) {
5419
+ if (isStewardRoster && !canManageStewards(await resolveCallerEmail(userId))) {
5266
5420
  return c.json(
5267
5421
  {
5268
5422
  error: "You don't have permission to manage stewards. Only the super admin can do this."
@@ -5281,7 +5435,7 @@ function createApp() {
5281
5435
  const isNodeRevert = c.req.method === "POST" && parts.length >= 4 && parts[parts.length - 1] === "revert";
5282
5436
  let stewardEditorBypass = false;
5283
5437
  if (required === "write" && permission === "read" && parts[1] === "nodes") {
5284
- const userEmail = resolveCallerEmail(userId);
5438
+ const userEmail = await resolveCallerEmail(userId);
5285
5439
  if (parts.length >= 3 && (c.req.method === "PATCH" || c.req.method === "DELETE" || isNodeRevert)) {
5286
5440
  const idParts = isNodeRevert ? parts.slice(2, -1) : parts.slice(2);
5287
5441
  const rawNodeId = idParts.join("/");
@@ -5290,12 +5444,12 @@ function createApp() {
5290
5444
  nodeId = decodeURIComponent(rawNodeId);
5291
5445
  } catch {
5292
5446
  }
5293
- const resolved = resolveStewardsForNode(nestId, nodeId);
5447
+ const resolved = await resolveStewardsForNode(nestId, nodeId);
5294
5448
  stewardEditorBypass = resolved.some(
5295
5449
  (r) => r.steward.userEmail.toLowerCase() === userEmail.toLowerCase() && r.steward.role === "editor"
5296
5450
  );
5297
5451
  } else if (parts.length === 2 && c.req.method === "POST") {
5298
- const resolved = resolveStewardsForNode(nestId, "");
5452
+ const resolved = await resolveStewardsForNode(nestId, "");
5299
5453
  stewardEditorBypass = resolved.some(
5300
5454
  (r) => r.steward.userEmail.toLowerCase() === userEmail.toLowerCase() && r.steward.role === "editor" && r.steward.scope === "nest"
5301
5455
  );
@@ -5403,28 +5557,30 @@ function createApp() {
5403
5557
 
5404
5558
  // src/db/backfill.ts
5405
5559
  import { NestStorage } from "@promptowl/contextnest-engine";
5406
- import { join as join5 } from "path";
5407
5560
  var MIGRATION_ID = "005_backfill_node_versions_from_history";
5408
5561
  async function backfillNodeVersionsFromHistory(db) {
5409
- const already = db.prepare("SELECT id FROM schema_migrations WHERE id = ?").get(MIGRATION_ID);
5562
+ const already = await db.get(
5563
+ "SELECT id FROM schema_migrations WHERE id = ?",
5564
+ [MIGRATION_ID]
5565
+ );
5410
5566
  if (already) return;
5411
- const nests = db.prepare("SELECT id FROM nests").all();
5412
- const insert = db.prepare(
5413
- `INSERT OR IGNORE INTO node_versions
5567
+ const nests = await db.all("SELECT id FROM nests");
5568
+ const insertSql = insertOrIgnore(
5569
+ db,
5570
+ `INSERT INTO node_versions
5414
5571
  (nest_id, node_id, version, content_hash, author, status, change_note, created_at)
5415
5572
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
5416
5573
  );
5417
- const approvedPin = db.prepare(
5418
- `INSERT OR REPLACE INTO approved_versions
5574
+ const approvedPinSql = `INSERT INTO approved_versions
5419
5575
  (nest_id, node_id, approved_version, approved_by, approved_at)
5420
- VALUES (?, ?, ?, ?, COALESCE(
5421
- (SELECT approved_at FROM approved_versions WHERE nest_id = ? AND node_id = ?),
5422
- datetime('now')))`
5423
- );
5576
+ VALUES (?, ?, ?, ?, ${nowExpr(db)})
5577
+ ON CONFLICT (nest_id, node_id) DO UPDATE SET
5578
+ approved_version = excluded.approved_version,
5579
+ approved_by = excluded.approved_by`;
5424
5580
  let totalInserted = 0;
5425
5581
  let totalDocs = 0;
5426
5582
  for (const { id: nestId } of nests) {
5427
- const nestPath = join5(config.DATA_ROOT, "nests", nestId);
5583
+ const nestPath = resolveNestPath(nestId);
5428
5584
  const storage = new NestStorage(nestPath);
5429
5585
  let docs;
5430
5586
  try {
@@ -5445,15 +5601,16 @@ async function backfillNodeVersionsFromHistory(db) {
5445
5601
  history = null;
5446
5602
  }
5447
5603
  if (!history || history.versions.length === 0) continue;
5448
- const existing = db.prepare(
5449
- `SELECT version FROM node_versions WHERE nest_id = ? AND node_id = ?`
5450
- ).all(nestId, doc.id);
5604
+ const existing = await db.all(
5605
+ `SELECT version FROM node_versions WHERE nest_id = ? AND node_id = ?`,
5606
+ [nestId, doc.id]
5607
+ );
5451
5608
  const known = new Set(existing.map((r) => r.version));
5452
5609
  const tagsJson = doc.frontmatter.tags ? JSON.stringify(doc.frontmatter.tags) : null;
5453
5610
  const latestVersion = history.versions[history.versions.length - 1].version;
5454
5611
  for (const entry of history.versions) {
5455
5612
  if (known.has(entry.version)) continue;
5456
- insert.run(
5613
+ await db.run(insertSql, [
5457
5614
  nestId,
5458
5615
  doc.id,
5459
5616
  entry.version,
@@ -5462,33 +5619,33 @@ async function backfillNodeVersionsFromHistory(db) {
5462
5619
  "approved",
5463
5620
  entry.note || null,
5464
5621
  entry.edited_at || (/* @__PURE__ */ new Date()).toISOString()
5465
- );
5622
+ ]);
5466
5623
  totalInserted += 1;
5467
5624
  }
5468
- const pin = db.prepare(
5469
- `SELECT approved_version FROM approved_versions WHERE nest_id = ? AND node_id = ?`
5470
- ).get(nestId, doc.id);
5625
+ const pin = await db.get(
5626
+ `SELECT approved_version FROM approved_versions WHERE nest_id = ? AND node_id = ?`,
5627
+ [nestId, doc.id]
5628
+ );
5471
5629
  if (!pin || pin.approved_version < latestVersion) {
5472
- approvedPin.run(
5630
+ await db.run(approvedPinSql, [
5473
5631
  nestId,
5474
5632
  doc.id,
5475
5633
  latestVersion,
5476
- history.versions[history.versions.length - 1].edited_by || "system:backfill",
5477
- nestId,
5478
- doc.id
5479
- );
5634
+ history.versions[history.versions.length - 1].edited_by || "system:backfill"
5635
+ ]);
5480
5636
  }
5481
5637
  if (tagsJson) {
5482
- const updateTags = db.prepare(
5638
+ await db.run(
5483
5639
  `UPDATE node_versions SET tags_json = ?
5484
- WHERE nest_id = ? AND node_id = ? AND version = ? AND tags_json IS NULL`
5640
+ WHERE nest_id = ? AND node_id = ? AND version = ? AND tags_json IS NULL`,
5641
+ [tagsJson, nestId, doc.id, latestVersion]
5485
5642
  );
5486
- updateTags.run(tagsJson, nestId, doc.id, latestVersion);
5487
5643
  }
5488
5644
  }
5489
5645
  }
5490
- db.prepare("INSERT OR IGNORE INTO schema_migrations (id) VALUES (?)").run(
5491
- MIGRATION_ID
5646
+ await db.run(
5647
+ insertOrIgnore(db, "INSERT INTO schema_migrations (id) VALUES (?)"),
5648
+ [MIGRATION_ID]
5492
5649
  );
5493
5650
  console.log(
5494
5651
  `[backfill] node_versions: scanned ${totalDocs} docs across ${nests.length} nests, inserted ${totalInserted} rows`
@@ -5497,7 +5654,7 @@ async function backfillNodeVersionsFromHistory(db) {
5497
5654
 
5498
5655
  // src/index.ts
5499
5656
  async function main() {
5500
- const db = getDb();
5657
+ const db = await initDb();
5501
5658
  try {
5502
5659
  await backfillNodeVersionsFromHistory(db);
5503
5660
  } catch (err) {
@@ -5531,7 +5688,8 @@ async function main() {
5531
5688
  }
5532
5689
  const app = createApp();
5533
5690
  startLicenseSafetyPoll();
5534
- const driftScanIntervalMs = Number(process.env.DRIFT_SCAN_INTERVAL_MS) || 3e4;
5691
+ const driftRaw = process.env.DRIFT_SCAN_INTERVAL_MS;
5692
+ const driftScanIntervalMs = driftRaw != null && driftRaw !== "" && Number.isFinite(Number(driftRaw)) ? Number(driftRaw) : 3e4;
5535
5693
  if (driftScanIntervalMs > 0) {
5536
5694
  startDriftScanner(driftScanIntervalMs);
5537
5695
  }
@@ -5542,11 +5700,13 @@ async function main() {
5542
5700
  licensed: license.valid
5543
5701
  });
5544
5702
  const authDesc = config.AUTH_MODE === "open" ? "open (no auth \u2014 single-user / LAN only)" : "key (API key required on every request)";
5703
+ const dbDesc = db.dialect === "postgres" ? `postgres (${config.CLOUD_SQL_CONNECTION_NAME ? `cloudsql ${config.CLOUD_SQL_CONNECTION_NAME}` : config.DATABASE_URL ? "DATABASE_URL" : `${config.DB_HOST || "localhost"}:${config.DB_PORT}/${config.DB_NAME}`})` : `sqlite (${config.DATABASE_PATH})`;
5545
5704
  console.log(`
5546
5705
  ContextNest Community Server v0.1.0
5547
5706
  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
5548
5707
  Port: ${config.PORT}
5549
5708
  Data: ${config.DATA_ROOT}
5709
+ Database: ${dbDesc}
5550
5710
  License: ${license.valid ? `${license.tier}${license.org ? ` (${license.org})` : ""}` : "unlicensed (register at promptowl.ai)"}
5551
5711
  Auth: ${authDesc}
5552
5712
  Telemetry: ${config.TELEMETRY_ENABLED ? "on" : "off"}