@promptowl/contextnest-community 1.22.0 → 1.24.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.
Files changed (89) hide show
  1. package/API.md +2092 -0
  2. package/CONFIGURATION.md +5 -2
  3. package/README.md +62 -10
  4. package/STEWARDSHIP.md +229 -0
  5. package/dist/{chunk-PX3P4FTU.js → chunk-2TPQTN4Y.js} +5 -4
  6. package/dist/{chunk-OQXZ43HG.js → chunk-5EZOPA47.js} +40 -10
  7. package/dist/{chunk-MY4JIWQD.js → chunk-I3CSD6CK.js} +1 -1
  8. package/dist/{chunk-W5ILNGPD.js → chunk-KIAAEHWL.js} +80 -28
  9. package/dist/{chunk-J2OQ3MEB.js → chunk-LA3VTQ22.js} +63 -1
  10. package/dist/{chunk-ZAV4QUZA.js → chunk-XUIWAWDO.js} +28 -33
  11. package/dist/{chunk-6WESEA75.js → chunk-ZTT4U4NE.js} +2 -2
  12. package/dist/{client-CSCHXUNX.js → client-KEY4PYJH.js} +1 -1
  13. package/dist/{engine-XKRP7GOQ.js → engine-S3QBQ7LH.js} +2 -2
  14. package/dist/{external-edit-service-TXW7SXSF.js → external-edit-service-6FNFPOJJ.js} +3 -3
  15. package/dist/{grants-service-UZ2MGDO5.js → grants-service-UT3EQ3R7.js} +2 -2
  16. package/dist/index.js +1261 -305
  17. package/dist/{migrations.postgres-KKICIH2C.js → migrations.postgres-AIQ7WSU7.js} +60 -4
  18. package/dist/{review-service-XFWTENTN.js → review-service-HHGOTO6P.js} +6 -6
  19. package/dist/{stewardship-service-5HBQGDMK.js → stewardship-service-4DHNRULG.js} +3 -3
  20. package/dist/{version-service-TKNYJMCM.js → version-service-KIOQME6U.js} +3 -3
  21. package/dist/web3/assets/ActivityTracePage-BOZHgJ8S.js +1 -0
  22. package/dist/web3/assets/AgentDocsPage-CIiaBqMy.js +1 -0
  23. package/dist/web3/assets/CollaboratorManager-By91wrIr.js +1 -0
  24. package/dist/web3/assets/CollaboratorsTab-D1EE64Fs.js +1 -0
  25. package/dist/web3/assets/DocumentEditor-DrQfrW3d.js +36 -0
  26. package/dist/web3/assets/DocumentsTab-Vba-AzBv.js +6 -0
  27. package/dist/web3/assets/ExternalEditsTab-BSzAQIGM.js +1 -0
  28. package/dist/web3/assets/MarkdownEditor-74N_naNQ.css +1 -0
  29. package/dist/web3/assets/MarkdownEditor-C9zqZjC5.js +643 -0
  30. package/dist/web3/assets/NestPageHeader-CoLUV1Oz.js +1 -0
  31. package/dist/web3/assets/NestView-CTZ55tXD.js +63 -0
  32. package/dist/web3/assets/OverviewTab-Dxi-Hu-N.js +1 -0
  33. package/dist/web3/assets/PersonCombobox-CPOlNs6G.js +1 -0
  34. package/dist/web3/assets/ReasonDialog-DgLxfRWv.js +1 -0
  35. package/dist/web3/assets/ReviewActions-OfOdqzCn.js +6 -0
  36. package/dist/web3/assets/ReviewTab-CyX8yxd3.js +1 -0
  37. package/dist/web3/assets/StewardsTab-DBEEfAQI.js +1 -0
  38. package/dist/web3/assets/SubmitForReviewModal-Ci2DdWs0.js +1 -0
  39. package/dist/web3/assets/alert-dialog-DTozKlmV.js +7 -0
  40. package/dist/web3/assets/arrow-left-BCKt4CwQ.js +6 -0
  41. package/dist/web3/assets/backlinks-CYd4xENL.js +24 -0
  42. package/dist/web3/assets/card-XG2dP5Xf.js +1 -0
  43. package/dist/web3/assets/chevron-left-BPwUT9DS.js +6 -0
  44. package/dist/web3/assets/circle-check-DI8IBGeo.js +6 -0
  45. package/dist/web3/assets/circle-x-VKVhC88W.js +6 -0
  46. package/dist/web3/assets/code-xml-CMabYZvH.js +6 -0
  47. package/dist/web3/assets/corner-down-right-BGxwK6wH.js +6 -0
  48. package/dist/web3/assets/count-skeleton-DMbdpscX.js +1 -0
  49. package/dist/web3/assets/dates-BCxbm4_q.js +1 -0
  50. package/dist/web3/assets/earth-BEp1EKTo.js +6 -0
  51. package/dist/web3/assets/file-exclamation-point-r8qZGIjP.js +6 -0
  52. package/dist/web3/assets/folder-input-Cmtx1Jhk.js +11 -0
  53. package/dist/web3/assets/folder-target-CUSWqImF.js +1 -0
  54. package/dist/web3/assets/index-BM-h3DwI.css +1 -0
  55. package/dist/web3/assets/index-C1wTSjfP.js +29 -0
  56. package/dist/web3/assets/index-EaX2yql0.js +389 -0
  57. package/dist/web3/assets/page-B3yvQvHy.js +1 -0
  58. package/dist/web3/assets/page-BExMvttJ.js +1 -0
  59. package/dist/web3/assets/page-BUOADv2X.js +1 -0
  60. package/dist/web3/assets/page-BYUFhYWZ.js +1 -0
  61. package/dist/web3/assets/page-Bicf02Cz.js +1 -0
  62. package/dist/web3/assets/page-BsPMDm5d.js +45 -0
  63. package/dist/web3/assets/page-ByyVLDVo.js +16 -0
  64. package/dist/web3/assets/page-CC0NJi8v.js +1 -0
  65. package/dist/web3/assets/page-CHb0F5hV.js +24 -0
  66. package/dist/web3/assets/page-CPjWQNKx.js +11 -0
  67. package/dist/web3/assets/page-CQt3ZGbf.js +2 -0
  68. package/dist/web3/assets/page-Cj1NetQ-.js +1 -0
  69. package/dist/web3/assets/page-CtaW65El.js +1 -0
  70. package/dist/web3/assets/page-DGOL9l8i.js +6 -0
  71. package/dist/web3/assets/page-title-ClvvBsIo.js +1 -0
  72. package/dist/web3/assets/play-C5YNKpAH.js +6 -0
  73. package/dist/web3/assets/refresh-cw-BhHzSH9m.js +6 -0
  74. package/dist/web3/assets/scroll-area-dRWncRqa.css +1 -0
  75. package/dist/web3/assets/scroll-area-hz-xtayP.js +1 -0
  76. package/dist/web3/assets/select-DBFTrUmI.js +6 -0
  77. package/dist/web3/assets/send-0CHvmluT.js +6 -0
  78. package/dist/web3/assets/settings-CeeF4LEb.js +6 -0
  79. package/dist/web3/assets/share-2-CMnOlOt-.js +6 -0
  80. package/dist/web3/assets/tag-DQ_6J5Gv.js +11 -0
  81. package/dist/web3/assets/trash-2-HBi-Pslz.js +6 -0
  82. package/dist/web3/assets/triangle-alert-CDy8-7sv.js +6 -0
  83. package/dist/web3/assets/user-plus-CvBNcpn4.js +6 -0
  84. package/dist/web3/assets/x-By7piikG.js +6 -0
  85. package/dist/web3/assets/zap-C2T8riBp.js +11 -0
  86. package/dist/web3/index.html +2 -2
  87. package/package.json +4 -2
  88. package/dist/web3/assets/index-C2hW_9j9.js +0 -1382
  89. package/dist/web3/assets/index-T4pilFNL.css +0 -1
@@ -8,7 +8,7 @@ import {
8
8
  config,
9
9
  getDb,
10
10
  isEmailish
11
- } from "./chunk-J2OQ3MEB.js";
11
+ } from "./chunk-LA3VTQ22.js";
12
12
  import {
13
13
  ANON_USER_ID
14
14
  } from "./chunk-YB3LKF7U.js";
@@ -138,6 +138,12 @@ function startTelemetryLoop() {
138
138
  function nowExpr(db) {
139
139
  return db.dialect === "sqlite" ? "datetime('now')" : "to_char((now() AT TIME ZONE 'utc'), 'YYYY-MM-DD HH24:MI:SS')";
140
140
  }
141
+ function suffixOf(db, column, length) {
142
+ return db.dialect === "sqlite" ? `substr(${column}, -${length})` : `right(${column}, ${length})`;
143
+ }
144
+ function nowMsExpr(db) {
145
+ return db.dialect === "sqlite" ? "strftime('%Y-%m-%d %H:%M:%f','now')" : "to_char((now() AT TIME ZONE 'utc'), 'YYYY-MM-DD HH24:MI:SS.MS')";
146
+ }
141
147
  function insertOrIgnore(db, insertSql) {
142
148
  return db.dialect === "sqlite" ? insertSql.replace(/^\s*INSERT\s+INTO/i, "INSERT OR IGNORE INTO") : `${insertSql} ON CONFLICT DO NOTHING`;
143
149
  }
@@ -657,6 +663,7 @@ function renderText(message) {
657
663
  }
658
664
  var STATUS_META = {
659
665
  approved: { subjectWord: "approved", label: "Approved", color: "#16a34a" },
666
+ mention: { subjectWord: "mentioned you", label: "Mention", color: "#7c3aed" },
660
667
  rejected: { subjectWord: "rejected", label: "Rejected", color: "#dc2626" },
661
668
  pending_review: {
662
669
  subjectWord: "awaiting review",
@@ -715,6 +722,17 @@ var TEMPLATES = {
715
722
  lineH: (x) => `${x.doc}${x.vH} has been approved${x.byH}${x.noteH}.`,
716
723
  rows: governanceRows
717
724
  },
725
+ // A mention names one person, so this is the one template addressed to
726
+ // "you". `note` carries the comment's own words when there are any — a
727
+ // mention with no context reads as a summons.
728
+ mention: {
729
+ heading: (x) => `${x.d.actor || "Someone"} mentioned you in ${x.title}`,
730
+ detailsHeader: "Document details",
731
+ linkLabel: "Open document",
732
+ lineT: (x) => `You were mentioned in ${x.title}${x.byT}${x.noteT}.`,
733
+ lineH: (x) => `You were mentioned in ${x.doc}${x.byH}${x.noteH}.`,
734
+ rows: (x) => [plainRow("Nest", x.d.path)]
735
+ },
718
736
  rejected: {
719
737
  heading: (x) => `${x.title} is ${x.meta.subjectWord}`,
720
738
  detailsHeader: "Document details",
@@ -1278,8 +1296,11 @@ async function listNotifications(userEmail, opts = {}) {
1278
1296
  CASE WHEN dr.target_type = 'document' THEN dr.node_id END,
1279
1297
  -- A mention names the document directly; there's no request row.
1280
1298
  CASE WHEN n.kind = 'mention' THEN n.subject_id END
1281
- ) AS node_id
1299
+ ) AS node_id,
1300
+ -- Name resolved here so the inbox never fetches the nest list for it.
1301
+ COALESCE(ns.name, n.nest_id) AS nest_name
1282
1302
  FROM notifications n
1303
+ LEFT JOIN nests ns ON ns.id = n.nest_id
1283
1304
  LEFT JOIN review_requests rr ON rr.id = n.subject_id
1284
1305
  LEFT JOIN deletion_requests dr ON dr.id = n.subject_id
1285
1306
  WHERE LOWER(n.user_email) = LOWER(?)${opts.unreadOnly ? " AND n.read_at IS NULL" : ""}
@@ -1304,6 +1325,12 @@ async function markNotificationsRead(userEmail, ids) {
1304
1325
  );
1305
1326
  }
1306
1327
  }
1328
+ async function markNotificationsReadBySubject(subjectId, kind) {
1329
+ await getDb().run(
1330
+ "UPDATE notifications SET read_at = ? WHERE subject_id = ? AND kind = ? AND read_at IS NULL",
1331
+ [(/* @__PURE__ */ new Date()).toISOString(), subjectId, kind]
1332
+ );
1333
+ }
1307
1334
 
1308
1335
  // src/governance/teams-service.ts
1309
1336
  var VALID_TEAM_ROLES = ["admin", "editor", "viewer"];
@@ -2195,22 +2222,23 @@ var LIVE_ONLY_N = "n.archived_at IS NULL";
2195
2222
  async function listNests(userId, opts = {}) {
2196
2223
  const db = getDb();
2197
2224
  const scope = opts.archived ? "archived_at IS NOT NULL" : LIVE_ONLY;
2225
+ const orderBy = opts.archived ? "archived_at DESC" : "created_at DESC";
2198
2226
  if (userId === ANON_USER_ID) {
2199
2227
  return await db.all(
2200
- `SELECT * FROM nests WHERE user_id = ? AND ${scope} ORDER BY created_at DESC`,
2228
+ `SELECT * FROM nests WHERE user_id = ? AND ${scope} ORDER BY ${orderBy}`,
2201
2229
  [ANON_USER_ID]
2202
2230
  );
2203
2231
  }
2204
2232
  const includeAnon = config.AUTH_MODE === "open" || await isLicenseAdminUserId(userId);
2205
2233
  if (!includeAnon) {
2206
2234
  return await db.all(
2207
- `SELECT * FROM nests WHERE user_id = ? AND ${scope} ORDER BY created_at DESC`,
2235
+ `SELECT * FROM nests WHERE user_id = ? AND ${scope} ORDER BY ${orderBy}`,
2208
2236
  [userId]
2209
2237
  );
2210
2238
  }
2211
2239
  return await db.all(
2212
2240
  `SELECT * FROM nests WHERE (user_id = ? OR user_id = ?) AND ${scope}
2213
- ORDER BY created_at DESC`,
2241
+ ORDER BY ${orderBy}`,
2214
2242
  [userId, ANON_USER_ID]
2215
2243
  );
2216
2244
  }
@@ -2463,6 +2491,9 @@ var FlatNestStorage = class extends NestStorage2 {
2463
2491
  }
2464
2492
  };
2465
2493
 
2494
+ // src/nodes/node-index.ts
2495
+ import { listSuggestions } from "@promptowl/contextnest-engine";
2496
+
2466
2497
  // src/shared/node-id.ts
2467
2498
  function assertSafeNodeId(rawId) {
2468
2499
  let id = rawId;
@@ -2591,13 +2622,21 @@ async function ensureNodeIndex(nestId) {
2591
2622
  return run;
2592
2623
  }
2593
2624
  async function rebuildNodeIndex(nestId) {
2594
- const { engineCache: engineCache2 } = await import("./engine-XKRP7GOQ.js");
2595
- const { documentsWithSuggestions } = await import("./external-edit-service-TXW7SXSF.js");
2625
+ const { engineCache: engineCache2 } = await import("./engine-S3QBQ7LH.js");
2626
+ const { documentsWithSuggestions } = await import("./external-edit-service-6FNFPOJJ.js");
2596
2627
  const { storage, dropDiscoveryCache } = await engineCache2.get(nestId);
2597
2628
  dropDiscoveryCache();
2598
2629
  const startedAt = writeGeneration.get(nestId) ?? 0;
2599
2630
  const documents = await storage.discoverDocuments({ includeRetired: true });
2600
2631
  const staged = await documentsWithSuggestions(nestId);
2632
+ const stagedMetas = (await Promise.all(
2633
+ [...staged].map(
2634
+ (id) => listSuggestions(storage, id).catch((err) => {
2635
+ console.error("[node-index] listSuggestions failed", nestId, id, err);
2636
+ return [];
2637
+ })
2638
+ )
2639
+ )).flat();
2601
2640
  const db = getDb();
2602
2641
  const insertSql = insertOrReplace(
2603
2642
  db,
@@ -2605,18 +2644,15 @@ async function rebuildNodeIndex(nestId) {
2605
2644
  ["nest_id", "node_id"],
2606
2645
  UPSERT_SET
2607
2646
  );
2608
- const stagedSql = insertOrIgnore(
2609
- db,
2610
- "INSERT INTO node_suggestions (nest_id, node_id) VALUES (?, ?)"
2611
- );
2647
+ const stagedSql = insertOrIgnore(db, SUGGESTION_INSERT);
2612
2648
  await db.transaction(async (tx) => {
2613
2649
  await tx.run("DELETE FROM node_index WHERE nest_id = ?", [nestId]);
2614
2650
  for (const doc of documents) {
2615
2651
  await tx.run(insertSql, [nestId, ...rowValues(doc)]);
2616
2652
  }
2617
2653
  await tx.run("DELETE FROM node_suggestions WHERE nest_id = ?", [nestId]);
2618
- for (const id of staged) {
2619
- await tx.run(stagedSql, [nestId, id]);
2654
+ for (const m of stagedMetas) {
2655
+ await tx.run(stagedSql, suggestionValues(nestId, m));
2620
2656
  }
2621
2657
  if ((writeGeneration.get(nestId) ?? 0) !== startedAt) return;
2622
2658
  await tx.run(
@@ -2626,30 +2662,42 @@ async function rebuildNodeIndex(nestId) {
2626
2662
  });
2627
2663
  return documents.length;
2628
2664
  }
2629
- async function setSuggestionFlag(nestId, nodeId, present) {
2665
+ var SUGGESTION_INSERT = "INSERT INTO node_suggestions (nest_id, node_id, suggestion_id, source, actor, detected_at, target_hash, proposed_hash, note) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
2666
+ var suggestionValues = (nestId, m) => [
2667
+ nestId,
2668
+ m.document_id,
2669
+ m.suggestion_id,
2670
+ m.source,
2671
+ m.actor,
2672
+ m.detected_at,
2673
+ m.target_hash,
2674
+ m.proposed_hash,
2675
+ m.note ?? null
2676
+ ];
2677
+ async function syncSuggestionRows(nestId, nodeId, metas) {
2630
2678
  try {
2631
2679
  const db = getDb();
2632
- if (!present) {
2633
- await db.run(
2680
+ const insertSql = insertOrIgnore(db, SUGGESTION_INSERT);
2681
+ await db.transaction(async (tx) => {
2682
+ await tx.run(
2634
2683
  "DELETE FROM node_suggestions WHERE nest_id = ? AND node_id = ?",
2635
2684
  [nestId, nodeId]
2636
2685
  );
2637
- return;
2638
- }
2639
- await db.run(
2640
- insertOrIgnore(
2641
- db,
2642
- "INSERT INTO node_suggestions (nest_id, node_id) VALUES (?, ?)"
2643
- ),
2644
- [nestId, nodeId]
2645
- );
2686
+ for (const m of metas) await tx.run(insertSql, suggestionValues(nestId, m));
2687
+ });
2646
2688
  } catch (err) {
2647
- console.error("[node-index] suggestion flag failed", nestId, nodeId, err);
2689
+ console.error("[node-index] suggestion rows failed", nestId, nodeId, err);
2648
2690
  }
2649
2691
  }
2692
+ async function listSuggestionRows(nestId) {
2693
+ return await getDb().all(
2694
+ "SELECT * FROM node_suggestions WHERE nest_id = ? ORDER BY detected_at DESC",
2695
+ [nestId]
2696
+ );
2697
+ }
2650
2698
  async function nodesWithSuggestions(nestId) {
2651
2699
  const rows = await getDb().all(
2652
- "SELECT node_id FROM node_suggestions WHERE nest_id = ?",
2700
+ "SELECT DISTINCT node_id FROM node_suggestions WHERE nest_id = ?",
2653
2701
  [nestId]
2654
2702
  );
2655
2703
  return new Set(rows.map((r) => r.node_id));
@@ -2826,6 +2874,8 @@ async function opContext(nestId, actor, onProgress) {
2826
2874
 
2827
2875
  export {
2828
2876
  nowExpr,
2877
+ suffixOf,
2878
+ nowMsExpr,
2829
2879
  insertOrIgnore,
2830
2880
  insertOrReplace,
2831
2881
  trackEvent,
@@ -2889,7 +2939,8 @@ export {
2889
2939
  markIndexStale,
2890
2940
  ensureNodeIndex,
2891
2941
  rebuildNodeIndex,
2892
- setSuggestionFlag,
2942
+ syncSuggestionRows,
2943
+ listSuggestionRows,
2893
2944
  nodesWithSuggestions,
2894
2945
  indexedNodeIds,
2895
2946
  readIndexedNodes,
@@ -2914,6 +2965,7 @@ export {
2914
2965
  notifyNestInvite,
2915
2966
  listNotifications,
2916
2967
  markNotificationsRead,
2968
+ markNotificationsReadBySubject,
2917
2969
  createTeam,
2918
2970
  findTeamByExternalId,
2919
2971
  listTeamsForUser,
@@ -334,6 +334,28 @@ var config = {
334
334
  get OIDC_DEPARTMENT_TAGGING() {
335
335
  return process.env.OIDC_DEPARTMENT_TAGGING === "true";
336
336
  },
337
+ /**
338
+ * Widen the add-a-person suggestions (GET /people/suggest) from "people you
339
+ * already work with" to every registered account on this server.
340
+ *
341
+ * OFF BY DEFAULT, and the default is the point. On a single-company
342
+ * self-hosted server every account is a colleague, so the whole directory is
343
+ * the useful answer and this saves asking someone for their address. On the
344
+ * PromptOwl-hosted deployment, one server holds many unrelated customers —
345
+ * so the same list is one customer's staff being offered another customer's
346
+ * email addresses. The hosted deployment leaves this unset; a self-hosted
347
+ * operator who knows their user table is their own staff turns it on.
348
+ *
349
+ * Narrows nothing when off: the default scoping (shared nests and teams,
350
+ * plus everything for a server admin, who administers the accounts anyway)
351
+ * is unchanged. Suggestions never gate the add either way — an address that
352
+ * appears in no list is still valid to type, so this only ever affects what
353
+ * is *offered*, never who can be invited. Superadmin-togglable at runtime
354
+ * via /admin/settings.
355
+ */
356
+ get PEOPLE_SUGGEST_ALL_USERS() {
357
+ return process.env.PEOPLE_SUGGEST_ALL_USERS === "true";
358
+ },
337
359
  /**
338
360
  * Path to the .env file the server reads its config from and the license
339
361
  * install flow persists PROMPTOWL_KEY into. Defaults UNDER DATA_ROOT (not
@@ -1719,6 +1741,46 @@ function runMigrations(db) {
1719
1741
  widenNestVisibilityWithGuard(db);
1720
1742
  recordMigration("041_nest_visibility_org");
1721
1743
  }
1744
+ if (!hasMigration("042_community_sites")) {
1745
+ db.transaction(() => {
1746
+ db.exec(`
1747
+ CREATE TABLE IF NOT EXISTS community_sites (
1748
+ id TEXT PRIMARY KEY,
1749
+ name TEXT NOT NULL,
1750
+ url TEXT NOT NULL UNIQUE,
1751
+ secret TEXT NOT NULL,
1752
+ is_active INTEGER NOT NULL DEFAULT 1,
1753
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1754
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
1755
+ );
1756
+ `);
1757
+ recordMigration("042_community_sites");
1758
+ })();
1759
+ }
1760
+ if (!hasMigration("043_node_suggestion_details")) {
1761
+ db.transaction(() => {
1762
+ db.exec(`
1763
+ DROP TABLE IF EXISTS node_suggestions;
1764
+ CREATE TABLE node_suggestions (
1765
+ nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
1766
+ node_id TEXT NOT NULL,
1767
+ suggestion_id TEXT NOT NULL,
1768
+ source TEXT NOT NULL,
1769
+ actor TEXT NOT NULL,
1770
+ detected_at TEXT NOT NULL,
1771
+ target_hash TEXT NOT NULL,
1772
+ proposed_hash TEXT NOT NULL,
1773
+ note TEXT,
1774
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1775
+ PRIMARY KEY (nest_id, node_id, suggestion_id)
1776
+ );
1777
+ CREATE INDEX IF NOT EXISTS idx_node_suggestions_nest_detected
1778
+ ON node_suggestions(nest_id, detected_at DESC);
1779
+ UPDATE nests SET index_synced_at = NULL;
1780
+ `);
1781
+ recordMigration("043_node_suggestion_details");
1782
+ })();
1783
+ }
1722
1784
  }
1723
1785
  function fkViolationCounts(db) {
1724
1786
  const rows = db.pragma("foreign_key_check");
@@ -1993,7 +2055,7 @@ async function initDb() {
1993
2055
  if (config.DB_DRIVER === "postgres") {
1994
2056
  const { Pool } = await import("pg");
1995
2057
  const { PostgresAdapter } = await import("./adapter.postgres-6VZCMPQL.js");
1996
- const { runPostgresMigrations } = await import("./migrations.postgres-KKICIH2C.js");
2058
+ const { runPostgresMigrations } = await import("./migrations.postgres-AIQ7WSU7.js");
1997
2059
  const pool = new Pool(buildPgConfig());
1998
2060
  adapter = new PostgresAdapter(pool);
1999
2061
  await runPostgresMigrations(adapter);
@@ -2,13 +2,14 @@ import {
2
2
  engineCache,
3
3
  indexedNodeIds,
4
4
  isNestArchived,
5
+ listSuggestionRows,
5
6
  markIndexStale,
6
7
  resolveNestPath,
7
- setSuggestionFlag
8
- } from "./chunk-W5ILNGPD.js";
8
+ syncSuggestionRows
9
+ } from "./chunk-KIAAEHWL.js";
9
10
  import {
10
11
  getDb
11
- } from "./chunk-J2OQ3MEB.js";
12
+ } from "./chunk-LA3VTQ22.js";
12
13
 
13
14
  // src/governance/external-edit-service.ts
14
15
  import { readFile, readdir } from "fs/promises";
@@ -82,7 +83,7 @@ async function scanDocumentForDriftInternal(nestId, documentId, actor) {
82
83
  if (await bodyMatchesLatestVersion(storage, documentId, drift.actualHash)) {
83
84
  return null;
84
85
  }
85
- const { hasUnsealedDraft } = await import("./version-service-TKNYJMCM.js");
86
+ const { hasUnsealedDraft } = await import("./version-service-KIOQME6U.js");
86
87
  if (await hasUnsealedDraft(nestId, documentId)) {
87
88
  return null;
88
89
  }
@@ -91,7 +92,7 @@ async function scanDocumentForDriftInternal(nestId, documentId, actor) {
91
92
  const existing = await listSuggestions(storage, documentId);
92
93
  const dup = existing.find((s) => s.proposed_hash === drift.actualHash);
93
94
  if (dup) {
94
- await setSuggestionFlag(nestId, documentId, true);
95
+ await syncSuggestionRows(nestId, documentId, existing);
95
96
  return { meta: dup, created: false };
96
97
  }
97
98
  const result = await stageSuggestion({
@@ -103,13 +104,13 @@ async function scanDocumentForDriftInternal(nestId, documentId, actor) {
103
104
  actor,
104
105
  docTier: "standard"
105
106
  });
106
- await setSuggestionFlag(nestId, documentId, true);
107
+ await syncSuggestionRows(nestId, documentId, [...existing, result.meta]);
107
108
  return { meta: result.meta, created: true };
108
109
  }
109
110
  async function refreshSuggestionFlag(nestId, storage, documentId) {
110
111
  try {
111
112
  const remaining = await listSuggestions(storage, documentId);
112
- await setSuggestionFlag(nestId, documentId, remaining.length > 0);
113
+ await syncSuggestionRows(nestId, documentId, remaining);
113
114
  } catch (err) {
114
115
  console.error("[external-edit] flag refresh failed", nestId, documentId, err);
115
116
  }
@@ -170,33 +171,27 @@ async function getPendingChange(nestId, documentId) {
170
171
  return null;
171
172
  }
172
173
  async function listNestExternalEdits(nestId) {
174
+ const rows = await listSuggestionRows(nestId);
175
+ if (rows.length === 0) return [];
173
176
  const { storage } = await engineCache.get(nestId);
174
- const docs = await storage.discoverDocuments();
175
- const lists = await Promise.all(
176
- docs.map(async (doc) => {
177
- const metas = await listSuggestions(storage, doc.id);
178
- const fresh = [];
179
- for (const meta of metas) {
180
- if (await bodyMatchesLatestVersion(storage, doc.id, meta.proposed_hash)) {
181
- continue;
182
- }
183
- fresh.push(meta);
184
- }
185
- return fresh;
186
- })
177
+ const kept = await Promise.all(
178
+ rows.map(
179
+ async (r) => await bodyMatchesLatestVersion(storage, r.node_id, r.proposed_hash) ? null : r
180
+ )
181
+ );
182
+ return kept.flatMap(
183
+ (r) => r ? [{
184
+ suggestion_id: r.suggestion_id,
185
+ nest_id: nestId,
186
+ document_id: r.node_id,
187
+ source: r.source,
188
+ detected_at: r.detected_at,
189
+ actor: r.actor,
190
+ target_hash: r.target_hash,
191
+ proposed_hash: r.proposed_hash,
192
+ note: r.note ?? void 0
193
+ }] : []
187
194
  );
188
- const entries = lists.flat().map((meta) => ({
189
- suggestion_id: meta.suggestion_id,
190
- nest_id: nestId,
191
- document_id: meta.document_id,
192
- source: meta.source,
193
- detected_at: meta.detected_at,
194
- actor: meta.actor,
195
- target_hash: meta.target_hash,
196
- proposed_hash: meta.proposed_hash,
197
- note: meta.note
198
- }));
199
- return entries.sort((a, b) => b.detected_at.localeCompare(a.detected_at));
200
195
  }
201
196
  async function getExternalEditDetail(nestId, documentId, suggestionId) {
202
197
  const { storage } = await engineCache.get(nestId);
@@ -259,7 +254,7 @@ async function listExternalEditVerdicts(nestId, documentId) {
259
254
  async function mirrorVersion(input) {
260
255
  const { storage } = await engineCache.get(input.nestId);
261
256
  const node = await storage.readDocument(input.documentId);
262
- const { upsertVersion, setApprovedVersion } = await import("./version-service-TKNYJMCM.js");
257
+ const { upsertVersion, setApprovedVersion } = await import("./version-service-KIOQME6U.js");
263
258
  await upsertVersion({
264
259
  nestId: input.nestId,
265
260
  nodeId: input.documentId,
@@ -2,10 +2,10 @@ import {
2
2
  buildTitleMap,
3
3
  insertOrReplace,
4
4
  nowExpr
5
- } from "./chunk-W5ILNGPD.js";
5
+ } from "./chunk-KIAAEHWL.js";
6
6
  import {
7
7
  getDb
8
- } from "./chunk-J2OQ3MEB.js";
8
+ } from "./chunk-LA3VTQ22.js";
9
9
 
10
10
  // src/governance/version-service.ts
11
11
  import { createHash } from "crypto";
@@ -2,7 +2,7 @@ import {
2
2
  getDb,
3
3
  initDb,
4
4
  resetDb
5
- } from "./chunk-J2OQ3MEB.js";
5
+ } from "./chunk-LA3VTQ22.js";
6
6
  import "./chunk-YB3LKF7U.js";
7
7
  export {
8
8
  getDb,
@@ -2,9 +2,9 @@ import {
2
2
  engineApi,
3
3
  engineCache,
4
4
  opContext
5
- } from "./chunk-W5ILNGPD.js";
5
+ } from "./chunk-KIAAEHWL.js";
6
6
  import "./chunk-YVMSM7LS.js";
7
- import "./chunk-J2OQ3MEB.js";
7
+ import "./chunk-LA3VTQ22.js";
8
8
  import "./chunk-YB3LKF7U.js";
9
9
  import "./chunk-FRQJWGN3.js";
10
10
  export {
@@ -11,10 +11,10 @@ import {
11
11
  scanNestForDrift,
12
12
  startDriftScanner,
13
13
  stopDriftScanner
14
- } from "./chunk-ZAV4QUZA.js";
15
- import "./chunk-W5ILNGPD.js";
14
+ } from "./chunk-XUIWAWDO.js";
15
+ import "./chunk-KIAAEHWL.js";
16
16
  import "./chunk-YVMSM7LS.js";
17
- import "./chunk-J2OQ3MEB.js";
17
+ import "./chunk-LA3VTQ22.js";
18
18
  import "./chunk-YB3LKF7U.js";
19
19
  import "./chunk-FRQJWGN3.js";
20
20
  export {
@@ -6,9 +6,9 @@ import {
6
6
  listGrants,
7
7
  listUserGrants,
8
8
  resolveNodeGrant
9
- } from "./chunk-MY4JIWQD.js";
9
+ } from "./chunk-I3CSD6CK.js";
10
10
  import "./chunk-YVMSM7LS.js";
11
- import "./chunk-J2OQ3MEB.js";
11
+ import "./chunk-LA3VTQ22.js";
12
12
  import "./chunk-YB3LKF7U.js";
13
13
  export {
14
14
  createGrant,