@promptowl/contextnest-community 1.19.0 → 1.21.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/CONFIGURATION.md +83 -1
- package/README.md +1 -1
- package/dist/{chunk-FBDEESNJ.js → chunk-4KJGQHWB.js} +113 -218
- package/dist/{chunk-ENDHMQPD.js → chunk-6JGQX4GA.js} +415 -64
- package/dist/{chunk-TNAZJYL5.js → chunk-6PJQSKKV.js} +12 -4
- package/dist/{chunk-BXUVIAMP.js → chunk-IGPJ74O4.js} +60 -12
- package/dist/{chunk-HYSTFMFG.js → chunk-LPPKPEYI.js} +131 -1
- package/dist/{chunk-3GMGLYTZ.js → chunk-N2DTJFCD.js} +2 -2
- package/dist/{chunk-GSYMJ3A4.js → chunk-XWGEXQU3.js} +6 -2
- package/dist/{client-SW5I6ZBK.js → client-BD6PAUCZ.js} +1 -1
- package/dist/{engine-V4DY4PYN.js → engine-IKZQ46P7.js} +2 -2
- package/dist/{external-edit-service-3NK5DGLA.js → external-edit-service-LTON57HO.js} +3 -3
- package/dist/{grants-service-CADI6LIB.js → grants-service-LSNZN6NG.js} +2 -2
- package/dist/index.js +2704 -1156
- package/dist/{migrations.postgres-5VI4RDTS.js → migrations.postgres-JA2YGLS3.js} +66 -1
- package/dist/{review-service-LK4M2C43.js → review-service-6JK64IRT.js} +8 -6
- package/dist/{stewardship-service-JAFSU5GN.js → stewardship-service-2YKGBP63.js} +3 -3
- package/dist/{version-service-G7SFFKFO.js → version-service-PJV74PVQ.js} +11 -3
- package/dist/web3/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/web3/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/web3/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/web3/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/web3/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/web3/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/web3/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/web3/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/web3/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/web3/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/web3/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/web3/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/web3/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/web3/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/web3/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/web3/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/web3/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/web3/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/web3/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/web3/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/web3/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/web3/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/web3/assets/index-T4pilFNL.css +1 -0
- package/dist/web3/assets/index-deyhV93k.js +1382 -0
- package/dist/web3/index.html +2 -2
- package/package.json +6 -1
- package/dist/web3/assets/index-C26nM5Kn.css +0 -1
- package/dist/web3/assets/index-C4oalc2K.js +0 -1065
|
@@ -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-
|
|
8
|
+
} from "./chunk-6JGQX4GA.js";
|
|
8
9
|
import {
|
|
9
10
|
getDb
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-LPPKPEYI.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-PJV74PVQ.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-
|
|
262
|
+
const { upsertVersion, setApprovedVersion } = await import("./version-service-PJV74PVQ.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(
|
|
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,10 +2,10 @@ import {
|
|
|
2
2
|
buildTitleMap,
|
|
3
3
|
insertOrReplace,
|
|
4
4
|
nowExpr
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6JGQX4GA.js";
|
|
6
6
|
import {
|
|
7
7
|
getDb
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LPPKPEYI.js";
|
|
9
9
|
|
|
10
10
|
// src/governance/version-service.ts
|
|
11
11
|
import { createHash } from "crypto";
|
|
@@ -136,6 +136,44 @@ async function setApprovedVersion(nestId, nodeId, version, approvedBy) {
|
|
|
136
136
|
);
|
|
137
137
|
await db.run(sql, [nestId, nodeId, version, approvedBy]);
|
|
138
138
|
}
|
|
139
|
+
async function getSealedVersion(nestId, nodeId) {
|
|
140
|
+
const approved = await getApprovedVersion(nestId, nodeId);
|
|
141
|
+
if (approved != null) return approved;
|
|
142
|
+
const db = getDb();
|
|
143
|
+
const row = await db.get(
|
|
144
|
+
`SELECT MAX(version) as v FROM node_versions
|
|
145
|
+
WHERE nest_id = ? AND node_id = ? AND status IN ('published', 'approved')`,
|
|
146
|
+
[nestId, nodeId]
|
|
147
|
+
);
|
|
148
|
+
return row?.v ?? null;
|
|
149
|
+
}
|
|
150
|
+
async function deleteSupersededDrafts(nestId, nodeId, belowVersion) {
|
|
151
|
+
const db = getDb();
|
|
152
|
+
await db.run(
|
|
153
|
+
`DELETE FROM node_versions
|
|
154
|
+
WHERE nest_id = ? AND node_id = ? AND status = 'draft' AND version < ?`,
|
|
155
|
+
[nestId, nodeId, belowVersion]
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
async function deleteUnsealedVersions(nestId, nodeId, sealedVersion) {
|
|
159
|
+
const db = getDb();
|
|
160
|
+
await db.run(
|
|
161
|
+
`DELETE FROM node_versions
|
|
162
|
+
WHERE nest_id = ? AND node_id = ? AND version > ?
|
|
163
|
+
AND status IN ('draft', 'rejected', 'pending_review')`,
|
|
164
|
+
[nestId, nodeId, sealedVersion]
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
async function hasUnsealedDraft(nestId, nodeId) {
|
|
168
|
+
const db = getDb();
|
|
169
|
+
const row = await db.get(
|
|
170
|
+
`SELECT status FROM node_versions
|
|
171
|
+
WHERE nest_id = ? AND node_id = ?
|
|
172
|
+
ORDER BY version DESC LIMIT 1`,
|
|
173
|
+
[nestId, nodeId]
|
|
174
|
+
);
|
|
175
|
+
return row?.status === "draft" || row?.status === "rejected" || row?.status === "pending_review";
|
|
176
|
+
}
|
|
139
177
|
async function checkConflict(nestId, nodeId, baseVersion) {
|
|
140
178
|
const db = getDb();
|
|
141
179
|
const current = await db.get(
|
|
@@ -265,20 +303,16 @@ async function listMyDrafts(params) {
|
|
|
265
303
|
WHERE r.nest_id = v.nest_id AND r.node_id = v.node_id
|
|
266
304
|
AND r.status = 'pending')`;
|
|
267
305
|
const args = [params.authorEmail.toLowerCase(), ...params.nestIds];
|
|
268
|
-
const total = Number(
|
|
269
|
-
(await db.get(
|
|
270
|
-
`SELECT COUNT(*) as c FROM node_versions v ${where}`,
|
|
271
|
-
args
|
|
272
|
-
)).c
|
|
273
|
-
);
|
|
274
306
|
const limit = Math.min(Math.max(params.limit ?? 25, 1), 100);
|
|
275
307
|
const offset = Math.max(params.offset ?? 0, 0);
|
|
308
|
+
const q = params.q?.trim().toLowerCase() || "";
|
|
309
|
+
const SCAN_CAP = 500;
|
|
276
310
|
const rows = await db.all(
|
|
277
311
|
`SELECT v.nest_id, v.node_id, v.status, v.version, v.created_at
|
|
278
312
|
FROM node_versions v ${where}
|
|
279
313
|
ORDER BY v.created_at DESC
|
|
280
|
-
LIMIT
|
|
281
|
-
[...args,
|
|
314
|
+
LIMIT ?`,
|
|
315
|
+
[...args, SCAN_CAP]
|
|
282
316
|
);
|
|
283
317
|
const titlesByNest = new Map(
|
|
284
318
|
await Promise.all(
|
|
@@ -290,7 +324,7 @@ async function listMyDrafts(params) {
|
|
|
290
324
|
)
|
|
291
325
|
)
|
|
292
326
|
);
|
|
293
|
-
|
|
327
|
+
let drafts = rows.filter((r) => titlesByNest.get(r.nest_id)?.has(r.node_id)).map((r) => ({
|
|
294
328
|
nestId: r.nest_id,
|
|
295
329
|
nodeId: r.node_id,
|
|
296
330
|
title: titlesByNest.get(r.nest_id).get(r.node_id) || r.node_id,
|
|
@@ -300,7 +334,17 @@ async function listMyDrafts(params) {
|
|
|
300
334
|
version: Number(r.version),
|
|
301
335
|
updatedAt: r.created_at
|
|
302
336
|
}));
|
|
303
|
-
|
|
337
|
+
if (q) {
|
|
338
|
+
drafts = drafts.filter(
|
|
339
|
+
(d) => [d.title, params.nestNames?.get(d.nestId) ?? "", d.nodeId].some(
|
|
340
|
+
(s) => s.toLowerCase().includes(q)
|
|
341
|
+
)
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (params.sort === "title") {
|
|
345
|
+
drafts.sort((a, b) => a.title.localeCompare(b.title));
|
|
346
|
+
}
|
|
347
|
+
return { drafts: drafts.slice(offset, offset + limit), total: drafts.length };
|
|
304
348
|
}
|
|
305
349
|
function rowToVersion(row) {
|
|
306
350
|
return {
|
|
@@ -327,6 +371,10 @@ export {
|
|
|
327
371
|
getApprovedVersion,
|
|
328
372
|
getApprovedVersions,
|
|
329
373
|
setApprovedVersion,
|
|
374
|
+
getSealedVersion,
|
|
375
|
+
deleteSupersededDrafts,
|
|
376
|
+
deleteUnsealedVersions,
|
|
377
|
+
hasUnsealedDraft,
|
|
330
378
|
checkConflict,
|
|
331
379
|
getNodeTags,
|
|
332
380
|
getDisplayStatus,
|
|
@@ -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,78 @@ 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
|
+
}
|
|
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
|
+
}
|
|
1575
1705
|
}
|
|
1576
1706
|
function mergeCaseCollidingUsers(db) {
|
|
1577
1707
|
const groups = db.prepare(
|
|
@@ -1789,7 +1919,7 @@ async function initDb() {
|
|
|
1789
1919
|
if (config.DB_DRIVER === "postgres") {
|
|
1790
1920
|
const { Pool } = await import("pg");
|
|
1791
1921
|
const { PostgresAdapter } = await import("./adapter.postgres-YOODX2BI.js");
|
|
1792
|
-
const { runPostgresMigrations } = await import("./migrations.postgres-
|
|
1922
|
+
const { runPostgresMigrations } = await import("./migrations.postgres-JA2YGLS3.js");
|
|
1793
1923
|
const pool = new Pool(buildPgConfig());
|
|
1794
1924
|
adapter = new PostgresAdapter(pool);
|
|
1795
1925
|
await runPostgresMigrations(adapter);
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resolveNestPermission,
|
|
12
12
|
resolveTeamRolesForUser,
|
|
13
13
|
sendEmailToRecipient
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-6JGQX4GA.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-
|
|
23
|
+
} from "./chunk-LPPKPEYI.js";
|
|
24
24
|
|
|
25
25
|
// src/governance/stewardship-service.ts
|
|
26
26
|
import { v4 as uuid } from "uuid";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-YVMSM7LS.js";
|
|
4
4
|
import {
|
|
5
5
|
getDb
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-LPPKPEYI.js";
|
|
7
7
|
|
|
8
8
|
// src/governance/grants-service.ts
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
@@ -11,7 +11,10 @@ var rank = (r) => r === "write" ? 2 : 1;
|
|
|
11
11
|
async function resolveNodeGrant(nestId, userId, nodeId) {
|
|
12
12
|
if (!userId || !nodeId) return null;
|
|
13
13
|
const rows = await getDb().all(
|
|
14
|
-
|
|
14
|
+
`SELECT g.target, g.role, n.archived_at
|
|
15
|
+
FROM grants g
|
|
16
|
+
JOIN nests n ON n.id = g.nest_id
|
|
17
|
+
WHERE g.nest_id = ? AND g.user_id = ?`,
|
|
15
18
|
[nestId, userId]
|
|
16
19
|
);
|
|
17
20
|
let best = null;
|
|
@@ -20,6 +23,7 @@ async function resolveNodeGrant(nestId, userId, nodeId) {
|
|
|
20
23
|
if (!best || rank(r.role) > rank(best)) best = r.role;
|
|
21
24
|
}
|
|
22
25
|
}
|
|
26
|
+
if (best === "write" && rows[0]?.archived_at) return "read";
|
|
23
27
|
return best;
|
|
24
28
|
}
|
|
25
29
|
async function listUserGrants(nestId, userId) {
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
engineApi,
|
|
3
3
|
engineCache,
|
|
4
4
|
opContext
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6JGQX4GA.js";
|
|
6
6
|
import "./chunk-YVMSM7LS.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-LPPKPEYI.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-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-6PJQSKKV.js";
|
|
15
|
+
import "./chunk-6JGQX4GA.js";
|
|
16
16
|
import "./chunk-YVMSM7LS.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-LPPKPEYI.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-
|
|
9
|
+
} from "./chunk-XWGEXQU3.js";
|
|
10
10
|
import "./chunk-YVMSM7LS.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-LPPKPEYI.js";
|
|
12
12
|
import "./chunk-YB3LKF7U.js";
|
|
13
13
|
export {
|
|
14
14
|
createGrant,
|