@promptowl/contextnest-community 0.1.0-alpha.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.
@@ -0,0 +1,127 @@
1
+ import {
2
+ getDb
3
+ } from "./chunk-USIDOGVJ.js";
4
+
5
+ // src/governance/version-service.ts
6
+ import { createHash } from "crypto";
7
+ function hashContent(content) {
8
+ return createHash("sha256").update(content).digest("hex");
9
+ }
10
+ function createVersion(params) {
11
+ const db = getDb();
12
+ const contentHash = hashContent(params.content);
13
+ db.prepare(
14
+ `INSERT INTO node_versions (nest_id, node_id, version, content_hash, author, status, change_note, tags_json)
15
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
16
+ ).run(
17
+ params.nestId,
18
+ params.nodeId,
19
+ params.version,
20
+ contentHash,
21
+ params.author,
22
+ params.status,
23
+ params.changeNote || null,
24
+ params.tags ? JSON.stringify(params.tags) : null
25
+ );
26
+ return {
27
+ version: params.version,
28
+ content: params.content,
29
+ editedBy: params.author,
30
+ editedAt: (/* @__PURE__ */ new Date()).toISOString(),
31
+ changeNote: params.changeNote,
32
+ status: params.status
33
+ };
34
+ }
35
+ function getVersions(nestId, nodeId) {
36
+ const db = getDb();
37
+ const rows = db.prepare(
38
+ "SELECT * FROM node_versions WHERE nest_id = ? AND node_id = ? ORDER BY version DESC"
39
+ ).all(nestId, nodeId);
40
+ return rows.map(rowToVersion);
41
+ }
42
+ function getVersion(nestId, nodeId, version) {
43
+ const db = getDb();
44
+ const row = db.prepare(
45
+ "SELECT * FROM node_versions WHERE nest_id = ? AND node_id = ? AND version = ?"
46
+ ).get(nestId, nodeId, version);
47
+ return row ? rowToVersion(row) : null;
48
+ }
49
+ function getCurrentVersion(nestId, nodeId) {
50
+ const db = getDb();
51
+ const row = db.prepare(
52
+ "SELECT MAX(version) as v FROM node_versions WHERE nest_id = ? AND node_id = ?"
53
+ ).get(nestId, nodeId);
54
+ return row?.v || 0;
55
+ }
56
+ function getApprovedVersion(nestId, nodeId) {
57
+ const db = getDb();
58
+ const row = db.prepare(
59
+ "SELECT approved_version FROM approved_versions WHERE nest_id = ? AND node_id = ?"
60
+ ).get(nestId, nodeId);
61
+ return row?.approved_version ?? null;
62
+ }
63
+ function setApprovedVersion(nestId, nodeId, version, approvedBy) {
64
+ const db = getDb();
65
+ db.prepare(
66
+ `INSERT OR REPLACE INTO approved_versions (nest_id, node_id, approved_version, approved_by)
67
+ VALUES (?, ?, ?, ?)`
68
+ ).run(nestId, nodeId, version, approvedBy);
69
+ }
70
+ function checkConflict(nestId, nodeId, baseVersion) {
71
+ const db = getDb();
72
+ const current = db.prepare(
73
+ "SELECT version, content_hash, author, created_at FROM node_versions WHERE nest_id = ? AND node_id = ? ORDER BY version DESC LIMIT 1"
74
+ ).get(nestId, nodeId);
75
+ if (!current) {
76
+ return { conflict: false, currentVersion: 0, currentHash: "" };
77
+ }
78
+ if (current.version !== baseVersion) {
79
+ return {
80
+ conflict: true,
81
+ currentVersion: current.version,
82
+ currentHash: current.content_hash,
83
+ updatedBy: current.author,
84
+ updatedAt: current.created_at
85
+ };
86
+ }
87
+ return {
88
+ conflict: false,
89
+ currentVersion: current.version,
90
+ currentHash: current.content_hash
91
+ };
92
+ }
93
+ function getNodeTags(nestId, nodeId) {
94
+ const db = getDb();
95
+ const row = db.prepare(
96
+ "SELECT tags_json FROM node_versions WHERE nest_id = ? AND node_id = ? ORDER BY version DESC LIMIT 1"
97
+ ).get(nestId, nodeId);
98
+ if (!row?.tags_json) return [];
99
+ try {
100
+ return JSON.parse(row.tags_json);
101
+ } catch {
102
+ return [];
103
+ }
104
+ }
105
+ function rowToVersion(row) {
106
+ return {
107
+ version: row.version,
108
+ content: "",
109
+ // Content lives on disk, not in the versions table
110
+ editedBy: row.author,
111
+ editedAt: row.created_at,
112
+ changeNote: row.change_note || void 0,
113
+ status: row.status
114
+ };
115
+ }
116
+
117
+ export {
118
+ hashContent,
119
+ createVersion,
120
+ getVersions,
121
+ getVersion,
122
+ getCurrentVersion,
123
+ getApprovedVersion,
124
+ setApprovedVersion,
125
+ checkConflict,
126
+ getNodeTags
127
+ };
@@ -0,0 +1,491 @@
1
+ import {
2
+ config,
3
+ getDb
4
+ } from "./chunk-USIDOGVJ.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, can_approve, can_reject, 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.canApprove ? 1 : 0,
123
+ data.canReject ? 1 : 0,
124
+ data.assignedBy,
125
+ data.isActive ? 1 : 0
126
+ );
127
+ return { ...data, id };
128
+ }
129
+ function removeSteward(id) {
130
+ const db = getDb();
131
+ db.prepare("UPDATE stewards SET is_active = 0 WHERE id = ?").run(id);
132
+ }
133
+ function getSteward(id) {
134
+ const db = getDb();
135
+ const row = db.prepare("SELECT * FROM stewards WHERE id = ?").get(id);
136
+ return row ? rowToSteward(row) : null;
137
+ }
138
+ function getStewardsForNest(nestId) {
139
+ const db = getDb();
140
+ const rows = db.prepare("SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1").all(nestId);
141
+ return rows.map(rowToSteward);
142
+ }
143
+ function getStewardsForScope(params) {
144
+ const db = getDb();
145
+ let sql = "SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1";
146
+ const args = [params.nestId];
147
+ if (params.scope) {
148
+ sql += " AND scope = ?";
149
+ args.push(params.scope);
150
+ }
151
+ if (params.scopeTarget) {
152
+ sql += " AND (node_pattern = ? OR tag_name = ?)";
153
+ args.push(params.scopeTarget, params.scopeTarget);
154
+ }
155
+ return db.prepare(sql).all(...args).map(rowToSteward);
156
+ }
157
+ function listStewards(params) {
158
+ const db = getDb();
159
+ let sql = "SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1";
160
+ const args = [params.nestId];
161
+ if (params.scope) {
162
+ sql += " AND scope = ?";
163
+ args.push(params.scope);
164
+ }
165
+ if (params.search) {
166
+ sql += " AND (user_email LIKE ? OR tag_name LIKE ? OR node_pattern LIKE ?)";
167
+ const like = `%${params.search.toLowerCase()}%`;
168
+ args.push(like, like, like);
169
+ }
170
+ sql += " ORDER BY scope, COALESCE(node_pattern, tag_name, ''), user_email";
171
+ return db.prepare(sql).all(...args).map(rowToSteward);
172
+ }
173
+ function createStewardRecord(params) {
174
+ if (params.users.length === 0) {
175
+ throw new Error("At least one user is required");
176
+ }
177
+ let nodePattern;
178
+ let tagName;
179
+ switch (params.scope) {
180
+ case "document":
181
+ if (!params.documentId) throw new Error("documentId required for document scope");
182
+ nodePattern = params.documentId;
183
+ break;
184
+ case "folder":
185
+ if (!params.folderPath) throw new Error("folderPath required for folder scope");
186
+ nodePattern = params.folderPath;
187
+ break;
188
+ case "tag":
189
+ if (!params.tagName) throw new Error("tagName required for tag scope");
190
+ tagName = params.tagName.trim().replace(/^#+/, "").toLowerCase();
191
+ break;
192
+ case "nest":
193
+ break;
194
+ }
195
+ const db = getDb();
196
+ const results = [];
197
+ for (const user of params.users) {
198
+ const email = user.email.trim().toLowerCase();
199
+ if (!email) continue;
200
+ const existing = db.prepare(
201
+ `SELECT * FROM stewards
202
+ WHERE nest_id = ? AND is_active = 1 AND scope = ? AND user_email = ?
203
+ AND COALESCE(node_pattern, '') = COALESCE(?, '')
204
+ AND COALESCE(tag_name, '') = COALESCE(?, '')`
205
+ ).get(
206
+ params.nestId,
207
+ params.scope,
208
+ email,
209
+ nodePattern ?? null,
210
+ tagName ?? null
211
+ );
212
+ if (existing) {
213
+ results.push(rowToSteward(existing));
214
+ continue;
215
+ }
216
+ const userRow = db.prepare("SELECT id FROM users WHERE email = ?").get(email);
217
+ const created = assignSteward({
218
+ nestId: params.nestId,
219
+ scope: params.scope,
220
+ nodePattern,
221
+ tagName,
222
+ userEmail: email,
223
+ userId: userRow?.id,
224
+ role: user.role ?? "reviewer",
225
+ canApprove: user.canApprove !== false,
226
+ canReject: user.canReject !== false,
227
+ assignedBy: params.assignedBy,
228
+ assignedAt: (/* @__PURE__ */ new Date()).toISOString(),
229
+ isActive: true
230
+ });
231
+ results.push(created);
232
+ }
233
+ db.prepare(
234
+ "UPDATE nests SET stewardship_enabled = 1 WHERE id = ? AND stewardship_enabled = 0"
235
+ ).run(params.nestId);
236
+ return results;
237
+ }
238
+ function resolveStewardsForNode(nestId, nodeId) {
239
+ return resolve(nestId, nodeId).stewards;
240
+ }
241
+ function resolveStewardsWithFallback(nestId, nodeId) {
242
+ return resolve(nestId, nodeId);
243
+ }
244
+ function resolve(nestId, nodeId) {
245
+ const db = getDb();
246
+ const rows = db.prepare(
247
+ `
248
+ SELECT s.*, 1 AS priority, ('document: ' || s.node_pattern) AS match_source
249
+ FROM stewards s
250
+ WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'document'
251
+ AND s.node_pattern = ?
252
+ UNION ALL
253
+ SELECT s.*, 2 AS priority, ('folder: ' || s.node_pattern) AS match_source
254
+ FROM stewards s
255
+ WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'folder'
256
+ AND s.node_pattern IS NOT NULL
257
+ AND instr(s.node_pattern, '*') = 0
258
+ AND ? LIKE s.node_pattern || '/%'
259
+ UNION ALL
260
+ SELECT s.*, 3 AS priority, ('tag: ' || s.tag_name) AS match_source
261
+ FROM stewards s
262
+ JOIN node_tag_index nt
263
+ ON nt.nest_id = s.nest_id
264
+ AND nt.tag_name = s.tag_name
265
+ WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'tag'
266
+ AND nt.node_id = ?
267
+ UNION ALL
268
+ SELECT s.*, 4 AS priority, 'nest-level steward' AS match_source
269
+ FROM stewards s
270
+ WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'nest'
271
+ ORDER BY priority ASC, user_email ASC
272
+ `
273
+ ).all(nestId, nodeId, nestId, nodeId, nestId, nodeId, nestId);
274
+ const resolved = rows.map((row) => ({
275
+ steward: rowToSteward(row),
276
+ priority: row.priority,
277
+ source: row.match_source
278
+ }));
279
+ const legacyFolderGlobs = db.prepare(
280
+ `SELECT * FROM stewards
281
+ WHERE nest_id = ? AND is_active = 1 AND scope = 'folder'
282
+ AND node_pattern IS NOT NULL AND instr(node_pattern, '*') > 0`
283
+ ).all(nestId);
284
+ for (const row of legacyFolderGlobs) {
285
+ if (globMatch(nodeId, row.node_pattern)) {
286
+ resolved.push({
287
+ steward: rowToSteward(row),
288
+ priority: 2,
289
+ source: `folder: ${row.node_pattern}`
290
+ });
291
+ }
292
+ }
293
+ if (legacyFolderGlobs.length > 0) {
294
+ resolved.sort((a, b) => a.priority - b.priority);
295
+ }
296
+ if (resolved.length > 0) {
297
+ return { stewards: resolved, fallbackToOwner: false };
298
+ }
299
+ const owner = db.prepare(
300
+ `SELECT u.email FROM nests n
301
+ JOIN users u ON u.id = n.user_id
302
+ WHERE n.id = ?`
303
+ ).get(nestId);
304
+ return {
305
+ stewards: [],
306
+ fallbackToOwner: true,
307
+ ownerEmail: owner?.email
308
+ };
309
+ }
310
+ function isSuperAdmin2(userEmail) {
311
+ const cfg = getAccessConfig();
312
+ return !!cfg?.super_admins?.includes(userEmail);
313
+ }
314
+ function getNestOwnerEmail(nestId) {
315
+ const db = getDb();
316
+ const row = db.prepare(
317
+ `SELECT u.email FROM nests n
318
+ JOIN users u ON u.id = n.user_id
319
+ WHERE n.id = ?`
320
+ ).get(nestId);
321
+ return row?.email ?? null;
322
+ }
323
+ function canUserEdit(nestId, nodeId, userEmail) {
324
+ if (isSuperAdmin2(userEmail)) {
325
+ return { allowed: true, reason: "super admin", role: "super_admin" };
326
+ }
327
+ const owner = getNestOwnerEmail(nestId);
328
+ if (owner && owner.toLowerCase() === userEmail.toLowerCase()) {
329
+ return { allowed: true, reason: "nest owner", role: "owner" };
330
+ }
331
+ const resolved = resolveStewardsForNode(nestId, nodeId);
332
+ const match = resolved.find(
333
+ (r) => r.steward.userEmail.toLowerCase() === userEmail.toLowerCase() && (r.steward.role === "editor" || r.steward.role === "reviewer")
334
+ );
335
+ if (match) {
336
+ return { allowed: true, reason: match.source, role: match.steward.role };
337
+ }
338
+ return { allowed: false, reason: "no editor/reviewer role on this node", role: null };
339
+ }
340
+ function getCurrentVersionAuthor(nestId, nodeId) {
341
+ const db = getDb();
342
+ const row = db.prepare(
343
+ `SELECT author FROM node_versions
344
+ WHERE nest_id = ? AND node_id = ?
345
+ ORDER BY version DESC LIMIT 1`
346
+ ).get(nestId, nodeId);
347
+ return row?.author ?? null;
348
+ }
349
+ function canUserApprove(nestId, nodeId, userEmail) {
350
+ if (isSuperAdmin2(userEmail)) {
351
+ return { allowed: true, reason: "super admin", role: "super_admin" };
352
+ }
353
+ const resolved = resolveStewardsForNode(nestId, nodeId);
354
+ if (resolved.length === 0) {
355
+ return {
356
+ allowed: false,
357
+ reason: "no stewards configured for this node",
358
+ role: null
359
+ };
360
+ }
361
+ const match = resolved.find(
362
+ (r) => r.steward.userEmail.toLowerCase() === userEmail.toLowerCase() && r.steward.role === "reviewer" && r.steward.canApprove
363
+ );
364
+ if (!match) {
365
+ return {
366
+ allowed: false,
367
+ reason: "not a reviewer steward for this node",
368
+ role: null
369
+ };
370
+ }
371
+ const author = getCurrentVersionAuthor(nestId, nodeId);
372
+ if (author && author.toLowerCase() === userEmail.toLowerCase()) {
373
+ return {
374
+ allowed: false,
375
+ reason: "cannot approve a version you authored (separation of duties)",
376
+ role: "reviewer"
377
+ };
378
+ }
379
+ return { allowed: true, reason: match.source, role: "reviewer" };
380
+ }
381
+ function canUserAccess(nestId, nodeId, userEmail) {
382
+ if (isSuperAdmin2(userEmail)) {
383
+ return { allowed: true, reason: "super admin", role: "super_admin" };
384
+ }
385
+ const owner = getNestOwnerEmail(nestId);
386
+ if (owner && owner.toLowerCase() === userEmail.toLowerCase()) {
387
+ return { allowed: true, reason: "nest owner", role: "owner" };
388
+ }
389
+ const resolved = resolveStewardsForNode(nestId, nodeId);
390
+ const match = resolved.find(
391
+ (r) => r.steward.userEmail.toLowerCase() === userEmail.toLowerCase()
392
+ );
393
+ if (match) {
394
+ return { allowed: true, reason: match.source, role: match.steward.role };
395
+ }
396
+ return { allowed: false, reason: "no steward assignment", role: null };
397
+ }
398
+ function globMatch(value, pattern) {
399
+ if (pattern === "*") return true;
400
+ if (!pattern.includes("*")) return value === pattern;
401
+ const regex = new RegExp(
402
+ "^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"
403
+ );
404
+ return regex.test(value);
405
+ }
406
+ function syncFromConfig(nestId, config2) {
407
+ const db = getDb();
408
+ let count = 0;
409
+ db.prepare(
410
+ "UPDATE stewards SET is_active = 0 WHERE nest_id = ?"
411
+ ).run(nestId);
412
+ db.prepare(
413
+ "UPDATE nests SET stewardship_enabled = 1 WHERE id = ?"
414
+ ).run(nestId);
415
+ const addEntries = (scope, entries, target) => {
416
+ for (const entry of entries) {
417
+ const user = db.prepare("SELECT id FROM users WHERE email = ?").get(entry.email);
418
+ const rawRole = entry.role || "reviewer";
419
+ const role = rawRole === "admin" ? "reviewer" : rawRole;
420
+ assignSteward({
421
+ nestId,
422
+ scope,
423
+ nodePattern: target?.nodePattern,
424
+ tagName: target?.tagName ? target.tagName.trim().replace(/^#+/, "").toLowerCase() : void 0,
425
+ userEmail: entry.email.toLowerCase(),
426
+ userId: user?.id,
427
+ role,
428
+ canApprove: entry.can_approve !== false,
429
+ canReject: entry.can_reject !== false,
430
+ assignedBy: "config",
431
+ assignedAt: (/* @__PURE__ */ new Date()).toISOString(),
432
+ isActive: true
433
+ });
434
+ count++;
435
+ }
436
+ };
437
+ if (config2.nest) {
438
+ addEntries("nest", config2.nest);
439
+ }
440
+ if (config2.folders) {
441
+ for (const [pattern, entries] of Object.entries(config2.folders)) {
442
+ addEntries("folder", entries, { nodePattern: pattern });
443
+ }
444
+ }
445
+ if (config2.tags) {
446
+ for (const [tagName, entries] of Object.entries(config2.tags)) {
447
+ addEntries("tag", entries, { tagName });
448
+ }
449
+ }
450
+ if (config2.documents) {
451
+ for (const [docPattern, entries] of Object.entries(config2.documents)) {
452
+ addEntries("document", entries, { nodePattern: docPattern });
453
+ }
454
+ }
455
+ return count;
456
+ }
457
+ function rowToSteward(row) {
458
+ return {
459
+ id: row.id,
460
+ nestId: row.nest_id,
461
+ scope: row.scope,
462
+ nodePattern: row.node_pattern || void 0,
463
+ tagName: row.tag_name || void 0,
464
+ userEmail: row.user_email,
465
+ userId: row.user_id || void 0,
466
+ role: row.role,
467
+ canApprove: !!row.can_approve,
468
+ canReject: !!row.can_reject,
469
+ assignedBy: row.assigned_by,
470
+ assignedAt: row.assigned_at,
471
+ isActive: !!row.is_active
472
+ };
473
+ }
474
+
475
+ export {
476
+ loadAccessConfig,
477
+ isSuperAdmin,
478
+ assignSteward,
479
+ removeSteward,
480
+ getSteward,
481
+ getStewardsForNest,
482
+ getStewardsForScope,
483
+ listStewards,
484
+ createStewardRecord,
485
+ resolveStewardsForNode,
486
+ resolveStewardsWithFallback,
487
+ canUserEdit,
488
+ canUserApprove,
489
+ canUserAccess,
490
+ syncFromConfig
491
+ };