@enfyra/mcp-server 0.1.5 → 0.1.7
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/README.md +2 -2
- package/package.json +1 -1
- package/src/lib/mcp-examples.js +29 -20
- package/src/lib/mcp-instructions.js +1 -1
- package/src/lib/platform-operation-tools.js +85 -21
- package/src/lib/required-knowledge.js +63 -2
- package/src/lib/table-tools.js +86 -35
- package/src/mcp-server-entry.mjs +97 -48
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
assertDynamicCodeKnowledgeAck,
|
|
7
7
|
assertDynamicCodeKnowledgeAckIf,
|
|
8
8
|
assertExtensionKnowledgeAck,
|
|
9
|
+
assertGlobalRulesAck,
|
|
9
10
|
dynamicCodeKnowledgeAckParam,
|
|
10
11
|
extensionKnowledgeAckParam,
|
|
12
|
+
globalRulesAckParam,
|
|
11
13
|
} from './required-knowledge.js';
|
|
12
14
|
|
|
13
15
|
function unwrapData(result) {
|
|
@@ -111,7 +113,8 @@ async function resolveRoute(apiUrl, { path, routeId }) {
|
|
|
111
113
|
return { route, routes, path: route.path };
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
async function updateRouteMethods(apiUrl, { path, routeId, methods, mode, isEnabled }) {
|
|
116
|
+
async function updateRouteMethods(apiUrl, { path, routeId, methods, mode, isEnabled, globalRulesAckKey }) {
|
|
117
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
115
118
|
const [{ route }, { methodMap, methodIdNameMap }] = await Promise.all([
|
|
116
119
|
resolveRoute(apiUrl, { path, routeId }),
|
|
117
120
|
getMethodContext(apiUrl),
|
|
@@ -141,7 +144,8 @@ async function updateRouteMethods(apiUrl, { path, routeId, methods, mode, isEnab
|
|
|
141
144
|
};
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
async function updateRoutePublicMethods(apiUrl, { path, routeId, methods, mode }) {
|
|
147
|
+
async function updateRoutePublicMethods(apiUrl, { path, routeId, methods, mode, globalRulesAckKey }) {
|
|
148
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
145
149
|
const [{ route }, { methodMap, methodIdNameMap }] = await Promise.all([
|
|
146
150
|
resolveRoute(apiUrl, { path, routeId }),
|
|
147
151
|
getMethodContext(apiUrl),
|
|
@@ -171,7 +175,8 @@ async function updateRoutePublicMethods(apiUrl, { path, routeId, methods, mode }
|
|
|
171
175
|
};
|
|
172
176
|
}
|
|
173
177
|
|
|
174
|
-
async function setRouteEnabled(apiUrl, { path, routeId, isEnabled }) {
|
|
178
|
+
async function setRouteEnabled(apiUrl, { path, routeId, isEnabled, globalRulesAckKey }) {
|
|
179
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
175
180
|
const { route } = await resolveRoute(apiUrl, { path, routeId });
|
|
176
181
|
const before = route?.isEnabled !== false;
|
|
177
182
|
if (before === isEnabled) {
|
|
@@ -235,7 +240,7 @@ async function deleteRows(apiUrl, tableName, rows) {
|
|
|
235
240
|
return deleted;
|
|
236
241
|
}
|
|
237
242
|
|
|
238
|
-
async function deleteRoute(apiUrl, { path, routeId, expectedPath, confirm }) {
|
|
243
|
+
async function deleteRoute(apiUrl, { path, routeId, expectedPath, confirm, globalRulesAckKey }) {
|
|
239
244
|
const { route } = await resolveRoute(apiUrl, { path, routeId });
|
|
240
245
|
if (expectedPath && route.path !== normalizeRestPath(expectedPath)) {
|
|
241
246
|
throw new Error(`Route path mismatch: resolved ${route.path}, expected ${normalizeRestPath(expectedPath)}.`);
|
|
@@ -255,6 +260,7 @@ async function deleteRoute(apiUrl, { path, routeId, expectedPath, confirm }) {
|
|
|
255
260
|
next: 'Call delete_route again with confirm=true and expectedPath set to this route path to delete the route and related handlers/hooks/permissions/guards.',
|
|
256
261
|
};
|
|
257
262
|
}
|
|
263
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
258
264
|
|
|
259
265
|
await deleteRows(apiUrl, 'enfyra_route_handler', dependencies.handlers);
|
|
260
266
|
await deleteRows(apiUrl, 'enfyra_pre_hook', dependencies.preHooks);
|
|
@@ -641,7 +647,9 @@ async function ensureMenu(apiUrl, {
|
|
|
641
647
|
permission,
|
|
642
648
|
description,
|
|
643
649
|
isEnabled = true,
|
|
650
|
+
globalRulesAckKey,
|
|
644
651
|
}) {
|
|
652
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
645
653
|
const normalizedPath = path ? normalizeRestPath(path) : undefined;
|
|
646
654
|
const existing = normalizedPath
|
|
647
655
|
? await findRecord(apiUrl, 'enfyra_menu', { path: { _eq: normalizedPath } }, 'id,_id,path,label')
|
|
@@ -673,8 +681,10 @@ async function ensureExtension(apiUrl, {
|
|
|
673
681
|
description,
|
|
674
682
|
isEnabled = true,
|
|
675
683
|
version = '1.0.0',
|
|
684
|
+
globalRulesAckKey,
|
|
676
685
|
extensionKnowledgeAckKey,
|
|
677
686
|
}) {
|
|
687
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
678
688
|
assertExtensionKnowledgeAck(extensionKnowledgeAckKey);
|
|
679
689
|
if (type === 'page' && !menuId) {
|
|
680
690
|
throw new Error('menuId is required for page extensions. Use ensure_menu first, then ensure_page_extension.');
|
|
@@ -711,7 +721,9 @@ async function ensureFlow(apiUrl, {
|
|
|
711
721
|
maxExecutions = 100,
|
|
712
722
|
isEnabled = true,
|
|
713
723
|
description,
|
|
724
|
+
globalRulesAckKey,
|
|
714
725
|
}) {
|
|
726
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
715
727
|
const existing = await findRecord(apiUrl, 'enfyra_flow', { name: { _eq: name } }, 'id,_id,name');
|
|
716
728
|
const operation = await createOrPatch(apiUrl, 'enfyra_flow', existing, {
|
|
717
729
|
name,
|
|
@@ -737,8 +749,10 @@ async function ensureFlowStep(apiUrl, {
|
|
|
737
749
|
scriptLanguage,
|
|
738
750
|
timeout,
|
|
739
751
|
isEnabled,
|
|
752
|
+
globalRulesAckKey,
|
|
740
753
|
knowledgeAckKey,
|
|
741
754
|
}) {
|
|
755
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
742
756
|
if (!flowName && !flowId) throw new Error('Provide flowName or flowId.');
|
|
743
757
|
if (flowName && flowId) throw new Error('Provide flowName or flowId, not both.');
|
|
744
758
|
const flow = flowId
|
|
@@ -1124,6 +1138,7 @@ async function runApiEndpointWorkflow(apiUrl, opts) {
|
|
|
1124
1138
|
const operations = [];
|
|
1125
1139
|
let completedEphemeralStepId = null;
|
|
1126
1140
|
if (opts.apply || opts.applyAll) {
|
|
1141
|
+
assertGlobalRulesAck(opts.globalRulesAckKey);
|
|
1127
1142
|
if (opts.applyAll && state.steps.some((item) => item.id === 'save_handler' && ['pending', 'waiting'].includes(item.status))) {
|
|
1128
1143
|
assertDynamicCodeKnowledgeAck(opts.knowledgeAckKey);
|
|
1129
1144
|
}
|
|
@@ -1227,8 +1242,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1227
1242
|
{
|
|
1228
1243
|
tableName: z.string().describe('Table name, alias, or id.'),
|
|
1229
1244
|
isEnabled: z.boolean().describe('Desired GraphQL enabled state for the table.'),
|
|
1245
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1230
1246
|
},
|
|
1231
|
-
async ({ tableName, isEnabled }) => {
|
|
1247
|
+
async ({ tableName, isEnabled, globalRulesAckKey }) => {
|
|
1248
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1232
1249
|
const table = resolveTable(await getMetadataTables(ENFYRA_API_URL), tableName);
|
|
1233
1250
|
const existing = await findRecord(ENFYRA_API_URL, 'enfyra_graphql', { table: { id: { _eq: getId(table) } } }, 'id,_id,table.id,isEnabled');
|
|
1234
1251
|
const operation = await createOrPatch(ENFYRA_API_URL, 'enfyra_graphql', existing, {
|
|
@@ -1254,13 +1271,15 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1254
1271
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1255
1272
|
methods: z.array(z.string()).min(1).describe('HTTP method names to add.'),
|
|
1256
1273
|
isEnabled: z.boolean().optional().describe('Optionally enable/disable the route in the same safe patch.'),
|
|
1274
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1257
1275
|
},
|
|
1258
|
-
async ({ path, routeId, methods, isEnabled }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1276
|
+
async ({ path, routeId, methods, isEnabled, globalRulesAckKey }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1259
1277
|
path,
|
|
1260
1278
|
routeId,
|
|
1261
1279
|
methods,
|
|
1262
1280
|
mode: 'merge',
|
|
1263
1281
|
isEnabled,
|
|
1282
|
+
globalRulesAckKey,
|
|
1264
1283
|
})),
|
|
1265
1284
|
);
|
|
1266
1285
|
|
|
@@ -1272,13 +1291,15 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1272
1291
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1273
1292
|
methods: z.array(z.string()).min(1).describe('Exact HTTP method names for availableMethods.'),
|
|
1274
1293
|
isEnabled: z.boolean().optional().describe('Optionally enable/disable the route in the same safe patch.'),
|
|
1294
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1275
1295
|
},
|
|
1276
|
-
async ({ path, routeId, methods, isEnabled }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1296
|
+
async ({ path, routeId, methods, isEnabled, globalRulesAckKey }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1277
1297
|
path,
|
|
1278
1298
|
routeId,
|
|
1279
1299
|
methods,
|
|
1280
1300
|
mode: 'replace',
|
|
1281
1301
|
isEnabled,
|
|
1302
|
+
globalRulesAckKey,
|
|
1282
1303
|
})),
|
|
1283
1304
|
);
|
|
1284
1305
|
|
|
@@ -1290,13 +1311,15 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1290
1311
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1291
1312
|
methods: z.array(z.string()).min(1).describe('HTTP method names to remove.'),
|
|
1292
1313
|
isEnabled: z.boolean().optional().describe('Optionally enable/disable the route in the same safe patch.'),
|
|
1314
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1293
1315
|
},
|
|
1294
|
-
async ({ path, routeId, methods, isEnabled }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1316
|
+
async ({ path, routeId, methods, isEnabled, globalRulesAckKey }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1295
1317
|
path,
|
|
1296
1318
|
routeId,
|
|
1297
1319
|
methods,
|
|
1298
1320
|
mode: 'remove',
|
|
1299
1321
|
isEnabled,
|
|
1322
|
+
globalRulesAckKey,
|
|
1300
1323
|
})),
|
|
1301
1324
|
);
|
|
1302
1325
|
|
|
@@ -1306,11 +1329,13 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1306
1329
|
{
|
|
1307
1330
|
path: z.string().optional().describe('Route path, e.g. /sum. Use either path or routeId.'),
|
|
1308
1331
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1332
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1309
1333
|
},
|
|
1310
|
-
async ({ path, routeId }) => jsonText(await setRouteEnabled(ENFYRA_API_URL, {
|
|
1334
|
+
async ({ path, routeId, globalRulesAckKey }) => jsonText(await setRouteEnabled(ENFYRA_API_URL, {
|
|
1311
1335
|
path,
|
|
1312
1336
|
routeId,
|
|
1313
1337
|
isEnabled: true,
|
|
1338
|
+
globalRulesAckKey,
|
|
1314
1339
|
})),
|
|
1315
1340
|
);
|
|
1316
1341
|
|
|
@@ -1320,11 +1345,13 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1320
1345
|
{
|
|
1321
1346
|
path: z.string().optional().describe('Route path, e.g. /sum. Use either path or routeId.'),
|
|
1322
1347
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1348
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1323
1349
|
},
|
|
1324
|
-
async ({ path, routeId }) => jsonText(await setRouteEnabled(ENFYRA_API_URL, {
|
|
1350
|
+
async ({ path, routeId, globalRulesAckKey }) => jsonText(await setRouteEnabled(ENFYRA_API_URL, {
|
|
1325
1351
|
path,
|
|
1326
1352
|
routeId,
|
|
1327
1353
|
isEnabled: false,
|
|
1354
|
+
globalRulesAckKey,
|
|
1328
1355
|
})),
|
|
1329
1356
|
);
|
|
1330
1357
|
|
|
@@ -1336,6 +1363,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1336
1363
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1337
1364
|
expectedPath: z.string().optional().describe('Optional safety check. When confirm=true, pass the path returned by the preview.'),
|
|
1338
1365
|
confirm: z.boolean().optional().default(false).describe('false returns a dependency preview only; true deletes the route and related route-owned records.'),
|
|
1366
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1339
1367
|
},
|
|
1340
1368
|
async (input) => jsonText(await deleteRoute(ENFYRA_API_URL, input)),
|
|
1341
1369
|
);
|
|
@@ -1347,12 +1375,14 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1347
1375
|
path: z.string().optional().describe('Route path, e.g. /sum. Use either path or routeId.'),
|
|
1348
1376
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1349
1377
|
methods: z.array(z.string()).min(1).describe('HTTP method names to make public. They must already be available on the route.'),
|
|
1378
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1350
1379
|
},
|
|
1351
|
-
async ({ path, routeId, methods }) => jsonText(await updateRoutePublicMethods(ENFYRA_API_URL, {
|
|
1380
|
+
async ({ path, routeId, methods, globalRulesAckKey }) => jsonText(await updateRoutePublicMethods(ENFYRA_API_URL, {
|
|
1352
1381
|
path,
|
|
1353
1382
|
routeId,
|
|
1354
1383
|
methods,
|
|
1355
1384
|
mode: 'merge',
|
|
1385
|
+
globalRulesAckKey,
|
|
1356
1386
|
})),
|
|
1357
1387
|
);
|
|
1358
1388
|
|
|
@@ -1363,12 +1393,14 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1363
1393
|
path: z.string().optional().describe('Route path, e.g. /sum. Use either path or routeId.'),
|
|
1364
1394
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1365
1395
|
methods: z.array(z.string()).min(1).describe('HTTP method names to remove from publicMethods.'),
|
|
1396
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1366
1397
|
},
|
|
1367
|
-
async ({ path, routeId, methods }) => jsonText(await updateRoutePublicMethods(ENFYRA_API_URL, {
|
|
1398
|
+
async ({ path, routeId, methods, globalRulesAckKey }) => jsonText(await updateRoutePublicMethods(ENFYRA_API_URL, {
|
|
1368
1399
|
path,
|
|
1369
1400
|
routeId,
|
|
1370
1401
|
methods,
|
|
1371
1402
|
mode: 'remove',
|
|
1403
|
+
globalRulesAckKey,
|
|
1372
1404
|
})),
|
|
1373
1405
|
);
|
|
1374
1406
|
|
|
@@ -1399,6 +1431,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1399
1431
|
apply: z.boolean().optional().default(false).describe('false returns plan only; true applies exactly the next pending step.'),
|
|
1400
1432
|
applyAll: z.boolean().optional().default(false).describe('true applies all safe pending steps in order. Prefer apply=true for production changes.'),
|
|
1401
1433
|
stepId: z.string().optional().describe('Optional pending step id to apply. Omit to apply the next pending step.'),
|
|
1434
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when apply/applyAll mutates metadata. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1402
1435
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required when apply/applyAll reaches the save_handler step. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1403
1436
|
},
|
|
1404
1437
|
async (input) => jsonText(await runApiEndpointWorkflow(ENFYRA_API_URL, input)),
|
|
@@ -1408,7 +1441,8 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1408
1441
|
'create_api_endpoint',
|
|
1409
1442
|
[
|
|
1410
1443
|
'Business operation: create or update a custom REST endpoint with a handler in one safe operation.',
|
|
1411
|
-
'
|
|
1444
|
+
'Prefer api_endpoint_workflow when route access, role/user permissions, overwrite decisions, or multi-step planning matter.',
|
|
1445
|
+
'Use this one-shot helper only when the endpoint contract is already clear and no authenticated route-permission step is needed in the same operation, such as a simple public webhook or private admin-only utility that will be granted separately.',
|
|
1412
1446
|
'It creates the route without mainTableId, ensures the method is available, validates sourceCode, creates or overwrites the route handler, optionally makes the method public, reloads routes, and can smoke-test the endpoint.',
|
|
1413
1447
|
'Use table/schema tools separately when the user needs persisted data. This tool is for custom behavior endpoints.',
|
|
1414
1448
|
].join(' '),
|
|
@@ -1423,9 +1457,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1423
1457
|
overwrite: z.boolean().optional().default(false).describe('If a handler already exists for route+method, false fails; true updates its sourceCode.'),
|
|
1424
1458
|
smokeTestQuery: z.string().optional().describe('Optional query JSON object for a smoke test after save, e.g. {"a":"1","b":"2"}.'),
|
|
1425
1459
|
smokeTestBody: z.string().optional().describe('Optional body JSON object for a smoke test after save.'),
|
|
1460
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1426
1461
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1427
1462
|
},
|
|
1428
|
-
async ({ path, method, sourceCode, scriptLanguage, public: makePublic, description, timeout, overwrite, smokeTestQuery, smokeTestBody, knowledgeAckKey }) => {
|
|
1463
|
+
async ({ path, method, sourceCode, scriptLanguage, public: makePublic, description, timeout, overwrite, smokeTestQuery, smokeTestBody, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1464
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1429
1465
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
1430
1466
|
const normalizedPath = normalizeRestPath(path);
|
|
1431
1467
|
const methodName = normalizeMethodName(method);
|
|
@@ -1556,8 +1592,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1556
1592
|
message: z.string().optional().describe('Custom validation error message.'),
|
|
1557
1593
|
description: z.string().optional().describe('Admin note.'),
|
|
1558
1594
|
isEnabled: z.boolean().optional().default(true).describe('Enable the rule.'),
|
|
1595
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1559
1596
|
},
|
|
1560
|
-
async ({ tableName, columnName, ruleType, value, message, description, isEnabled }) => {
|
|
1597
|
+
async ({ tableName, columnName, ruleType, value, message, description, isEnabled, globalRulesAckKey }) => {
|
|
1598
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1561
1599
|
const table = resolveTable(await getMetadataTables(ENFYRA_API_URL), tableName);
|
|
1562
1600
|
const column = resolveColumn(table, columnName);
|
|
1563
1601
|
const existing = await findRecord(ENFYRA_API_URL, 'enfyra_column_rule', {
|
|
@@ -1597,8 +1635,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1597
1635
|
condition: z.string().optional().describe('Condition JSON object using field permission DSL.'),
|
|
1598
1636
|
description: z.string().optional().describe('Admin note.'),
|
|
1599
1637
|
isEnabled: z.boolean().optional().default(true).describe('Enable the permission.'),
|
|
1638
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1600
1639
|
},
|
|
1601
|
-
async ({ tableName, columnName, relationName, action, effect, roleId, roleName, allowedUserIds, condition, description, isEnabled }) => {
|
|
1640
|
+
async ({ tableName, columnName, relationName, action, effect, roleId, roleName, allowedUserIds, condition, description, isEnabled, globalRulesAckKey }) => {
|
|
1641
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1602
1642
|
if (!!columnName === !!relationName) throw new Error('Provide exactly one of columnName or relationName.');
|
|
1603
1643
|
assertOneScope({ roleId, roleName, allowedUserIds });
|
|
1604
1644
|
const [tables, role] = await Promise.all([
|
|
@@ -1656,8 +1696,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1656
1696
|
description: z.string().optional().describe('Admin note.'),
|
|
1657
1697
|
rules: z.string().optional().describe('Rules JSON array: [{type, config, priority, isEnabled, description, userIds}].'),
|
|
1658
1698
|
rulesMode: z.enum(['append', 'replace', 'none']).optional().default('append').describe('append creates rules, replace disables existing rules first, none leaves rules unchanged.'),
|
|
1699
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1659
1700
|
},
|
|
1660
|
-
async ({ name, guardId, position, routeId, path, methods, combinator, priority, isGlobal, isEnabled, description, rules, rulesMode }) => {
|
|
1701
|
+
async ({ name, guardId, position, routeId, path, methods, combinator, priority, isGlobal, isEnabled, description, rules, rulesMode, globalRulesAckKey }) => {
|
|
1702
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1661
1703
|
if (path && routeId) throw new Error('Provide path or routeId, not both.');
|
|
1662
1704
|
const ruleInputs = parseJsonArrayArg('rules', rules, []);
|
|
1663
1705
|
if (position === 'pre_auth') {
|
|
@@ -1733,9 +1775,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1733
1775
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language for connection handler.'),
|
|
1734
1776
|
isEnabled: z.boolean().optional().default(true).describe('Enable gateway.'),
|
|
1735
1777
|
description: z.string().optional().describe('Admin note.'),
|
|
1778
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1736
1779
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required when sourceCode is provided. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1737
1780
|
},
|
|
1738
|
-
async ({ path, sourceCode, scriptLanguage, isEnabled, description, knowledgeAckKey }) => {
|
|
1781
|
+
async ({ path, sourceCode, scriptLanguage, isEnabled, description, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1782
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1739
1783
|
assertDynamicCodeKnowledgeAckIf(sourceCode !== undefined, knowledgeAckKey);
|
|
1740
1784
|
const normalizedPath = normalizeRestPath(path);
|
|
1741
1785
|
const validation = sourceCode === undefined
|
|
@@ -1765,9 +1809,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1765
1809
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
|
|
1766
1810
|
isEnabled: z.boolean().optional().default(true).describe('Enable event.'),
|
|
1767
1811
|
description: z.string().optional().describe('Admin note.'),
|
|
1812
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1768
1813
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1769
1814
|
},
|
|
1770
|
-
async ({ gatewayPath, gatewayId, eventName, sourceCode, scriptLanguage, isEnabled, description, knowledgeAckKey }) => {
|
|
1815
|
+
async ({ gatewayPath, gatewayId, eventName, sourceCode, scriptLanguage, isEnabled, description, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1816
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1771
1817
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
1772
1818
|
if (!gatewayPath && !gatewayId) throw new Error('Provide gatewayPath or gatewayId.');
|
|
1773
1819
|
if (gatewayPath && gatewayId) throw new Error('Provide gatewayPath or gatewayId, not both.');
|
|
@@ -1802,14 +1848,16 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1802
1848
|
maxExecutions: z.number().int().positive().optional().default(100).describe('Execution history cap.'),
|
|
1803
1849
|
isEnabled: z.boolean().optional().default(true).describe('Enable flow.'),
|
|
1804
1850
|
description: z.string().optional().describe('Admin note.'),
|
|
1851
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1805
1852
|
},
|
|
1806
|
-
async ({ name, timeout, maxExecutions, isEnabled, description }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
|
|
1853
|
+
async ({ name, timeout, maxExecutions, isEnabled, description, globalRulesAckKey }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
|
|
1807
1854
|
name,
|
|
1808
1855
|
triggerType: 'manual',
|
|
1809
1856
|
timeout,
|
|
1810
1857
|
maxExecutions,
|
|
1811
1858
|
isEnabled,
|
|
1812
1859
|
description,
|
|
1860
|
+
globalRulesAckKey,
|
|
1813
1861
|
})),
|
|
1814
1862
|
);
|
|
1815
1863
|
|
|
@@ -1823,8 +1871,9 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1823
1871
|
maxExecutions: z.number().int().positive().optional().default(100).describe('Execution history cap.'),
|
|
1824
1872
|
isEnabled: z.boolean().optional().default(true).describe('Enable flow.'),
|
|
1825
1873
|
description: z.string().optional().describe('Admin note.'),
|
|
1874
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1826
1875
|
},
|
|
1827
|
-
async ({ name, triggerConfig, timeout, maxExecutions, isEnabled, description }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
|
|
1876
|
+
async ({ name, triggerConfig, timeout, maxExecutions, isEnabled, description, globalRulesAckKey }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
|
|
1828
1877
|
name,
|
|
1829
1878
|
triggerType: 'schedule',
|
|
1830
1879
|
triggerConfig,
|
|
@@ -1832,6 +1881,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1832
1881
|
maxExecutions,
|
|
1833
1882
|
isEnabled,
|
|
1834
1883
|
description,
|
|
1884
|
+
globalRulesAckKey,
|
|
1835
1885
|
})),
|
|
1836
1886
|
);
|
|
1837
1887
|
|
|
@@ -1870,6 +1920,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1870
1920
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
|
|
1871
1921
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1872
1922
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
1923
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1873
1924
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1874
1925
|
},
|
|
1875
1926
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
@@ -1891,6 +1942,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1891
1942
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
|
|
1892
1943
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1893
1944
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
1945
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1894
1946
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1895
1947
|
},
|
|
1896
1948
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
@@ -1910,6 +1962,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1910
1962
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1911
1963
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1912
1964
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
1965
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1913
1966
|
},
|
|
1914
1967
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1915
1968
|
...input,
|
|
@@ -1928,6 +1981,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1928
1981
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1929
1982
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1930
1983
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
1984
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1931
1985
|
},
|
|
1932
1986
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1933
1987
|
...input,
|
|
@@ -1946,6 +2000,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1946
2000
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1947
2001
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1948
2002
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2003
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1949
2004
|
},
|
|
1950
2005
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1951
2006
|
...input,
|
|
@@ -1964,6 +2019,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1964
2019
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1965
2020
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1966
2021
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2022
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1967
2023
|
},
|
|
1968
2024
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1969
2025
|
...input,
|
|
@@ -1982,6 +2038,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1982
2038
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1983
2039
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1984
2040
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2041
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1985
2042
|
},
|
|
1986
2043
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1987
2044
|
...input,
|
|
@@ -2000,6 +2057,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2000
2057
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
2001
2058
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
2002
2059
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2060
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2003
2061
|
},
|
|
2004
2062
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
2005
2063
|
...input,
|
|
@@ -2018,6 +2076,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2018
2076
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
2019
2077
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
2020
2078
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2079
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2021
2080
|
},
|
|
2022
2081
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
2023
2082
|
...input,
|
|
@@ -2036,6 +2095,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2036
2095
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
2037
2096
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
2038
2097
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2098
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2039
2099
|
},
|
|
2040
2100
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
2041
2101
|
...input,
|
|
@@ -2055,6 +2115,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2055
2115
|
permission: z.string().optional().describe('Menu permission JSON object.'),
|
|
2056
2116
|
description: z.string().optional().describe('Admin note.'),
|
|
2057
2117
|
isEnabled: z.boolean().optional().default(true).describe('Enable menu.'),
|
|
2118
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2058
2119
|
},
|
|
2059
2120
|
async (input) => jsonText({
|
|
2060
2121
|
action: 'menu_ensured',
|
|
@@ -2072,6 +2133,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2072
2133
|
description: z.string().optional().describe('Extension description.'),
|
|
2073
2134
|
isEnabled: z.boolean().optional().default(true).describe('Enable extension.'),
|
|
2074
2135
|
version: z.string().optional().default('1.0.0').describe('Extension version.'),
|
|
2136
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2075
2137
|
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
|
|
2076
2138
|
},
|
|
2077
2139
|
async (input) => jsonText({
|
|
@@ -2089,6 +2151,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2089
2151
|
description: z.string().optional().describe('Extension description.'),
|
|
2090
2152
|
isEnabled: z.boolean().optional().default(true).describe('Enable extension.'),
|
|
2091
2153
|
version: z.string().optional().default('1.0.0').describe('Extension version.'),
|
|
2154
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2092
2155
|
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
|
|
2093
2156
|
},
|
|
2094
2157
|
async (input) => jsonText({
|
|
@@ -2106,6 +2169,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2106
2169
|
description: z.string().optional().describe('Extension description.'),
|
|
2107
2170
|
isEnabled: z.boolean().optional().default(true).describe('Enable extension.'),
|
|
2108
2171
|
version: z.string().optional().default('1.0.0').describe('Extension version.'),
|
|
2172
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2109
2173
|
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
|
|
2110
2174
|
},
|
|
2111
2175
|
async (input) => jsonText({
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
export const GLOBAL_RULES_ACK_KEY = 'EFYRA::GLOBAL-RULES::READ-LIVE-CONTEXT-FIRST::2bW-20260701';
|
|
1
2
|
export const DYNAMIC_CODE_KNOWLEDGE_ACK_KEY = 'EFYRA::SECURE-REPO-CONTRACT::R9x-kelp-42Q::NO-RAW-TRUSTED';
|
|
2
3
|
export const EXTENSION_KNOWLEDGE_ACK_KEY = 'EFYRA::EXTENSION-THEME-CONTRACT::VIOLET-IS-NOT-A-PLAN::7mQ';
|
|
3
4
|
|
|
4
|
-
const REQUIRED_KNOWLEDGE_VERSION = '2026-
|
|
5
|
+
const REQUIRED_KNOWLEDGE_VERSION = '2026-07-01.global-rules-v1';
|
|
6
|
+
|
|
7
|
+
export function globalRulesAckParam(z) {
|
|
8
|
+
return z.string().describe('Required global-rules acknowledgement key from get_enfyra_required_knowledge. Call that tool, read the global Enfyra MCP rules, then pass globalRulesAckKey exactly.');
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
export function dynamicCodeKnowledgeAckParam(z) {
|
|
7
12
|
return z.string().describe('Required dynamic-code acknowledgement key from get_enfyra_required_knowledge. Call that tool, read the dynamic server code knowledge, then pass dynamicCodeAckKey exactly.');
|
|
@@ -11,6 +16,16 @@ export function extensionKnowledgeAckParam(z) {
|
|
|
11
16
|
return z.string().describe('Required extension acknowledgement key from get_enfyra_required_knowledge. Call that tool, read the extension/theme knowledge, then pass extensionAckKey exactly.');
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
export function assertGlobalRulesAck(key) {
|
|
20
|
+
if (key !== GLOBAL_RULES_ACK_KEY) {
|
|
21
|
+
throw new Error('Missing or invalid global-rules acknowledgement. Call get_enfyra_required_knowledge, read the global Enfyra MCP rules, then pass globalRulesAckKey as globalRulesAckKey.');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function assertGlobalRulesAckIf(condition, key) {
|
|
26
|
+
if (condition) assertGlobalRulesAck(key);
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
export function assertDynamicCodeKnowledgeAck(key) {
|
|
15
30
|
if (key !== DYNAMIC_CODE_KNOWLEDGE_ACK_KEY) {
|
|
16
31
|
throw new Error('Missing or invalid dynamic-code knowledge acknowledgement. Call get_enfyra_required_knowledge, read the dynamic server code contracts, then pass dynamicCodeAckKey as knowledgeAckKey.');
|
|
@@ -34,13 +49,59 @@ export function assertExtensionKnowledgeAckIf(condition, key) {
|
|
|
34
49
|
export function buildRequiredKnowledgePayload() {
|
|
35
50
|
return {
|
|
36
51
|
version: REQUIRED_KNOWLEDGE_VERSION,
|
|
37
|
-
purpose: 'Read this before
|
|
52
|
+
purpose: 'Read this before mutating Enfyra metadata, schema, routes, permissions, menus, packages, cache state, dynamic server code, or extension UI through MCP.',
|
|
53
|
+
globalRulesAckKey: GLOBAL_RULES_ACK_KEY,
|
|
38
54
|
dynamicCodeAckKey: DYNAMIC_CODE_KNOWLEDGE_ACK_KEY,
|
|
39
55
|
extensionAckKey: EXTENSION_KNOWLEDGE_ACK_KEY,
|
|
40
56
|
usage: [
|
|
57
|
+
'Pass globalRulesAckKey exactly as globalRulesAckKey when calling MCP tools that mutate Enfyra metadata, schema, routes, permissions, menus, packages, cache state, dynamic code, or extension UI.',
|
|
41
58
|
'Pass dynamicCodeAckKey exactly as knowledgeAckKey when calling MCP tools that create or update dynamic server code.',
|
|
42
59
|
'Pass extensionAckKey exactly as extensionKnowledgeAckKey when calling MCP tools that create or update Enfyra extension code.',
|
|
43
60
|
],
|
|
61
|
+
globalRules: [
|
|
62
|
+
{
|
|
63
|
+
id: 'examples-are-reasoning-anchors',
|
|
64
|
+
rules: [
|
|
65
|
+
'Examples explain transferable decisions, not copy-paste mandates.',
|
|
66
|
+
'Preserve platform contracts and safety boundaries, then adapt names, routes, fields, menus, labels, and lifecycle to live metadata and the user goal.',
|
|
67
|
+
'When examples use chat, order, report, cloud, email, or support domains, treat those as analogies unless the current task is exactly that domain.',
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'discover-before-changing',
|
|
72
|
+
rules: [
|
|
73
|
+
'Inspect live metadata/routes/features before schema, route, permission, extension, flow, or handler changes.',
|
|
74
|
+
'Use narrow inspection tools for the table, route, feature, or script being changed instead of broad discovery after the target is known.',
|
|
75
|
+
'Read sourceCode, not compiledCode, for editable dynamic scripts.',
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'mutations-are-intentional',
|
|
80
|
+
rules: [
|
|
81
|
+
'Prefer business operation tools over generic CRUD when a specific tool exists.',
|
|
82
|
+
'Destructive operations are preview-first; pass confirm=true only after explicit user approval.',
|
|
83
|
+
'Do not manually reload caches unless natural partial reload is proven stale or a concrete reload error requires it.',
|
|
84
|
+
'Never fabricate ids, field names, relation names, paths, package names, or permission scopes.',
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'security-first',
|
|
89
|
+
rules: [
|
|
90
|
+
'Treat permission and owner/tenant scope as the first design step for any route, handler, hook, flow, extension, websocket, or data surface.',
|
|
91
|
+
'Route permission only lets authenticated users reach a route after RoleGuard; handlers, hooks, RLS, and scripts still enforce record ownership and tenant/project scope.',
|
|
92
|
+
'Do not expose unpublished fields, private relation facts, secret values, token hashes, stack traces, SQL, provider payloads, or generated passwords to user-facing clients.',
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'shell-signals',
|
|
97
|
+
rules: [
|
|
98
|
+
'For app shell menu/account-panel notifications, decide the signal source before choosing count or dot.',
|
|
99
|
+
'Use a count only when the shell receives an exact or bounded count from a notification/summary source.',
|
|
100
|
+
'Use a dot when realtime only proves new attention exists.',
|
|
101
|
+
'Do not fetch destination domain lists such as messages, tickets, orders, or jobs solely to decorate the menu; the destination page owns domain fetching.',
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
44
105
|
dynamicServerCode: [
|
|
45
106
|
{
|
|
46
107
|
id: 'secure-vs-trusted-repositories',
|