@promptowl/contextnest-community 1.19.0 → 1.20.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 (82) hide show
  1. package/CONFIGURATION.md +83 -1
  2. package/dist/{chunk-GSYMJ3A4.js → chunk-3LUUJUWS.js} +1 -1
  3. package/dist/{chunk-ENDHMQPD.js → chunk-7CPP6FIU.js} +251 -39
  4. package/dist/{chunk-TNAZJYL5.js → chunk-EY7WOKJR.js} +3 -3
  5. package/dist/{chunk-FBDEESNJ.js → chunk-GVUBZTI3.js} +26 -184
  6. package/dist/{chunk-BXUVIAMP.js → chunk-KHMSDTJJ.js} +2 -2
  7. package/dist/{chunk-3GMGLYTZ.js → chunk-O5DYL7ZG.js} +2 -2
  8. package/dist/{chunk-HYSTFMFG.js → chunk-XCFKS65Y.js} +107 -1
  9. package/dist/{client-SW5I6ZBK.js → client-7Z2IVCPW.js} +1 -1
  10. package/dist/{engine-V4DY4PYN.js → engine-VZTSDUPB.js} +2 -2
  11. package/dist/{external-edit-service-3NK5DGLA.js → external-edit-service-3FVK3OD7.js} +3 -3
  12. package/dist/{grants-service-CADI6LIB.js → grants-service-WC27TRY3.js} +2 -2
  13. package/dist/index.js +2203 -1056
  14. package/dist/{migrations.postgres-5VI4RDTS.js → migrations.postgres-L42OXR7K.js} +47 -1
  15. package/dist/{review-service-LK4M2C43.js → review-service-TJRHRMMZ.js} +6 -6
  16. package/dist/{stewardship-service-JAFSU5GN.js → stewardship-service-HRVD7SIS.js} +3 -3
  17. package/dist/{version-service-G7SFFKFO.js → version-service-5PIFT2OY.js} +3 -3
  18. package/dist/web3/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  19. package/dist/web3/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  20. package/dist/web3/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  21. package/dist/web3/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  22. package/dist/web3/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  23. package/dist/web3/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  24. package/dist/web3/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  25. package/dist/web3/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  26. package/dist/web3/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  27. package/dist/web3/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  28. package/dist/web3/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  29. package/dist/web3/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  30. package/dist/web3/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  31. package/dist/web3/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  32. package/dist/web3/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  33. package/dist/web3/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  34. package/dist/web3/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  35. package/dist/web3/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  36. package/dist/web3/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  37. package/dist/web3/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  38. package/dist/web3/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  39. package/dist/web3/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  40. package/dist/web3/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  41. package/dist/web3/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  42. package/dist/web3/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  43. package/dist/web3/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  44. package/dist/web3/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  45. package/dist/web3/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  46. package/dist/web3/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  47. package/dist/web3/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  48. package/dist/web3/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  49. package/dist/web3/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  50. package/dist/web3/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  51. package/dist/web3/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  52. package/dist/web3/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  53. package/dist/web3/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  54. package/dist/web3/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  55. package/dist/web3/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  56. package/dist/web3/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  57. package/dist/web3/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  58. package/dist/web3/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  59. package/dist/web3/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  60. package/dist/web3/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  61. package/dist/web3/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  62. package/dist/web3/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  63. package/dist/web3/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  64. package/dist/web3/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  65. package/dist/web3/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  66. package/dist/web3/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  67. package/dist/web3/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  68. package/dist/web3/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  69. package/dist/web3/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  70. package/dist/web3/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  71. package/dist/web3/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  72. package/dist/web3/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  73. package/dist/web3/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  74. package/dist/web3/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  75. package/dist/web3/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  76. package/dist/web3/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  77. package/dist/web3/assets/index-D4UG3T04.js +1365 -0
  78. package/dist/web3/assets/index-Dw1UTDIk.css +1 -0
  79. package/dist/web3/index.html +2 -2
  80. package/package.json +6 -1
  81. package/dist/web3/assets/index-C26nM5Kn.css +0 -1
  82. package/dist/web3/assets/index-C4oalc2K.js +0 -1065
@@ -4,19 +4,19 @@ import {
4
4
  resolveNestWideRoles,
5
5
  resolveStewardsForNode,
6
6
  stewardCoverageForUser
7
- } from "./chunk-3GMGLYTZ.js";
7
+ } from "./chunk-O5DYL7ZG.js";
8
8
  import {
9
9
  grantCoversNode,
10
10
  listUserGrants,
11
11
  resolveNodeGrant
12
- } from "./chunk-GSYMJ3A4.js";
12
+ } from "./chunk-3LUUJUWS.js";
13
13
  import {
14
14
  createVersion,
15
15
  getApprovedVersion,
16
16
  getApprovedVersions,
17
17
  getCurrentVersion,
18
18
  setApprovedVersion
19
- } from "./chunk-BXUVIAMP.js";
19
+ } from "./chunk-KHMSDTJJ.js";
20
20
  import {
21
21
  buildDocContext,
22
22
  buildTitleMap,
@@ -28,14 +28,15 @@ import {
28
28
  isStewardshipEnabled,
29
29
  nestName,
30
30
  notifyEmailForNest,
31
+ notifyReviewRequested,
32
+ notifyReviewResolved,
31
33
  nowExpr,
32
34
  opContext,
33
- permissionLabel,
34
35
  prettyFolderPath,
35
36
  resolveNestPermission,
36
37
  sendEmailToRecipient,
37
38
  titleForNode
38
- } from "./chunk-ENDHMQPD.js";
39
+ } from "./chunk-7CPP6FIU.js";
39
40
  import {
40
41
  ConflictError,
41
42
  NotFoundError,
@@ -44,175 +45,10 @@ import {
44
45
  import {
45
46
  config,
46
47
  getDb
47
- } from "./chunk-HYSTFMFG.js";
48
+ } from "./chunk-XCFKS65Y.js";
48
49
 
49
50
  // src/governance/review-service.ts
50
- import { v4 as uuid3 } from "uuid";
51
-
52
- // src/governance/notify-service.ts
53
- import { v4 as uuid } from "uuid";
54
- async function listWatchers(nestId, nodeId) {
55
- return await getDb().all(
56
- `SELECT id, node_id, user_email, created_by, created_at
57
- FROM watchers WHERE nest_id = ? AND node_id = ? ORDER BY created_at`,
58
- [nestId, nodeId]
59
- );
60
- }
61
- async function addWatcher(nestId, nodeId, userEmail, createdBy) {
62
- await getDb().run(
63
- `INSERT INTO watchers (id, nest_id, node_id, user_email, created_by, created_at)
64
- VALUES (?, ?, ?, ?, ?, ?)
65
- ON CONFLICT(nest_id, node_id, user_email) DO NOTHING`,
66
- [uuid(), nestId, nodeId, userEmail.toLowerCase(), createdBy, (/* @__PURE__ */ new Date()).toISOString()]
67
- );
68
- }
69
- async function removeWatcher(nestId, nodeId, userEmail) {
70
- const db = getDb();
71
- const row = await db.get(
72
- "SELECT id FROM watchers WHERE nest_id = ? AND node_id = ? AND LOWER(user_email) = LOWER(?)",
73
- [nestId, nodeId, userEmail]
74
- );
75
- if (!row) return false;
76
- await db.run("DELETE FROM watchers WHERE id = ?", [row.id]);
77
- return true;
78
- }
79
- async function notifyReviewRequested(params) {
80
- const { nestId, nodeId, requestedBy, reviewId } = params;
81
- const db = getDb();
82
- try {
83
- const rows = await db.all(
84
- `SELECT DISTINCT w.user_email, w.node_id AS via
85
- FROM watchers w
86
- WHERE w.nest_id = ?
87
- AND (w.node_id = ?
88
- OR w.node_id IN (
89
- SELECT e.from_node FROM edges e
90
- JOIN edge_types t ON t.id = e.type_id
91
- WHERE e.nest_id = ? AND e.to_node = ? AND t.is_flow = 1
92
- ))`,
93
- [nestId, nodeId, nestId, nodeId]
94
- );
95
- const now = (/* @__PURE__ */ new Date()).toISOString();
96
- const label = await describeNode(nestId, nodeId);
97
- let created = 0;
98
- for (const r of rows) {
99
- if (r.user_email.toLowerCase() === requestedBy.toLowerCase()) continue;
100
- await db.run(
101
- `INSERT INTO notifications (id, nest_id, user_email, kind, subject_id, message, created_at)
102
- VALUES (?, ?, ?, 'review_requested', ?, ?, ?)`,
103
- [
104
- uuid(),
105
- nestId,
106
- r.user_email.toLowerCase(),
107
- reviewId,
108
- r.via === nodeId ? `"${label}" has a new version awaiting review (submitted by ${requestedBy})` : `Agent "${await describeNode(nestId, r.via)}" produced output on "${label}" \u2014 awaiting review`,
109
- now
110
- ]
111
- );
112
- created++;
113
- }
114
- return created;
115
- } catch (err) {
116
- console.error("[notify] review fan-out failed", nestId, nodeId, err);
117
- return 0;
118
- }
119
- }
120
- async function notifyReviewResolved(params) {
121
- const { nestId, nodeId, status, resolvedBy, requestedBy, reviewId } = params;
122
- const db = getDb();
123
- try {
124
- const rows = await db.all(
125
- `SELECT DISTINCT w.user_email
126
- FROM watchers w
127
- WHERE w.nest_id = ?
128
- AND (w.node_id = ?
129
- OR w.node_id IN (
130
- SELECT e.from_node FROM edges e
131
- JOIN edge_types t ON t.id = e.type_id
132
- WHERE e.nest_id = ? AND e.to_node = ? AND t.is_flow = 1
133
- ))`,
134
- [nestId, nodeId, nestId, nodeId]
135
- );
136
- const recipients = new Set(rows.map((r) => r.user_email.toLowerCase()));
137
- recipients.add(requestedBy.toLowerCase());
138
- recipients.delete(resolvedBy.toLowerCase());
139
- const now = (/* @__PURE__ */ new Date()).toISOString();
140
- const label = await describeNode(nestId, nodeId);
141
- let created = 0;
142
- for (const email of recipients) {
143
- await db.run(
144
- `INSERT INTO notifications (id, nest_id, user_email, kind, subject_id, message, created_at)
145
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
146
- [
147
- uuid(),
148
- nestId,
149
- email,
150
- `review_${status}`,
151
- reviewId,
152
- `"${label}" was ${status} by ${resolvedBy}`,
153
- now
154
- ]
155
- );
156
- created++;
157
- }
158
- return created;
159
- } catch (err) {
160
- console.error("[notify] resolution fan-out failed", nestId, nodeId, err);
161
- return 0;
162
- }
163
- }
164
- async function notifyNestInvite(params) {
165
- const { nestId, inviteeEmail, permission, nestName: nestName2 } = params;
166
- try {
167
- await getDb().run(
168
- `INSERT INTO notifications (id, nest_id, user_email, kind, subject_id, message, created_at)
169
- VALUES (?, ?, ?, 'nest_invite', ?, ?, ?)`,
170
- [
171
- uuid(),
172
- nestId,
173
- inviteeEmail.toLowerCase(),
174
- nestId,
175
- `You were added to "${nestName2}" as ${permissionLabel(permission)}`,
176
- (/* @__PURE__ */ new Date()).toISOString()
177
- ]
178
- );
179
- } catch (err) {
180
- console.error("[notify] invite inbox insert failed", nestId, inviteeEmail, err);
181
- }
182
- }
183
- async function listNotifications(userEmail, opts = {}) {
184
- const limit = Math.min(opts.limit ?? 50, 200);
185
- return await getDb().all(
186
- `SELECT n.id, n.nest_id, n.kind, n.subject_id, n.message, n.created_at, n.read_at,
187
- COALESCE(
188
- rr.node_id,
189
- CASE WHEN dr.target_type = 'document' THEN dr.node_id END
190
- ) AS node_id
191
- FROM notifications n
192
- LEFT JOIN review_requests rr ON rr.id = n.subject_id
193
- LEFT JOIN deletion_requests dr ON dr.id = n.subject_id
194
- WHERE LOWER(n.user_email) = LOWER(?)${opts.unreadOnly ? " AND n.read_at IS NULL" : ""}
195
- ORDER BY n.created_at DESC LIMIT ${limit}`,
196
- [userEmail]
197
- );
198
- }
199
- async function markNotificationsRead(userEmail, ids) {
200
- const db = getDb();
201
- const now = (/* @__PURE__ */ new Date()).toISOString();
202
- if (ids === "all") {
203
- await db.run(
204
- "UPDATE notifications SET read_at = ? WHERE LOWER(user_email) = LOWER(?) AND read_at IS NULL",
205
- [now, userEmail]
206
- );
207
- return;
208
- }
209
- for (const id of ids.slice(0, 200)) {
210
- await db.run(
211
- "UPDATE notifications SET read_at = ? WHERE id = ? AND LOWER(user_email) = LOWER(?)",
212
- [now, id, userEmail]
213
- );
214
- }
215
- }
51
+ import { v4 as uuid2 } from "uuid";
216
52
 
217
53
  // src/workflow/env-routes.ts
218
54
  import { Hono as Hono2 } from "hono";
@@ -270,7 +106,7 @@ async function filterAccessible(nestId, userId, userEmail, nodes, approvedVersio
270
106
 
271
107
  // src/workflow/edge-type-routes.ts
272
108
  import { Hono } from "hono";
273
- import { v4 as uuid2 } from "uuid";
109
+ import { v4 as uuid } from "uuid";
274
110
 
275
111
  // src/shared/json.ts
276
112
  var safeJson = (s) => {
@@ -363,7 +199,7 @@ async function seedDefaultEdgeTypes(nestId) {
363
199
  (id, nest_id, name, description, direction, is_flow, condition_schema, color, created_by, created_at, updated_at)
364
200
  VALUES (?, ?, ?, ?, 'directed', ?, NULL, ?, ?, ?, ?)
365
201
  ON CONFLICT DO NOTHING`,
366
- [uuid2(), nestId, name, description, isFlow, color, createdBy, now, now]
202
+ [uuid(), nestId, name, description, isFlow, color, createdBy, now, now]
367
203
  );
368
204
  }
369
205
  }
@@ -451,7 +287,7 @@ edgeTypeRoutes.post("/", requireWorkflowPlane, async (c) => {
451
287
  `INSERT INTO edge_types
452
288
  (id, nest_id, name, description, direction, is_flow, condition_schema, color, created_by, created_at, updated_at)
453
289
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
454
- [uuid2(), nestId, name, body.description.trim(), direction, isFlow, conditionSchema, color, definedBy, now, now]
290
+ [uuid(), nestId, name, body.description.trim(), direction, isFlow, conditionSchema, color, definedBy, now, now]
455
291
  );
456
292
  }
457
293
  });
@@ -869,7 +705,7 @@ async function submitForReview(params) {
869
705
  if (existing) {
870
706
  throw new Error("A review is already pending for this node");
871
707
  }
872
- const id = uuid3();
708
+ const id = uuid2();
873
709
  await db.run(
874
710
  `INSERT INTO review_requests
875
711
  (id, nest_id, node_id, version, requested_by, request_note, priority)
@@ -1168,6 +1004,18 @@ async function cancelReview(params) {
1168
1004
  [params.nestId, params.nodeId]
1169
1005
  );
1170
1006
  if (!pending) return null;
1007
+ if (String(pending.requested_by).toLowerCase() !== params.cancelledBy.toLowerCase()) {
1008
+ const verdict = await canUserApprove(
1009
+ params.nestId,
1010
+ params.nodeId,
1011
+ params.cancelledBy
1012
+ );
1013
+ if (!verdict.allowed) {
1014
+ throw new Error(
1015
+ "Only the submitter or a reviewer can withdraw this review"
1016
+ );
1017
+ }
1018
+ }
1171
1019
  await db.run(
1172
1020
  `UPDATE review_requests
1173
1021
  SET status = 'cancelled', resolved_by = ?, resolved_at = ${nowExpr(db)}
@@ -1282,7 +1130,7 @@ async function inboxInsert(nestId, recipients, kind, subjectId, message) {
1282
1130
  await db.run(
1283
1131
  `INSERT INTO notifications (id, nest_id, user_email, kind, subject_id, message, created_at)
1284
1132
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
1285
- [uuid3(), nestId, email.toLowerCase(), kind, subjectId, message, now]
1133
+ [uuid2(), nestId, email.toLowerCase(), kind, subjectId, message, now]
1286
1134
  );
1287
1135
  } catch (err) {
1288
1136
  console.error("[notify] deletion inbox insert failed", nestId, email, err);
@@ -1420,7 +1268,7 @@ async function requestDeletion(params) {
1420
1268
  if (existing) {
1421
1269
  throw new ConflictError(`A deletion request is already pending for this ${noun}`);
1422
1270
  }
1423
- const id = uuid3();
1271
+ const id = uuid2();
1424
1272
  try {
1425
1273
  await db.run(
1426
1274
  `INSERT INTO deletion_requests (id, nest_id, node_id, target_type, requested_by, reason)
@@ -1584,12 +1432,6 @@ function rowToReviewRequest(row) {
1584
1432
  }
1585
1433
 
1586
1434
  export {
1587
- listWatchers,
1588
- addWatcher,
1589
- removeWatcher,
1590
- notifyNestInvite,
1591
- listNotifications,
1592
- markNotificationsRead,
1593
1435
  resolveCallerEmail,
1594
1436
  canReadNode,
1595
1437
  canRequestDeletion,
@@ -2,10 +2,10 @@ import {
2
2
  buildTitleMap,
3
3
  insertOrReplace,
4
4
  nowExpr
5
- } from "./chunk-ENDHMQPD.js";
5
+ } from "./chunk-7CPP6FIU.js";
6
6
  import {
7
7
  getDb
8
- } from "./chunk-HYSTFMFG.js";
8
+ } from "./chunk-XCFKS65Y.js";
9
9
 
10
10
  // src/governance/version-service.ts
11
11
  import { createHash } from "crypto";
@@ -11,7 +11,7 @@ import {
11
11
  resolveNestPermission,
12
12
  resolveTeamRolesForUser,
13
13
  sendEmailToRecipient
14
- } from "./chunk-ENDHMQPD.js";
14
+ } from "./chunk-7CPP6FIU.js";
15
15
  import {
16
16
  ConflictError,
17
17
  ValidationError
@@ -20,7 +20,7 @@ import {
20
20
  config,
21
21
  getDb,
22
22
  isEmailish
23
- } from "./chunk-HYSTFMFG.js";
23
+ } from "./chunk-XCFKS65Y.js";
24
24
 
25
25
  // src/governance/stewardship-service.ts
26
26
  import { v4 as uuid } from "uuid";
@@ -176,6 +176,58 @@ var config = {
176
176
  get PUBLIC_BASE_URL() {
177
177
  return (process.env.PUBLIC_BASE_URL || "").replace(/\/$/, "");
178
178
  },
179
+ /**
180
+ * Dedicated secret this deployment uses to SIGN the short-lived MCP tickets
181
+ * minted by the SSO token-exchange (POST /auth/token-exchange). Kept distinct
182
+ * from OFFICIAL_COMMUNITY_SSO_SECRET so a SELF-HOSTED server can issue its own
183
+ * MCP bearers without the official PromptOwl trust relationship. The MCP
184
+ * acceptor verifies a ticket signed with EITHER secret. Unset ⇒ token-exchange
185
+ * cannot mint (and the acceptor simply doesn't offer this key).
186
+ */
187
+ get MCP_SIGNING_SECRET() {
188
+ return process.env.MCP_SIGNING_SECRET || "";
189
+ },
190
+ /**
191
+ * Master switch for the SSO token-exchange endpoint. Default OFF — a
192
+ * deployment must deliberately enable it (and set MCP_SIGNING_SECRET + at
193
+ * least one provider) before /auth/token-exchange will mint anything.
194
+ */
195
+ get SSO_TOKEN_EXCHANGE_ENABLED() {
196
+ return process.env.SSO_TOKEN_EXCHANGE_ENABLED === "true";
197
+ },
198
+ get MS_TOKEN_EXCHANGE_ENABLED() {
199
+ return process.env.MS_TOKEN_EXCHANGE_ENABLED === "true";
200
+ },
201
+ get GOOGLE_TOKEN_EXCHANGE_ENABLED() {
202
+ return process.env.GOOGLE_TOKEN_EXCHANGE_ENABLED === "true";
203
+ },
204
+ /**
205
+ * Microsoft Entra: the app (client) id inbound tokens must be issued for
206
+ * (audience), and the tenant id used to build the expected issuer
207
+ * https://login.microsoftonline.com/<tenant>/v2.0.
208
+ */
209
+ get MS_CLIENT_ID() {
210
+ return (process.env.MS_CLIENT_ID || "").trim();
211
+ },
212
+ get MS_TENANT_ID() {
213
+ return (process.env.MS_TENANT_ID || "").trim();
214
+ },
215
+ /**
216
+ * Google: the OAuth client id inbound tokens must be issued for (audience).
217
+ * Issuer is fixed at https://accounts.google.com.
218
+ */
219
+ get GOOGLE_CLIENT_ID() {
220
+ return (process.env.GOOGLE_CLIENT_ID || "").trim();
221
+ },
222
+ /**
223
+ * Lifetime of a minted MCP ticket, in seconds. Clamped to [30, 3600]; short
224
+ * by design so a leaked exchange token expires quickly. Default 300.
225
+ */
226
+ get MCP_TOKEN_TTL_SECONDS() {
227
+ const raw = parseInt(process.env.MCP_TOKEN_TTL_SECONDS || "", 10);
228
+ if (!Number.isFinite(raw)) return 300;
229
+ return Math.max(30, Math.min(3600, raw));
230
+ },
179
231
  /**
180
232
  * Restrict "Sign in with PromptOwl":
181
233
  * "open" — anyone may sign in with PromptOwl (default)
@@ -744,6 +796,12 @@ function runMigrations(db) {
744
796
  if (!nestCols.includes("index_synced_at")) {
745
797
  db.exec("ALTER TABLE nests ADD COLUMN index_synced_at TEXT");
746
798
  }
799
+ if (!nestCols.includes("reader_mode")) {
800
+ db.exec("ALTER TABLE nests ADD COLUMN reader_mode INTEGER NOT NULL DEFAULT 0");
801
+ }
802
+ if (!nestCols.includes("reader_home_node")) {
803
+ db.exec("ALTER TABLE nests ADD COLUMN reader_home_node TEXT");
804
+ }
747
805
  const userCols = db.prepare("PRAGMA table_info(users)").all().map((c) => c.name);
748
806
  if (!userCols.includes("is_admin")) {
749
807
  db.exec("ALTER TABLE users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0");
@@ -1572,6 +1630,54 @@ function runMigrations(db) {
1572
1630
  })();
1573
1631
  recordMigration("036_node_index");
1574
1632
  }
1633
+ if (!hasMigration("037_publish")) {
1634
+ db.transaction(() => {
1635
+ db.exec(`
1636
+ CREATE TABLE IF NOT EXISTS publish (
1637
+ id TEXT PRIMARY KEY,
1638
+ nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
1639
+ node_id TEXT NOT NULL,
1640
+ slug TEXT NOT NULL,
1641
+ access_mode TEXT NOT NULL DEFAULT 'public'
1642
+ CHECK(access_mode IN ('public','email','code','invite')),
1643
+ code_hash TEXT,
1644
+ comments_enabled INTEGER NOT NULL DEFAULT 0,
1645
+ watermark INTEGER NOT NULL DEFAULT 0,
1646
+ no_download INTEGER NOT NULL DEFAULT 0,
1647
+ created_by TEXT NOT NULL,
1648
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1649
+ expires_at TEXT,
1650
+ revoked_at TEXT
1651
+ );
1652
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_publish_slug ON publish(slug);
1653
+ CREATE INDEX IF NOT EXISTS idx_publish_nest ON publish(nest_id, node_id);
1654
+
1655
+ CREATE TABLE IF NOT EXISTS access_grant (
1656
+ id TEXT PRIMARY KEY,
1657
+ publish_id TEXT NOT NULL REFERENCES publish(id) ON DELETE CASCADE,
1658
+ kind TEXT NOT NULL
1659
+ CHECK(kind IN ('invite','link','session','view')),
1660
+ email TEXT,
1661
+ token_hash TEXT,
1662
+ expires_at TEXT,
1663
+ used_at TEXT,
1664
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1665
+ );
1666
+ CREATE INDEX IF NOT EXISTS idx_access_grant_publish ON access_grant(publish_id, kind);
1667
+ CREATE INDEX IF NOT EXISTS idx_access_grant_token ON access_grant(token_hash);
1668
+ `);
1669
+ })();
1670
+ recordMigration("037_publish");
1671
+ }
1672
+ {
1673
+ const publishCols = db.prepare("PRAGMA table_info(publish)").all().map((c) => c.name);
1674
+ if (publishCols.length && !publishCols.includes("watermark")) {
1675
+ db.exec("ALTER TABLE publish ADD COLUMN watermark INTEGER NOT NULL DEFAULT 0");
1676
+ }
1677
+ if (publishCols.length && !publishCols.includes("no_download")) {
1678
+ db.exec("ALTER TABLE publish ADD COLUMN no_download INTEGER NOT NULL DEFAULT 0");
1679
+ }
1680
+ }
1575
1681
  }
1576
1682
  function mergeCaseCollidingUsers(db) {
1577
1683
  const groups = db.prepare(
@@ -1789,7 +1895,7 @@ async function initDb() {
1789
1895
  if (config.DB_DRIVER === "postgres") {
1790
1896
  const { Pool } = await import("pg");
1791
1897
  const { PostgresAdapter } = await import("./adapter.postgres-YOODX2BI.js");
1792
- const { runPostgresMigrations } = await import("./migrations.postgres-5VI4RDTS.js");
1898
+ const { runPostgresMigrations } = await import("./migrations.postgres-L42OXR7K.js");
1793
1899
  const pool = new Pool(buildPgConfig());
1794
1900
  adapter = new PostgresAdapter(pool);
1795
1901
  await runPostgresMigrations(adapter);
@@ -2,7 +2,7 @@ import {
2
2
  getDb,
3
3
  initDb,
4
4
  resetDb
5
- } from "./chunk-HYSTFMFG.js";
5
+ } from "./chunk-XCFKS65Y.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-ENDHMQPD.js";
5
+ } from "./chunk-7CPP6FIU.js";
6
6
  import "./chunk-YVMSM7LS.js";
7
- import "./chunk-HYSTFMFG.js";
7
+ import "./chunk-XCFKS65Y.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-TNAZJYL5.js";
15
- import "./chunk-ENDHMQPD.js";
14
+ } from "./chunk-EY7WOKJR.js";
15
+ import "./chunk-7CPP6FIU.js";
16
16
  import "./chunk-YVMSM7LS.js";
17
- import "./chunk-HYSTFMFG.js";
17
+ import "./chunk-XCFKS65Y.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-GSYMJ3A4.js";
9
+ } from "./chunk-3LUUJUWS.js";
10
10
  import "./chunk-YVMSM7LS.js";
11
- import "./chunk-HYSTFMFG.js";
11
+ import "./chunk-XCFKS65Y.js";
12
12
  import "./chunk-YB3LKF7U.js";
13
13
  export {
14
14
  createGrant,