@objectstack/lint 13.0.0 → 14.5.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/dist/index.cjs +234 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -26
- package/dist/index.d.ts +59 -26
- package/dist/index.js +226 -11
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1298,6 +1298,88 @@ function validateCapabilityReferences(stack) {
|
|
|
1298
1298
|
return findings;
|
|
1299
1299
|
}
|
|
1300
1300
|
|
|
1301
|
+
// src/validate-approval-approvers.ts
|
|
1302
|
+
import { ApproverType, APPROVAL_NODE_TYPE } from "@objectstack/spec/automation";
|
|
1303
|
+
var APPROVAL_ROLE_NOT_MEMBERSHIP_TIER = "approval-role-not-membership-tier";
|
|
1304
|
+
var APPROVAL_APPROVER_TYPE_UNKNOWN = "approval-approver-type-unknown";
|
|
1305
|
+
var APPROVAL_ESCALATION_REASSIGN_NO_TARGET = "approval-escalation-reassign-no-target";
|
|
1306
|
+
var MEMBERSHIP_TIERS = /* @__PURE__ */ new Set(["owner", "admin", "member", "guest"]);
|
|
1307
|
+
var TYPE_FIX = {
|
|
1308
|
+
business_unit: "department",
|
|
1309
|
+
bu: "department"
|
|
1310
|
+
};
|
|
1311
|
+
function asArray13(v) {
|
|
1312
|
+
if (Array.isArray(v)) return v;
|
|
1313
|
+
if (v && typeof v === "object") {
|
|
1314
|
+
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
1315
|
+
}
|
|
1316
|
+
return [];
|
|
1317
|
+
}
|
|
1318
|
+
function validateApprovalApprovers(stack) {
|
|
1319
|
+
const findings = [];
|
|
1320
|
+
if (!stack || typeof stack !== "object") return findings;
|
|
1321
|
+
const flows = asArray13(stack.flows);
|
|
1322
|
+
const validTypes = new Set(ApproverType.options);
|
|
1323
|
+
for (let fi = 0; fi < flows.length; fi++) {
|
|
1324
|
+
const flow = flows[fi];
|
|
1325
|
+
if (!flow || typeof flow !== "object") continue;
|
|
1326
|
+
const flowName = typeof flow.name === "string" ? flow.name : `(flow ${fi})`;
|
|
1327
|
+
const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
|
|
1328
|
+
for (let ni = 0; ni < nodes.length; ni++) {
|
|
1329
|
+
const node = nodes[ni];
|
|
1330
|
+
if (!node || node.type !== APPROVAL_NODE_TYPE) continue;
|
|
1331
|
+
const nodeId = typeof node.id === "string" ? node.id : `(node ${ni})`;
|
|
1332
|
+
const cfg = node.config ?? {};
|
|
1333
|
+
const approvers = Array.isArray(cfg.approvers) ? cfg.approvers : [];
|
|
1334
|
+
const where = `flow "${flowName}" \xB7 node "${nodeId}"`;
|
|
1335
|
+
for (let ai = 0; ai < approvers.length; ai++) {
|
|
1336
|
+
const a = approvers[ai];
|
|
1337
|
+
if (!a || typeof a !== "object") continue;
|
|
1338
|
+
const type = typeof a.type === "string" ? a.type : "";
|
|
1339
|
+
const value = typeof a.value === "string" ? a.value : "";
|
|
1340
|
+
const path = `flows[${fi}].nodes[${ni}].config.approvers[${ai}]`;
|
|
1341
|
+
if (type && !validTypes.has(type)) {
|
|
1342
|
+
const fix = TYPE_FIX[type];
|
|
1343
|
+
findings.push({
|
|
1344
|
+
severity: "warning",
|
|
1345
|
+
rule: APPROVAL_APPROVER_TYPE_UNKNOWN,
|
|
1346
|
+
where,
|
|
1347
|
+
path: `${path}.type`,
|
|
1348
|
+
message: `approver type '${type}' is not an ApproverType (${ApproverType.options.join(" | ")}).`,
|
|
1349
|
+
hint: fix ? `Use the spec value: { type: '${fix}', value: '${value}' }.` : `Pick one of the spec values; unmapped types degrade to an inert '${type}:${value}' literal at runtime.`
|
|
1350
|
+
});
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
if (type === "role" && value && !MEMBERSHIP_TIERS.has(value.toLowerCase())) {
|
|
1354
|
+
findings.push({
|
|
1355
|
+
severity: "warning",
|
|
1356
|
+
rule: APPROVAL_ROLE_NOT_MEMBERSHIP_TIER,
|
|
1357
|
+
where,
|
|
1358
|
+
path: `${path}.value`,
|
|
1359
|
+
message: `approver { type: 'role', value: '${value}' } resolves against the better-auth org-membership tier (sys_member.role: owner/admin/member) \u2014 '${value}' is not a membership tier, so this approver matches nobody and the request stalls.`,
|
|
1360
|
+
hint: `If '${value}' is an org position, author { type: 'position', value: '${value}' } (resolved via sys_user_position, ADR-0090 D3). Keep type 'role' only for membership tiers (owner/admin/member).`
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
const escalation = cfg.escalation ?? null;
|
|
1365
|
+
if (escalation && typeof escalation === "object" && escalation.action === "reassign") {
|
|
1366
|
+
const target = typeof escalation.escalateTo === "string" ? escalation.escalateTo.trim() : "";
|
|
1367
|
+
if (!target) {
|
|
1368
|
+
findings.push({
|
|
1369
|
+
severity: "warning",
|
|
1370
|
+
rule: APPROVAL_ESCALATION_REASSIGN_NO_TARGET,
|
|
1371
|
+
where,
|
|
1372
|
+
path: `flows[${fi}].nodes[${ni}].config.escalation.escalateTo`,
|
|
1373
|
+
message: `escalation.action is 'reassign' but escalateTo is empty \u2014 at runtime the escalation degrades to a notify and the request stays with the original approvers.`,
|
|
1374
|
+
hint: `Set escalateTo to a position machine name (expanded via sys_user_position, ADR-0090 D3) or a specific user id, or change action to 'notify'.`
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return findings;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1301
1383
|
// src/validate-security-posture.ts
|
|
1302
1384
|
import { describeAnchorForbiddenBits } from "@objectstack/spec/security";
|
|
1303
1385
|
var SECURITY_OWD_UNSET = "security-owd-unset";
|
|
@@ -1306,7 +1388,12 @@ var SECURITY_EXTERNAL_WIDER = "security-external-wider-than-internal";
|
|
|
1306
1388
|
var SECURITY_WILDCARD_VAMA = "security-wildcard-vama";
|
|
1307
1389
|
var SECURITY_ANCHOR_HIGH_PRIVILEGE = "security-anchor-high-privilege";
|
|
1308
1390
|
var SECURITY_ROLE_WORD = "security-role-word";
|
|
1391
|
+
var SECURITY_BOOK_AUDIENCE_UNKNOWN_SET = "security-book-audience-unknown-set";
|
|
1309
1392
|
var SECURITY_PRIVATE_NO_READSCOPE = "security-private-no-readscope";
|
|
1393
|
+
var SECURITY_MASTER_DETAIL_UNGRANTED = "security-master-detail-ungranted";
|
|
1394
|
+
var SECURITY_FLS_UNQUALIFIED_KEY = "security-fls-unqualified-key";
|
|
1395
|
+
var SECURITY_GRANT_EXPIRED_AT_AUTHORING = "security-grant-expired-at-authoring";
|
|
1396
|
+
var SECURITY_DELEGATION_MISSING_REASON = "security-delegation-missing-reason";
|
|
1310
1397
|
var CANONICAL_OWD = ["private", "public_read", "public_read_write", "controlled_by_parent"];
|
|
1311
1398
|
var OWD_ALIAS_FIX = {
|
|
1312
1399
|
read: "public_read",
|
|
@@ -1319,7 +1406,7 @@ var OWD_WIDTH = {
|
|
|
1319
1406
|
public_read: 1,
|
|
1320
1407
|
public_read_write: 2
|
|
1321
1408
|
};
|
|
1322
|
-
function
|
|
1409
|
+
function asArray14(v) {
|
|
1323
1410
|
if (Array.isArray(v)) return v;
|
|
1324
1411
|
if (v && typeof v === "object") {
|
|
1325
1412
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1340,11 +1427,26 @@ function labelHasRoleWord(label) {
|
|
|
1340
1427
|
if (typeof label !== "string") return false;
|
|
1341
1428
|
return /\brole(s)?\b/i.test(label);
|
|
1342
1429
|
}
|
|
1343
|
-
function
|
|
1430
|
+
function refOf(def) {
|
|
1431
|
+
const r = def.reference ?? def.reference_to;
|
|
1432
|
+
return typeof r === "string" && r ? r : void 0;
|
|
1433
|
+
}
|
|
1434
|
+
function firstMasterDetailField(obj) {
|
|
1435
|
+
for (const f of asArray14(obj.fields)) {
|
|
1436
|
+
if (f.type === "master_detail") {
|
|
1437
|
+
return { name: String(f.name ?? "?"), parent: refOf(f) };
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return void 0;
|
|
1441
|
+
}
|
|
1442
|
+
function grantsObjectAccess(p) {
|
|
1443
|
+
return p.allowRead === true || p.allowCreate === true || p.allowEdit === true || p.allowDelete === true || p.viewAllRecords === true || p.modifyAllRecords === true;
|
|
1444
|
+
}
|
|
1445
|
+
function validateSecurityPosture(stack, opts) {
|
|
1344
1446
|
const findings = [];
|
|
1345
1447
|
if (!stack || typeof stack !== "object") return findings;
|
|
1346
|
-
const objects =
|
|
1347
|
-
const permissionSets =
|
|
1448
|
+
const objects = asArray14(stack.objects);
|
|
1449
|
+
const permissionSets = asArray14(stack.permissions);
|
|
1348
1450
|
for (let i = 0; i < objects.length; i++) {
|
|
1349
1451
|
const obj = objects[i];
|
|
1350
1452
|
if (!obj || typeof obj !== "object") continue;
|
|
@@ -1410,6 +1512,18 @@ function validateSecurityPosture(stack) {
|
|
|
1410
1512
|
const psName = typeof ps.name === "string" ? ps.name : `(permission set ${i})`;
|
|
1411
1513
|
const psPath = `permissions[${i}]`;
|
|
1412
1514
|
const objectsMap = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
|
|
1515
|
+
const flsMap = ps.fields && typeof ps.fields === "object" ? ps.fields : {};
|
|
1516
|
+
for (const flsKey of Object.keys(flsMap)) {
|
|
1517
|
+
if (flsKey.includes(".")) continue;
|
|
1518
|
+
findings.push({
|
|
1519
|
+
severity: "error",
|
|
1520
|
+
rule: SECURITY_FLS_UNQUALIFIED_KEY,
|
|
1521
|
+
where: `permission set "${psName}"`,
|
|
1522
|
+
path: `${psPath}.fields["${flsKey}"]`,
|
|
1523
|
+
message: `field-permission key '${flsKey}' is not object-qualified \u2014 the runtime matches FLS keys by '<object>.<field>' prefix, so a bare key is silently IGNORED and the declared masking never enforces.`,
|
|
1524
|
+
hint: `Qualify the key with its object, e.g. 'crm_opportunity.${flsKey}': { readable: true, editable: false }.`
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1413
1527
|
const wildcard = objectsMap["*"];
|
|
1414
1528
|
if (wildcard && (wildcard.viewAllRecords === true || wildcard.modifyAllRecords === true)) {
|
|
1415
1529
|
findings.push({
|
|
@@ -1461,10 +1575,10 @@ function validateSecurityPosture(stack) {
|
|
|
1461
1575
|
if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
|
|
1462
1576
|
const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
|
|
1463
1577
|
flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
|
|
1464
|
-
for (const f of
|
|
1578
|
+
for (const f of asArray14(obj.fields)) {
|
|
1465
1579
|
flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
|
|
1466
1580
|
}
|
|
1467
|
-
for (const [ai, action] of
|
|
1581
|
+
for (const [ai, action] of asArray14(obj.actions).entries()) {
|
|
1468
1582
|
flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
|
|
1469
1583
|
}
|
|
1470
1584
|
}
|
|
@@ -1473,12 +1587,34 @@ function validateSecurityPosture(stack) {
|
|
|
1473
1587
|
if (!ps || typeof ps !== "object") continue;
|
|
1474
1588
|
flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
|
|
1475
1589
|
}
|
|
1476
|
-
for (const [i, pos] of
|
|
1590
|
+
for (const [i, pos] of asArray14(stack.positions).entries()) {
|
|
1477
1591
|
flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
|
|
1478
1592
|
}
|
|
1479
|
-
for (const [i, app] of
|
|
1593
|
+
for (const [i, app] of asArray14(stack.apps).entries()) {
|
|
1480
1594
|
flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
|
|
1481
1595
|
}
|
|
1596
|
+
for (const [i, book] of asArray14(stack.books).entries()) {
|
|
1597
|
+
flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
|
|
1598
|
+
}
|
|
1599
|
+
const stackSetNames = new Set(
|
|
1600
|
+
permissionSets.map((ps) => typeof ps.name === "string" ? ps.name : void 0).filter((n) => !!n)
|
|
1601
|
+
);
|
|
1602
|
+
for (const [i, book] of asArray14(stack.books).entries()) {
|
|
1603
|
+
const audience = book.audience;
|
|
1604
|
+
if (!audience || typeof audience !== "object") continue;
|
|
1605
|
+
const setName = audience.permissionSet;
|
|
1606
|
+
if (typeof setName !== "string" || setName.length === 0) continue;
|
|
1607
|
+
if (!stackSetNames.has(setName)) {
|
|
1608
|
+
findings.push({
|
|
1609
|
+
severity: "warning",
|
|
1610
|
+
rule: SECURITY_BOOK_AUDIENCE_UNKNOWN_SET,
|
|
1611
|
+
where: `book "${String(book.name ?? i)}"`,
|
|
1612
|
+
path: `books[${i}].audience.permissionSet`,
|
|
1613
|
+
message: `book audience references permission set "${setName}", which this stack does not declare. The runtime fails closed \u2014 no holder means NO reader can open the book.`,
|
|
1614
|
+
hint: `Gate the book on one of this package's own permission sets (ADR-0090 D9, e.g. its admin set), or fix the typo. Ignore if the set is intentionally provided by another installed package.`
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1482
1618
|
const privateObjects = new Set(
|
|
1483
1619
|
objects.filter((o) => o && typeof o === "object" && !isSystemObject(o)).filter((o) => {
|
|
1484
1620
|
const owd = owdOf(o);
|
|
@@ -1507,11 +1643,82 @@ function validateSecurityPosture(stack) {
|
|
|
1507
1643
|
}
|
|
1508
1644
|
}
|
|
1509
1645
|
}
|
|
1646
|
+
if (permissionSets.length > 0) {
|
|
1647
|
+
const wildcardGrantsAll = permissionSets.some(
|
|
1648
|
+
(ps) => grantsObjectAccess(ps.objects?.["*"] ?? {})
|
|
1649
|
+
);
|
|
1650
|
+
if (!wildcardGrantsAll) {
|
|
1651
|
+
const grantedObjects = /* @__PURE__ */ new Set();
|
|
1652
|
+
for (const ps of permissionSets) {
|
|
1653
|
+
const objectsMap = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
|
|
1654
|
+
for (const [objName, rawPerm] of Object.entries(objectsMap)) {
|
|
1655
|
+
if (objName === "*") continue;
|
|
1656
|
+
if (grantsObjectAccess(rawPerm ?? {})) grantedObjects.add(objName);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
for (let i = 0; i < objects.length; i++) {
|
|
1660
|
+
const obj = objects[i];
|
|
1661
|
+
if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
|
|
1662
|
+
const objName = typeof obj.name === "string" ? obj.name : "";
|
|
1663
|
+
if (!objName || grantedObjects.has(objName)) continue;
|
|
1664
|
+
const md = firstMasterDetailField(obj);
|
|
1665
|
+
if (!md) continue;
|
|
1666
|
+
const parentText = md.parent ? ` \u2192 "${md.parent}"` : "";
|
|
1667
|
+
findings.push({
|
|
1668
|
+
severity: "warning",
|
|
1669
|
+
rule: SECURITY_MASTER_DETAIL_UNGRANTED,
|
|
1670
|
+
where: `object "${objName}"`,
|
|
1671
|
+
path: `objects[${i}].fields.${md.name}`,
|
|
1672
|
+
message: `detail object "${objName}" (master_detail "${md.name}"${parentText}) has no object-level CRUD grant in any permission set. A master-detail child derives its RECORD-level access from the master (ADR-0055 controlled_by_parent), but object-level CRUD is a SEPARATE gate that is never derived \u2014 role-bound non-admin users are denied (403) before the parent-derived access is ever consulted (the silent "can't submit the subtable" trap).`,
|
|
1673
|
+
hint: `Grant "${objName}" in at least one permission set that already grants its master${md.parent ? ` "${md.parent}"` : ""} \u2014 e.g. permissions[i].objects.${objName} = { allowRead: true, allowCreate: true, allowEdit: true }. If no role should ever touch it (a pure system/internal table), name it sys_* or set isSystem: true.`
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
const GRANT_SEED_OBJECTS = /* @__PURE__ */ new Set(["sys_user_position", "sys_user_permission_set"]);
|
|
1679
|
+
const nowMs = opts?.nowMs ?? Date.now();
|
|
1680
|
+
for (const [i, seed] of asArray14(stack.data).entries()) {
|
|
1681
|
+
const seedObject = typeof seed.object === "string" ? seed.object : "";
|
|
1682
|
+
if (!GRANT_SEED_OBJECTS.has(seedObject)) continue;
|
|
1683
|
+
const records = Array.isArray(seed.records) ? seed.records : [];
|
|
1684
|
+
for (let j = 0; j < records.length; j++) {
|
|
1685
|
+
const rec = records[j] ?? {};
|
|
1686
|
+
const where = `seed "${seedObject}" record #${j}`;
|
|
1687
|
+
const until = rec.valid_until;
|
|
1688
|
+
if (until != null && until !== "") {
|
|
1689
|
+
const ms = typeof until === "number" ? until < 1e12 ? until * 1e3 : until : until instanceof Date ? until.getTime() : typeof until === "string" ? Date.parse(until) : Number.NaN;
|
|
1690
|
+
if (Number.isNaN(ms) || ms <= nowMs) {
|
|
1691
|
+
findings.push({
|
|
1692
|
+
severity: "error",
|
|
1693
|
+
rule: SECURITY_GRANT_EXPIRED_AT_AUTHORING,
|
|
1694
|
+
where,
|
|
1695
|
+
path: `data[${i}].records[${j}].valid_until`,
|
|
1696
|
+
message: Number.isNaN(ms) ? `valid_until ${JSON.stringify(until)} is not a parseable timestamp \u2014 the resolver fails closed (ADR-0091 D2), so this grant will NEVER be active.` : `valid_until ${JSON.stringify(until)} is already in the past \u2014 this grant is expired at authoring time and will never resolve (ADR-0091 D2 filters it fail-closed).`,
|
|
1697
|
+
hint: `Set valid_until to a future instant (ISO-8601 UTC), or drop the column for an unbounded grant. If the row is a historical record, it belongs in audit history, not seed data.`
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
const delegatedFrom = rec.delegated_from;
|
|
1702
|
+
if (delegatedFrom != null && delegatedFrom !== "") {
|
|
1703
|
+
const reason = rec.reason;
|
|
1704
|
+
if (typeof reason !== "string" || reason.trim().length === 0) {
|
|
1705
|
+
findings.push({
|
|
1706
|
+
severity: "error",
|
|
1707
|
+
rule: SECURITY_DELEGATION_MISSING_REASON,
|
|
1708
|
+
where,
|
|
1709
|
+
path: `data[${i}].records[${j}].reason`,
|
|
1710
|
+
message: `delegation row (delegated_from = ${JSON.stringify(delegatedFrom)}) has no reason. ADR-0091 D3 requires a mandatory reason on every delegation for the dual audit trail (granted_by = writer, delegated_from = authority source, reason = why).`,
|
|
1711
|
+
hint: `Add reason: 'vacation stand-in for \u5F20\u4E09, 2026-08-01..15' (free text, required).`
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1510
1717
|
return findings;
|
|
1511
1718
|
}
|
|
1512
1719
|
|
|
1513
1720
|
// src/build-access-matrix.ts
|
|
1514
|
-
function
|
|
1721
|
+
function asArray15(v) {
|
|
1515
1722
|
if (Array.isArray(v)) return v;
|
|
1516
1723
|
if (v && typeof v === "object") {
|
|
1517
1724
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1522,13 +1729,13 @@ function buildAccessMatrix(stack) {
|
|
|
1522
1729
|
const entries = [];
|
|
1523
1730
|
if (!stack || typeof stack !== "object") return { version: 1, entries };
|
|
1524
1731
|
const owdByObject = /* @__PURE__ */ new Map();
|
|
1525
|
-
for (const obj of
|
|
1732
|
+
for (const obj of asArray15(stack.objects)) {
|
|
1526
1733
|
const name = typeof obj.name === "string" ? obj.name : "";
|
|
1527
1734
|
if (!name) continue;
|
|
1528
1735
|
const owd = obj.sharingModel ?? obj.security?.sharingModel;
|
|
1529
1736
|
if (typeof owd === "string") owdByObject.set(name, owd);
|
|
1530
1737
|
}
|
|
1531
|
-
for (const ps of
|
|
1738
|
+
for (const ps of asArray15(stack.permissions)) {
|
|
1532
1739
|
const psName = typeof ps.name === "string" ? ps.name : "";
|
|
1533
1740
|
if (!psName) continue;
|
|
1534
1741
|
const objects = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
|
|
@@ -1599,6 +1806,9 @@ function diffAccessMatrix(before, after) {
|
|
|
1599
1806
|
return lines;
|
|
1600
1807
|
}
|
|
1601
1808
|
export {
|
|
1809
|
+
APPROVAL_APPROVER_TYPE_UNKNOWN,
|
|
1810
|
+
APPROVAL_ESCALATION_REASSIGN_NO_TARGET,
|
|
1811
|
+
APPROVAL_ROLE_NOT_MEMBERSHIP_TIER,
|
|
1602
1812
|
CAPABILITY_REFERENCE_UNKNOWN,
|
|
1603
1813
|
CHART_CONFIG_MISSING,
|
|
1604
1814
|
CHART_FIELD_UNKNOWN,
|
|
@@ -1610,7 +1820,11 @@ export {
|
|
|
1610
1820
|
MEASURE_AGGREGATE_INCOHERENT,
|
|
1611
1821
|
PAGE_SOURCE_CLASSNAME,
|
|
1612
1822
|
SECURITY_ANCHOR_HIGH_PRIVILEGE,
|
|
1823
|
+
SECURITY_BOOK_AUDIENCE_UNKNOWN_SET,
|
|
1824
|
+
SECURITY_DELEGATION_MISSING_REASON,
|
|
1613
1825
|
SECURITY_EXTERNAL_WIDER,
|
|
1826
|
+
SECURITY_GRANT_EXPIRED_AT_AUTHORING,
|
|
1827
|
+
SECURITY_MASTER_DETAIL_UNGRANTED,
|
|
1614
1828
|
SECURITY_OWD_ALIAS,
|
|
1615
1829
|
SECURITY_OWD_UNSET,
|
|
1616
1830
|
SECURITY_PRIVATE_NO_READSCOPE,
|
|
@@ -1630,6 +1844,7 @@ export {
|
|
|
1630
1844
|
WIDGET_MEASURE_UNKNOWN,
|
|
1631
1845
|
buildAccessMatrix,
|
|
1632
1846
|
diffAccessMatrix,
|
|
1847
|
+
validateApprovalApprovers,
|
|
1633
1848
|
validateCapabilityReferences,
|
|
1634
1849
|
validateFormLayout,
|
|
1635
1850
|
validateJsxPages,
|