@promptowl/contextnest-community 1.20.0 → 1.21.1

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 (24) hide show
  1. package/README.md +1 -1
  2. package/dist/{adapter.postgres-YOODX2BI.js → adapter.postgres-6VZCMPQL.js} +11 -1
  3. package/dist/{chunk-KHMSDTJJ.js → chunk-FXYFLUE7.js} +60 -12
  4. package/dist/{chunk-7CPP6FIU.js → chunk-G4AL7ETF.js} +173 -34
  5. package/dist/{chunk-3LUUJUWS.js → chunk-IVXVOXYN.js} +6 -2
  6. package/dist/{chunk-O5DYL7ZG.js → chunk-LL2JU5AA.js} +2 -2
  7. package/dist/{chunk-GVUBZTI3.js → chunk-NH7L2S6S.js} +93 -40
  8. package/dist/{chunk-XCFKS65Y.js → chunk-SKZ7YYT4.js} +26 -2
  9. package/dist/{chunk-EY7WOKJR.js → chunk-UVOAYQTU.js} +12 -4
  10. package/dist/{client-7Z2IVCPW.js → client-KBK4VIWJ.js} +1 -1
  11. package/dist/{engine-VZTSDUPB.js → engine-WXWER2V7.js} +2 -2
  12. package/dist/{external-edit-service-3FVK3OD7.js → external-edit-service-Y757EFMS.js} +3 -3
  13. package/dist/{grants-service-WC27TRY3.js → grants-service-FKINSDLZ.js} +2 -2
  14. package/dist/index.js +551 -150
  15. package/dist/{migrations.postgres-L42OXR7K.js → migrations.postgres-JA2YGLS3.js} +20 -1
  16. package/dist/{review-service-TJRHRMMZ.js → review-service-PPR5BV5J.js} +8 -6
  17. package/dist/{stewardship-service-HRVD7SIS.js → stewardship-service-IKC7MXLP.js} +3 -3
  18. package/dist/{version-service-5PIFT2OY.js → version-service-GWSDSUZR.js} +11 -3
  19. package/dist/web3/assets/index-T4pilFNL.css +1 -0
  20. package/dist/web3/assets/index-deyhV93k.js +1382 -0
  21. package/dist/web3/index.html +2 -2
  22. package/package.json +1 -1
  23. package/dist/web3/assets/index-D4UG3T04.js +0 -1365
  24. package/dist/web3/assets/index-Dw1UTDIk.css +0 -1
@@ -4,19 +4,19 @@ import {
4
4
  resolveNestWideRoles,
5
5
  resolveStewardsForNode,
6
6
  stewardCoverageForUser
7
- } from "./chunk-O5DYL7ZG.js";
7
+ } from "./chunk-LL2JU5AA.js";
8
8
  import {
9
9
  grantCoversNode,
10
10
  listUserGrants,
11
11
  resolveNodeGrant
12
- } from "./chunk-3LUUJUWS.js";
12
+ } from "./chunk-IVXVOXYN.js";
13
13
  import {
14
14
  createVersion,
15
15
  getApprovedVersion,
16
16
  getApprovedVersions,
17
17
  getCurrentVersion,
18
18
  setApprovedVersion
19
- } from "./chunk-KHMSDTJJ.js";
19
+ } from "./chunk-FXYFLUE7.js";
20
20
  import {
21
21
  buildDocContext,
22
22
  buildTitleMap,
@@ -36,7 +36,7 @@ import {
36
36
  resolveNestPermission,
37
37
  sendEmailToRecipient,
38
38
  titleForNode
39
- } from "./chunk-7CPP6FIU.js";
39
+ } from "./chunk-G4AL7ETF.js";
40
40
  import {
41
41
  ConflictError,
42
42
  NotFoundError,
@@ -45,7 +45,7 @@ import {
45
45
  import {
46
46
  config,
47
47
  getDb
48
- } from "./chunk-XCFKS65Y.js";
48
+ } from "./chunk-SKZ7YYT4.js";
49
49
 
50
50
  // src/governance/review-service.ts
51
51
  import { v4 as uuid2 } from "uuid";
@@ -119,6 +119,20 @@ var safeJson = (s) => {
119
119
  };
120
120
 
121
121
  // src/workflow/flow-graph.ts
122
+ async function nodeTrail(db, nestId, ids) {
123
+ const unique = [...new Set(ids)];
124
+ const titles = /* @__PURE__ */ new Map();
125
+ try {
126
+ const rows = await db.all(
127
+ `SELECT node_id, title FROM node_index
128
+ WHERE nest_id = ? AND node_id IN (${unique.map(() => "?").join(",")})`,
129
+ [nestId, ...unique]
130
+ );
131
+ for (const r of rows) if (r.title) titles.set(r.node_id, r.title);
132
+ } catch {
133
+ }
134
+ return ids.map((id) => `"${titles.get(id) ?? id}"`).join(" \u2192 ");
135
+ }
122
136
  async function assertFlowGraphAcyclic(nestId, extraFlowTypeId) {
123
137
  const db = getDb();
124
138
  const rows = await db.all(
@@ -133,7 +147,7 @@ async function assertFlowGraphAcyclic(nestId, extraFlowTypeId) {
133
147
  for (const r of rows) {
134
148
  if (r.from_node === r.to_node) {
135
149
  throw new ConflictError(
136
- `cycle: ["${r.from_node}"] \u2014 a flow edge cannot be a self-loop`
150
+ `cycle: ${await nodeTrail(db, nestId, [r.from_node])} cannot flow into itself`
137
151
  );
138
152
  }
139
153
  nodes.add(r.from_node);
@@ -698,31 +712,60 @@ function withNodeWriteLock(key, fn) {
698
712
  // src/governance/review-service.ts
699
713
  async function submitForReview(params) {
700
714
  const db = getDb();
701
- const existing = await db.get(
702
- "SELECT id FROM review_requests WHERE nest_id = ? AND node_id = ? AND status = 'pending'",
703
- [params.nestId, params.nodeId]
704
- );
705
- if (existing) {
706
- throw new Error("A review is already pending for this node");
707
- }
708
- const id = uuid2();
709
- await db.run(
710
- `INSERT INTO review_requests
715
+ const { id, version } = await withNodeWriteLock(
716
+ nodeWriteKey(params.nestId, params.nodeId),
717
+ async () => {
718
+ const existing = await db.get(
719
+ "SELECT id FROM review_requests WHERE nest_id = ? AND node_id = ? AND status = 'pending'",
720
+ [params.nestId, params.nodeId]
721
+ );
722
+ if (existing) {
723
+ throw new Error("A review is already pending for this node");
724
+ }
725
+ const version2 = await getCurrentVersion(params.nestId, params.nodeId) || params.version;
726
+ const id2 = uuid2();
727
+ await db.run(
728
+ `INSERT INTO review_requests
711
729
  (id, nest_id, node_id, version, requested_by, request_note, priority)
712
730
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
713
- [
714
- id,
715
- params.nestId,
716
- params.nodeId,
717
- params.version,
718
- params.requestedBy,
719
- params.note || null,
720
- params.priority || "normal"
721
- ]
722
- );
723
- await db.run(
724
- "UPDATE node_versions SET status = 'pending_review' WHERE nest_id = ? AND node_id = ? AND version = ?",
725
- [params.nestId, params.nodeId, params.version]
731
+ [
732
+ id2,
733
+ params.nestId,
734
+ params.nodeId,
735
+ version2,
736
+ params.requestedBy,
737
+ params.note || null,
738
+ params.priority || "normal"
739
+ ]
740
+ );
741
+ await db.run(
742
+ "UPDATE node_versions SET status = 'pending_review' WHERE nest_id = ? AND node_id = ? AND version = ?",
743
+ [params.nestId, params.nodeId, version2]
744
+ );
745
+ try {
746
+ const { storage, versions: versionManager } = await engineCache.get(
747
+ params.nestId
748
+ );
749
+ const node = await storage.readDocument(params.nodeId);
750
+ const history = await versionManager.getHistory(params.nodeId);
751
+ const alreadySealed = (history?.versions ?? []).some(
752
+ (v) => v.version === (node.frontmatter.version ?? 1)
753
+ );
754
+ if (!alreadySealed) {
755
+ await versionManager.createVersion(node, params.requestedBy, {
756
+ note: params.note || "Submitted for review"
757
+ });
758
+ }
759
+ } catch (err) {
760
+ console.error(
761
+ "VersionManager.createVersion failed (submit for review)",
762
+ params.nestId,
763
+ params.nodeId,
764
+ err
765
+ );
766
+ }
767
+ return { id: id2, version: version2 };
768
+ }
726
769
  );
727
770
  const reviewCtx = await buildDocContext(params.nestId, params.nodeId, params.baseUrl);
728
771
  void dispatchEvent({
@@ -730,18 +773,18 @@ async function submitForReview(params) {
730
773
  nestId: params.nestId,
731
774
  subjectId: params.nodeId,
732
775
  actor: params.requestedBy,
733
- message: `Review requested on *${reviewCtx.label}* (v${params.version}) by ${params.requestedBy}${params.note ? ` \u2014 "${params.note}"` : ""}`
776
+ message: `Review requested on *${reviewCtx.label}* (v${version}) by ${params.requestedBy}${params.note ? ` \u2014 "${params.note}"` : ""}`
734
777
  });
735
778
  void notifyNestEvent(
736
779
  params.nestId,
737
- `:memo: Review requested on *${reviewCtx.label}* (v${params.version}) by ${params.requestedBy}${params.note ? ` \u2014 "${params.note}"` : ""}`,
780
+ `:memo: Review requested on *${reviewCtx.label}* (v${version}) by ${params.requestedBy}${params.note ? ` \u2014 "${params.note}"` : ""}`,
738
781
  {
739
782
  status: "pending_review",
740
783
  docTitle: reviewCtx.docTitle,
741
784
  path: reviewCtx.path,
742
785
  link: reviewCtx.link,
743
786
  actor: params.requestedBy,
744
- version: params.version,
787
+ version,
745
788
  note: params.note || null
746
789
  }
747
790
  );
@@ -765,7 +808,7 @@ async function submitForReview(params) {
765
808
  path: reviewCtx.path,
766
809
  link: reviewCtx.link,
767
810
  actor: params.requestedBy,
768
- version: params.version,
811
+ version,
769
812
  note: params.note || null
770
813
  });
771
814
  }
@@ -1106,9 +1149,12 @@ async function getPendingReview(nestId, nodeId) {
1106
1149
  );
1107
1150
  return row ? rowToReviewRequest(row) : null;
1108
1151
  }
1152
+ function isNestTarget(t) {
1153
+ return t === "nest" || t === "nest_archive";
1154
+ }
1109
1155
  async function deletionTargetTitle(request, titles) {
1110
1156
  if (request.targetType === "folder") return prettyFolderPath(request.nodeId);
1111
- if (request.targetType === "nest") return await nestName(request.nestId);
1157
+ if (isNestTarget(request.targetType)) return await nestName(request.nestId);
1112
1158
  return titles?.get(request.nodeId) ?? await titleForNode(request.nestId, request.nodeId);
1113
1159
  }
1114
1160
  async function nestAdminEmails(nestId) {
@@ -1145,7 +1191,7 @@ async function deletionTargetContext(request, baseUrl) {
1145
1191
  const name = await nestName(request.nestId);
1146
1192
  const label = request.targetType === "folder" ? prettyFolderPath(request.nodeId) : name;
1147
1193
  return {
1148
- noun: request.targetType,
1194
+ noun: request.targetType === "nest_archive" ? "nest" : request.targetType,
1149
1195
  label,
1150
1196
  docTitle: label,
1151
1197
  path: name,
@@ -1210,7 +1256,8 @@ async function notifyDeletionRequested(request, baseUrl) {
1210
1256
  async function notifyDeletionResolved(params) {
1211
1257
  try {
1212
1258
  const { outcome, resolvedBy, note } = params;
1213
- const verb = outcome === "deleted" ? "deleted" : "rejected the deletion of";
1259
+ const archivedInstead = outcome === "deleted" && params.targetType === "nest_archive";
1260
+ const verb = archivedInstead ? "archived" : outcome === "deleted" ? "deleted" : "rejected the deletion of";
1214
1261
  const line = `${resolvedBy} ${verb} *${params.docTitle}*${note ? ` \u2014 "${note}"` : ""}`;
1215
1262
  const recipient = params.requestedBy.toLowerCase();
1216
1263
  const canInbox = !(params.targetType === "nest" && outcome === "deleted");
@@ -1220,7 +1267,7 @@ async function notifyDeletionResolved(params) {
1220
1267
  [recipient],
1221
1268
  `deletion_${outcome}`,
1222
1269
  params.requestId,
1223
- outcome === "deleted" ? `"${params.docTitle}" was deleted by ${resolvedBy} \u2014 your request was accepted` : `Your deletion request for "${params.docTitle}" was rejected by ${resolvedBy}${note ? ` \u2014 "${note}"` : ""}`
1270
+ outcome === "deleted" ? `"${params.docTitle}" was ${archivedInstead ? "archived" : "deleted"} by ${resolvedBy} \u2014 your request was accepted` : `Your deletion request for "${params.docTitle}" was rejected by ${resolvedBy}${note ? ` \u2014 "${note}"` : ""}`
1224
1271
  );
1225
1272
  }
1226
1273
  void dispatchEvent({
@@ -1240,7 +1287,10 @@ async function notifyDeletionResolved(params) {
1240
1287
  if (recipient !== resolvedBy.toLowerCase()) {
1241
1288
  const link = params.baseUrl ? `${params.baseUrl}/?nest=${encodeURIComponent(params.nestId)}` : "";
1242
1289
  void sendEmailToRecipient(recipient, {
1243
- status: outcome === "deleted" ? "deletion_deleted" : "deletion_declined",
1290
+ // The email has to agree with the inbox row and the channel line above:
1291
+ // an honoured archive is not a deletion, and the deletion template says
1292
+ // the nest is gone.
1293
+ status: archivedInstead ? "deletion_archived" : outcome === "deleted" ? "deletion_deleted" : "deletion_declined",
1244
1294
  docTitle: params.docTitle,
1245
1295
  path: params.nestId,
1246
1296
  link: outcome === "deleted" ? link : void 0,
@@ -1261,12 +1311,13 @@ async function requestDeletion(params) {
1261
1311
  const db = getDb();
1262
1312
  const targetType = params.targetType || "document";
1263
1313
  const noun = targetType === "document" ? "node" : targetType === "folder" ? "folder" : "nest";
1314
+ const kind = targetType === "nest_archive" ? "An archive request" : "A deletion request";
1264
1315
  const existing = await db.get(
1265
1316
  "SELECT id FROM deletion_requests WHERE nest_id = ? AND node_id = ? AND target_type = ? AND status = 'pending'",
1266
1317
  [params.nestId, params.nodeId, targetType]
1267
1318
  );
1268
1319
  if (existing) {
1269
- throw new ConflictError(`A deletion request is already pending for this ${noun}`);
1320
+ throw new ConflictError(`${kind} is already pending for this ${noun}`);
1270
1321
  }
1271
1322
  const id = uuid2();
1272
1323
  try {
@@ -1289,7 +1340,7 @@ async function requestDeletion(params) {
1289
1340
  params.nodeId,
1290
1341
  err
1291
1342
  );
1292
- throw new ConflictError(`A deletion request is already pending for this ${noun}`);
1343
+ throw new ConflictError(`${kind} is already pending for this ${noun}`);
1293
1344
  }
1294
1345
  const request = await getDeletionRequest(id);
1295
1346
  await notifyDeletionRequested(request, params.baseUrl);
@@ -1437,6 +1488,7 @@ export {
1437
1488
  canRequestDeletion,
1438
1489
  filterAccessible,
1439
1490
  safeJson,
1491
+ nodeTrail,
1440
1492
  requireWorkflowPlane,
1441
1493
  seedDefaultEdgeTypes,
1442
1494
  parseConditionSchema,
@@ -1458,6 +1510,7 @@ export {
1458
1510
  getReviewQueue,
1459
1511
  getReviewHistory,
1460
1512
  getPendingReview,
1513
+ isNestTarget,
1461
1514
  deletionTargetTitle,
1462
1515
  notifyDeletionResolved,
1463
1516
  requestDeletion,
@@ -1678,6 +1678,30 @@ function runMigrations(db) {
1678
1678
  db.exec("ALTER TABLE publish ADD COLUMN no_download INTEGER NOT NULL DEFAULT 0");
1679
1679
  }
1680
1680
  }
1681
+ if (!hasMigration("038_nest_archive")) {
1682
+ const cols = db.prepare("PRAGMA table_info(nests)").all().map((c) => c.name);
1683
+ if (!cols.includes("archived_at")) {
1684
+ db.exec("ALTER TABLE nests ADD COLUMN archived_at TEXT");
1685
+ }
1686
+ recordMigration("038_nest_archive");
1687
+ }
1688
+ if (!hasMigration("039_nest_pins")) {
1689
+ db.transaction(() => {
1690
+ db.exec(`
1691
+ CREATE TABLE IF NOT EXISTS nest_pins (
1692
+ -- BOTH sides cascade, as grants and nest_collaborators do. Without it
1693
+ -- on user_id, deleting an account with a single pin fails the whole
1694
+ -- transaction on a foreign-key violation (foreign_keys is ON).
1695
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
1696
+ nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
1697
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1698
+ PRIMARY KEY (user_id, nest_id)
1699
+ );
1700
+ CREATE INDEX IF NOT EXISTS idx_nest_pins_user ON nest_pins(user_id);
1701
+ `);
1702
+ })();
1703
+ recordMigration("039_nest_pins");
1704
+ }
1681
1705
  }
1682
1706
  function mergeCaseCollidingUsers(db) {
1683
1707
  const groups = db.prepare(
@@ -1894,8 +1918,8 @@ async function initDb() {
1894
1918
  if (adapter) return adapter;
1895
1919
  if (config.DB_DRIVER === "postgres") {
1896
1920
  const { Pool } = await import("pg");
1897
- const { PostgresAdapter } = await import("./adapter.postgres-YOODX2BI.js");
1898
- const { runPostgresMigrations } = await import("./migrations.postgres-L42OXR7K.js");
1921
+ const { PostgresAdapter } = await import("./adapter.postgres-6VZCMPQL.js");
1922
+ const { runPostgresMigrations } = await import("./migrations.postgres-JA2YGLS3.js");
1899
1923
  const pool = new Pool(buildPgConfig());
1900
1924
  adapter = new PostgresAdapter(pool);
1901
1925
  await runPostgresMigrations(adapter);
@@ -1,13 +1,14 @@
1
1
  import {
2
2
  engineCache,
3
3
  indexedNodeIds,
4
+ isNestArchived,
4
5
  markIndexStale,
5
6
  resolveNestPath,
6
7
  setSuggestionFlag
7
- } from "./chunk-7CPP6FIU.js";
8
+ } from "./chunk-G4AL7ETF.js";
8
9
  import {
9
10
  getDb
10
- } from "./chunk-XCFKS65Y.js";
11
+ } from "./chunk-SKZ7YYT4.js";
11
12
 
12
13
  // src/governance/external-edit-service.ts
13
14
  import { readFile, readdir } from "fs/promises";
@@ -81,6 +82,10 @@ async function scanDocumentForDriftInternal(nestId, documentId, actor) {
81
82
  if (await bodyMatchesLatestVersion(storage, documentId, drift.actualHash)) {
82
83
  return null;
83
84
  }
85
+ const { hasUnsealedDraft } = await import("./version-service-GWSDSUZR.js");
86
+ if (await hasUnsealedDraft(nestId, documentId)) {
87
+ return null;
88
+ }
84
89
  const approved = await loadChainHead(storage, documentId);
85
90
  if (!approved) return null;
86
91
  const existing = await listSuggestions(storage, documentId);
@@ -110,6 +115,7 @@ async function refreshSuggestionFlag(nestId, storage, documentId) {
110
115
  }
111
116
  }
112
117
  async function scanNestForDrift(nestId, actor = "system:scanner") {
118
+ if (await isNestArchived(nestId)) return { scanned: 0, staged: 0 };
113
119
  const { storage, dropDiscoveryCache } = await engineCache.get(nestId);
114
120
  dropDiscoveryCache();
115
121
  const docs = await storage.discoverDocuments();
@@ -253,7 +259,7 @@ async function listExternalEditVerdicts(nestId, documentId) {
253
259
  async function mirrorVersion(input) {
254
260
  const { storage } = await engineCache.get(input.nestId);
255
261
  const node = await storage.readDocument(input.documentId);
256
- const { upsertVersion, setApprovedVersion } = await import("./version-service-5PIFT2OY.js");
262
+ const { upsertVersion, setApprovedVersion } = await import("./version-service-GWSDSUZR.js");
257
263
  await upsertVersion({
258
264
  nestId: input.nestId,
259
265
  nodeId: input.documentId,
@@ -338,7 +344,9 @@ async function rejectExternalEdit(input) {
338
344
  var scannerTimer = null;
339
345
  async function scanAllNests() {
340
346
  const db = getDb();
341
- const rows = await db.all("SELECT id FROM nests");
347
+ const rows = await db.all(
348
+ "SELECT id FROM nests WHERE archived_at IS NULL"
349
+ );
342
350
  await Promise.all(
343
351
  rows.map(
344
352
  ({ id }) => scanNestForDrift(id).catch(
@@ -2,7 +2,7 @@ import {
2
2
  getDb,
3
3
  initDb,
4
4
  resetDb
5
- } from "./chunk-XCFKS65Y.js";
5
+ } from "./chunk-SKZ7YYT4.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-7CPP6FIU.js";
5
+ } from "./chunk-G4AL7ETF.js";
6
6
  import "./chunk-YVMSM7LS.js";
7
- import "./chunk-XCFKS65Y.js";
7
+ import "./chunk-SKZ7YYT4.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-EY7WOKJR.js";
15
- import "./chunk-7CPP6FIU.js";
14
+ } from "./chunk-UVOAYQTU.js";
15
+ import "./chunk-G4AL7ETF.js";
16
16
  import "./chunk-YVMSM7LS.js";
17
- import "./chunk-XCFKS65Y.js";
17
+ import "./chunk-SKZ7YYT4.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-3LUUJUWS.js";
9
+ } from "./chunk-IVXVOXYN.js";
10
10
  import "./chunk-YVMSM7LS.js";
11
- import "./chunk-XCFKS65Y.js";
11
+ import "./chunk-SKZ7YYT4.js";
12
12
  import "./chunk-YB3LKF7U.js";
13
13
  export {
14
14
  createGrant,