@promptowl/contextnest-community 1.0.1 → 1.1.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 +6 -4
- package/README.md +80 -7
- package/dist/{chunk-JMZ75ZCD.js → chunk-7UTMBL6Z.js} +1 -1
- package/dist/{chunk-5VHKEIAW.js → chunk-S2EWN2VA.js} +18 -36
- package/dist/{chunk-KQCWNHDM.js → chunk-TDAX3JOT.js} +25 -0
- package/dist/chunk-WCOUCBDJ.js +1406 -0
- package/dist/{chunk-7K2LLJXK.js → chunk-XRK6SQSC.js} +1 -1
- package/dist/index.js +1022 -1230
- package/dist/{keys-YV33AJK3.js → keys-73STFJJB.js} +1 -1
- package/dist/{review-service-4WS3XL6K.js → review-service-3OJIPYNV.js} +4 -4
- package/dist/{stewardship-service-C5D2O7ZE.js → stewardship-service-3XGX7QIN.js} +20 -4
- package/dist/{version-service-TFEYNPH7.js → version-service-UODXLAOJ.js} +2 -2
- package/dist/web3/assets/index-BLxRS7jD.js +673 -0
- package/dist/web3/assets/index-DszK6Vkc.css +1 -0
- package/dist/web3/index.html +2 -2
- package/package.json +136 -134
- package/dist/chunk-K22GWPT4.js +0 -498
- package/dist/web3/assets/index-DkLevP7k.js +0 -624
- package/dist/web3/assets/index-DpoBdKrd.css +0 -1
package/dist/chunk-K22GWPT4.js
DELETED
|
@@ -1,498 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
config,
|
|
3
|
-
getDb
|
|
4
|
-
} from "./chunk-KQCWNHDM.js";
|
|
5
|
-
|
|
6
|
-
// src/governance/stewardship-service.ts
|
|
7
|
-
import { v4 as uuid } from "uuid";
|
|
8
|
-
|
|
9
|
-
// src/governance/access-service.ts
|
|
10
|
-
import { readFileSync, existsSync } from "fs";
|
|
11
|
-
import { join } from "path";
|
|
12
|
-
var accessConfig = null;
|
|
13
|
-
function loadAccessConfig() {
|
|
14
|
-
const candidates = [
|
|
15
|
-
join(config.DATA_ROOT, "access.yaml"),
|
|
16
|
-
join(config.DATA_ROOT, "access.yml")
|
|
17
|
-
];
|
|
18
|
-
for (const path of candidates) {
|
|
19
|
-
if (existsSync(path)) {
|
|
20
|
-
const content = readFileSync(path, "utf-8");
|
|
21
|
-
accessConfig = parseAccessYaml(content);
|
|
22
|
-
return accessConfig;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
accessConfig = null;
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
function getAccessConfig() {
|
|
29
|
-
return accessConfig;
|
|
30
|
-
}
|
|
31
|
-
function isSuperAdmin(email) {
|
|
32
|
-
if (!accessConfig?.super_admins) return false;
|
|
33
|
-
return accessConfig.super_admins.map((e) => e.toLowerCase()).includes(email.toLowerCase());
|
|
34
|
-
}
|
|
35
|
-
function parseAccessYaml(content) {
|
|
36
|
-
const result = {};
|
|
37
|
-
const lines = content.split("\n");
|
|
38
|
-
let currentSection = null;
|
|
39
|
-
let currentGroup = null;
|
|
40
|
-
let inMembers = false;
|
|
41
|
-
for (const rawLine of lines) {
|
|
42
|
-
const line = rawLine.trimEnd();
|
|
43
|
-
if (!line || line.startsWith("#")) continue;
|
|
44
|
-
if (!line.startsWith(" ") && !line.startsWith(" ")) {
|
|
45
|
-
const match = line.match(/^(\w+):\s*(.*)?$/);
|
|
46
|
-
if (!match) continue;
|
|
47
|
-
const key = match[1];
|
|
48
|
-
const value = match[2]?.trim();
|
|
49
|
-
if (key === "mode") {
|
|
50
|
-
result.mode = value;
|
|
51
|
-
currentSection = null;
|
|
52
|
-
} else if (key === "allowed_users") {
|
|
53
|
-
currentSection = "allowed_users";
|
|
54
|
-
result.allowed_users = [];
|
|
55
|
-
} else if (key === "groups") {
|
|
56
|
-
currentSection = "groups";
|
|
57
|
-
result.groups = {};
|
|
58
|
-
} else if (key === "super_admins") {
|
|
59
|
-
currentSection = "super_admins";
|
|
60
|
-
result.super_admins = [];
|
|
61
|
-
}
|
|
62
|
-
currentGroup = null;
|
|
63
|
-
inMembers = false;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
const listMatch = line.match(/^\s+-\s+["']?([^"'\n]+?)["']?\s*$/);
|
|
67
|
-
if (currentSection === "allowed_users" && listMatch) {
|
|
68
|
-
result.allowed_users.push(listMatch[1].trim());
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (currentSection === "super_admins" && listMatch) {
|
|
72
|
-
result.super_admins.push(listMatch[1].trim());
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (currentSection === "groups") {
|
|
76
|
-
const groupMatch = line.match(/^ (\w+):$/);
|
|
77
|
-
if (groupMatch) {
|
|
78
|
-
currentGroup = groupMatch[1];
|
|
79
|
-
result.groups[currentGroup] = { members: [], default_permission: "read" };
|
|
80
|
-
inMembers = false;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (currentGroup) {
|
|
84
|
-
const propMatch = line.match(/^\s{4}(\w+):\s*(.*)?$/);
|
|
85
|
-
if (propMatch) {
|
|
86
|
-
const prop = propMatch[1];
|
|
87
|
-
const val = propMatch[2]?.trim();
|
|
88
|
-
if (prop === "default_permission" && val) {
|
|
89
|
-
result.groups[currentGroup].default_permission = val;
|
|
90
|
-
}
|
|
91
|
-
if (prop === "members") {
|
|
92
|
-
inMembers = true;
|
|
93
|
-
}
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (inMembers && listMatch) {
|
|
97
|
-
result.groups[currentGroup].members.push(listMatch[1].trim());
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// src/governance/stewardship-service.ts
|
|
106
|
-
function assignSteward(data) {
|
|
107
|
-
const db = getDb();
|
|
108
|
-
const id = uuid();
|
|
109
|
-
db.prepare(
|
|
110
|
-
`INSERT INTO stewards
|
|
111
|
-
(id, nest_id, scope, node_pattern, tag_name, user_email, user_id, role, assigned_by, is_active)
|
|
112
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
113
|
-
).run(
|
|
114
|
-
id,
|
|
115
|
-
data.nestId,
|
|
116
|
-
data.scope,
|
|
117
|
-
data.nodePattern || null,
|
|
118
|
-
data.tagName || null,
|
|
119
|
-
data.userEmail,
|
|
120
|
-
data.userId || null,
|
|
121
|
-
data.role,
|
|
122
|
-
data.assignedBy,
|
|
123
|
-
data.isActive ? 1 : 0
|
|
124
|
-
);
|
|
125
|
-
return { ...data, id };
|
|
126
|
-
}
|
|
127
|
-
function removeSteward(id) {
|
|
128
|
-
const db = getDb();
|
|
129
|
-
const remove = db.transaction((stewardId) => {
|
|
130
|
-
const row = db.prepare("SELECT nest_id, user_email FROM stewards WHERE id = ?").get(stewardId);
|
|
131
|
-
db.prepare("DELETE FROM stewards WHERE id = ?").run(stewardId);
|
|
132
|
-
if (!row) return;
|
|
133
|
-
const remaining = db.prepare(
|
|
134
|
-
"SELECT 1 FROM stewards WHERE nest_id = ? AND LOWER(user_email) = LOWER(?) AND is_active = 1 LIMIT 1"
|
|
135
|
-
).get(row.nest_id, row.user_email);
|
|
136
|
-
if (remaining) return;
|
|
137
|
-
const user = db.prepare("SELECT id FROM users WHERE LOWER(email) = LOWER(?)").get(row.user_email);
|
|
138
|
-
if (!user) return;
|
|
139
|
-
db.prepare(
|
|
140
|
-
"DELETE FROM nest_collaborators WHERE nest_id = ? AND user_id = ?"
|
|
141
|
-
).run(row.nest_id, user.id);
|
|
142
|
-
});
|
|
143
|
-
remove(id);
|
|
144
|
-
}
|
|
145
|
-
function getSteward(id) {
|
|
146
|
-
const db = getDb();
|
|
147
|
-
const row = db.prepare("SELECT * FROM stewards WHERE id = ?").get(id);
|
|
148
|
-
return row ? rowToSteward(row) : null;
|
|
149
|
-
}
|
|
150
|
-
function getStewardsForNest(nestId) {
|
|
151
|
-
const db = getDb();
|
|
152
|
-
const rows = db.prepare("SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1").all(nestId);
|
|
153
|
-
return rows.map(rowToSteward);
|
|
154
|
-
}
|
|
155
|
-
function getStewardsForScope(params) {
|
|
156
|
-
const db = getDb();
|
|
157
|
-
let sql = "SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1";
|
|
158
|
-
const args = [params.nestId];
|
|
159
|
-
if (params.scope) {
|
|
160
|
-
sql += " AND scope = ?";
|
|
161
|
-
args.push(params.scope);
|
|
162
|
-
}
|
|
163
|
-
if (params.scopeTarget) {
|
|
164
|
-
sql += " AND (node_pattern = ? OR tag_name = ?)";
|
|
165
|
-
args.push(params.scopeTarget, params.scopeTarget);
|
|
166
|
-
}
|
|
167
|
-
return db.prepare(sql).all(...args).map(rowToSteward);
|
|
168
|
-
}
|
|
169
|
-
function listStewards(params) {
|
|
170
|
-
const db = getDb();
|
|
171
|
-
let sql = "SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1";
|
|
172
|
-
const args = [params.nestId];
|
|
173
|
-
if (params.scope) {
|
|
174
|
-
sql += " AND scope = ?";
|
|
175
|
-
args.push(params.scope);
|
|
176
|
-
}
|
|
177
|
-
if (params.search) {
|
|
178
|
-
sql += " AND (user_email LIKE ? OR tag_name LIKE ? OR node_pattern LIKE ?)";
|
|
179
|
-
const like = `%${params.search.toLowerCase()}%`;
|
|
180
|
-
args.push(like, like, like);
|
|
181
|
-
}
|
|
182
|
-
sql += " ORDER BY scope, COALESCE(node_pattern, tag_name, ''), user_email";
|
|
183
|
-
return db.prepare(sql).all(...args).map(rowToSteward);
|
|
184
|
-
}
|
|
185
|
-
function rolePermission(role) {
|
|
186
|
-
return role === "editor" ? "write" : "read";
|
|
187
|
-
}
|
|
188
|
-
async function ensureCollaborator(nestId, email, permission, grantedBy) {
|
|
189
|
-
const db = getDb();
|
|
190
|
-
let userRow = db.prepare("SELECT id FROM users WHERE email = ?").get(email);
|
|
191
|
-
if (!userRow) {
|
|
192
|
-
const { hashPassword } = await import("./keys-YV33AJK3.js");
|
|
193
|
-
const newId = uuid();
|
|
194
|
-
db.prepare(
|
|
195
|
-
"INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)"
|
|
196
|
-
).run(newId, email, null, await hashPassword(uuid()));
|
|
197
|
-
userRow = { id: newId };
|
|
198
|
-
}
|
|
199
|
-
const nestRow = db.prepare("SELECT user_id FROM nests WHERE id = ?").get(nestId);
|
|
200
|
-
if (nestRow && nestRow.user_id === userRow.id) return;
|
|
201
|
-
const existing = db.prepare(
|
|
202
|
-
"SELECT id FROM nest_collaborators WHERE nest_id = ? AND user_id = ?"
|
|
203
|
-
).get(nestId, userRow.id);
|
|
204
|
-
if (existing) return;
|
|
205
|
-
const granterRow = db.prepare("SELECT id FROM users WHERE email = ?").get(grantedBy);
|
|
206
|
-
const granterId = granterRow?.id ?? nestRow?.user_id;
|
|
207
|
-
if (!granterId) return;
|
|
208
|
-
db.prepare(
|
|
209
|
-
"INSERT INTO nest_collaborators (id, nest_id, user_id, permission, granted_by) VALUES (?, ?, ?, ?, ?)"
|
|
210
|
-
).run(uuid(), nestId, userRow.id, permission, granterId);
|
|
211
|
-
}
|
|
212
|
-
async function createStewardRecord(params) {
|
|
213
|
-
if (params.users.length === 0) {
|
|
214
|
-
throw new Error("At least one user is required");
|
|
215
|
-
}
|
|
216
|
-
let nodePattern;
|
|
217
|
-
let tagName;
|
|
218
|
-
switch (params.scope) {
|
|
219
|
-
case "document":
|
|
220
|
-
if (!params.documentId) throw new Error("documentId required for document scope");
|
|
221
|
-
nodePattern = params.documentId;
|
|
222
|
-
break;
|
|
223
|
-
case "tag":
|
|
224
|
-
if (!params.tagName) throw new Error("tagName required for tag scope");
|
|
225
|
-
tagName = params.tagName.trim().replace(/^#+/, "").toLowerCase();
|
|
226
|
-
break;
|
|
227
|
-
case "nest":
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
const db = getDb();
|
|
231
|
-
const results = [];
|
|
232
|
-
for (const user of params.users) {
|
|
233
|
-
const email = user.email.trim().toLowerCase();
|
|
234
|
-
if (!email) continue;
|
|
235
|
-
const existing = db.prepare(
|
|
236
|
-
`SELECT * FROM stewards
|
|
237
|
-
WHERE nest_id = ? AND is_active = 1 AND scope = ? AND user_email = ?
|
|
238
|
-
AND COALESCE(node_pattern, '') = COALESCE(?, '')
|
|
239
|
-
AND COALESCE(tag_name, '') = COALESCE(?, '')`
|
|
240
|
-
).get(
|
|
241
|
-
params.nestId,
|
|
242
|
-
params.scope,
|
|
243
|
-
email,
|
|
244
|
-
nodePattern ?? null,
|
|
245
|
-
tagName ?? null
|
|
246
|
-
);
|
|
247
|
-
if (existing) {
|
|
248
|
-
results.push(rowToSteward(existing));
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
const userRow = db.prepare("SELECT id FROM users WHERE email = ?").get(email);
|
|
252
|
-
const created = assignSteward({
|
|
253
|
-
nestId: params.nestId,
|
|
254
|
-
scope: params.scope,
|
|
255
|
-
nodePattern,
|
|
256
|
-
tagName,
|
|
257
|
-
userEmail: email,
|
|
258
|
-
userId: userRow?.id,
|
|
259
|
-
role: user.role ?? "reviewer",
|
|
260
|
-
assignedBy: params.assignedBy,
|
|
261
|
-
assignedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
262
|
-
isActive: true
|
|
263
|
-
});
|
|
264
|
-
results.push(created);
|
|
265
|
-
await ensureCollaborator(
|
|
266
|
-
params.nestId,
|
|
267
|
-
email,
|
|
268
|
-
rolePermission(user.role),
|
|
269
|
-
params.assignedBy
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
db.prepare(
|
|
273
|
-
"UPDATE nests SET stewardship_enabled = 1 WHERE id = ? AND stewardship_enabled = 0"
|
|
274
|
-
).run(params.nestId);
|
|
275
|
-
return results;
|
|
276
|
-
}
|
|
277
|
-
function resolveStewardsForNode(nestId, nodeId) {
|
|
278
|
-
return resolve(nestId, nodeId).stewards;
|
|
279
|
-
}
|
|
280
|
-
function resolveStewardsWithFallback(nestId, nodeId) {
|
|
281
|
-
return resolve(nestId, nodeId);
|
|
282
|
-
}
|
|
283
|
-
function resolve(nestId, nodeId) {
|
|
284
|
-
const db = getDb();
|
|
285
|
-
const rows = db.prepare(
|
|
286
|
-
`
|
|
287
|
-
SELECT s.*, 1 AS priority, ('document: ' || s.node_pattern) AS match_source
|
|
288
|
-
FROM stewards s
|
|
289
|
-
WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'document'
|
|
290
|
-
AND s.node_pattern = ?
|
|
291
|
-
UNION ALL
|
|
292
|
-
SELECT s.*, 2 AS priority, ('tag: ' || s.tag_name) AS match_source
|
|
293
|
-
FROM stewards s
|
|
294
|
-
JOIN node_tag_index nt
|
|
295
|
-
ON nt.nest_id = s.nest_id
|
|
296
|
-
AND nt.tag_name = s.tag_name
|
|
297
|
-
WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'tag'
|
|
298
|
-
AND nt.node_id = ?
|
|
299
|
-
UNION ALL
|
|
300
|
-
SELECT s.*, 3 AS priority, 'nest-level steward' AS match_source
|
|
301
|
-
FROM stewards s
|
|
302
|
-
WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'nest'
|
|
303
|
-
ORDER BY priority ASC, user_email ASC
|
|
304
|
-
`
|
|
305
|
-
).all(
|
|
306
|
-
nestId,
|
|
307
|
-
nodeId,
|
|
308
|
-
// document branch
|
|
309
|
-
nestId,
|
|
310
|
-
nodeId,
|
|
311
|
-
// tag branch
|
|
312
|
-
nestId
|
|
313
|
-
// nest branch
|
|
314
|
-
);
|
|
315
|
-
const resolved = rows.map((row) => ({
|
|
316
|
-
steward: rowToSteward(row),
|
|
317
|
-
priority: row.priority,
|
|
318
|
-
source: row.match_source
|
|
319
|
-
}));
|
|
320
|
-
if (resolved.length > 0) {
|
|
321
|
-
return { stewards: resolved, fallbackToOwner: false };
|
|
322
|
-
}
|
|
323
|
-
const owner = db.prepare(
|
|
324
|
-
`SELECT u.email FROM nests n
|
|
325
|
-
JOIN users u ON u.id = n.user_id
|
|
326
|
-
WHERE n.id = ?`
|
|
327
|
-
).get(nestId);
|
|
328
|
-
return {
|
|
329
|
-
stewards: [],
|
|
330
|
-
fallbackToOwner: true,
|
|
331
|
-
ownerEmail: owner?.email
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
function isSuperAdmin2(userEmail) {
|
|
335
|
-
const cfg = getAccessConfig();
|
|
336
|
-
return !!cfg?.super_admins?.includes(userEmail);
|
|
337
|
-
}
|
|
338
|
-
function getNestOwnerEmail(nestId) {
|
|
339
|
-
const db = getDb();
|
|
340
|
-
const row = db.prepare(
|
|
341
|
-
`SELECT u.email FROM nests n
|
|
342
|
-
JOIN users u ON u.id = n.user_id
|
|
343
|
-
WHERE n.id = ?`
|
|
344
|
-
).get(nestId);
|
|
345
|
-
return row?.email ?? null;
|
|
346
|
-
}
|
|
347
|
-
function canUserEdit(nestId, nodeId, userEmail) {
|
|
348
|
-
if (isSuperAdmin2(userEmail)) {
|
|
349
|
-
return { allowed: true, reason: "super admin", role: "super_admin" };
|
|
350
|
-
}
|
|
351
|
-
const owner = getNestOwnerEmail(nestId);
|
|
352
|
-
if (owner && owner.toLowerCase() === userEmail.toLowerCase()) {
|
|
353
|
-
return { allowed: true, reason: "nest owner", role: "owner" };
|
|
354
|
-
}
|
|
355
|
-
const resolved = resolveStewardsForNode(nestId, nodeId);
|
|
356
|
-
const match = resolved.find(
|
|
357
|
-
(r) => r.steward.userEmail.toLowerCase() === userEmail.toLowerCase() && (r.steward.role === "editor" || r.steward.role === "reviewer")
|
|
358
|
-
);
|
|
359
|
-
if (match) {
|
|
360
|
-
return { allowed: true, reason: match.source, role: match.steward.role };
|
|
361
|
-
}
|
|
362
|
-
return { allowed: false, reason: "no editor/reviewer role on this node", role: null };
|
|
363
|
-
}
|
|
364
|
-
function getCurrentVersionAuthor(nestId, nodeId) {
|
|
365
|
-
const db = getDb();
|
|
366
|
-
const row = db.prepare(
|
|
367
|
-
`SELECT author FROM node_versions
|
|
368
|
-
WHERE nest_id = ? AND node_id = ?
|
|
369
|
-
ORDER BY version DESC LIMIT 1`
|
|
370
|
-
).get(nestId, nodeId);
|
|
371
|
-
return row?.author ?? null;
|
|
372
|
-
}
|
|
373
|
-
function canUserApprove(nestId, nodeId, userEmail) {
|
|
374
|
-
if (isSuperAdmin2(userEmail)) {
|
|
375
|
-
return { allowed: true, reason: "super admin", role: "super_admin" };
|
|
376
|
-
}
|
|
377
|
-
const resolved = resolveStewardsForNode(nestId, nodeId);
|
|
378
|
-
if (resolved.length === 0) {
|
|
379
|
-
return {
|
|
380
|
-
allowed: false,
|
|
381
|
-
reason: "no stewards configured for this node",
|
|
382
|
-
role: null
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
const match = resolved.find(
|
|
386
|
-
(r) => r.steward.userEmail.toLowerCase() === userEmail.toLowerCase() && r.steward.role === "reviewer"
|
|
387
|
-
);
|
|
388
|
-
if (!match) {
|
|
389
|
-
return {
|
|
390
|
-
allowed: false,
|
|
391
|
-
reason: "not a reviewer steward for this node",
|
|
392
|
-
role: null
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
const author = getCurrentVersionAuthor(nestId, nodeId);
|
|
396
|
-
if (author && author.toLowerCase() === userEmail.toLowerCase()) {
|
|
397
|
-
return {
|
|
398
|
-
allowed: false,
|
|
399
|
-
reason: "cannot approve a version you authored (separation of duties)",
|
|
400
|
-
role: "reviewer"
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
return { allowed: true, reason: match.source, role: "reviewer" };
|
|
404
|
-
}
|
|
405
|
-
function canUserAccess(nestId, nodeId, userEmail) {
|
|
406
|
-
if (isSuperAdmin2(userEmail)) {
|
|
407
|
-
return { allowed: true, reason: "super admin", role: "super_admin" };
|
|
408
|
-
}
|
|
409
|
-
const owner = getNestOwnerEmail(nestId);
|
|
410
|
-
if (owner && owner.toLowerCase() === userEmail.toLowerCase()) {
|
|
411
|
-
return { allowed: true, reason: "nest owner", role: "owner" };
|
|
412
|
-
}
|
|
413
|
-
const resolved = resolveStewardsForNode(nestId, nodeId);
|
|
414
|
-
const match = resolved.find(
|
|
415
|
-
(r) => r.steward.userEmail.toLowerCase() === userEmail.toLowerCase()
|
|
416
|
-
);
|
|
417
|
-
if (match) {
|
|
418
|
-
return { allowed: true, reason: match.source, role: match.steward.role };
|
|
419
|
-
}
|
|
420
|
-
return { allowed: false, reason: "no steward assignment", role: null };
|
|
421
|
-
}
|
|
422
|
-
function syncFromConfig(nestId, config2) {
|
|
423
|
-
const db = getDb();
|
|
424
|
-
let count = 0;
|
|
425
|
-
db.prepare(
|
|
426
|
-
"UPDATE stewards SET is_active = 0 WHERE nest_id = ?"
|
|
427
|
-
).run(nestId);
|
|
428
|
-
db.prepare(
|
|
429
|
-
"UPDATE nests SET stewardship_enabled = 1 WHERE id = ?"
|
|
430
|
-
).run(nestId);
|
|
431
|
-
const addEntries = (scope, entries, target) => {
|
|
432
|
-
for (const entry of entries) {
|
|
433
|
-
const user = db.prepare("SELECT id FROM users WHERE email = ?").get(entry.email);
|
|
434
|
-
const rawRole = entry.role || "reviewer";
|
|
435
|
-
const role = rawRole === "admin" ? "reviewer" : rawRole;
|
|
436
|
-
assignSteward({
|
|
437
|
-
nestId,
|
|
438
|
-
scope,
|
|
439
|
-
nodePattern: target?.nodePattern,
|
|
440
|
-
tagName: target?.tagName ? target.tagName.trim().replace(/^#+/, "").toLowerCase() : void 0,
|
|
441
|
-
userEmail: entry.email.toLowerCase(),
|
|
442
|
-
userId: user?.id,
|
|
443
|
-
role,
|
|
444
|
-
assignedBy: "config",
|
|
445
|
-
assignedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
446
|
-
isActive: true
|
|
447
|
-
});
|
|
448
|
-
count++;
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
if (config2.nest) {
|
|
452
|
-
addEntries("nest", config2.nest);
|
|
453
|
-
}
|
|
454
|
-
if (config2.tags) {
|
|
455
|
-
for (const [tagName, entries] of Object.entries(config2.tags)) {
|
|
456
|
-
addEntries("tag", entries, { tagName });
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
if (config2.documents) {
|
|
460
|
-
for (const [docPattern, entries] of Object.entries(config2.documents)) {
|
|
461
|
-
addEntries("document", entries, { nodePattern: docPattern });
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return count;
|
|
465
|
-
}
|
|
466
|
-
function rowToSteward(row) {
|
|
467
|
-
return {
|
|
468
|
-
id: row.id,
|
|
469
|
-
nestId: row.nest_id,
|
|
470
|
-
scope: row.scope,
|
|
471
|
-
nodePattern: row.node_pattern || void 0,
|
|
472
|
-
tagName: row.tag_name || void 0,
|
|
473
|
-
userEmail: row.user_email,
|
|
474
|
-
userId: row.user_id || void 0,
|
|
475
|
-
role: row.role,
|
|
476
|
-
assignedBy: row.assigned_by,
|
|
477
|
-
assignedAt: row.assigned_at,
|
|
478
|
-
isActive: !!row.is_active
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
export {
|
|
483
|
-
loadAccessConfig,
|
|
484
|
-
isSuperAdmin,
|
|
485
|
-
assignSteward,
|
|
486
|
-
removeSteward,
|
|
487
|
-
getSteward,
|
|
488
|
-
getStewardsForNest,
|
|
489
|
-
getStewardsForScope,
|
|
490
|
-
listStewards,
|
|
491
|
-
createStewardRecord,
|
|
492
|
-
resolveStewardsForNode,
|
|
493
|
-
resolveStewardsWithFallback,
|
|
494
|
-
canUserEdit,
|
|
495
|
-
canUserApprove,
|
|
496
|
-
canUserAccess,
|
|
497
|
-
syncFromConfig
|
|
498
|
-
};
|