@objectstack/lint 17.0.0-rc.6 → 17.0.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/CHANGELOG.md +6996 -1
- package/dist/index.cjs +1164 -482
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -15
- package/dist/index.d.ts +138 -15
- package/dist/index.js +1141 -465
- package/dist/index.js.map +1 -1
- package/dist/{runtime-B50yywI_.d.cts → runtime-H-nDodRy.d.cts} +86 -6
- package/dist/{runtime-B50yywI_.d.ts → runtime-H-nDodRy.d.ts} +86 -6
- package/dist/runtime.cjs +1140 -482
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1127 -463
- package/dist/runtime.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -3,7 +3,11 @@ import { isIncoherentAggregate } from "@objectstack/spec/data";
|
|
|
3
3
|
import { ChartTypeSchema } from "@objectstack/spec/ui";
|
|
4
4
|
|
|
5
5
|
// src/system-fields.ts
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
FIELD_GROUP_SYSTEM_FIELDS,
|
|
8
|
+
resolveInjectedSystemColumns,
|
|
9
|
+
unprovisionedInjectedColumns
|
|
10
|
+
} from "@objectstack/spec/data";
|
|
7
11
|
import { SystemFieldName } from "@objectstack/spec/system";
|
|
8
12
|
var SYSTEM_FIELDS = /* @__PURE__ */ new Set([
|
|
9
13
|
...FIELD_GROUP_SYSTEM_FIELDS,
|
|
@@ -12,6 +16,34 @@ var SYSTEM_FIELDS = /* @__PURE__ */ new Set([
|
|
|
12
16
|
function injectedColumnsFor(objectDef) {
|
|
13
17
|
return resolveInjectedSystemColumns(objectDef).names;
|
|
14
18
|
}
|
|
19
|
+
function unprovisionedInjectedColumnsFor(objectDef) {
|
|
20
|
+
return new Set(unprovisionedInjectedColumns(objectDef));
|
|
21
|
+
}
|
|
22
|
+
function objectDefsOf(stack) {
|
|
23
|
+
if (!stack || typeof stack !== "object") return [];
|
|
24
|
+
const objects = stack.objects;
|
|
25
|
+
if (Array.isArray(objects)) return objects.filter((o) => !!o && typeof o === "object");
|
|
26
|
+
if (objects && typeof objects === "object") {
|
|
27
|
+
return Object.entries(objects).filter(([, def]) => !!def && typeof def === "object").map(([name, def]) => ({ name, ...def }));
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
function indexUnprovisionedAnchors(stack) {
|
|
32
|
+
const index = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const obj of objectDefsOf(stack)) {
|
|
34
|
+
const name = typeof obj.name === "string" && obj.name.length > 0 ? obj.name : void 0;
|
|
35
|
+
if (!name) continue;
|
|
36
|
+
const anchors = unprovisionedInjectedColumnsFor(obj);
|
|
37
|
+
if (anchors.size > 0) index.set(name, anchors);
|
|
38
|
+
}
|
|
39
|
+
return index;
|
|
40
|
+
}
|
|
41
|
+
function unprovisionedAnchorCause(objectName, field) {
|
|
42
|
+
return `'${field}' is an injected system column with NO storage behind it: '${objectName}' is an external object (ADR-0015), so the remote database owns its schema and the platform registers this anchor without provisioning a column`;
|
|
43
|
+
}
|
|
44
|
+
function unprovisionedAnchorHint(objectName, field) {
|
|
45
|
+
return `If the remote table really carries '${field}', declare it in ${objectName}'s own fields (mapped through the external binding's columnMap) so the reference resolves to a column you vouch for; otherwise drop the reference, or opt the object out of the injection (\`ownership: 'none'\` for the ownership anchors, \`systemFields: { audit: false }\` for the audit family).`;
|
|
46
|
+
}
|
|
15
47
|
|
|
16
48
|
// src/validate-widget-bindings.ts
|
|
17
49
|
var WIDGET_DATASET_UNKNOWN = "widget-dataset-unknown";
|
|
@@ -24,6 +56,7 @@ var MEASURE_AGGREGATE_INCOHERENT = "measure-aggregate-incoherent";
|
|
|
24
56
|
var WIDGET_LEGACY_ANALYTICS_SHAPE = "widget-legacy-analytics-shape";
|
|
25
57
|
var WIDGET_LEGACY_ANALYTICS_UNRENDERABLE = "widget-legacy-analytics-unrenderable";
|
|
26
58
|
var DASHBOARD_FILTER_FIELD_UNKNOWN = "dashboard-filter-field-unknown";
|
|
59
|
+
var DASHBOARD_FILTER_FIELD_UNPROVISIONED = "dashboard-filter-field-unprovisioned";
|
|
27
60
|
var LEGACY_ANALYTICS_KEYS = [
|
|
28
61
|
"categoryField",
|
|
29
62
|
"valueField",
|
|
@@ -143,6 +176,7 @@ function validateWidgetBindings(stack) {
|
|
|
143
176
|
}
|
|
144
177
|
objectFieldTypes.set(o.name, fm);
|
|
145
178
|
}
|
|
179
|
+
const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
|
|
146
180
|
const datasetList = asArray(stack.datasets);
|
|
147
181
|
for (let i = 0; i < datasetList.length; i++) {
|
|
148
182
|
const ds = datasetList[i];
|
|
@@ -229,13 +263,24 @@ function validateWidgetBindings(stack) {
|
|
|
229
263
|
if (dashFilterDefs.length > 0) {
|
|
230
264
|
const datasetObject = typeof dataset.object === "string" ? dataset.object : void 0;
|
|
231
265
|
const objectFields = datasetObject ? objectFieldTypes.get(datasetObject) : void 0;
|
|
232
|
-
|
|
266
|
+
const anchors = datasetObject ? unprovisionedAnchors.get(datasetObject) : void 0;
|
|
267
|
+
if (objectFields && datasetObject) {
|
|
233
268
|
for (const def of dashFilterDefs) {
|
|
234
269
|
const eff = effectiveFilterField(w, def);
|
|
235
270
|
if (!eff) continue;
|
|
236
271
|
const field = eff.field;
|
|
237
272
|
if (field.includes(".")) continue;
|
|
238
|
-
if (objectFields.has(field) || SYSTEM_FIELDS.has(field))
|
|
273
|
+
if (objectFields.has(field) || SYSTEM_FIELDS.has(field)) {
|
|
274
|
+
if (anchors?.has(field)) {
|
|
275
|
+
push2({
|
|
276
|
+
severity: "warning",
|
|
277
|
+
rule: DASHBOARD_FILTER_FIELD_UNPROVISIONED,
|
|
278
|
+
message: (eff.explicit ? `binds dashboard filter \`${def.name}\` to field \`${field}\` (via filterBindings), but ` : `inherits dashboard filter \`${def.name}(${field})\`, but `) + `${unprovisionedAnchorCause(datasetObject, field)}. The filter is ANDed into this widget's analytics query (#2501), so it can never match a real value \u2014 on SQLite it silently degrades to constant-false and the widget renders empty (HTTP 200, zero rows, no error).`,
|
|
279
|
+
hint: `${unprovisionedAnchorHint(datasetObject, field)} A widget can also opt out with filterBindings: { ${def.name}: false }. Suppress with suppressWarnings: ['${DASHBOARD_FILTER_FIELD_UNPROVISIONED}'] if the remote schema resolves it some other way.`
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
239
284
|
push2({
|
|
240
285
|
severity: "error",
|
|
241
286
|
rule: DASHBOARD_FILTER_FIELD_UNKNOWN,
|
|
@@ -333,7 +378,7 @@ function validateWidgetBindings(stack) {
|
|
|
333
378
|
}
|
|
334
379
|
|
|
335
380
|
// src/validate-expressions.ts
|
|
336
|
-
import { validateExpression, collectCelRootIdentifiers, SCOPE_ROOTS } from "@objectstack/formula";
|
|
381
|
+
import { validateExpression, collectCelRootIdentifiers, parseCelToAst as parseCelToAst2, SCOPE_ROOTS } from "@objectstack/formula";
|
|
337
382
|
import { collectFlowGraphs, resolveFlowNodeExpressions } from "@objectstack/spec/automation";
|
|
338
383
|
|
|
339
384
|
// src/validate-null-guards.ts
|
|
@@ -568,6 +613,37 @@ function buildFieldIndex(objects) {
|
|
|
568
613
|
}
|
|
569
614
|
return idx;
|
|
570
615
|
}
|
|
616
|
+
var BOUND_RECORD_ROOTS = ["record", "previous"];
|
|
617
|
+
function isCelNode(v) {
|
|
618
|
+
return !!v && typeof v === "object" && typeof v.op === "string";
|
|
619
|
+
}
|
|
620
|
+
function collectBoundRecordReads(source) {
|
|
621
|
+
const out = /* @__PURE__ */ new Map();
|
|
622
|
+
const ast = parseCelToAst2(source);
|
|
623
|
+
if (!ast) return out;
|
|
624
|
+
const pending = [ast];
|
|
625
|
+
while (pending.length > 0) {
|
|
626
|
+
const celNode = pending.pop();
|
|
627
|
+
if (!isCelNode(celNode)) continue;
|
|
628
|
+
if ((celNode.op === "." || celNode.op === ".?") && Array.isArray(celNode.args) && celNode.args.length >= 2) {
|
|
629
|
+
const [celRecv, seg] = celNode.args;
|
|
630
|
+
if (typeof seg === "string" && isCelNode(celRecv) && celRecv.op === "id" && typeof celRecv.args === "string" && BOUND_RECORD_ROOTS.includes(celRecv.args)) {
|
|
631
|
+
if (!out.has(seg)) out.set(seg, `${celRecv.args}.${seg}`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const celArgs = celNode.args;
|
|
635
|
+
if (isCelNode(celArgs)) pending.push(celArgs);
|
|
636
|
+
else if (Array.isArray(celArgs)) {
|
|
637
|
+
for (const a of celArgs) {
|
|
638
|
+
if (isCelNode(a)) pending.push(a);
|
|
639
|
+
else if (Array.isArray(a)) {
|
|
640
|
+
for (const b of a) if (isCelNode(b)) pending.push(b);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return out;
|
|
646
|
+
}
|
|
571
647
|
function buildFieldTypeIndex(objects) {
|
|
572
648
|
const idx = /* @__PURE__ */ new Map();
|
|
573
649
|
for (const obj of objects) {
|
|
@@ -667,6 +743,29 @@ function validateStackExpressions(stack) {
|
|
|
667
743
|
const fieldIndex = buildFieldIndex(objects);
|
|
668
744
|
const fieldTypeIndex = buildFieldTypeIndex(objects);
|
|
669
745
|
const nullableIndex = buildNullableFieldIndex(objects);
|
|
746
|
+
const unprovisionedIndex = /* @__PURE__ */ new Map();
|
|
747
|
+
for (const obj of objects) {
|
|
748
|
+
const name = typeof obj.name === "string" ? obj.name : void 0;
|
|
749
|
+
if (!name) continue;
|
|
750
|
+
const anchors = unprovisionedInjectedColumnsFor(obj);
|
|
751
|
+
if (anchors.size > 0) unprovisionedIndex.set(name, anchors);
|
|
752
|
+
}
|
|
753
|
+
const warnUnprovisionedAnchors = (where, raw, objectName) => {
|
|
754
|
+
if (!objectName) return;
|
|
755
|
+
const anchors = unprovisionedIndex.get(objectName);
|
|
756
|
+
if (!anchors) return;
|
|
757
|
+
const source = celSourceOf(raw);
|
|
758
|
+
if (!source) return;
|
|
759
|
+
for (const [field, operand] of collectBoundRecordReads(source)) {
|
|
760
|
+
if (!anchors.has(field)) continue;
|
|
761
|
+
issues.push({
|
|
762
|
+
where,
|
|
763
|
+
message: `\`${operand}\` reads '${field}', an injected system column with NO storage behind it: '${objectName}' is an external object (ADR-0015), so the remote database owns its schema and the platform registers this anchor without provisioning a column. The predicate can never match a real value \u2014 on SQLite it silently degrades to constant-false (HTTP 200, zero rows, no error). If the remote table really carries this column, declare '${field}' in the object's own fields (mapped through the external binding's columnMap) so the reference resolves to a column you vouch for; otherwise drop the reference, or opt the object out of the injection (\`ownership: 'none'\` for the ownership anchors, \`systemFields: { audit: false }\` for the audit family).`,
|
|
764
|
+
source,
|
|
765
|
+
severity: "warning"
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
};
|
|
670
769
|
const checkNullGuards = (where, subject, raw, objectName, outcome = "fail-closed") => {
|
|
671
770
|
if (!objectName) return;
|
|
672
771
|
const nullableFields = nullableIndex.get(objectName);
|
|
@@ -693,6 +792,7 @@ function validateStackExpressions(stack) {
|
|
|
693
792
|
);
|
|
694
793
|
for (const e of res.errors) issues.push({ where, message: e.message, source: e.source, severity: "error" });
|
|
695
794
|
for (const w of res.warnings) issues.push({ where, message: w.message, source: w.source, severity: "warning" });
|
|
795
|
+
warnUnprovisionedAnchors(where, raw, objectName);
|
|
696
796
|
};
|
|
697
797
|
const FIELD_RULE_BOUND_ROOTS = ["record", "previous", "parent"];
|
|
698
798
|
const FIELD_RULE_USER_ROOTS = ["current_user", "user", "ctx", "os"];
|
|
@@ -839,6 +939,7 @@ function validateStackExpressions(stack) {
|
|
|
839
939
|
const fieldWhere = `object '${objectName}' \xB7 field '${fname}' expression`;
|
|
840
940
|
for (const e of res.errors) issues.push({ where: fieldWhere, message: e.message, source: e.source, severity: "error" });
|
|
841
941
|
for (const w of res.warnings) issues.push({ where: fieldWhere, message: w.message, source: w.source, severity: "warning" });
|
|
942
|
+
warnUnprovisionedAnchors(fieldWhere, f.expression, objectName);
|
|
842
943
|
}
|
|
843
944
|
}
|
|
844
945
|
}
|
|
@@ -1389,6 +1490,46 @@ function validateFunctionalCompleteness(stack) {
|
|
|
1389
1490
|
return out;
|
|
1390
1491
|
}
|
|
1391
1492
|
|
|
1493
|
+
// src/validate-managed-api-methods.ts
|
|
1494
|
+
import {
|
|
1495
|
+
checkManagedApiMethodAffordances,
|
|
1496
|
+
describeManagedApiMethodConflicts
|
|
1497
|
+
} from "@objectstack/spec/data";
|
|
1498
|
+
var MANAGED_API_METHOD_UNAFFORDABLE = "object/managed-api-method-unaffordable";
|
|
1499
|
+
var isRec2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
1500
|
+
function entriesOf2(v) {
|
|
1501
|
+
if (Array.isArray(v)) {
|
|
1502
|
+
return v.flatMap(
|
|
1503
|
+
(def, i) => isRec2(def) ? [{ name: String(def.name ?? i), def, key: `[${i}]` }] : []
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
if (isRec2(v)) {
|
|
1507
|
+
return Object.entries(v).flatMap(
|
|
1508
|
+
([name, def]) => isRec2(def) ? [{ name, def: { name, ...def }, key: `.${name}` }] : []
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
return [];
|
|
1512
|
+
}
|
|
1513
|
+
function validateManagedApiMethods(stack) {
|
|
1514
|
+
const out = [];
|
|
1515
|
+
if (!isRec2(stack)) return out;
|
|
1516
|
+
for (const [oi, obj] of entriesOf2(stack.objects).entries()) {
|
|
1517
|
+
const conflicts = checkManagedApiMethodAffordances(obj.def);
|
|
1518
|
+
if (conflicts.length === 0) continue;
|
|
1519
|
+
const verbs = conflicts.map((c) => c.verb).join(", ");
|
|
1520
|
+
const flags = [...new Set(conflicts.map((c) => c.needs))];
|
|
1521
|
+
out.push({
|
|
1522
|
+
severity: "error",
|
|
1523
|
+
rule: MANAGED_API_METHOD_UNAFFORDABLE,
|
|
1524
|
+
where: `object "${obj.name}"`,
|
|
1525
|
+
path: `objects[${oi}].enable.apiMethods`,
|
|
1526
|
+
message: `\`managedBy: '${String(obj.def.managedBy)}'\` object "${obj.name}" ` + describeManagedApiMethodConflicts(conflicts) + ` The registry STRIPS [${verbs}] at registration, so this declaration and the API you actually get already disagree \u2014 today the only trace is a line in the boot log.`,
|
|
1527
|
+
hint: `Either add \`userActions: { ${flags.map((f) => `${f}: true`).join(", ")} }\` to the object \u2014 only if the write is genuinely one a user context may perform, and only once the guard enforcing it exists (ADR-0092 D4: affordance never ships ahead of the guard) \u2014 or remove [${verbs}] from \`enable.apiMethods\`, which is what the runtime does for you today.`
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
return out;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1392
1533
|
// src/validate-flow-trigger-readiness.ts
|
|
1393
1534
|
import { TimeRelativeTriggerSchema } from "@objectstack/spec/automation";
|
|
1394
1535
|
var FLOW_TRIGGER_UNKNOWN_OBJECT = "flow-trigger-unknown-object";
|
|
@@ -1573,7 +1714,7 @@ function validateFlowTriggerReadiness(stack) {
|
|
|
1573
1714
|
|
|
1574
1715
|
// src/flow-walk.ts
|
|
1575
1716
|
import { FLOW_REGION_SLOTS_BY_TYPE, FLOW_REGION_CONFIG_KEYS } from "@objectstack/spec/automation";
|
|
1576
|
-
function
|
|
1717
|
+
function isRec3(v) {
|
|
1577
1718
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
1578
1719
|
}
|
|
1579
1720
|
function strName(v) {
|
|
@@ -1588,7 +1729,7 @@ function flowNodeLabel(node, index) {
|
|
|
1588
1729
|
return strName(node.label) ?? strName(node.id) ?? `#${index}`;
|
|
1589
1730
|
}
|
|
1590
1731
|
function stripRegions(config) {
|
|
1591
|
-
if (!
|
|
1732
|
+
if (!isRec3(config)) return void 0;
|
|
1592
1733
|
let out;
|
|
1593
1734
|
for (const key of Object.keys(config)) {
|
|
1594
1735
|
if (!REGION_CONFIG_KEYS.has(key)) continue;
|
|
@@ -1599,11 +1740,11 @@ function stripRegions(config) {
|
|
|
1599
1740
|
}
|
|
1600
1741
|
function walkFlowNodes(flow, flowPath) {
|
|
1601
1742
|
const out = [];
|
|
1602
|
-
if (!
|
|
1743
|
+
if (!isRec3(flow)) return out;
|
|
1603
1744
|
const visitList = (nodes, basePath, trail, depth) => {
|
|
1604
1745
|
if (!Array.isArray(nodes) || depth > MAX_REGION_DEPTH) return;
|
|
1605
1746
|
nodes.forEach((raw, index) => {
|
|
1606
|
-
if (!
|
|
1747
|
+
if (!isRec3(raw)) return;
|
|
1607
1748
|
const path = `${basePath}[${index}]`;
|
|
1608
1749
|
out.push({
|
|
1609
1750
|
node: raw,
|
|
@@ -1614,7 +1755,7 @@ function walkFlowNodes(flow, flowPath) {
|
|
|
1614
1755
|
});
|
|
1615
1756
|
const type = strName(raw.type);
|
|
1616
1757
|
const slots = type ? REGION_SLOTS.get(type) : void 0;
|
|
1617
|
-
if (!slots || !
|
|
1758
|
+
if (!slots || !isRec3(raw.config)) return;
|
|
1618
1759
|
const config = raw.config;
|
|
1619
1760
|
const here = `${type} "${flowNodeLabel(raw, index)}"`;
|
|
1620
1761
|
for (const slot of slots) {
|
|
@@ -1622,7 +1763,7 @@ function walkFlowNodes(flow, flowPath) {
|
|
|
1622
1763
|
if (slot === "branches") {
|
|
1623
1764
|
if (!Array.isArray(value)) continue;
|
|
1624
1765
|
value.forEach((branch, b) => {
|
|
1625
|
-
if (!
|
|
1766
|
+
if (!isRec3(branch)) return;
|
|
1626
1767
|
const branchName = strName(branch.name) ?? `#${b}`;
|
|
1627
1768
|
visitList(
|
|
1628
1769
|
branch.nodes,
|
|
@@ -1633,7 +1774,7 @@ function walkFlowNodes(flow, flowPath) {
|
|
|
1633
1774
|
});
|
|
1634
1775
|
continue;
|
|
1635
1776
|
}
|
|
1636
|
-
if (!
|
|
1777
|
+
if (!isRec3(value)) continue;
|
|
1637
1778
|
visitList(
|
|
1638
1779
|
value.nodes,
|
|
1639
1780
|
`${path}.config.${slot}.nodes`,
|
|
@@ -1653,6 +1794,7 @@ function joinTrail(trail, segment) {
|
|
|
1653
1794
|
// src/validate-flow-template-paths.ts
|
|
1654
1795
|
var FLOW_TEMPLATE_UNKNOWN_FIELD = "flow-template-unknown-field";
|
|
1655
1796
|
var FLOW_TEMPLATE_LOOKUP_TRAVERSAL = "flow-template-lookup-traversal";
|
|
1797
|
+
var FLOW_TEMPLATE_FIELD_UNPROVISIONED = "flow-template-field-unprovisioned";
|
|
1656
1798
|
function asArray5(v) {
|
|
1657
1799
|
if (Array.isArray(v)) return v;
|
|
1658
1800
|
if (v && typeof v === "object") {
|
|
@@ -1795,6 +1937,7 @@ function validateFlowTemplatePaths(stack) {
|
|
|
1795
1937
|
const obj = objectsByName.get(objectName);
|
|
1796
1938
|
if (!obj) return;
|
|
1797
1939
|
const fieldTypes = fieldTypesOf(obj);
|
|
1940
|
+
const unprovisionedAnchors = unprovisionedInjectedColumnsFor(obj);
|
|
1798
1941
|
const expandSet = declaredExpandOf(flow);
|
|
1799
1942
|
walkFlowNodes(flow, `flows[${flowIndex}]`).forEach(({ node, path: nodePath, regionTrail, localConfig }, walkIndex) => {
|
|
1800
1943
|
const nodeLabel = typeof node.type === "string" ? node.type : typeof node.id === "string" ? node.id : `#${walkIndex}`;
|
|
@@ -1806,6 +1949,7 @@ function validateFlowTemplatePaths(stack) {
|
|
|
1806
1949
|
if (leaves.length === 0) return;
|
|
1807
1950
|
const seenUnknown = /* @__PURE__ */ new Set();
|
|
1808
1951
|
const seenTraversal = /* @__PURE__ */ new Set();
|
|
1952
|
+
const seenUnprovisioned = /* @__PURE__ */ new Set();
|
|
1809
1953
|
for (const leaf of leaves) {
|
|
1810
1954
|
const inFilter = leaf.inFilter;
|
|
1811
1955
|
for (const rest of recordRefsIn(leaf.text)) {
|
|
@@ -1813,6 +1957,19 @@ function validateFlowTemplatePaths(stack) {
|
|
|
1813
1957
|
const hasSubPath = rest.length > 1;
|
|
1814
1958
|
const nextIsIdentifier = hasSubPath && !/^\d+$/.test(rest[1]);
|
|
1815
1959
|
const isKnown = fieldTypes.has(head) || IMPLICIT_HEADS.has(head);
|
|
1960
|
+
if (unprovisionedAnchors.has(head)) {
|
|
1961
|
+
if (!seenUnprovisioned.has(head)) {
|
|
1962
|
+
seenUnprovisioned.add(head);
|
|
1963
|
+
findings.push({
|
|
1964
|
+
severity: "warning",
|
|
1965
|
+
rule: FLOW_TEMPLATE_FIELD_UNPROVISIONED,
|
|
1966
|
+
where,
|
|
1967
|
+
path: nodePath,
|
|
1968
|
+
message: (inFilter ? `${nodeType} filter references ` : "template references ") + `'{record.${rest.join(".")}}', and ${unprovisionedAnchorCause(objectName, head)} \u2014 ` + (inFilter ? `the token resolves to nothing on every run, which DROPS the condition from the query instead of narrowing it; the node then refuses to run at execution time (#3810).` : `the token resolves to an empty string on every run (silently).`),
|
|
1969
|
+
hint: unprovisionedAnchorHint(objectName, head)
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1816
1973
|
if (!isKnown) {
|
|
1817
1974
|
if (seenUnknown.has(head)) continue;
|
|
1818
1975
|
seenUnknown.add(head);
|
|
@@ -1957,10 +2114,32 @@ function containerViewCount(rec) {
|
|
|
1957
2114
|
function validateViewContainers(stack) {
|
|
1958
2115
|
const out = [];
|
|
1959
2116
|
if (!stack || typeof stack !== "object") return out;
|
|
2117
|
+
const viewItems = stack.viewItems;
|
|
2118
|
+
if (viewItems != null && asEntries(viewItems).length > 0) {
|
|
2119
|
+
out.push({
|
|
2120
|
+
severity: "error",
|
|
2121
|
+
rule: VIEW_CONTAINER_SHAPE,
|
|
2122
|
+
where: "viewItems",
|
|
2123
|
+
path: "viewItems",
|
|
2124
|
+
message: "`viewItems` is the machine-assembled channel for non-container view artifacts in runtime-assembled manifests (package export, environment artifacts) \u2014 it is not an authoring surface.",
|
|
2125
|
+
hint: "Author views as defineView containers in `views:`; author a standalone view through the metadata door (Studio / `PUT /api/v1/meta/view`), not in stack source."
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
1960
2128
|
for (const { key, value } of asEntries(stack.views)) {
|
|
1961
2129
|
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
1962
2130
|
const rec = value;
|
|
1963
|
-
if (rec.viewKind != null)
|
|
2131
|
+
if (rec.viewKind != null) {
|
|
2132
|
+
const label3 = typeof rec.name === "string" ? ` ("${rec.name}")` : "";
|
|
2133
|
+
out.push({
|
|
2134
|
+
severity: "error",
|
|
2135
|
+
rule: VIEW_CONTAINER_SHAPE,
|
|
2136
|
+
where: `views${key}${label3}`,
|
|
2137
|
+
path: `views${key}`,
|
|
2138
|
+
message: "A ViewItem record is not a view container: the stack `views:` collection carries containers only \u2014 `viewKind` belongs to a single VIEW, not to the container. The registration loop refuses this entry (#5320).",
|
|
2139
|
+
hint: "Wrap it in a defineView container: defineView({ list: { type, data, columns, ... }, listViews: { ... } }) \u2014 or author the standalone view through the metadata door (Studio / `PUT /api/v1/meta/view`). Machine-assembled manifests carry it under `viewItems:`."
|
|
2140
|
+
});
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
1964
2143
|
if (containerViewCount(rec) > 0) continue;
|
|
1965
2144
|
const label2 = typeof rec.name === "string" ? ` ("${rec.name}")` : "";
|
|
1966
2145
|
const hasContainerSlot = CONTAINER_SLOT_KEYS.some((k) => k in rec);
|
|
@@ -2408,7 +2587,7 @@ function asArray10(v) {
|
|
|
2408
2587
|
}
|
|
2409
2588
|
return [];
|
|
2410
2589
|
}
|
|
2411
|
-
function
|
|
2590
|
+
function isRec4(v) {
|
|
2412
2591
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
2413
2592
|
}
|
|
2414
2593
|
function strName2(v) {
|
|
@@ -2484,7 +2663,7 @@ function distance(a, b) {
|
|
|
2484
2663
|
}
|
|
2485
2664
|
function indexObjectSearchTargets(stack) {
|
|
2486
2665
|
const fieldsByObject = /* @__PURE__ */ new Map();
|
|
2487
|
-
if (!
|
|
2666
|
+
if (!isRec4(stack)) return fieldsByObject;
|
|
2488
2667
|
for (const obj of asArray10(stack.objects)) {
|
|
2489
2668
|
const name = strName2(obj.name);
|
|
2490
2669
|
if (name) fieldsByObject.set(name, declaredFieldTarget(obj));
|
|
@@ -2566,7 +2745,7 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
|
|
|
2566
2745
|
}
|
|
2567
2746
|
function validateSearchableFields(stack) {
|
|
2568
2747
|
const findings = [];
|
|
2569
|
-
if (!
|
|
2748
|
+
if (!isRec4(stack)) return findings;
|
|
2570
2749
|
const objects = asArray10(stack.objects);
|
|
2571
2750
|
const fieldsByObject = indexObjectSearchTargets(stack);
|
|
2572
2751
|
const check = (declared, objectName, where, path, subject, role) => {
|
|
@@ -2576,7 +2755,7 @@ function validateSearchableFields(stack) {
|
|
|
2576
2755
|
};
|
|
2577
2756
|
for (let oi = 0; oi < objects.length; oi++) {
|
|
2578
2757
|
const obj = objects[oi];
|
|
2579
|
-
if (!
|
|
2758
|
+
if (!isRec4(obj)) continue;
|
|
2580
2759
|
const objName = strName2(obj.name);
|
|
2581
2760
|
const label2 = objName ? `object "${objName}"` : `objects[${oi}]`;
|
|
2582
2761
|
check(
|
|
@@ -2587,9 +2766,9 @@ function validateSearchableFields(stack) {
|
|
|
2587
2766
|
"searchableFields",
|
|
2588
2767
|
"canonical"
|
|
2589
2768
|
);
|
|
2590
|
-
if (
|
|
2769
|
+
if (isRec4(obj.listViews)) {
|
|
2591
2770
|
for (const [key, lv] of Object.entries(obj.listViews)) {
|
|
2592
|
-
if (!
|
|
2771
|
+
if (!isRec4(lv)) continue;
|
|
2593
2772
|
check(
|
|
2594
2773
|
lv.searchableFields,
|
|
2595
2774
|
// A built-in list view belongs to its object; an inline `data.object`
|
|
@@ -2606,10 +2785,10 @@ function validateSearchableFields(stack) {
|
|
|
2606
2785
|
const views = asArray10(stack.views);
|
|
2607
2786
|
for (let vi = 0; vi < views.length; vi++) {
|
|
2608
2787
|
const view = views[vi];
|
|
2609
|
-
if (!
|
|
2788
|
+
if (!isRec4(view)) continue;
|
|
2610
2789
|
const viewLabel2 = strName2(view.name) ?? strName2(view.objectName) ?? `#${vi}`;
|
|
2611
2790
|
const viewObject = strName2(view.objectName) ?? strName2(view.object);
|
|
2612
|
-
if (
|
|
2791
|
+
if (isRec4(view.list)) {
|
|
2613
2792
|
check(
|
|
2614
2793
|
view.list.searchableFields,
|
|
2615
2794
|
listViewObject(view.list) ?? viewObject,
|
|
@@ -2619,9 +2798,9 @@ function validateSearchableFields(stack) {
|
|
|
2619
2798
|
"narrowing"
|
|
2620
2799
|
);
|
|
2621
2800
|
}
|
|
2622
|
-
if (
|
|
2801
|
+
if (isRec4(view.listViews)) {
|
|
2623
2802
|
for (const [key, lv] of Object.entries(view.listViews)) {
|
|
2624
|
-
if (!
|
|
2803
|
+
if (!isRec4(lv)) continue;
|
|
2625
2804
|
check(
|
|
2626
2805
|
lv.searchableFields,
|
|
2627
2806
|
listViewObject(lv) ?? viewObject,
|
|
@@ -2637,11 +2816,11 @@ function validateSearchableFields(stack) {
|
|
|
2637
2816
|
}
|
|
2638
2817
|
function listViewObject(listView) {
|
|
2639
2818
|
const data = listView.data;
|
|
2640
|
-
return
|
|
2819
|
+
return isRec4(data) ? strName2(data.object) : void 0;
|
|
2641
2820
|
}
|
|
2642
2821
|
|
|
2643
2822
|
// src/page-walk.ts
|
|
2644
|
-
function
|
|
2823
|
+
function isRec5(v) {
|
|
2645
2824
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
2646
2825
|
}
|
|
2647
2826
|
function strName3(v) {
|
|
@@ -2654,19 +2833,19 @@ function isSourceAuthoredPage(page) {
|
|
|
2654
2833
|
}
|
|
2655
2834
|
function walkPageComponents(page, pagePath) {
|
|
2656
2835
|
const out = [];
|
|
2657
|
-
if (!
|
|
2836
|
+
if (!isRec5(page) || isSourceAuthoredPage(page)) return out;
|
|
2658
2837
|
const pageObject = strName3(page.object);
|
|
2659
2838
|
const visit = (node, path, inheritedObject) => {
|
|
2660
|
-
if (!
|
|
2661
|
-
const props =
|
|
2662
|
-
const dataSource =
|
|
2839
|
+
if (!isRec5(node)) return;
|
|
2840
|
+
const props = isRec5(node.properties) ? node.properties : void 0;
|
|
2841
|
+
const dataSource = isRec5(node.dataSource) ? node.dataSource : void 0;
|
|
2663
2842
|
const objectName = strName3(dataSource?.object) ?? strName3(props?.object) ?? inheritedObject;
|
|
2664
2843
|
out.push({ component: node, path, objectName });
|
|
2665
2844
|
if (!props) return;
|
|
2666
2845
|
if (Array.isArray(props.items)) {
|
|
2667
2846
|
for (let i = 0; i < props.items.length; i++) {
|
|
2668
2847
|
const item = props.items[i];
|
|
2669
|
-
if (!
|
|
2848
|
+
if (!isRec5(item) || !Array.isArray(item.children)) continue;
|
|
2670
2849
|
for (let c = 0; c < item.children.length; c++) {
|
|
2671
2850
|
visit(item.children[c], `${path}.properties.items[${i}].children[${c}]`, objectName);
|
|
2672
2851
|
}
|
|
@@ -2688,12 +2867,12 @@ function walkPageComponents(page, pagePath) {
|
|
|
2688
2867
|
const regions = Array.isArray(page.regions) ? page.regions : [];
|
|
2689
2868
|
for (let r = 0; r < regions.length; r++) {
|
|
2690
2869
|
const region = regions[r];
|
|
2691
|
-
if (!
|
|
2870
|
+
if (!isRec5(region) || !Array.isArray(region.components)) continue;
|
|
2692
2871
|
for (let c = 0; c < region.components.length; c++) {
|
|
2693
2872
|
visit(region.components[c], `${pagePath}.regions[${r}].components[${c}]`, pageObject);
|
|
2694
2873
|
}
|
|
2695
2874
|
}
|
|
2696
|
-
const slots =
|
|
2875
|
+
const slots = isRec5(page.slots) ? page.slots : void 0;
|
|
2697
2876
|
if (slots) {
|
|
2698
2877
|
for (const [slot, value] of Object.entries(slots)) {
|
|
2699
2878
|
const list3 = Array.isArray(value) ? value : [value];
|
|
@@ -2708,6 +2887,7 @@ function walkPageComponents(page, pagePath) {
|
|
|
2708
2887
|
|
|
2709
2888
|
// src/validate-page-field-bindings.ts
|
|
2710
2889
|
var PAGE_FIELD_UNKNOWN = "page-field-unknown";
|
|
2890
|
+
var PAGE_FIELD_UNPROVISIONED = "page-field-unprovisioned";
|
|
2711
2891
|
function asArray11(v) {
|
|
2712
2892
|
if (Array.isArray(v)) return v;
|
|
2713
2893
|
if (v && typeof v === "object") {
|
|
@@ -2718,7 +2898,7 @@ function asArray11(v) {
|
|
|
2718
2898
|
function strName4(v) {
|
|
2719
2899
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
2720
2900
|
}
|
|
2721
|
-
function
|
|
2901
|
+
function isRec6(v) {
|
|
2722
2902
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
2723
2903
|
}
|
|
2724
2904
|
function fieldRefsFrom(value, basePath) {
|
|
@@ -2729,7 +2909,7 @@ function fieldRefsFrom(value, basePath) {
|
|
|
2729
2909
|
out.push({ name: bare, path });
|
|
2730
2910
|
return;
|
|
2731
2911
|
}
|
|
2732
|
-
if (!
|
|
2912
|
+
if (!isRec6(v)) return;
|
|
2733
2913
|
const named = strName4(v.field) ?? strName4(v.name);
|
|
2734
2914
|
if (named) out.push({ name: named, path: `${path}.${strName4(v.field) ? "field" : "name"}` });
|
|
2735
2915
|
};
|
|
@@ -2787,15 +2967,15 @@ function componentFieldRefs(type, props, basePath, sep = ".") {
|
|
|
2787
2967
|
const sections = Array.isArray(props[key]) ? props[key] : [];
|
|
2788
2968
|
for (let si = 0; si < sections.length; si++) {
|
|
2789
2969
|
const section = sections[si];
|
|
2790
|
-
if (!
|
|
2970
|
+
if (!isRec6(section)) continue;
|
|
2791
2971
|
refs.push(...fieldRefsFrom(section.fields, `${basePath}${sep}${key}[${si}].fields`));
|
|
2792
2972
|
}
|
|
2793
2973
|
}
|
|
2794
2974
|
return refs;
|
|
2795
2975
|
}
|
|
2796
2976
|
function relatedListFieldRefs(props, basePath, sep = ".") {
|
|
2797
|
-
const add =
|
|
2798
|
-
const picker = add &&
|
|
2977
|
+
const add = isRec6(props.add) ? props.add : void 0;
|
|
2978
|
+
const picker = add && isRec6(add.picker) ? add.picker : void 0;
|
|
2799
2979
|
const at = (key) => `${basePath}${sep}${key}`;
|
|
2800
2980
|
return {
|
|
2801
2981
|
relatedObject: strName4(props.objectName),
|
|
@@ -2816,7 +2996,7 @@ function relatedListFieldRefs(props, basePath, sep = ".") {
|
|
|
2816
2996
|
}
|
|
2817
2997
|
function indexObjectFields(stack) {
|
|
2818
2998
|
const objectFields = /* @__PURE__ */ new Map();
|
|
2819
|
-
if (!
|
|
2999
|
+
if (!isRec6(stack)) return objectFields;
|
|
2820
3000
|
for (const obj of asArray11(stack.objects)) {
|
|
2821
3001
|
const name = strName4(obj.name);
|
|
2822
3002
|
if (!name) continue;
|
|
@@ -2829,14 +3009,27 @@ function indexObjectFields(stack) {
|
|
|
2829
3009
|
}
|
|
2830
3010
|
return objectFields;
|
|
2831
3011
|
}
|
|
2832
|
-
function checkFieldRefs(refs, objectName, objectFields, where, consequence2 = "skipped") {
|
|
3012
|
+
function checkFieldRefs(refs, objectName, objectFields, where, consequence2 = "skipped", unprovisionedAnchors) {
|
|
2833
3013
|
const findings = [];
|
|
2834
3014
|
if (!objectName) return findings;
|
|
2835
3015
|
const known = objectFields.get(objectName);
|
|
2836
3016
|
if (!known) return findings;
|
|
3017
|
+
const anchors = unprovisionedAnchors?.get(objectName);
|
|
2837
3018
|
for (const ref of refs) {
|
|
2838
3019
|
if (ref.name.includes(".")) continue;
|
|
2839
|
-
if (known.has(ref.name) || SYSTEM_FIELDS.has(ref.name))
|
|
3020
|
+
if (known.has(ref.name) || SYSTEM_FIELDS.has(ref.name)) {
|
|
3021
|
+
if (anchors?.has(ref.name)) {
|
|
3022
|
+
findings.push({
|
|
3023
|
+
severity: "warning",
|
|
3024
|
+
rule: PAGE_FIELD_UNPROVISIONED,
|
|
3025
|
+
where,
|
|
3026
|
+
path: ref.path,
|
|
3027
|
+
message: `field "${ref.name}" resolves on object "${objectName}", but ${unprovisionedAnchorCause(objectName, ref.name)}` + (consequence2 === "queried" ? ' \u2014 it is used in a QUERY, so the predicate can never match a real value: on SQLite it silently degrades to constant-false and the surface renders an empty result that looks exactly like "there is no data".' : " \u2014 the component renders it, blank, on every record."),
|
|
3028
|
+
hint: unprovisionedAnchorHint(objectName, ref.name)
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
2840
3033
|
findings.push({
|
|
2841
3034
|
severity: consequence2 === "queried" ? "error" : "warning",
|
|
2842
3035
|
rule: PAGE_FIELD_UNKNOWN,
|
|
@@ -2852,17 +3045,20 @@ function validatePageFieldBindings(stack) {
|
|
|
2852
3045
|
const findings = [];
|
|
2853
3046
|
if (!stack || typeof stack !== "object") return findings;
|
|
2854
3047
|
const objectFields = indexObjectFields(stack);
|
|
3048
|
+
const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
|
|
2855
3049
|
const pages = asArray11(stack.pages);
|
|
2856
3050
|
for (let pi = 0; pi < pages.length; pi++) {
|
|
2857
3051
|
const page = pages[pi];
|
|
2858
3052
|
if (!page || typeof page !== "object") continue;
|
|
2859
3053
|
const pageName = strName4(page.name) ?? `#${pi}`;
|
|
2860
3054
|
const checkRefs = (refs, objectName, where) => {
|
|
2861
|
-
findings.push(
|
|
3055
|
+
findings.push(
|
|
3056
|
+
...checkFieldRefs(refs, objectName, objectFields, where, "skipped", unprovisionedAnchors)
|
|
3057
|
+
);
|
|
2862
3058
|
};
|
|
2863
3059
|
for (const { component, path, objectName } of walkPageComponents(page, `pages[${pi}]`)) {
|
|
2864
3060
|
const type = strName4(component.type);
|
|
2865
|
-
const props =
|
|
3061
|
+
const props = isRec6(component.properties) ? component.properties : void 0;
|
|
2866
3062
|
if (!type || !props) continue;
|
|
2867
3063
|
const where = `page "${pageName}" \xB7 ${type}`;
|
|
2868
3064
|
const base = `${path}.properties`;
|
|
@@ -2877,7 +3073,7 @@ function validatePageFieldBindings(stack) {
|
|
|
2877
3073
|
if (!refs) continue;
|
|
2878
3074
|
checkRefs(refs, objectName, where);
|
|
2879
3075
|
}
|
|
2880
|
-
const cfg =
|
|
3076
|
+
const cfg = isRec6(page.interfaceConfig) ? page.interfaceConfig : void 0;
|
|
2881
3077
|
if (cfg) {
|
|
2882
3078
|
const cfgObject = strName4(cfg.source) ?? strName4(page.object);
|
|
2883
3079
|
const base = `pages[${pi}].interfaceConfig`;
|
|
@@ -2886,7 +3082,7 @@ function validatePageFieldBindings(stack) {
|
|
|
2886
3082
|
...sortFieldRefs(cfg.sort, `${base}.sort`),
|
|
2887
3083
|
...fieldRefsFrom(cfg.filterBy, `${base}.filterBy`)
|
|
2888
3084
|
];
|
|
2889
|
-
const userFilters =
|
|
3085
|
+
const userFilters = isRec6(cfg.userFilters) ? cfg.userFilters : void 0;
|
|
2890
3086
|
if (userFilters) {
|
|
2891
3087
|
refs.push(...fieldRefsFrom(userFilters.fields, `${base}.userFilters.fields`));
|
|
2892
3088
|
}
|
|
@@ -2897,11 +3093,11 @@ function validatePageFieldBindings(stack) {
|
|
|
2897
3093
|
}
|
|
2898
3094
|
|
|
2899
3095
|
// src/zod-issue-format.ts
|
|
2900
|
-
var
|
|
3096
|
+
var isRec7 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
2901
3097
|
var valueAtPath = (root, path) => {
|
|
2902
3098
|
let cur = root;
|
|
2903
3099
|
for (const key of path) {
|
|
2904
|
-
if (!
|
|
3100
|
+
if (!isRec7(cur) && !Array.isArray(cur)) return void 0;
|
|
2905
3101
|
cur = cur[key];
|
|
2906
3102
|
}
|
|
2907
3103
|
return cur;
|
|
@@ -3034,12 +3230,13 @@ function filterAttrValue(tsc, sf, attr) {
|
|
|
3034
3230
|
return perPosition(init.expression);
|
|
3035
3231
|
}
|
|
3036
3232
|
var REACT_CHART_FIELD_UNKNOWN = "react-chart-field-unknown";
|
|
3233
|
+
var REACT_CHART_FIELD_UNPROVISIONED = "react-chart-field-unprovisioned";
|
|
3037
3234
|
var REACT_CHART_AGGREGATE_INVALID = "react-chart-aggregate-invalid";
|
|
3038
3235
|
var REACT_CHART_AXIS_UNKNOWN = "react-chart-axis-unknown";
|
|
3039
3236
|
var REACT_CHART_DRILLDOWN_INVALID = "react-chart-drilldown-invalid";
|
|
3040
3237
|
function checkChartDrillDown(raw, push2) {
|
|
3041
3238
|
if (raw === void 0 || raw === NOT_STATIC) return;
|
|
3042
|
-
if (!
|
|
3239
|
+
if (!isRec8(raw)) {
|
|
3043
3240
|
push2(
|
|
3044
3241
|
"error",
|
|
3045
3242
|
REACT_CHART_DRILLDOWN_INVALID,
|
|
@@ -3062,7 +3259,7 @@ function checkChartDrillDown(raw, push2) {
|
|
|
3062
3259
|
}
|
|
3063
3260
|
function checkChartAggregate(raw, push2) {
|
|
3064
3261
|
if (raw === void 0 || raw === NOT_STATIC) return;
|
|
3065
|
-
if (!
|
|
3262
|
+
if (!isRec8(raw)) {
|
|
3066
3263
|
push2(
|
|
3067
3264
|
"error",
|
|
3068
3265
|
REACT_CHART_AGGREGATE_INVALID,
|
|
@@ -3093,9 +3290,9 @@ function checkChartAggregate(raw, push2) {
|
|
|
3093
3290
|
);
|
|
3094
3291
|
}
|
|
3095
3292
|
}
|
|
3096
|
-
var
|
|
3293
|
+
var isRec8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
3097
3294
|
var strOf = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
3098
|
-
function checkObjectChart(attrs, objectFields, findings) {
|
|
3295
|
+
function checkObjectChart(attrs, objectFields, findings, unprovisionedAnchors = /* @__PURE__ */ new Map()) {
|
|
3099
3296
|
const { values, where, path } = attrs;
|
|
3100
3297
|
const push2 = (severity, rule, message, hint) => findings.push({ severity, rule, where, path, message, hint });
|
|
3101
3298
|
checkChartDrillDown(values.get("drillDown"), push2);
|
|
@@ -3103,18 +3300,29 @@ function checkObjectChart(attrs, objectFields, findings) {
|
|
|
3103
3300
|
const aggregate = values.get("aggregate");
|
|
3104
3301
|
checkChartAggregate(aggregate, push2);
|
|
3105
3302
|
if (aggregate === void 0 || aggregate === NOT_STATIC) return;
|
|
3106
|
-
if (!
|
|
3303
|
+
if (!isRec8(aggregate)) return;
|
|
3107
3304
|
const fn = strOf(aggregate.function);
|
|
3108
3305
|
const field = strOf(aggregate.field);
|
|
3109
3306
|
const groupBy = aggregate.groupBy;
|
|
3110
|
-
const groupByField = strOf(groupBy) ?? (
|
|
3307
|
+
const groupByField = strOf(groupBy) ?? (isRec8(groupBy) ? strOf(groupBy.field) : void 0);
|
|
3111
3308
|
const objectName = strOf(values.get("objectName"));
|
|
3112
3309
|
const known = objectName ? objectFields.get(objectName) : void 0;
|
|
3113
3310
|
if (objectName && known) {
|
|
3311
|
+
const anchors = unprovisionedAnchors.get(objectName);
|
|
3114
3312
|
const fieldRef = (name, prop) => {
|
|
3115
3313
|
if (!name) return;
|
|
3116
3314
|
if (name.includes(".")) return;
|
|
3117
|
-
if (known.has(name) || SYSTEM_FIELDS.has(name))
|
|
3315
|
+
if (known.has(name) || SYSTEM_FIELDS.has(name)) {
|
|
3316
|
+
if (anchors?.has(name)) {
|
|
3317
|
+
push2(
|
|
3318
|
+
"warning",
|
|
3319
|
+
REACT_CHART_FIELD_UNPROVISIONED,
|
|
3320
|
+
`aggregate.${prop} "${name}" resolves on object "${objectName}", but ${unprovisionedAnchorCause(objectName, name)} \u2014 the aggregate query reads a column that is empty on every row, so the chart ${prop === "groupBy" ? "groups everything into one empty bucket" : "aggregates nothing"} instead of failing.`,
|
|
3321
|
+
unprovisionedAnchorHint(objectName, name)
|
|
3322
|
+
);
|
|
3323
|
+
}
|
|
3324
|
+
return;
|
|
3325
|
+
}
|
|
3118
3326
|
push2(
|
|
3119
3327
|
"error",
|
|
3120
3328
|
REACT_CHART_FIELD_UNKNOWN,
|
|
@@ -3140,18 +3348,18 @@ function checkObjectChart(attrs, objectFields, findings) {
|
|
|
3140
3348
|
);
|
|
3141
3349
|
};
|
|
3142
3350
|
const xAxisRaw = values.get("xAxis");
|
|
3143
|
-
const categoryAxis = strOf(values.get("xAxisKey")) ?? strOf(xAxisRaw) ?? (
|
|
3351
|
+
const categoryAxis = strOf(values.get("xAxisKey")) ?? strOf(xAxisRaw) ?? (isRec8(xAxisRaw) ? strOf(xAxisRaw.field) : void 0);
|
|
3144
3352
|
const categoryProp = values.has("xAxisKey") ? "xAxisKey" : "xAxis.field";
|
|
3145
3353
|
axisRef(categoryAxis, categoryProp);
|
|
3146
3354
|
const yAxisRaw = values.get("yAxis");
|
|
3147
3355
|
const yAxisList = Array.isArray(yAxisRaw) ? yAxisRaw : yAxisRaw !== void 0 ? [yAxisRaw] : [];
|
|
3148
3356
|
for (const a of yAxisList) {
|
|
3149
|
-
axisRef(strOf(a) ?? (
|
|
3357
|
+
axisRef(strOf(a) ?? (isRec8(a) ? strOf(a.field) : void 0), "yAxis[].field");
|
|
3150
3358
|
}
|
|
3151
3359
|
const series = values.get("series");
|
|
3152
3360
|
if (Array.isArray(series)) {
|
|
3153
3361
|
for (const s of series) {
|
|
3154
|
-
if (!
|
|
3362
|
+
if (!isRec8(s)) continue;
|
|
3155
3363
|
const dataKey = strOf(s.dataKey);
|
|
3156
3364
|
axisRef(dataKey ?? strOf(s.name), dataKey ? "series[].dataKey" : "series[].name");
|
|
3157
3365
|
}
|
|
@@ -3207,7 +3415,7 @@ function subformFieldRefs(value, basePath) {
|
|
|
3207
3415
|
if (!Array.isArray(value)) return { child, parent };
|
|
3208
3416
|
for (let i = 0; i < value.length; i++) {
|
|
3209
3417
|
const sub = value[i];
|
|
3210
|
-
if (!
|
|
3418
|
+
if (!isRec8(sub)) continue;
|
|
3211
3419
|
const at = (key) => `${basePath}[${i}].${key}`;
|
|
3212
3420
|
child.push({
|
|
3213
3421
|
objectName: strOf(sub.childObject),
|
|
@@ -3252,20 +3460,20 @@ function reactFieldRefs(spec, values, basePath) {
|
|
|
3252
3460
|
}
|
|
3253
3461
|
for (const key of spec.nestedFields ?? []) {
|
|
3254
3462
|
const v = readable(key);
|
|
3255
|
-
if (
|
|
3463
|
+
if (isRec8(v)) own.push(...fieldRefsFrom(v.fields, at(`${key}.fields`)));
|
|
3256
3464
|
}
|
|
3257
3465
|
for (const key of spec.sections ?? []) {
|
|
3258
3466
|
const v = readable(key);
|
|
3259
3467
|
if (!Array.isArray(v)) continue;
|
|
3260
3468
|
for (let i = 0; i < v.length; i++) {
|
|
3261
3469
|
const section = v[i];
|
|
3262
|
-
if (!
|
|
3470
|
+
if (!isRec8(section)) continue;
|
|
3263
3471
|
own.push(...fieldRefsFrom(section.fields, at(`${key}[${i}].fields`)));
|
|
3264
3472
|
}
|
|
3265
3473
|
}
|
|
3266
3474
|
for (const key of spec.keyedByField ?? []) {
|
|
3267
3475
|
const v = readable(key);
|
|
3268
|
-
if (!
|
|
3476
|
+
if (!isRec8(v)) continue;
|
|
3269
3477
|
for (const k of Object.keys(v)) own.push({ name: k, path: at(`${key}.${k}`) });
|
|
3270
3478
|
}
|
|
3271
3479
|
for (const key of spec.filterArrays ?? []) {
|
|
@@ -3273,22 +3481,30 @@ function reactFieldRefs(spec, values, basePath) {
|
|
|
3273
3481
|
}
|
|
3274
3482
|
return { own, queried };
|
|
3275
3483
|
}
|
|
3276
|
-
function checkBlockFieldProps(tag, values, objectFields, where, path) {
|
|
3484
|
+
function checkBlockFieldProps(tag, values, objectFields, where, path, unprovisionedAnchors) {
|
|
3277
3485
|
const objectName = strOf(values.get("objectName"));
|
|
3278
3486
|
const out = [];
|
|
3279
3487
|
const spec = REACT_FIELD_SPECS[tag];
|
|
3280
3488
|
if (spec) {
|
|
3281
3489
|
const { own, queried } = reactFieldRefs(spec, values, path);
|
|
3282
|
-
out.push(
|
|
3283
|
-
|
|
3490
|
+
out.push(
|
|
3491
|
+
...checkFieldRefs(own, objectName, objectFields, where, "skipped", unprovisionedAnchors)
|
|
3492
|
+
);
|
|
3493
|
+
out.push(
|
|
3494
|
+
...checkFieldRefs(queried, objectName, objectFields, where, "queried", unprovisionedAnchors)
|
|
3495
|
+
);
|
|
3284
3496
|
}
|
|
3285
3497
|
if (tag === "ObjectForm") {
|
|
3286
3498
|
const raw = values.get("subforms");
|
|
3287
3499
|
const subs = subformFieldRefs(raw === NOT_STATIC ? void 0 : raw, `${path}${PATH_SEP}subforms`);
|
|
3288
3500
|
for (const sub of subs.child) {
|
|
3289
|
-
out.push(
|
|
3501
|
+
out.push(
|
|
3502
|
+
...checkFieldRefs(sub.refs, sub.objectName, objectFields, where, "skipped", unprovisionedAnchors)
|
|
3503
|
+
);
|
|
3290
3504
|
}
|
|
3291
|
-
out.push(
|
|
3505
|
+
out.push(
|
|
3506
|
+
...checkFieldRefs(subs.parent, objectName, objectFields, where, "skipped", unprovisionedAnchors)
|
|
3507
|
+
);
|
|
3292
3508
|
}
|
|
3293
3509
|
const schemaType = tag === "Block" ? strOf(values.get("type")) : SCHEMA_TYPE_BY_TAG.get(tag);
|
|
3294
3510
|
if (schemaType && COMPONENT_FIELD_SPECS[schemaType]) {
|
|
@@ -3297,7 +3513,9 @@ function checkBlockFieldProps(tag, values, objectFields, where, path) {
|
|
|
3297
3513
|
componentFieldRefs(schemaType, readableProps(values), path, PATH_SEP) ?? [],
|
|
3298
3514
|
objectName,
|
|
3299
3515
|
objectFields,
|
|
3300
|
-
where
|
|
3516
|
+
where,
|
|
3517
|
+
"skipped",
|
|
3518
|
+
unprovisionedAnchors
|
|
3301
3519
|
)
|
|
3302
3520
|
);
|
|
3303
3521
|
}
|
|
@@ -3329,6 +3547,7 @@ function localComponentNames(tsc, sf) {
|
|
|
3329
3547
|
function validateReactPageProps(stack) {
|
|
3330
3548
|
const findings = [];
|
|
3331
3549
|
const objectFields = indexObjectFields(stack);
|
|
3550
|
+
const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
|
|
3332
3551
|
const searchTargets = indexObjectSearchTargets(stack);
|
|
3333
3552
|
const pages = asArray12(stack.pages);
|
|
3334
3553
|
for (let p = 0; p < pages.length; p++) {
|
|
@@ -3411,7 +3630,7 @@ function validateReactPageProps(stack) {
|
|
|
3411
3630
|
}
|
|
3412
3631
|
}
|
|
3413
3632
|
if (tag === "ObjectChart" && !hasSpread) {
|
|
3414
|
-
checkObjectChart({ values, where, path }, objectFields, findings);
|
|
3633
|
+
checkObjectChart({ values, where, path }, objectFields, findings, unprovisionedAnchors);
|
|
3415
3634
|
}
|
|
3416
3635
|
if (tag === "ListView" && !hasSpread) {
|
|
3417
3636
|
findings.push(
|
|
@@ -3427,7 +3646,7 @@ function validateReactPageProps(stack) {
|
|
|
3427
3646
|
}
|
|
3428
3647
|
if (!hasSpread) {
|
|
3429
3648
|
findings.push(
|
|
3430
|
-
...checkBlockFieldProps(tag, values, objectFields, where, path)
|
|
3649
|
+
...checkBlockFieldProps(tag, values, objectFields, where, path, unprovisionedAnchors)
|
|
3431
3650
|
);
|
|
3432
3651
|
}
|
|
3433
3652
|
}
|
|
@@ -3519,6 +3738,7 @@ var FIELD_GROUP_UNDECLARED = "field-group-undeclared";
|
|
|
3519
3738
|
var FIELD_GROUP_EMPTY = "field-group-empty";
|
|
3520
3739
|
var FIELD_GROUP_SHADOWED = "field-group-shadowed";
|
|
3521
3740
|
var SEMANTIC_ROLE_FIELD_UNKNOWN = "semantic-role-field-unknown";
|
|
3741
|
+
var SEMANTIC_ROLE_FIELD_UNPROVISIONED = "semantic-role-field-unprovisioned";
|
|
3522
3742
|
function asArray15(v) {
|
|
3523
3743
|
if (Array.isArray(v)) return v;
|
|
3524
3744
|
if (v && typeof v === "object") {
|
|
@@ -3537,6 +3757,15 @@ function validateSemanticRoles(stack) {
|
|
|
3537
3757
|
const path = `objects[${i}]`;
|
|
3538
3758
|
const fields = obj.fields && typeof obj.fields === "object" && !Array.isArray(obj.fields) ? obj.fields : {};
|
|
3539
3759
|
const fieldNames = /* @__PURE__ */ new Set([...Object.keys(fields), ...injectedColumnsFor(obj)]);
|
|
3760
|
+
const unprovisioned = unprovisionedInjectedColumnsFor(obj);
|
|
3761
|
+
const unprovisionedPointer = (slot, entry) => ({
|
|
3762
|
+
severity: "warning",
|
|
3763
|
+
rule: SEMANTIC_ROLE_FIELD_UNPROVISIONED,
|
|
3764
|
+
where,
|
|
3765
|
+
path: `${path}.${slot}`,
|
|
3766
|
+
message: `${objName}: ${slot} points at "${entry}", an injected system column with no storage behind it \u2014 this object is external (ADR-0015), so the platform registers the anchor but the remote schema owns the table and no column backs it. Every consumer renders it empty on every record.`,
|
|
3767
|
+
hint: `If the remote table really carries "${entry}", declare it in the object's own fields (mapped through the external binding's columnMap); otherwise point ${slot} at a real remote column.`
|
|
3768
|
+
});
|
|
3540
3769
|
const declaredGroups = new Set(
|
|
3541
3770
|
(Array.isArray(obj.fieldGroups) ? obj.fieldGroups : []).filter((g) => !!g && typeof g === "object").map((g) => g.key).filter((k) => typeof k === "string" && k.length > 0)
|
|
3542
3771
|
);
|
|
@@ -3578,10 +3807,16 @@ function validateSemanticRoles(stack) {
|
|
|
3578
3807
|
message: `${objName}: stageField "${stage}" is not a field on this object \u2014 consumers fall back to heuristic stage detection`,
|
|
3579
3808
|
hint: `Point stageField at an existing select/status field, or set stageField: false to declare the object has no linear lifecycle.`
|
|
3580
3809
|
});
|
|
3810
|
+
} else if (typeof stage === "string" && unprovisioned.has(stage)) {
|
|
3811
|
+
findings.push(unprovisionedPointer("stageField", stage));
|
|
3581
3812
|
}
|
|
3582
3813
|
const highlights = Array.isArray(obj.highlightFields) ? obj.highlightFields : Array.isArray(obj.compactLayout) ? obj.compactLayout : [];
|
|
3583
3814
|
for (const entry of highlights) {
|
|
3584
|
-
if (typeof entry !== "string" || entry.length === 0
|
|
3815
|
+
if (typeof entry !== "string" || entry.length === 0) continue;
|
|
3816
|
+
if (fieldNames.has(entry)) {
|
|
3817
|
+
if (unprovisioned.has(entry)) findings.push(unprovisionedPointer("highlightFields", entry));
|
|
3818
|
+
continue;
|
|
3819
|
+
}
|
|
3585
3820
|
findings.push({
|
|
3586
3821
|
severity: "warning",
|
|
3587
3822
|
rule: SEMANTIC_ROLE_FIELD_UNKNOWN,
|
|
@@ -3621,45 +3856,45 @@ function validateSemanticRoles(stack) {
|
|
|
3621
3856
|
}
|
|
3622
3857
|
|
|
3623
3858
|
// src/collection-entries.ts
|
|
3624
|
-
function
|
|
3859
|
+
function isRec9(v) {
|
|
3625
3860
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
3626
3861
|
}
|
|
3627
3862
|
function collectionEntries(v, base) {
|
|
3628
3863
|
if (Array.isArray(v)) {
|
|
3629
3864
|
const out = [];
|
|
3630
3865
|
for (let i = 0; i < v.length; i++) {
|
|
3631
|
-
if (
|
|
3866
|
+
if (isRec9(v[i])) out.push({ rec: v[i], path: `${base}[${i}]` });
|
|
3632
3867
|
}
|
|
3633
3868
|
return out;
|
|
3634
3869
|
}
|
|
3635
|
-
if (
|
|
3636
|
-
return Object.entries(v).filter(([, def]) =>
|
|
3870
|
+
if (isRec9(v)) {
|
|
3871
|
+
return Object.entries(v).filter(([, def]) => isRec9(def)).map(([name, def]) => ({ rec: { name, ...def }, path: `${base}.${name}` }));
|
|
3637
3872
|
}
|
|
3638
3873
|
return [];
|
|
3639
3874
|
}
|
|
3640
3875
|
|
|
3641
3876
|
// src/view-walk.ts
|
|
3642
|
-
function
|
|
3877
|
+
function isRec10(v) {
|
|
3643
3878
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
3644
3879
|
}
|
|
3645
3880
|
function strName5(v) {
|
|
3646
3881
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
3647
3882
|
}
|
|
3648
3883
|
function viewObjectName(view) {
|
|
3649
|
-
return strName5(view.objectName) ?? strName5(view.object) ?? (
|
|
3884
|
+
return strName5(view.objectName) ?? strName5(view.object) ?? (isRec10(view.data) ? strName5(view.data.object) : void 0);
|
|
3650
3885
|
}
|
|
3651
3886
|
function viewContainerSites(view, basePath) {
|
|
3652
|
-
if (!
|
|
3887
|
+
if (!isRec10(view)) return [];
|
|
3653
3888
|
const sites = [{ view, path: basePath, surface: "", kind: "self" }];
|
|
3654
|
-
if (
|
|
3889
|
+
if (isRec10(view.form)) {
|
|
3655
3890
|
sites.push({ view: view.form, path: `${basePath}.form`, surface: "form", kind: "form" });
|
|
3656
3891
|
}
|
|
3657
3892
|
for (const key of ["listViews", "formViews"]) {
|
|
3658
3893
|
const container = view[key];
|
|
3659
|
-
if (!
|
|
3894
|
+
if (!isRec10(container)) continue;
|
|
3660
3895
|
const kind = key === "listViews" ? "listView" : "formView";
|
|
3661
3896
|
for (const [subKey, sub] of Object.entries(container)) {
|
|
3662
|
-
if (!
|
|
3897
|
+
if (!isRec10(sub)) continue;
|
|
3663
3898
|
sites.push({
|
|
3664
3899
|
view: sub,
|
|
3665
3900
|
path: `${basePath}.${key}.${subKey}`,
|
|
@@ -3684,7 +3919,7 @@ function asArray16(v) {
|
|
|
3684
3919
|
}
|
|
3685
3920
|
return [];
|
|
3686
3921
|
}
|
|
3687
|
-
function
|
|
3922
|
+
function isRec11(v) {
|
|
3688
3923
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
3689
3924
|
}
|
|
3690
3925
|
function strName6(v) {
|
|
@@ -3718,7 +3953,7 @@ function validateFormLayout(stack) {
|
|
|
3718
3953
|
const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
|
|
3719
3954
|
for (let s = 0; s < sections.length; s++) {
|
|
3720
3955
|
const sec = sections[s];
|
|
3721
|
-
const secFields =
|
|
3956
|
+
const secFields = isRec11(sec) && Array.isArray(sec.fields) ? sec.fields : [];
|
|
3722
3957
|
for (let f = 0; f < secFields.length; f++) {
|
|
3723
3958
|
const entry = secFields[f];
|
|
3724
3959
|
const fname = fieldNameOf(entry);
|
|
@@ -3733,7 +3968,7 @@ function validateFormLayout(stack) {
|
|
|
3733
3968
|
hint: `Fix the field name, or add "${fname}" to ${objName}. Section field references must match the object's field names exactly.`
|
|
3734
3969
|
});
|
|
3735
3970
|
}
|
|
3736
|
-
const colSpan =
|
|
3971
|
+
const colSpan = isRec11(entry) ? entry.colSpan : void 0;
|
|
3737
3972
|
if (colSpan != null) {
|
|
3738
3973
|
findings.push({
|
|
3739
3974
|
severity: "warning",
|
|
@@ -3756,9 +3991,67 @@ function validateFormLayout(stack) {
|
|
|
3756
3991
|
import {
|
|
3757
3992
|
collectCelRootIdentifiers as collectCelRootIdentifiers2,
|
|
3758
3993
|
firstUndeclaredReference,
|
|
3759
|
-
parseCelToAst as
|
|
3994
|
+
parseCelToAst as parseCelToAst3,
|
|
3760
3995
|
parseCelToAstWithReason
|
|
3761
3996
|
} from "@objectstack/formula";
|
|
3997
|
+
|
|
3998
|
+
// src/predicate-rhs-position.ts
|
|
3999
|
+
function isNode2(v) {
|
|
4000
|
+
return !!v && typeof v === "object" && typeof v.op === "string";
|
|
4001
|
+
}
|
|
4002
|
+
var EQUALITY_OPS = /* @__PURE__ */ new Set(["==", "!="]);
|
|
4003
|
+
var COMPREHENSION_MACROS = /* @__PURE__ */ new Set(["all", "exists", "exists_one", "map", "filter"]);
|
|
4004
|
+
function bareId(node) {
|
|
4005
|
+
if (!isNode2(node)) return null;
|
|
4006
|
+
return node.op === "id" && typeof node.args === "string" ? node.args : null;
|
|
4007
|
+
}
|
|
4008
|
+
function bareRhsOnlyIdentifiers(ast) {
|
|
4009
|
+
const rhs = /* @__PURE__ */ new Set();
|
|
4010
|
+
const elsewhere = /* @__PURE__ */ new Set();
|
|
4011
|
+
const walk = (node, suppressible) => {
|
|
4012
|
+
if (Array.isArray(node)) {
|
|
4013
|
+
for (const child of node) walk(child, suppressible);
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
4016
|
+
if (!isNode2(node)) return;
|
|
4017
|
+
const args = node.args;
|
|
4018
|
+
if (node.op === "rcall" && Array.isArray(args) && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
|
|
4019
|
+
walk(args[1], suppressible);
|
|
4020
|
+
walk(args[2], false);
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
if (typeof node.op === "string" && EQUALITY_OPS.has(node.op) && Array.isArray(args) && args.length === 2) {
|
|
4024
|
+
const right = suppressible ? bareId(args[1]) : null;
|
|
4025
|
+
walk(args[0], suppressible);
|
|
4026
|
+
if (right !== null) {
|
|
4027
|
+
rhs.add(right);
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
walk(args[1], suppressible);
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
const name = bareId(node);
|
|
4034
|
+
if (name !== null) {
|
|
4035
|
+
elsewhere.add(name);
|
|
4036
|
+
return;
|
|
4037
|
+
}
|
|
4038
|
+
walk(args, suppressible);
|
|
4039
|
+
};
|
|
4040
|
+
walk(ast, true);
|
|
4041
|
+
for (const name of elsewhere) rhs.delete(name);
|
|
4042
|
+
return rhs;
|
|
4043
|
+
}
|
|
4044
|
+
function isRec12(v) {
|
|
4045
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
4046
|
+
}
|
|
4047
|
+
function schemaIdOf(view) {
|
|
4048
|
+
const data = view.data;
|
|
4049
|
+
if (!isRec12(data)) return void 0;
|
|
4050
|
+
if (data.provider !== "schema") return void 0;
|
|
4051
|
+
return typeof data.schemaId === "string" ? data.schemaId : void 0;
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
// src/validate-visibility-predicates.ts
|
|
3762
4055
|
var VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
|
|
3763
4056
|
var VISIBILITY_BARE_IDENTIFIER = "visibility-bare-identifier";
|
|
3764
4057
|
var VISIBILITY_PREDICATE_SYNTAX = "visibility-predicate-syntax";
|
|
@@ -3806,7 +4099,7 @@ function boundName(overrun) {
|
|
|
3806
4099
|
return overrun.limit && overrun.limitValue !== null ? `the \`${overrun.limit}\` budget (platform limit ${overrun.limitValue})` : "one of the platform's parse budgets";
|
|
3807
4100
|
}
|
|
3808
4101
|
var VIEW_PAGE_EXTRA_ROOTS = ["current_user", "page"];
|
|
3809
|
-
function
|
|
4102
|
+
function isNode3(v) {
|
|
3810
4103
|
return !!v && typeof v === "object" && typeof v.op === "string";
|
|
3811
4104
|
}
|
|
3812
4105
|
function namespaceRoots(node, out) {
|
|
@@ -3814,22 +4107,27 @@ function namespaceRoots(node, out) {
|
|
|
3814
4107
|
for (const child of node) namespaceRoots(child, out);
|
|
3815
4108
|
return;
|
|
3816
4109
|
}
|
|
3817
|
-
if (!
|
|
4110
|
+
if (!isNode3(node)) return;
|
|
3818
4111
|
const args = node.args;
|
|
3819
4112
|
if (Array.isArray(args)) {
|
|
3820
4113
|
const receiver = node.op === "rcall" ? args[1] : args[0];
|
|
3821
|
-
if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") &&
|
|
4114
|
+
if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode3(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
|
|
3822
4115
|
out.add(receiver.args);
|
|
3823
4116
|
}
|
|
3824
4117
|
}
|
|
3825
4118
|
namespaceRoots(args, out);
|
|
3826
4119
|
}
|
|
3827
|
-
function firstBareIdentifier(source) {
|
|
3828
|
-
const ast =
|
|
4120
|
+
function firstBareIdentifier(source, literalRhs) {
|
|
4121
|
+
const ast = parseCelToAst3(source);
|
|
3829
4122
|
if (!ast) return null;
|
|
3830
4123
|
const rooted = /* @__PURE__ */ new Set();
|
|
3831
4124
|
namespaceRoots(ast, rooted);
|
|
3832
|
-
|
|
4125
|
+
const literalSlot = literalRhs ? bareRhsOnlyIdentifiers(ast) : [];
|
|
4126
|
+
return firstUndeclaredReference(source, [
|
|
4127
|
+
...VIEW_PAGE_EXTRA_ROOTS,
|
|
4128
|
+
...rooted,
|
|
4129
|
+
...literalSlot
|
|
4130
|
+
]);
|
|
3833
4131
|
}
|
|
3834
4132
|
var CANONICAL_ROOT_BY_LAYER = {
|
|
3835
4133
|
runtime: "record",
|
|
@@ -3838,16 +4136,16 @@ var CANONICAL_ROOT_BY_LAYER = {
|
|
|
3838
4136
|
var MISLAYER_BY_LAYER = {
|
|
3839
4137
|
runtime: {
|
|
3840
4138
|
forbiddenRoot: "data",
|
|
3841
|
-
message: "visibility predicate is rooted at `data.` \u2014 that is the metadata-editing
|
|
4139
|
+
message: "visibility predicate is rooted at `data.` \u2014 that is the root a metadata-editing form binds (the row under edit), not a runtime surface. A runtime view/page predicate that binds `data.` never matches and the element renders unconditionally (ADR-0089).",
|
|
3842
4140
|
hint: "Runtime record surfaces bind `record` + `current_user` (pages also expose `page.<var>`). Use e.g. `record.status == 'open'` instead of `data.status == 'open'`."
|
|
3843
4141
|
},
|
|
3844
4142
|
metadata: {
|
|
3845
4143
|
forbiddenRoot: "record",
|
|
3846
|
-
message: "visibility predicate is rooted at `record.` \u2014 that is the
|
|
4144
|
+
message: "visibility predicate is rooted at `record.` \u2014 that is the root a runtime view/page surface binds (the live record), not the root a metadata-editing form binds. On a metadata-editing form \u2014 the row under edit \u2014 a `record.`-rooted predicate never matches and the element renders unconditionally (ADR-0089).",
|
|
3847
4145
|
hint: "Metadata-editing forms bind `data` (the row under edit). Use e.g. `data.type == 'grid'` instead of `record.type == 'grid'`."
|
|
3848
4146
|
}
|
|
3849
4147
|
};
|
|
3850
|
-
function checkElement(el, where, path, layer, findings) {
|
|
4148
|
+
function checkElement(el, where, path, layer, findings, literalRhs = false) {
|
|
3851
4149
|
const raw = el[CANONICAL] ?? el.visibleOn ?? el.visibility;
|
|
3852
4150
|
const source = predicateSource(raw);
|
|
3853
4151
|
const rule = MISLAYER_BY_LAYER[layer];
|
|
@@ -3885,7 +4183,7 @@ function checkElement(el, where, path, layer, findings) {
|
|
|
3885
4183
|
});
|
|
3886
4184
|
}
|
|
3887
4185
|
if (source && !refusal) {
|
|
3888
|
-
const bare = firstBareIdentifier(source);
|
|
4186
|
+
const bare = firstBareIdentifier(source, literalRhs);
|
|
3889
4187
|
if (bare) {
|
|
3890
4188
|
const root = CANONICAL_ROOT_BY_LAYER[layer];
|
|
3891
4189
|
findings.push({
|
|
@@ -3894,7 +4192,7 @@ function checkElement(el, where, path, layer, findings) {
|
|
|
3894
4192
|
where,
|
|
3895
4193
|
path,
|
|
3896
4194
|
message: `visibility predicate references \`${bare}\` as a bare identifier. Values are bound under a namespace on this surface \u2014 they are never flattened to top level \u2014 so \`${bare}\` resolves to nothing, the predicate can never evaluate, and the console falls OPEN: the element renders unconditionally and looks exactly like one with no predicate at all (#5149).`,
|
|
3897
|
-
hint: `Write \`${root}.${bare}\` instead of \`${bare}\`` + (layer === "runtime" ? " (runtime view/page surfaces bind `record` + `current_user`; a page component also exposes page state as `page.<var>`)." : " (a
|
|
4195
|
+
hint: `Write \`${root}.${bare}\` instead of \`${bare}\`` + (layer === "runtime" ? " (runtime view/page surfaces bind `record` + `current_user`; a page component also exposes page state as `page.<var>`)." : " (a metadata-editing form binds the row under edit as `data`).")
|
|
3898
4196
|
});
|
|
3899
4197
|
}
|
|
3900
4198
|
}
|
|
@@ -3903,24 +4201,27 @@ function isFieldObject(entry) {
|
|
|
3903
4201
|
return !!entry && typeof entry === "object" && !Array.isArray(entry);
|
|
3904
4202
|
}
|
|
3905
4203
|
function validateVisibilityPredicates(stack, opts = {}) {
|
|
3906
|
-
const
|
|
4204
|
+
const declaredLayer = opts.layer ?? "runtime";
|
|
3907
4205
|
const findings = [];
|
|
3908
4206
|
for (const { rec: view, path: viewPath } of collectionEntries(stack.views, "views")) {
|
|
3909
4207
|
const viewName = typeof view.name === "string" ? view.name : typeof view.object === "string" ? view.object : viewPath;
|
|
3910
4208
|
for (const site of formViewSites(view, viewPath)) {
|
|
3911
4209
|
const where = site.surface ? `view "${viewName}" \xB7 ${site.surface}` : `view "${viewName}"`;
|
|
4210
|
+
const schemaBound = schemaIdOf(site.view) !== void 0;
|
|
4211
|
+
const literalRhs = schemaBound;
|
|
4212
|
+
const layer = schemaBound ? "metadata" : declaredLayer;
|
|
3912
4213
|
for (const bucket of ["sections", "groups"]) {
|
|
3913
4214
|
const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
|
|
3914
4215
|
for (let s = 0; s < sections.length; s++) {
|
|
3915
4216
|
const sec = sections[s];
|
|
3916
4217
|
if (!sec || typeof sec !== "object") continue;
|
|
3917
4218
|
const secPath = `${site.path}.${bucket}[${s}]`;
|
|
3918
|
-
checkElement(sec, where, secPath, layer, findings);
|
|
4219
|
+
checkElement(sec, where, secPath, layer, findings, literalRhs);
|
|
3919
4220
|
const secFields = Array.isArray(sec.fields) ? sec.fields : [];
|
|
3920
4221
|
for (let f = 0; f < secFields.length; f++) {
|
|
3921
4222
|
const entry = secFields[f];
|
|
3922
4223
|
if (isFieldObject(entry)) {
|
|
3923
|
-
checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings);
|
|
4224
|
+
checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings, literalRhs);
|
|
3924
4225
|
}
|
|
3925
4226
|
}
|
|
3926
4227
|
}
|
|
@@ -3931,21 +4232,21 @@ function validateVisibilityPredicates(stack, opts = {}) {
|
|
|
3931
4232
|
const pageName = typeof page.name === "string" ? page.name : void 0;
|
|
3932
4233
|
const where = `page "${pageName ?? pagePath}"`;
|
|
3933
4234
|
for (const walked of walkPageComponents(page, pagePath)) {
|
|
3934
|
-
checkElement(walked.component, where, walked.path,
|
|
4235
|
+
checkElement(walked.component, where, walked.path, declaredLayer, findings);
|
|
3935
4236
|
}
|
|
3936
4237
|
}
|
|
3937
4238
|
return findings;
|
|
3938
4239
|
}
|
|
3939
4240
|
|
|
3940
4241
|
// src/validate-predicate-path-refs.ts
|
|
3941
|
-
import { parseCelToAst as
|
|
4242
|
+
import { parseCelToAst as parseCelToAst4 } from "@objectstack/formula";
|
|
3942
4243
|
import { getMetadataTypeSchema } from "@objectstack/spec/kernel";
|
|
3943
4244
|
import { findClosestMatches, formatSuggestion } from "@objectstack/spec";
|
|
3944
4245
|
var PREDICATE_PATH_UNRESOLVED = "predicate-path-unresolved";
|
|
3945
4246
|
var PREDICATE_PATH_UNROOTED = "predicate-path-unrooted";
|
|
4247
|
+
var PREDICATE_RHS_PATH_SHAPED = "predicate-rhs-path-shaped";
|
|
3946
4248
|
var PREDICATE_KEYS = ["visibleWhen", "visibleOn"];
|
|
3947
4249
|
var ROOT = "data";
|
|
3948
|
-
var COMPREHENSION_MACROS = /* @__PURE__ */ new Set(["all", "exists", "exists_one", "map", "filter"]);
|
|
3949
4250
|
function defOf(schema) {
|
|
3950
4251
|
if (!schema || typeof schema !== "object" && typeof schema !== "function") return void 0;
|
|
3951
4252
|
const s = schema;
|
|
@@ -4038,11 +4339,11 @@ function stepInto(scope, segment) {
|
|
|
4038
4339
|
if (!declared.includes(segment)) return { kind: "undeclared", declared };
|
|
4039
4340
|
return { kind: "declared", next: propertyOf(u, segment) };
|
|
4040
4341
|
}
|
|
4041
|
-
function
|
|
4342
|
+
function isNode4(v) {
|
|
4042
4343
|
return !!v && typeof v === "object" && typeof v.op === "string";
|
|
4043
4344
|
}
|
|
4044
4345
|
function memberChain(node) {
|
|
4045
|
-
if (!
|
|
4346
|
+
if (!isNode4(node)) return null;
|
|
4046
4347
|
if (node.op === "id" && typeof node.args === "string") return [node.args];
|
|
4047
4348
|
if (node.op === "." && Array.isArray(node.args) && typeof node.args[1] === "string") {
|
|
4048
4349
|
const head = memberChain(node.args[0]);
|
|
@@ -4055,7 +4356,7 @@ function rootedPaths(node, out) {
|
|
|
4055
4356
|
for (const child of node) rootedPaths(child, out);
|
|
4056
4357
|
return;
|
|
4057
4358
|
}
|
|
4058
|
-
if (!
|
|
4359
|
+
if (!isNode4(node)) return;
|
|
4059
4360
|
if (node.op === ".") {
|
|
4060
4361
|
const chain = memberChain(node);
|
|
4061
4362
|
if (chain && chain[0] === ROOT && chain.length > 1) {
|
|
@@ -4065,23 +4366,40 @@ function rootedPaths(node, out) {
|
|
|
4065
4366
|
}
|
|
4066
4367
|
rootedPaths(node.args, out);
|
|
4067
4368
|
}
|
|
4369
|
+
var PATH_SHAPED_RHS = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/;
|
|
4370
|
+
function equalitySites(node, out) {
|
|
4371
|
+
if (Array.isArray(node)) {
|
|
4372
|
+
for (const child of node) equalitySites(child, out);
|
|
4373
|
+
return;
|
|
4374
|
+
}
|
|
4375
|
+
if (!isNode4(node)) return;
|
|
4376
|
+
const args = node.args;
|
|
4377
|
+
if (node.op === "rcall" && Array.isArray(args) && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
|
|
4378
|
+
equalitySites(args[1], out);
|
|
4379
|
+
return;
|
|
4380
|
+
}
|
|
4381
|
+
if (typeof node.op === "string" && EQUALITY_OPS.has(node.op) && Array.isArray(args) && args.length === 2) {
|
|
4382
|
+
out.push({ op: node.op, right: args[1] });
|
|
4383
|
+
}
|
|
4384
|
+
equalitySites(args, out);
|
|
4385
|
+
}
|
|
4068
4386
|
function classifyIdentifiers(node, values, excluded) {
|
|
4069
4387
|
if (Array.isArray(node)) {
|
|
4070
4388
|
for (const child of node) classifyIdentifiers(child, values, excluded);
|
|
4071
4389
|
return;
|
|
4072
4390
|
}
|
|
4073
|
-
if (!
|
|
4391
|
+
if (!isNode4(node)) return;
|
|
4074
4392
|
const args = node.args;
|
|
4075
4393
|
if (Array.isArray(args)) {
|
|
4076
4394
|
const receiver = node.op === "rcall" ? args[1] : args[0];
|
|
4077
|
-
if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") &&
|
|
4395
|
+
if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode4(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
|
|
4078
4396
|
excluded.add(receiver.args);
|
|
4079
4397
|
}
|
|
4080
4398
|
if (node.op === "rcall" && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
|
|
4081
4399
|
const macroArgs = args[2];
|
|
4082
4400
|
if (Array.isArray(macroArgs) && macroArgs.length >= 2) {
|
|
4083
4401
|
const bound = macroArgs[0];
|
|
4084
|
-
if (
|
|
4402
|
+
if (isNode4(bound) && bound.op === "id" && typeof bound.args === "string") {
|
|
4085
4403
|
excluded.add(bound.args);
|
|
4086
4404
|
}
|
|
4087
4405
|
}
|
|
@@ -4100,17 +4418,11 @@ function predicateSource2(v) {
|
|
|
4100
4418
|
}
|
|
4101
4419
|
return void 0;
|
|
4102
4420
|
}
|
|
4103
|
-
function
|
|
4421
|
+
function isRec13(v) {
|
|
4104
4422
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
4105
4423
|
}
|
|
4106
|
-
function schemaIdOf(view) {
|
|
4107
|
-
const data = view.data;
|
|
4108
|
-
if (!isRec11(data)) return void 0;
|
|
4109
|
-
if (data.provider !== "schema") return void 0;
|
|
4110
|
-
return typeof data.schemaId === "string" ? data.schemaId : void 0;
|
|
4111
|
-
}
|
|
4112
4424
|
function checkPredicate(source, scope, where, path, findings) {
|
|
4113
|
-
const ast =
|
|
4425
|
+
const ast = parseCelToAst4(source);
|
|
4114
4426
|
if (!ast) return;
|
|
4115
4427
|
const paths = [];
|
|
4116
4428
|
rootedPaths(ast, paths);
|
|
@@ -4138,19 +4450,38 @@ function checkPredicate(source, scope, where, path, findings) {
|
|
|
4138
4450
|
}
|
|
4139
4451
|
}
|
|
4140
4452
|
const declaredHere = keysOf(scope);
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4453
|
+
const rhsOnly = bareRhsOnlyIdentifiers(ast);
|
|
4454
|
+
if (declaredHere) {
|
|
4455
|
+
const values = /* @__PURE__ */ new Set();
|
|
4456
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
4457
|
+
classifyIdentifiers(ast, values, excluded);
|
|
4458
|
+
for (const id of values) {
|
|
4459
|
+
if (excluded.has(id) || rhsOnly.has(id) || !declaredHere.includes(id)) continue;
|
|
4460
|
+
findings.push({
|
|
4461
|
+
severity: "error",
|
|
4462
|
+
rule: PREDICATE_PATH_UNROOTED,
|
|
4463
|
+
where,
|
|
4464
|
+
path,
|
|
4465
|
+
message: `predicate references \`${id}\` as a bare identifier, but \`${id}\` is a key of the schema this form edits \u2014 the binding root was dropped. Values are bound under \`${ROOT}\` and are never flattened to top level, so \`${id}\` resolves to nothing, the predicate can never evaluate and the console falls OPEN: the element renders unconditionally and looks exactly like one carrying no predicate at all (#5149, #6254).`,
|
|
4466
|
+
hint: `Write \`${ROOT}.${id}\` instead of \`${id}\`. A metadata-editing form binds the row under edit as \`${ROOT}\` at every depth \u2014 inside a repeater \`${ROOT}\` is the ROW, but it is still spelled \`${ROOT}\` (there is no implicit row scope).`
|
|
4467
|
+
});
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
const sites = [];
|
|
4471
|
+
equalitySites(ast, sites);
|
|
4472
|
+
for (const { op, right } of sites) {
|
|
4473
|
+
const chain = memberChain(right);
|
|
4474
|
+
if (!chain) continue;
|
|
4475
|
+
const text = chain.join(".");
|
|
4476
|
+
if (!PATH_SHAPED_RHS.test(text)) continue;
|
|
4477
|
+
const dotted = chain.length > 1;
|
|
4147
4478
|
findings.push({
|
|
4148
|
-
severity: "error",
|
|
4149
|
-
rule:
|
|
4479
|
+
severity: dotted ? "error" : "warning",
|
|
4480
|
+
rule: PREDICATE_RHS_PATH_SHAPED,
|
|
4150
4481
|
where,
|
|
4151
4482
|
path,
|
|
4152
|
-
message: `predicate
|
|
4153
|
-
hint: `
|
|
4483
|
+
message: dotted ? `predicate compares against \`${text}\` on the RIGHT of \`${op}\`, which is a path but is not evaluated as one. A metadata-editing form resolves paths on the LEFT of \`${op}\` only; the right-hand side goes to the literal parser, so \`${text}\` is compared as the literal string "${text}". The verdict therefore does not depend on the right-hand path at all: \`a == ${text}\` is FALSE even when both sides hold the same value, and \`a != ${text}\` is correspondingly TRUE. An \`==\` written this way hides the element on every row, and nothing in the console says why (objectui#4049).` : `predicate compares against the unquoted word \`${text}\` on the RIGHT of \`${op}\`. The right-hand side of \`${op}\` is a literal, never a reference, so this is read as the literal string "${text}" \u2014 which is probably what you meant, and is why it appears to work. It is outside the declared subset all the same (\`path == 'literal'\`), and it stops working when this surface moves to the real CEL evaluator, where a bare \`${text}\` resolves to nothing (objectui#4049). The token also reads as a \`${ROOT}.\` root someone dropped, so this one finding carries BOTH readings: which one you meant is the thing no linter can know, and it changes the fix (#7696).`,
|
|
4484
|
+
hint: dotted ? `Two sanctioned spellings. (1) If you meant the TEXT, quote it: \`${op} '${text}'\`. (2) If you meant the PATH, restructure so the path is on the LEFT and a literal is on the right \u2014 comparing one path against another is outside the subset this surface renders, which is \`path == 'literal'\` / \`path != 'literal'\` and nothing wider. There is no third spelling that compares two paths here.` : `Two sanctioned spellings, and you must pick \u2014 they are not the same predicate. (1) If you meant the TEXT \`${text}\`, quote it: \`${op} '${text}'\`. That is what this renders as today, so it changes no behaviour and is the fix unless you know otherwise. (2) If you meant the FIELD \`${ROOT}.${text}\`, move it to the LEFT and put a literal on the right, e.g. \`${ROOT}.${text} == 'yes'\`. \u26D4 Do NOT simply add the root in place: \`${op} ${ROOT}.${text}\` is a path on the RIGHT, which this surface parses as the literal string "${ROOT}.${text}" \u2014 it is refused by this same rule at \`error\`, and it is FALSE on every row. The subset here is \`path == 'literal'\` and nothing wider.`
|
|
4154
4485
|
});
|
|
4155
4486
|
}
|
|
4156
4487
|
}
|
|
@@ -4158,7 +4489,7 @@ function walkFields(entries, scope, where, base, findings, depth) {
|
|
|
4158
4489
|
if (!Array.isArray(entries) || depth > 12) return;
|
|
4159
4490
|
for (let i = 0; i < entries.length; i++) {
|
|
4160
4491
|
const entry = entries[i];
|
|
4161
|
-
if (!
|
|
4492
|
+
if (!isRec13(entry)) continue;
|
|
4162
4493
|
const path = `${base}[${i}]`;
|
|
4163
4494
|
for (const key of PREDICATE_KEYS) {
|
|
4164
4495
|
const source = predicateSource2(entry[key]);
|
|
@@ -4185,15 +4516,14 @@ function validatePredicatePathRefs(stack, opts = {}) {
|
|
|
4185
4516
|
try {
|
|
4186
4517
|
root = resolveSchema(schemaId);
|
|
4187
4518
|
} catch {
|
|
4188
|
-
|
|
4519
|
+
root = void 0;
|
|
4189
4520
|
}
|
|
4190
|
-
if (!root) continue;
|
|
4191
4521
|
const where = site.surface ? `view "${viewName}" \xB7 ${site.surface} (schema "${schemaId}")` : `view "${viewName}" (schema "${schemaId}")`;
|
|
4192
4522
|
for (const bucket of ["sections", "groups"]) {
|
|
4193
4523
|
const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
|
|
4194
4524
|
for (let s = 0; s < sections.length; s++) {
|
|
4195
4525
|
const section = sections[s];
|
|
4196
|
-
if (!
|
|
4526
|
+
if (!isRec13(section)) continue;
|
|
4197
4527
|
const sectionPath = `${site.path}.${bucket}[${s}]`;
|
|
4198
4528
|
for (const key of PREDICATE_KEYS) {
|
|
4199
4529
|
const source = predicateSource2(section[key]);
|
|
@@ -4642,6 +4972,7 @@ var SECURITY_MASTER_DETAIL_UNGRANTED = "security-master-detail-ungranted";
|
|
|
4642
4972
|
var SECURITY_FLS_UNQUALIFIED_KEY = "security-fls-unqualified-key";
|
|
4643
4973
|
var SECURITY_GRANT_EXPIRED_AT_AUTHORING = "security-grant-expired-at-authoring";
|
|
4644
4974
|
var SECURITY_DELEGATION_MISSING_REASON = "security-delegation-missing-reason";
|
|
4975
|
+
var SECURITY_CBP_NO_RELATION = "security-controlled-by-parent-no-relation";
|
|
4645
4976
|
var CANONICAL_OWD = ["private", "public_read", "public_read_write", "controlled_by_parent"];
|
|
4646
4977
|
var OWD_ALIAS_FIX = {
|
|
4647
4978
|
read: "public_read",
|
|
@@ -4687,6 +5018,13 @@ function firstMasterDetailField(obj) {
|
|
|
4687
5018
|
}
|
|
4688
5019
|
return void 0;
|
|
4689
5020
|
}
|
|
5021
|
+
function resolveCbpRelation(obj) {
|
|
5022
|
+
const entries = asArray19(obj.fields);
|
|
5023
|
+
const pick = (pred) => entries.find((f) => pred(f) && refOf(f));
|
|
5024
|
+
const found = pick((f) => f.type === "master_detail" && !!f.required) ?? pick((f) => f.type === "master_detail") ?? pick((f) => f.type === "lookup" && !!f.required);
|
|
5025
|
+
if (!found) return void 0;
|
|
5026
|
+
return { field: String(found.name ?? "?"), type: String(found.type), master: refOf(found) };
|
|
5027
|
+
}
|
|
4690
5028
|
function grantsObjectAccess(p) {
|
|
4691
5029
|
return p.allowRead === true || p.allowCreate === true || p.allowEdit === true || p.allowDelete === true || p.viewAllRecords === true || p.modifyAllRecords === true;
|
|
4692
5030
|
}
|
|
@@ -4732,6 +5070,16 @@ function validateSecurityPosture(stack, opts) {
|
|
|
4732
5070
|
});
|
|
4733
5071
|
}
|
|
4734
5072
|
}
|
|
5073
|
+
if (owd === "controlled_by_parent" && !resolveCbpRelation(obj)) {
|
|
5074
|
+
findings.push({
|
|
5075
|
+
severity: "error",
|
|
5076
|
+
rule: SECURITY_CBP_NO_RELATION,
|
|
5077
|
+
where: `object "${objName}"`,
|
|
5078
|
+
path: `${objPath}.sharingModel`,
|
|
5079
|
+
message: `"${objName}" declares sharingModel 'controlled_by_parent' but has no relation the platform can derive access from. ADR-0055 resolves the master through a required master_detail, then any master_detail, then a required lookup \u2014 each of which must also name a reference target \u2014 and this object matches none of the three. At runtime every read is DENIED and every write is refused with 422 INVALID_METADATA (#7474), so the object is unusable rather than merely locked down.`,
|
|
5080
|
+
hint: `Add the master relation this object is derived from, e.g. fields.parent: { type: 'master_detail', reference: '<master_object>', required: true }. If the object has no master, its baseline is its own decision \u2014 use sharingModel: 'private' (owner + shares), 'public_read', or 'public_read_write'.`
|
|
5081
|
+
});
|
|
5082
|
+
}
|
|
4735
5083
|
if (typeof external === "string") {
|
|
4736
5084
|
if (OWD_ALIAS_FIX[external]) {
|
|
4737
5085
|
findings.push({
|
|
@@ -4797,53 +5145,6 @@ function validateSecurityPosture(stack, opts) {
|
|
|
4797
5145
|
}
|
|
4798
5146
|
}
|
|
4799
5147
|
}
|
|
4800
|
-
const flagRole = (kind, name, label2, where, path) => {
|
|
4801
|
-
if (identifierHasRoleToken(name)) {
|
|
4802
|
-
findings.push({
|
|
4803
|
-
severity: "error",
|
|
4804
|
-
rule: SECURITY_ROLE_WORD,
|
|
4805
|
-
where,
|
|
4806
|
-
path,
|
|
4807
|
-
message: `${kind} name "${String(name)}" uses the reserved word "role" \u2014 the platform vocabulary is permission_set (capability), position (distribution), business_unit (hierarchy) (ADR-0090 D3).`,
|
|
4808
|
-
hint: `Rename using 'position' for distribution groups or a domain word (e.g. 'function', 'duty').`
|
|
4809
|
-
});
|
|
4810
|
-
} else if (labelHasRoleWord(label2)) {
|
|
4811
|
-
findings.push({
|
|
4812
|
-
severity: "error",
|
|
4813
|
-
rule: SECURITY_ROLE_WORD,
|
|
4814
|
-
where,
|
|
4815
|
-
path: `${path.replace(/\.name$/, "")}.label`,
|
|
4816
|
-
message: `${kind} label "${String(label2)}" uses the reserved word "role" (ADR-0090 D3).`,
|
|
4817
|
-
hint: `Relabel with 'Position' (distribution) or a domain word \u2014 admins must meet ONE vocabulary.`
|
|
4818
|
-
});
|
|
4819
|
-
}
|
|
4820
|
-
};
|
|
4821
|
-
for (let i = 0; i < objects.length; i++) {
|
|
4822
|
-
const obj = objects[i];
|
|
4823
|
-
if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
|
|
4824
|
-
const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
|
|
4825
|
-
flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
|
|
4826
|
-
for (const f of asArray19(obj.fields)) {
|
|
4827
|
-
flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
|
|
4828
|
-
}
|
|
4829
|
-
for (const [ai, action] of asArray19(obj.actions).entries()) {
|
|
4830
|
-
flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
|
|
4831
|
-
}
|
|
4832
|
-
}
|
|
4833
|
-
for (let i = 0; i < permissionSets.length; i++) {
|
|
4834
|
-
const ps = permissionSets[i];
|
|
4835
|
-
if (!ps || typeof ps !== "object") continue;
|
|
4836
|
-
flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
|
|
4837
|
-
}
|
|
4838
|
-
for (const [i, pos] of asArray19(stack.positions).entries()) {
|
|
4839
|
-
flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
|
|
4840
|
-
}
|
|
4841
|
-
for (const [i, app] of asArray19(stack.apps).entries()) {
|
|
4842
|
-
flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
|
|
4843
|
-
}
|
|
4844
|
-
for (const [i, book] of asArray19(stack.books).entries()) {
|
|
4845
|
-
flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
|
|
4846
|
-
}
|
|
4847
5148
|
const stackSetNames = new Set(
|
|
4848
5149
|
permissionSets.map((ps) => typeof ps.name === "string" ? ps.name : void 0).filter((n) => !!n)
|
|
4849
5150
|
);
|
|
@@ -4964,6 +5265,60 @@ function validateSecurityPosture(stack, opts) {
|
|
|
4964
5265
|
}
|
|
4965
5266
|
return findings;
|
|
4966
5267
|
}
|
|
5268
|
+
function validateSecurityRoleWord(stack) {
|
|
5269
|
+
const findings = [];
|
|
5270
|
+
if (!stack || typeof stack !== "object") return findings;
|
|
5271
|
+
const objects = asArray19(stack.objects);
|
|
5272
|
+
const permissionSets = asArray19(stack.permissions);
|
|
5273
|
+
const flagRole = (kind, name, label2, where, path) => {
|
|
5274
|
+
if (identifierHasRoleToken(name)) {
|
|
5275
|
+
findings.push({
|
|
5276
|
+
severity: "error",
|
|
5277
|
+
rule: SECURITY_ROLE_WORD,
|
|
5278
|
+
where,
|
|
5279
|
+
path,
|
|
5280
|
+
message: `${kind} name "${String(name)}" uses the reserved word "role" \u2014 the platform vocabulary is permission_set (capability), position (distribution), business_unit (hierarchy) (ADR-0090 D3).`,
|
|
5281
|
+
hint: `Rename using 'position' for distribution groups or a domain word (e.g. 'function', 'duty').`
|
|
5282
|
+
});
|
|
5283
|
+
} else if (labelHasRoleWord(label2)) {
|
|
5284
|
+
findings.push({
|
|
5285
|
+
severity: "error",
|
|
5286
|
+
rule: SECURITY_ROLE_WORD,
|
|
5287
|
+
where,
|
|
5288
|
+
path: `${path.replace(/\.name$/, "")}.label`,
|
|
5289
|
+
message: `${kind} label "${String(label2)}" uses the reserved word "role" (ADR-0090 D3).`,
|
|
5290
|
+
hint: `Relabel with 'Position' (distribution) or a domain word \u2014 admins must meet ONE vocabulary.`
|
|
5291
|
+
});
|
|
5292
|
+
}
|
|
5293
|
+
};
|
|
5294
|
+
for (let i = 0; i < objects.length; i++) {
|
|
5295
|
+
const obj = objects[i];
|
|
5296
|
+
if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
|
|
5297
|
+
const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
|
|
5298
|
+
flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
|
|
5299
|
+
for (const f of asArray19(obj.fields)) {
|
|
5300
|
+
flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
|
|
5301
|
+
}
|
|
5302
|
+
for (const [ai, action] of asArray19(obj.actions).entries()) {
|
|
5303
|
+
flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
for (let i = 0; i < permissionSets.length; i++) {
|
|
5307
|
+
const ps = permissionSets[i];
|
|
5308
|
+
if (!ps || typeof ps !== "object") continue;
|
|
5309
|
+
flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
|
|
5310
|
+
}
|
|
5311
|
+
for (const [i, pos] of asArray19(stack.positions).entries()) {
|
|
5312
|
+
flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
|
|
5313
|
+
}
|
|
5314
|
+
for (const [i, app] of asArray19(stack.apps).entries()) {
|
|
5315
|
+
flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
|
|
5316
|
+
}
|
|
5317
|
+
for (const [i, book] of asArray19(stack.books).entries()) {
|
|
5318
|
+
flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
|
|
5319
|
+
}
|
|
5320
|
+
return findings;
|
|
5321
|
+
}
|
|
4967
5322
|
|
|
4968
5323
|
// src/validate-org-axis-red-lines.ts
|
|
4969
5324
|
var ORG_AXIS_PERMISSION_INHERITANCE = "org-axis-permission-inheritance";
|
|
@@ -5773,10 +6128,10 @@ function validateObjectReferences(stack) {
|
|
|
5773
6128
|
|
|
5774
6129
|
// src/validate-nav-target-refs.ts
|
|
5775
6130
|
var NAV_TARGET_UNRESOLVED = "nav-target-unresolved";
|
|
5776
|
-
var
|
|
6131
|
+
var isRec14 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
5777
6132
|
function asArray26(v) {
|
|
5778
|
-
if (Array.isArray(v)) return v.filter(
|
|
5779
|
-
if (
|
|
6133
|
+
if (Array.isArray(v)) return v.filter(isRec14);
|
|
6134
|
+
if (isRec14(v)) return Object.entries(v).map(([name, def]) => isRec14(def) ? { name, ...def } : { name });
|
|
5780
6135
|
return [];
|
|
5781
6136
|
}
|
|
5782
6137
|
function strName9(v) {
|
|
@@ -5798,7 +6153,7 @@ function namesOf(collection) {
|
|
|
5798
6153
|
}
|
|
5799
6154
|
function validateNavTargetRefs(stack) {
|
|
5800
6155
|
const findings = [];
|
|
5801
|
-
if (!
|
|
6156
|
+
if (!isRec14(stack)) return findings;
|
|
5802
6157
|
const apps = asArray26(stack.apps);
|
|
5803
6158
|
if (apps.length === 0) return findings;
|
|
5804
6159
|
const declared = /* @__PURE__ */ new Map();
|
|
@@ -5810,7 +6165,7 @@ function validateNavTargetRefs(stack) {
|
|
|
5810
6165
|
const walk = (items, basePath) => {
|
|
5811
6166
|
if (!Array.isArray(items)) return;
|
|
5812
6167
|
for (const [ni, raw] of items.entries()) {
|
|
5813
|
-
if (!
|
|
6168
|
+
if (!isRec14(raw)) continue;
|
|
5814
6169
|
const nav = raw;
|
|
5815
6170
|
const navPath = `${basePath}[${ni}]`;
|
|
5816
6171
|
for (const [type, prop, collection, noun] of NAV_TARGETS) {
|
|
@@ -5841,16 +6196,84 @@ function validateNavTargetRefs(stack) {
|
|
|
5841
6196
|
return findings;
|
|
5842
6197
|
}
|
|
5843
6198
|
|
|
6199
|
+
// src/validate-nav-object-servability.ts
|
|
6200
|
+
import { canServeApiOperation } from "@objectstack/spec/data";
|
|
6201
|
+
var NAV_OBJECT_UNSERVABLE = "nav-object-unservable";
|
|
6202
|
+
var isRec15 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
6203
|
+
function asArray27(v) {
|
|
6204
|
+
if (Array.isArray(v)) return v.filter(isRec15);
|
|
6205
|
+
if (isRec15(v)) return Object.entries(v).map(([name, def]) => isRec15(def) ? { name, ...def } : { name });
|
|
6206
|
+
return [];
|
|
6207
|
+
}
|
|
6208
|
+
function strName10(v) {
|
|
6209
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
6210
|
+
}
|
|
6211
|
+
var isInterpolated3 = (s) => s.includes("${") || s.includes("{");
|
|
6212
|
+
function validateNavObjectServability(stack) {
|
|
6213
|
+
const findings = [];
|
|
6214
|
+
if (!isRec15(stack)) return findings;
|
|
6215
|
+
const apps = asArray27(stack.apps);
|
|
6216
|
+
if (apps.length === 0) return findings;
|
|
6217
|
+
const ownEnable = /* @__PURE__ */ new Map();
|
|
6218
|
+
const objects = asArray27(stack.objects);
|
|
6219
|
+
for (const [oi, obj] of objects.entries()) {
|
|
6220
|
+
const n = strName10(obj.name);
|
|
6221
|
+
if (!n) continue;
|
|
6222
|
+
ownEnable.set(n, { enable: obj.enable, path: `objects[${oi}].enable` });
|
|
6223
|
+
}
|
|
6224
|
+
if (ownEnable.size === 0) return findings;
|
|
6225
|
+
for (const [ai, app] of apps.entries()) {
|
|
6226
|
+
const appName = strName10(app.name) ?? `#${ai}`;
|
|
6227
|
+
const walk = (items, basePath) => {
|
|
6228
|
+
if (!Array.isArray(items)) return;
|
|
6229
|
+
for (const [ni, raw] of items.entries()) {
|
|
6230
|
+
if (!isRec15(raw)) continue;
|
|
6231
|
+
const nav = raw;
|
|
6232
|
+
const navPath = `${basePath}[${ni}]`;
|
|
6233
|
+
if (nav.type === "object") {
|
|
6234
|
+
const target = strName10(nav.objectName);
|
|
6235
|
+
const declared = target && !isInterpolated3(target) ? ownEnable.get(target) : void 0;
|
|
6236
|
+
if (target && declared && !canServeApiOperation(declared.enable, "list")) {
|
|
6237
|
+
const enable = isRec15(declared.enable) ? declared.enable : {};
|
|
6238
|
+
const apiDisabled = enable.apiEnabled === false;
|
|
6239
|
+
const condition = apiDisabled ? "`enable.apiEnabled: false`" : "`enable.apiMethods` does not grant `list`" + (Array.isArray(enable.apiMethods) ? ` (declared: ${enable.apiMethods.length === 0 ? "[] \u2014 deny-all" : enable.apiMethods.map((m) => `\`${String(m)}\``).join(", ")})` : "");
|
|
6240
|
+
const answer = apiDisabled ? "404 `OBJECT_API_DISABLED`" : "405 `OBJECT_API_METHOD_NOT_ALLOWED`";
|
|
6241
|
+
const offendingKey = apiDisabled ? `${declared.path}.apiEnabled` : `${declared.path}.apiMethods`;
|
|
6242
|
+
findings.push({
|
|
6243
|
+
severity: "error",
|
|
6244
|
+
rule: NAV_OBJECT_UNSERVABLE,
|
|
6245
|
+
where: `app "${appName}" \xB7 nav "${strName10(nav.id) ?? strName10(nav.label) ?? `#${ni}`}"`,
|
|
6246
|
+
// The nav entry is where the dead row is authored; the `enable`
|
|
6247
|
+
// key that condemns it is named in the message, because the fix
|
|
6248
|
+
// may belong at either end.
|
|
6249
|
+
path: `${navPath}.objectName`,
|
|
6250
|
+
message: `Navigation targets object "${target}", which cannot serve a list: ${condition} (\`${offendingKey}\`), so the list request answers ${answer} for EVERY user \u2014 platform administrators included, since that gate reads only the object's \`enable\` block and never the caller. The entry cannot be rescued with \`requiredPermissions\`: they are independent conditions. The server prunes this entry from the served \`/meta\` payload (#7912), so publishing it ships a menu row that silently is not there.`,
|
|
6251
|
+
hint: `Remove the nav entry, or make "${target}" listable by setting \`enable.apiEnabled: true\` and granting \`list\` in \`enable.apiMethods\`. \u26D4 Do NOT open the API on an object that is disabled on purpose \u2014 several platform objects hold credential material and are API-disabled deliberately; for those the entry is the mistake, not the \`enable\` block.`
|
|
6252
|
+
});
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
if (Array.isArray(nav.children)) walk(nav.children, `${navPath}.children`);
|
|
6256
|
+
}
|
|
6257
|
+
};
|
|
6258
|
+
walk(app.navigation, `apps[${ai}].navigation`);
|
|
6259
|
+
for (const [ari, area] of asArray27(app.areas).entries()) {
|
|
6260
|
+
walk(area.items, `apps[${ai}].areas[${ari}].items`);
|
|
6261
|
+
walk(area.navigation, `apps[${ai}].areas[${ari}].navigation`);
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
return findings;
|
|
6265
|
+
}
|
|
6266
|
+
|
|
5844
6267
|
// src/validate-action-name-refs.ts
|
|
5845
6268
|
var ACTION_NAME_UNDEFINED = "action-name-undefined";
|
|
5846
|
-
function
|
|
6269
|
+
function asArray28(v) {
|
|
5847
6270
|
if (Array.isArray(v)) return v;
|
|
5848
6271
|
if (v && typeof v === "object") {
|
|
5849
6272
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
5850
6273
|
}
|
|
5851
6274
|
return [];
|
|
5852
6275
|
}
|
|
5853
|
-
function
|
|
6276
|
+
function strName11(v) {
|
|
5854
6277
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
5855
6278
|
}
|
|
5856
6279
|
function strList(v) {
|
|
@@ -5887,14 +6310,14 @@ function suggest4(target, known) {
|
|
|
5887
6310
|
}
|
|
5888
6311
|
function collectActionNames(stack) {
|
|
5889
6312
|
const names = /* @__PURE__ */ new Set();
|
|
5890
|
-
for (const action of
|
|
5891
|
-
const n =
|
|
6313
|
+
for (const action of asArray28(stack.actions)) {
|
|
6314
|
+
const n = strName11(action?.name);
|
|
5892
6315
|
if (n) names.add(n);
|
|
5893
6316
|
}
|
|
5894
|
-
for (const obj of
|
|
6317
|
+
for (const obj of asArray28(stack.objects)) {
|
|
5895
6318
|
if (!obj || typeof obj !== "object") continue;
|
|
5896
|
-
for (const action of
|
|
5897
|
-
const n =
|
|
6319
|
+
for (const action of asArray28(obj.actions)) {
|
|
6320
|
+
const n = strName11(action?.name);
|
|
5898
6321
|
if (n) names.add(n);
|
|
5899
6322
|
}
|
|
5900
6323
|
}
|
|
@@ -5937,7 +6360,7 @@ function validateActionNameRefs(stack) {
|
|
|
5937
6360
|
if (!def || typeof def !== "object") continue;
|
|
5938
6361
|
if (def.execution !== "aggregate") continue;
|
|
5939
6362
|
if (def.actionDef !== void 0) continue;
|
|
5940
|
-
const name =
|
|
6363
|
+
const name = strName11(def.name);
|
|
5941
6364
|
if (!name) continue;
|
|
5942
6365
|
check(
|
|
5943
6366
|
name,
|
|
@@ -5948,11 +6371,11 @@ function validateActionNameRefs(stack) {
|
|
|
5948
6371
|
);
|
|
5949
6372
|
}
|
|
5950
6373
|
};
|
|
5951
|
-
const views =
|
|
6374
|
+
const views = asArray28(stack.views);
|
|
5952
6375
|
for (let vi = 0; vi < views.length; vi++) {
|
|
5953
6376
|
const view = views[vi];
|
|
5954
6377
|
if (!view || typeof view !== "object") continue;
|
|
5955
|
-
const viewName =
|
|
6378
|
+
const viewName = strName11(view.name) ?? strName11(view.object) ?? `#${vi}`;
|
|
5956
6379
|
const owner = `view "${viewName}"`;
|
|
5957
6380
|
checkListContainer(view.list, owner, "list", `views[${vi}].list`);
|
|
5958
6381
|
const listViews = view.listViews;
|
|
@@ -5962,22 +6385,22 @@ function validateActionNameRefs(stack) {
|
|
|
5962
6385
|
}
|
|
5963
6386
|
}
|
|
5964
6387
|
}
|
|
5965
|
-
const objects =
|
|
6388
|
+
const objects = asArray28(stack.objects);
|
|
5966
6389
|
for (let oi = 0; oi < objects.length; oi++) {
|
|
5967
6390
|
const obj = objects[oi];
|
|
5968
6391
|
if (!obj || typeof obj !== "object") continue;
|
|
5969
6392
|
const objListViews = obj.listViews;
|
|
5970
6393
|
if (!objListViews || typeof objListViews !== "object" || Array.isArray(objListViews)) continue;
|
|
5971
|
-
const owner = `object "${
|
|
6394
|
+
const owner = `object "${strName11(obj.name) ?? `#${oi}`}"`;
|
|
5972
6395
|
for (const [key, lv] of Object.entries(objListViews)) {
|
|
5973
6396
|
checkListContainer(lv, owner, `listViews.${key}`, `objects[${oi}].listViews.${key}`);
|
|
5974
6397
|
}
|
|
5975
6398
|
}
|
|
5976
|
-
const pages =
|
|
6399
|
+
const pages = asArray28(stack.pages);
|
|
5977
6400
|
for (let pi = 0; pi < pages.length; pi++) {
|
|
5978
6401
|
const page = pages[pi];
|
|
5979
6402
|
if (!page || typeof page !== "object") continue;
|
|
5980
|
-
const pageName =
|
|
6403
|
+
const pageName = strName11(page.name) ?? `#${pi}`;
|
|
5981
6404
|
for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
|
|
5982
6405
|
const props = component.properties;
|
|
5983
6406
|
if (!props || typeof props !== "object") continue;
|
|
@@ -5985,39 +6408,39 @@ function validateActionNameRefs(stack) {
|
|
|
5985
6408
|
for (let ai = 0; ai < names.length; ai++) {
|
|
5986
6409
|
check(
|
|
5987
6410
|
names[ai],
|
|
5988
|
-
`page "${pageName}" \xB7 component "${
|
|
6411
|
+
`page "${pageName}" \xB7 component "${strName11(component.type) ?? "?"}"`,
|
|
5989
6412
|
`${path}.properties.actionNames[${ai}]`,
|
|
5990
6413
|
"Quick-actions bar"
|
|
5991
6414
|
);
|
|
5992
6415
|
}
|
|
5993
6416
|
}
|
|
5994
6417
|
}
|
|
5995
|
-
const apps =
|
|
6418
|
+
const apps = asArray28(stack.apps);
|
|
5996
6419
|
for (let ai = 0; ai < apps.length; ai++) {
|
|
5997
6420
|
const app = apps[ai];
|
|
5998
6421
|
if (!app || typeof app !== "object") continue;
|
|
5999
|
-
const appName =
|
|
6422
|
+
const appName = strName11(app.name) ?? `#${ai}`;
|
|
6000
6423
|
const walkNav = (items, basePath) => {
|
|
6001
|
-
const navItems =
|
|
6424
|
+
const navItems = asArray28(items);
|
|
6002
6425
|
for (let ni = 0; ni < navItems.length; ni++) {
|
|
6003
6426
|
const nav = navItems[ni];
|
|
6004
6427
|
if (!nav || typeof nav !== "object") continue;
|
|
6005
6428
|
const navPath = `${basePath}[${ni}]`;
|
|
6006
6429
|
const actionDef = nav.actionDef;
|
|
6007
|
-
const actionName =
|
|
6430
|
+
const actionName = strName11(actionDef?.actionName);
|
|
6008
6431
|
if (nav.type === "action" && actionName) {
|
|
6009
6432
|
check(
|
|
6010
6433
|
actionName,
|
|
6011
|
-
`app "${appName}" \xB7 nav "${
|
|
6434
|
+
`app "${appName}" \xB7 nav "${strName11(nav.id) ?? `#${ni}`}"`,
|
|
6012
6435
|
`${navPath}.actionDef.actionName`,
|
|
6013
6436
|
"Navigation action item"
|
|
6014
6437
|
);
|
|
6015
6438
|
}
|
|
6016
|
-
const runAction =
|
|
6439
|
+
const runAction = strName11(nav.runAction);
|
|
6017
6440
|
if (nav.type === "object" && runAction) {
|
|
6018
6441
|
check(
|
|
6019
6442
|
runAction,
|
|
6020
|
-
`app "${appName}" \xB7 nav "${
|
|
6443
|
+
`app "${appName}" \xB7 nav "${strName11(nav.id) ?? `#${ni}`}"`,
|
|
6021
6444
|
`${navPath}.runAction`,
|
|
6022
6445
|
"Navigation deep-link auto-run"
|
|
6023
6446
|
);
|
|
@@ -6026,7 +6449,7 @@ function validateActionNameRefs(stack) {
|
|
|
6026
6449
|
}
|
|
6027
6450
|
};
|
|
6028
6451
|
walkNav(app.navigation, `apps[${ai}].navigation`);
|
|
6029
|
-
const areas =
|
|
6452
|
+
const areas = asArray28(app.areas);
|
|
6030
6453
|
for (let ri = 0; ri < areas.length; ri++) {
|
|
6031
6454
|
walkNav(areas[ri]?.navigation, `apps[${ai}].areas[${ri}].navigation`);
|
|
6032
6455
|
}
|
|
@@ -6036,14 +6459,14 @@ function validateActionNameRefs(stack) {
|
|
|
6036
6459
|
|
|
6037
6460
|
// src/validate-action-locations.ts
|
|
6038
6461
|
var ACTION_NO_PLACEMENT = "action-no-placement";
|
|
6039
|
-
function
|
|
6462
|
+
function asArray29(v) {
|
|
6040
6463
|
if (Array.isArray(v)) return v;
|
|
6041
6464
|
if (v && typeof v === "object") {
|
|
6042
6465
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
6043
6466
|
}
|
|
6044
6467
|
return [];
|
|
6045
6468
|
}
|
|
6046
|
-
function
|
|
6469
|
+
function strName12(v) {
|
|
6047
6470
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
6048
6471
|
}
|
|
6049
6472
|
function strList2(v) {
|
|
@@ -6057,8 +6480,8 @@ function collectNamePlacedActions(stack) {
|
|
|
6057
6480
|
for (const key of ["rowActions", "bulkActions"]) {
|
|
6058
6481
|
for (const n of strList2(list3[key])) placed.add(n);
|
|
6059
6482
|
}
|
|
6060
|
-
for (const def of
|
|
6061
|
-
const n =
|
|
6483
|
+
for (const def of asArray29(list3.bulkActionDefs)) {
|
|
6484
|
+
const n = strName12(def?.name);
|
|
6062
6485
|
if (n) placed.add(n);
|
|
6063
6486
|
}
|
|
6064
6487
|
};
|
|
@@ -6066,12 +6489,12 @@ function collectNamePlacedActions(stack) {
|
|
|
6066
6489
|
if (!listViews || typeof listViews !== "object" || Array.isArray(listViews)) return;
|
|
6067
6490
|
for (const lv of Object.values(listViews)) harvest(lv);
|
|
6068
6491
|
};
|
|
6069
|
-
for (const view of
|
|
6492
|
+
for (const view of asArray29(stack.views)) {
|
|
6070
6493
|
if (!view || typeof view !== "object") continue;
|
|
6071
6494
|
harvest(view.list);
|
|
6072
6495
|
harvestListViews(view.listViews);
|
|
6073
6496
|
}
|
|
6074
|
-
for (const obj of
|
|
6497
|
+
for (const obj of asArray29(stack.objects)) {
|
|
6075
6498
|
if (!obj || typeof obj !== "object") continue;
|
|
6076
6499
|
harvestListViews(obj.listViews);
|
|
6077
6500
|
}
|
|
@@ -6084,7 +6507,7 @@ function validateActionLocations(stack) {
|
|
|
6084
6507
|
const check = (action, path) => {
|
|
6085
6508
|
if (!action || typeof action !== "object") return;
|
|
6086
6509
|
if ("locations" in action) return;
|
|
6087
|
-
const name =
|
|
6510
|
+
const name = strName12(action.name);
|
|
6088
6511
|
if (!name) return;
|
|
6089
6512
|
if (namePlaced.has(name)) return;
|
|
6090
6513
|
findings.push({
|
|
@@ -6096,13 +6519,13 @@ function validateActionLocations(stack) {
|
|
|
6096
6519
|
hint: "Add the surface it belongs on, e.g. `locations: ['record_header']` (or `list_item`, `list_toolbar`, `record_more`, `record_section`, `record_related`); or place it from a list view's `bulkActions` / `bulkActionDefs` if it acts on a selection. If it is meant to be callable over REST / MCP / AI with no UI surface, say so explicitly with `locations: []` \u2014 an empty array is the documented headless shape and is never flagged."
|
|
6097
6520
|
});
|
|
6098
6521
|
};
|
|
6099
|
-
const actions =
|
|
6522
|
+
const actions = asArray29(stack.actions);
|
|
6100
6523
|
for (let i = 0; i < actions.length; i++) check(actions[i], `actions[${i}]`);
|
|
6101
|
-
const objects =
|
|
6524
|
+
const objects = asArray29(stack.objects);
|
|
6102
6525
|
for (let oi = 0; oi < objects.length; oi++) {
|
|
6103
6526
|
const obj = objects[oi];
|
|
6104
6527
|
if (!obj || typeof obj !== "object") continue;
|
|
6105
|
-
const own =
|
|
6528
|
+
const own = asArray29(obj.actions);
|
|
6106
6529
|
for (let ai = 0; ai < own.length; ai++) check(own[ai], `objects[${oi}].actions[${ai}]`);
|
|
6107
6530
|
}
|
|
6108
6531
|
return findings;
|
|
@@ -6113,13 +6536,13 @@ import { ComponentPropsMap } from "@objectstack/spec/ui";
|
|
|
6113
6536
|
import { lintUnknownKeysAgainstSchema } from "@objectstack/spec";
|
|
6114
6537
|
var COMPONENT_PROPS_UNKNOWN_KEY = "component-props-unknown-key";
|
|
6115
6538
|
var COMPONENT_PROPS_INVALID = "component-props-invalid";
|
|
6116
|
-
function
|
|
6539
|
+
function isRec16(v) {
|
|
6117
6540
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
6118
6541
|
}
|
|
6119
|
-
function
|
|
6542
|
+
function strName13(v) {
|
|
6120
6543
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
6121
6544
|
}
|
|
6122
|
-
function
|
|
6545
|
+
function asArray30(v) {
|
|
6123
6546
|
if (Array.isArray(v)) return v;
|
|
6124
6547
|
if (v && typeof v === "object") {
|
|
6125
6548
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -6130,23 +6553,36 @@ var PROPS_SCHEMAS = ComponentPropsMap;
|
|
|
6130
6553
|
var DATASOURCE_SUPPLIED_PROP = "object";
|
|
6131
6554
|
function suppliedByDataSource(issue, component) {
|
|
6132
6555
|
if (issue.path.length !== 1 || issue.path[0] !== DATASOURCE_SUPPLIED_PROP) return false;
|
|
6133
|
-
const dataSource =
|
|
6134
|
-
return
|
|
6556
|
+
const dataSource = isRec16(component.dataSource) ? component.dataSource : void 0;
|
|
6557
|
+
return strName13(dataSource?.object) !== void 0;
|
|
6558
|
+
}
|
|
6559
|
+
function unrecognizedKeysFromUnionArm(issue) {
|
|
6560
|
+
if (issue.code !== "invalid_union") return void 0;
|
|
6561
|
+
const arms = issue.errors;
|
|
6562
|
+
if (!arms || arms.length === 0) return void 0;
|
|
6563
|
+
let found;
|
|
6564
|
+
for (const arm of arms) {
|
|
6565
|
+
const keyIssues = arm.filter((inner) => inner.code === "unrecognized_keys");
|
|
6566
|
+
if (keyIssues.length === 0) continue;
|
|
6567
|
+
if (keyIssues.length !== arm.length || found) return void 0;
|
|
6568
|
+
found = keyIssues[0];
|
|
6569
|
+
}
|
|
6570
|
+
return found;
|
|
6135
6571
|
}
|
|
6136
6572
|
function validateComponentProps(stack) {
|
|
6137
6573
|
const findings = [];
|
|
6138
|
-
if (!
|
|
6139
|
-
const pages =
|
|
6574
|
+
if (!isRec16(stack)) return findings;
|
|
6575
|
+
const pages = asArray30(stack.pages);
|
|
6140
6576
|
for (let pi = 0; pi < pages.length; pi++) {
|
|
6141
6577
|
const page = pages[pi];
|
|
6142
|
-
if (!
|
|
6143
|
-
const pageName =
|
|
6578
|
+
if (!isRec16(page)) continue;
|
|
6579
|
+
const pageName = strName13(page.name) ?? `#${pi}`;
|
|
6144
6580
|
for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
|
|
6145
|
-
const type =
|
|
6581
|
+
const type = strName13(component.type);
|
|
6146
6582
|
if (!type) continue;
|
|
6147
6583
|
const schema = PROPS_SCHEMAS[type];
|
|
6148
6584
|
if (!schema) continue;
|
|
6149
|
-
const props =
|
|
6585
|
+
const props = isRec16(component.properties) ? component.properties : void 0;
|
|
6150
6586
|
if (!props) continue;
|
|
6151
6587
|
const where = `page "${pageName}" \xB7 ${type}`;
|
|
6152
6588
|
const base = `${path}.properties`;
|
|
@@ -6165,6 +6601,20 @@ function validateComponentProps(stack) {
|
|
|
6165
6601
|
for (const issue of parsed.error?.issues ?? []) {
|
|
6166
6602
|
if (suppliedByDataSource(issue, component)) continue;
|
|
6167
6603
|
const at = issue.path.length ? `${base}.${issue.path.join(".")}` : base;
|
|
6604
|
+
const armIssue = unrecognizedKeysFromUnionArm(issue);
|
|
6605
|
+
if (armIssue) {
|
|
6606
|
+
for (const key of armIssue.keys ?? []) {
|
|
6607
|
+
findings.push({
|
|
6608
|
+
severity: "warning",
|
|
6609
|
+
rule: COMPONENT_PROPS_UNKNOWN_KEY,
|
|
6610
|
+
where,
|
|
6611
|
+
path: `${at}.${key}`,
|
|
6612
|
+
message: `\`${key}\` is not a prop \`${type}\` declares (ComponentPropsMap, @objectstack/spec/ui): ${armIssue.message}`,
|
|
6613
|
+
hint: `Remove \`${key}\`, or declare it on \`${type}\`'s props schema if the component honours it.`
|
|
6614
|
+
});
|
|
6615
|
+
}
|
|
6616
|
+
continue;
|
|
6617
|
+
}
|
|
6168
6618
|
if (issue.code === "unrecognized_keys") {
|
|
6169
6619
|
for (const key of issue.keys ?? []) {
|
|
6170
6620
|
findings.push({
|
|
@@ -6197,20 +6647,20 @@ var CHART_DIMENSION_UNKNOWN = "chart-dimension-unknown";
|
|
|
6197
6647
|
var CHART_MEASURE_UNKNOWN = "chart-measure-unknown";
|
|
6198
6648
|
var CHART_DATASET_UNKNOWN = "chart-dataset-unknown";
|
|
6199
6649
|
var CHART_AXIS_NOT_SELECTED = "chart-axis-not-selected";
|
|
6200
|
-
function
|
|
6650
|
+
function asArray31(v) {
|
|
6201
6651
|
if (Array.isArray(v)) return v;
|
|
6202
6652
|
if (v && typeof v === "object") {
|
|
6203
6653
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
6204
6654
|
}
|
|
6205
6655
|
return [];
|
|
6206
6656
|
}
|
|
6207
|
-
function
|
|
6657
|
+
function strName14(v) {
|
|
6208
6658
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
6209
6659
|
}
|
|
6210
6660
|
function strList3(v) {
|
|
6211
6661
|
return Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.length > 0) : [];
|
|
6212
6662
|
}
|
|
6213
|
-
function
|
|
6663
|
+
function isRec17(v) {
|
|
6214
6664
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
6215
6665
|
}
|
|
6216
6666
|
function distance4(a, b) {
|
|
@@ -6248,17 +6698,17 @@ function list2(names) {
|
|
|
6248
6698
|
}
|
|
6249
6699
|
function indexDatasets(stack) {
|
|
6250
6700
|
const out = /* @__PURE__ */ new Map();
|
|
6251
|
-
for (const ds of
|
|
6252
|
-
const name =
|
|
6701
|
+
for (const ds of asArray31(stack.datasets)) {
|
|
6702
|
+
const name = strName14(ds.name);
|
|
6253
6703
|
if (!name) continue;
|
|
6254
6704
|
const dimensions = /* @__PURE__ */ new Set();
|
|
6255
|
-
for (const d of
|
|
6256
|
-
const n =
|
|
6705
|
+
for (const d of asArray31(ds.dimensions)) {
|
|
6706
|
+
const n = strName14(d.name);
|
|
6257
6707
|
if (n) dimensions.add(n);
|
|
6258
6708
|
}
|
|
6259
6709
|
const measures = /* @__PURE__ */ new Set();
|
|
6260
|
-
for (const m of
|
|
6261
|
-
const n =
|
|
6710
|
+
for (const m of asArray31(ds.measures)) {
|
|
6711
|
+
const n = strName14(m.name);
|
|
6262
6712
|
if (n) measures.add(n);
|
|
6263
6713
|
}
|
|
6264
6714
|
out.set(name, { dimensions, measures });
|
|
@@ -6336,29 +6786,29 @@ function validateChartBindings(stack) {
|
|
|
6336
6786
|
if (binding.yAxis) measureRef(binding.yAxis.name, binding.yAxis.path, selected);
|
|
6337
6787
|
for (const s of binding.series ?? []) measureRef(s.name, s.path, selected);
|
|
6338
6788
|
};
|
|
6339
|
-
const reports =
|
|
6789
|
+
const reports = asArray31(stack.reports);
|
|
6340
6790
|
for (let ri = 0; ri < reports.length; ri++) {
|
|
6341
6791
|
const report = reports[ri];
|
|
6342
|
-
if (!
|
|
6343
|
-
const reportName =
|
|
6792
|
+
if (!isRec17(report)) continue;
|
|
6793
|
+
const reportName = strName14(report.name) ?? `#${ri}`;
|
|
6344
6794
|
const checkReportChart = (chart, dataset, values, where, path) => {
|
|
6345
|
-
if (!
|
|
6795
|
+
if (!isRec17(chart)) return;
|
|
6346
6796
|
check({
|
|
6347
6797
|
dataset,
|
|
6348
6798
|
// `values` is the report's measure SELECTION, not a chart ref; feeding
|
|
6349
6799
|
// it in lets the yAxis "declared but not selected" check work without
|
|
6350
6800
|
// reporting the selection itself twice.
|
|
6351
6801
|
values: { names: values, path: `${path}.values` },
|
|
6352
|
-
xAxis:
|
|
6353
|
-
yAxis:
|
|
6354
|
-
series:
|
|
6802
|
+
xAxis: strName14(chart.xAxis) ? { name: strName14(chart.xAxis), path: `${path}.chart.xAxis` } : void 0,
|
|
6803
|
+
yAxis: strName14(chart.yAxis) ? { name: strName14(chart.yAxis), path: `${path}.chart.yAxis` } : void 0,
|
|
6804
|
+
series: asArray31(chart.series).map((s, si) => ({ name: strName14(s.name), path: `${path}.chart.series[${si}].name` })).filter((s) => !!s.name),
|
|
6355
6805
|
where,
|
|
6356
6806
|
path: `${path}.chart`
|
|
6357
6807
|
});
|
|
6358
6808
|
};
|
|
6359
6809
|
checkReportChart(
|
|
6360
6810
|
report.chart,
|
|
6361
|
-
|
|
6811
|
+
strName14(report.dataset),
|
|
6362
6812
|
strList3(report.values),
|
|
6363
6813
|
`report "${reportName}" \xB7 chart`,
|
|
6364
6814
|
`reports[${ri}]`
|
|
@@ -6366,45 +6816,45 @@ function validateChartBindings(stack) {
|
|
|
6366
6816
|
const blocks = Array.isArray(report.blocks) ? report.blocks : [];
|
|
6367
6817
|
for (let bi = 0; bi < blocks.length; bi++) {
|
|
6368
6818
|
const block = blocks[bi];
|
|
6369
|
-
if (!
|
|
6819
|
+
if (!isRec17(block)) continue;
|
|
6370
6820
|
checkReportChart(
|
|
6371
6821
|
block.chart,
|
|
6372
|
-
|
|
6822
|
+
strName14(block.dataset),
|
|
6373
6823
|
strList3(block.values),
|
|
6374
|
-
`report "${reportName}" \xB7 block "${
|
|
6824
|
+
`report "${reportName}" \xB7 block "${strName14(block.name) ?? `#${bi}`}" chart`,
|
|
6375
6825
|
`reports[${ri}].blocks[${bi}]`
|
|
6376
6826
|
);
|
|
6377
6827
|
}
|
|
6378
6828
|
}
|
|
6379
6829
|
const checkListChart = (container, where, path) => {
|
|
6380
|
-
if (!
|
|
6830
|
+
if (!isRec17(container)) return;
|
|
6381
6831
|
const chart = container.chart;
|
|
6382
|
-
if (!
|
|
6832
|
+
if (!isRec17(chart)) return;
|
|
6383
6833
|
check({
|
|
6384
|
-
dataset:
|
|
6834
|
+
dataset: strName14(chart.dataset),
|
|
6385
6835
|
dimensions: { names: strList3(chart.dimensions), path: `${path}.chart.dimensions` },
|
|
6386
6836
|
values: { names: strList3(chart.values), path: `${path}.chart.values` },
|
|
6387
6837
|
where,
|
|
6388
6838
|
path: `${path}.chart`
|
|
6389
6839
|
});
|
|
6390
6840
|
};
|
|
6391
|
-
const views =
|
|
6841
|
+
const views = asArray31(stack.views);
|
|
6392
6842
|
for (let vi = 0; vi < views.length; vi++) {
|
|
6393
6843
|
const view = views[vi];
|
|
6394
|
-
if (!
|
|
6395
|
-
const viewName =
|
|
6844
|
+
if (!isRec17(view)) continue;
|
|
6845
|
+
const viewName = strName14(view.name) ?? strName14(view.objectName) ?? `#${vi}`;
|
|
6396
6846
|
checkListChart(view.list, `view "${viewName}" \xB7 list chart`, `views[${vi}].list`);
|
|
6397
|
-
if (
|
|
6847
|
+
if (isRec17(view.listViews)) {
|
|
6398
6848
|
for (const [key, lv] of Object.entries(view.listViews)) {
|
|
6399
6849
|
checkListChart(lv, `view "${viewName}" \xB7 listViews.${key} chart`, `views[${vi}].listViews.${key}`);
|
|
6400
6850
|
}
|
|
6401
6851
|
}
|
|
6402
6852
|
}
|
|
6403
|
-
const objects =
|
|
6853
|
+
const objects = asArray31(stack.objects);
|
|
6404
6854
|
for (let oi = 0; oi < objects.length; oi++) {
|
|
6405
6855
|
const obj = objects[oi];
|
|
6406
|
-
if (!
|
|
6407
|
-
const objName =
|
|
6856
|
+
if (!isRec17(obj) || !isRec17(obj.listViews)) continue;
|
|
6857
|
+
const objName = strName14(obj.name) ?? `#${oi}`;
|
|
6408
6858
|
for (const [key, lv] of Object.entries(obj.listViews)) {
|
|
6409
6859
|
checkListChart(
|
|
6410
6860
|
lv,
|
|
@@ -6413,22 +6863,22 @@ function validateChartBindings(stack) {
|
|
|
6413
6863
|
);
|
|
6414
6864
|
}
|
|
6415
6865
|
}
|
|
6416
|
-
const pages =
|
|
6866
|
+
const pages = asArray31(stack.pages);
|
|
6417
6867
|
for (let pi = 0; pi < pages.length; pi++) {
|
|
6418
6868
|
const page = pages[pi];
|
|
6419
|
-
if (!
|
|
6420
|
-
const pageName =
|
|
6869
|
+
if (!isRec17(page)) continue;
|
|
6870
|
+
const pageName = strName14(page.name) ?? `#${pi}`;
|
|
6421
6871
|
for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
|
|
6422
|
-
const props =
|
|
6423
|
-
if (!props || !
|
|
6424
|
-
const axisRefs =
|
|
6425
|
-
const seriesRefs =
|
|
6872
|
+
const props = isRec17(component.properties) ? component.properties : void 0;
|
|
6873
|
+
if (!props || !strName14(props.dataset)) continue;
|
|
6874
|
+
const axisRefs = asArray31(props.yAxis).map((a, ai) => ({ name: strName14(a.field), path: `${path}.properties.yAxis[${ai}].field` })).filter((a) => !!a.name);
|
|
6875
|
+
const seriesRefs = asArray31(props.series).map((s, si) => ({ name: strName14(s.name), path: `${path}.properties.series[${si}].name` })).filter((s) => !!s.name);
|
|
6426
6876
|
check({
|
|
6427
|
-
dataset:
|
|
6877
|
+
dataset: strName14(props.dataset),
|
|
6428
6878
|
dimensions: { names: strList3(props.dimensions), path: `${path}.properties.dimensions` },
|
|
6429
6879
|
values: { names: strList3(props.values), path: `${path}.properties.values` },
|
|
6430
6880
|
series: [...axisRefs, ...seriesRefs],
|
|
6431
|
-
where: `page "${pageName}" \xB7 ${
|
|
6881
|
+
where: `page "${pageName}" \xB7 ${strName14(component.type) ?? "chart"}`,
|
|
6432
6882
|
path: `${path}.properties`
|
|
6433
6883
|
});
|
|
6434
6884
|
}
|
|
@@ -6441,11 +6891,11 @@ import { createRequire as createRequire4 } from "module";
|
|
|
6441
6891
|
var VALIDATION_RULE_REGEX_UNCOMPILABLE = "validation-rule-regex-uncompilable";
|
|
6442
6892
|
var VALIDATION_RULE_SCHEMA_UNCOMPILABLE = "validation-rule-json-schema-uncompilable";
|
|
6443
6893
|
var RUNTIME_AJV_OPTIONS = { allErrors: true, strict: false };
|
|
6444
|
-
var
|
|
6445
|
-
function
|
|
6446
|
-
if (Array.isArray(v)) return v.filter(
|
|
6447
|
-
if (
|
|
6448
|
-
return Object.entries(v).filter(([, def]) =>
|
|
6894
|
+
var isRec18 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
6895
|
+
function asArray32(v) {
|
|
6896
|
+
if (Array.isArray(v)) return v.filter(isRec18);
|
|
6897
|
+
if (isRec18(v)) {
|
|
6898
|
+
return Object.entries(v).filter(([, def]) => isRec18(def)).map(([name, def]) => ({ name, ...def }));
|
|
6449
6899
|
}
|
|
6450
6900
|
return [];
|
|
6451
6901
|
}
|
|
@@ -6462,7 +6912,7 @@ function loadAjv() {
|
|
|
6462
6912
|
`@objectstack/lint: checking a \`json_schema\` validation rule requires the "ajv" package, which could not be loaded (${err instanceof Error ? err.message : String(err)}). It is a declared dependency of @objectstack/lint \u2014 if this deployment prunes packages, keep "ajv" in the image; it is only loaded when a stack declares a \`json_schema\` validation rule.`
|
|
6463
6913
|
);
|
|
6464
6914
|
}
|
|
6465
|
-
const ctor =
|
|
6915
|
+
const ctor = isRec18(mod) && "default" in mod ? mod.default : mod;
|
|
6466
6916
|
cachedAjv = ctor;
|
|
6467
6917
|
return ctor;
|
|
6468
6918
|
}
|
|
@@ -6477,7 +6927,7 @@ function loadAddFormats() {
|
|
|
6477
6927
|
`@objectstack/lint: checking a \`json_schema\` validation rule requires the "ajv-formats" package, which could not be loaded (${err instanceof Error ? err.message : String(err)}). It is a declared dependency of @objectstack/lint \u2014 if this deployment prunes packages, keep "ajv-formats" in the image; it is only loaded when a stack declares a \`json_schema\` validation rule. The runtime registers it too, and this gate must compile in the SAME environment or it starts disagreeing with the write path.`
|
|
6478
6928
|
);
|
|
6479
6929
|
}
|
|
6480
|
-
const plugin =
|
|
6930
|
+
const plugin = isRec18(mod) && "default" in mod ? mod.default : mod;
|
|
6481
6931
|
cachedAddFormats = plugin;
|
|
6482
6932
|
return plugin;
|
|
6483
6933
|
}
|
|
@@ -6507,17 +6957,17 @@ function flattenRules(rule, labelTrail, pathTrail, depth = 0) {
|
|
|
6507
6957
|
if (depth >= MAX_RULE_NESTING_DEPTH) return out;
|
|
6508
6958
|
for (const branch of ["then", "otherwise"]) {
|
|
6509
6959
|
const nested = rule[branch];
|
|
6510
|
-
if (
|
|
6960
|
+
if (isRec18(nested)) out.push(...flattenRules(nested, label2, `${path}.${branch}`, depth + 1));
|
|
6511
6961
|
}
|
|
6512
6962
|
return out;
|
|
6513
6963
|
}
|
|
6514
6964
|
function walkObjectValidationRules(stack) {
|
|
6515
6965
|
const walked = [];
|
|
6516
|
-
if (!
|
|
6517
|
-
for (const obj of
|
|
6966
|
+
if (!isRec18(stack)) return walked;
|
|
6967
|
+
for (const obj of asArray32(stack.objects)) {
|
|
6518
6968
|
const objectName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
|
|
6519
6969
|
const validations = obj.validations;
|
|
6520
|
-
for (const authored of
|
|
6970
|
+
for (const authored of asArray32(validations)) {
|
|
6521
6971
|
for (const { rule, label: label2, path } of flattenRules(authored, "", "")) {
|
|
6522
6972
|
walked.push({
|
|
6523
6973
|
rule,
|
|
@@ -6548,7 +6998,7 @@ function validateRuleCompilability(stack) {
|
|
|
6548
6998
|
});
|
|
6549
6999
|
}
|
|
6550
7000
|
}
|
|
6551
|
-
if (rule.type === "json_schema" &&
|
|
7001
|
+
if (rule.type === "json_schema" && isRec18(rule.schema)) {
|
|
6552
7002
|
try {
|
|
6553
7003
|
createRuntimeAjv().compile(rule.schema);
|
|
6554
7004
|
} catch (err) {
|
|
@@ -6568,7 +7018,7 @@ function validateRuleCompilability(stack) {
|
|
|
6568
7018
|
|
|
6569
7019
|
// src/validate-rule-schema-formats.ts
|
|
6570
7020
|
var VALIDATION_RULE_SCHEMA_UNKNOWN_FORMAT = "validation-rule-json-schema-unknown-format";
|
|
6571
|
-
var
|
|
7021
|
+
var isRec19 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
6572
7022
|
var SUBSCHEMA_KEYS = [
|
|
6573
7023
|
"additionalItems",
|
|
6574
7024
|
"additionalProperties",
|
|
@@ -6592,7 +7042,7 @@ var SUBSCHEMA_MAP_KEYS = [
|
|
|
6592
7042
|
var MAX_SCHEMA_WALK_DEPTH = 32;
|
|
6593
7043
|
var escapePointerSegment = (segment) => segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
6594
7044
|
function collectFormatUses(schema, pointer, out, depth) {
|
|
6595
|
-
if (!
|
|
7045
|
+
if (!isRec19(schema)) return;
|
|
6596
7046
|
if (typeof schema.format === "string") {
|
|
6597
7047
|
out.push({ pointer: `${pointer}/format`, name: schema.format });
|
|
6598
7048
|
}
|
|
@@ -6611,7 +7061,7 @@ function collectFormatUses(schema, pointer, out, depth) {
|
|
|
6611
7061
|
}
|
|
6612
7062
|
for (const key of SUBSCHEMA_MAP_KEYS) {
|
|
6613
7063
|
const value = schema[key];
|
|
6614
|
-
if (!
|
|
7064
|
+
if (!isRec19(value)) continue;
|
|
6615
7065
|
for (const [name, entry] of Object.entries(value)) {
|
|
6616
7066
|
collectFormatUses(entry, `${pointer}/${escapePointerSegment(key)}/${escapePointerSegment(name)}`, out, depth + 1);
|
|
6617
7067
|
}
|
|
@@ -6619,13 +7069,13 @@ function collectFormatUses(schema, pointer, out, depth) {
|
|
|
6619
7069
|
const items = schema.items;
|
|
6620
7070
|
if (Array.isArray(items)) {
|
|
6621
7071
|
items.forEach((entry, index) => collectFormatUses(entry, `${pointer}/items/${index}`, out, depth + 1));
|
|
6622
|
-
} else if (
|
|
7072
|
+
} else if (isRec19(items)) {
|
|
6623
7073
|
collectFormatUses(items, `${pointer}/items`, out, depth + 1);
|
|
6624
7074
|
}
|
|
6625
7075
|
const dependencies = schema.dependencies;
|
|
6626
|
-
if (
|
|
7076
|
+
if (isRec19(dependencies)) {
|
|
6627
7077
|
for (const [name, entry] of Object.entries(dependencies)) {
|
|
6628
|
-
if (!
|
|
7078
|
+
if (!isRec19(entry)) continue;
|
|
6629
7079
|
collectFormatUses(entry, `${pointer}/dependencies/${escapePointerSegment(name)}`, out, depth + 1);
|
|
6630
7080
|
}
|
|
6631
7081
|
}
|
|
@@ -6664,7 +7114,7 @@ function validateRuleSchemaFormats(stack) {
|
|
|
6664
7114
|
const findings = [];
|
|
6665
7115
|
const pending = [];
|
|
6666
7116
|
for (const { rule, objectName, label: label2, where, basePath } of walkObjectValidationRules(stack)) {
|
|
6667
|
-
if (rule.type !== "json_schema" || !
|
|
7117
|
+
if (rule.type !== "json_schema" || !isRec19(rule.schema)) continue;
|
|
6668
7118
|
const uses = [];
|
|
6669
7119
|
collectFormatUses(rule.schema, "", uses, 0);
|
|
6670
7120
|
for (const use of uses) pending.push({ use, where, label: label2, objectName, basePath });
|
|
@@ -6692,7 +7142,7 @@ function validateRuleSchemaFormats(stack) {
|
|
|
6692
7142
|
import { isPlatformProvidedObjectName as isPlatformProvidedObjectName2 } from "@objectstack/spec/system";
|
|
6693
7143
|
|
|
6694
7144
|
// src/build-access-matrix.ts
|
|
6695
|
-
function
|
|
7145
|
+
function asArray33(v) {
|
|
6696
7146
|
if (Array.isArray(v)) return v;
|
|
6697
7147
|
if (v && typeof v === "object") {
|
|
6698
7148
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -6703,13 +7153,13 @@ function buildAccessMatrix(stack) {
|
|
|
6703
7153
|
const entries = [];
|
|
6704
7154
|
if (!stack || typeof stack !== "object") return { version: 1, entries };
|
|
6705
7155
|
const owdByObject = /* @__PURE__ */ new Map();
|
|
6706
|
-
for (const obj of
|
|
7156
|
+
for (const obj of asArray33(stack.objects)) {
|
|
6707
7157
|
const name = typeof obj.name === "string" ? obj.name : "";
|
|
6708
7158
|
if (!name) continue;
|
|
6709
7159
|
const owd = obj.sharingModel ?? obj.security?.sharingModel;
|
|
6710
7160
|
if (typeof owd === "string") owdByObject.set(name, owd);
|
|
6711
7161
|
}
|
|
6712
|
-
for (const ps of
|
|
7162
|
+
for (const ps of asArray33(stack.permissions)) {
|
|
6713
7163
|
const psName = typeof ps.name === "string" ? ps.name : "";
|
|
6714
7164
|
if (!psName) continue;
|
|
6715
7165
|
const objects = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
|
|
@@ -6782,34 +7232,34 @@ function diffAccessMatrix(before, after) {
|
|
|
6782
7232
|
|
|
6783
7233
|
// src/validate-nav-access.ts
|
|
6784
7234
|
var NAV_OBJECT_UNGRANTED = "nav-object-ungranted";
|
|
6785
|
-
function
|
|
7235
|
+
function asArray34(v) {
|
|
6786
7236
|
if (Array.isArray(v)) return v;
|
|
6787
7237
|
if (v && typeof v === "object") {
|
|
6788
7238
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
6789
7239
|
}
|
|
6790
7240
|
return [];
|
|
6791
7241
|
}
|
|
6792
|
-
function
|
|
7242
|
+
function strName15(v) {
|
|
6793
7243
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
6794
7244
|
}
|
|
6795
7245
|
function collectNavExposures(stack) {
|
|
6796
7246
|
const out = [];
|
|
6797
|
-
const apps =
|
|
7247
|
+
const apps = asArray34(stack.apps);
|
|
6798
7248
|
for (let ai = 0; ai < apps.length; ai++) {
|
|
6799
7249
|
const app = apps[ai];
|
|
6800
7250
|
if (!app || typeof app !== "object") continue;
|
|
6801
|
-
const appName =
|
|
7251
|
+
const appName = strName15(app.name) ?? `#${ai}`;
|
|
6802
7252
|
const walk = (items, basePath) => {
|
|
6803
|
-
const navItems =
|
|
7253
|
+
const navItems = asArray34(items);
|
|
6804
7254
|
for (let ni = 0; ni < navItems.length; ni++) {
|
|
6805
7255
|
const nav = navItems[ni];
|
|
6806
7256
|
if (!nav || typeof nav !== "object") continue;
|
|
6807
7257
|
const navPath = `${basePath}[${ni}]`;
|
|
6808
|
-
const objectName =
|
|
7258
|
+
const objectName = strName15(nav.objectName);
|
|
6809
7259
|
if (nav.type === "object" && objectName) {
|
|
6810
7260
|
out.push({
|
|
6811
7261
|
objectName,
|
|
6812
|
-
where: `app "${appName}" \xB7 nav "${
|
|
7262
|
+
where: `app "${appName}" \xB7 nav "${strName15(nav.id) ?? `#${ni}`}"`,
|
|
6813
7263
|
path: `${navPath}.objectName`
|
|
6814
7264
|
});
|
|
6815
7265
|
}
|
|
@@ -6817,7 +7267,7 @@ function collectNavExposures(stack) {
|
|
|
6817
7267
|
}
|
|
6818
7268
|
};
|
|
6819
7269
|
walk(app.navigation, `apps[${ai}].navigation`);
|
|
6820
|
-
const areas =
|
|
7270
|
+
const areas = asArray34(app.areas);
|
|
6821
7271
|
for (let ri = 0; ri < areas.length; ri++) {
|
|
6822
7272
|
walk(areas[ri]?.navigation, `apps[${ai}].areas[${ri}].navigation`);
|
|
6823
7273
|
}
|
|
@@ -6827,13 +7277,13 @@ function collectNavExposures(stack) {
|
|
|
6827
7277
|
function validateNavAccess(stack) {
|
|
6828
7278
|
const findings = [];
|
|
6829
7279
|
if (!stack || typeof stack !== "object") return findings;
|
|
6830
|
-
const permissionSets =
|
|
7280
|
+
const permissionSets = asArray34(stack.permissions);
|
|
6831
7281
|
if (permissionSets.length === 0) return findings;
|
|
6832
7282
|
const exposures = collectNavExposures(stack);
|
|
6833
7283
|
if (exposures.length === 0) return findings;
|
|
6834
7284
|
const ownObjects = /* @__PURE__ */ new Set();
|
|
6835
|
-
for (const obj of
|
|
6836
|
-
const n =
|
|
7285
|
+
for (const obj of asArray34(stack.objects)) {
|
|
7286
|
+
const n = strName15(obj.name);
|
|
6837
7287
|
if (n) ownObjects.add(n);
|
|
6838
7288
|
}
|
|
6839
7289
|
const readable = /* @__PURE__ */ new Set();
|
|
@@ -6866,15 +7316,15 @@ import { expandViewContainer } from "@objectstack/spec";
|
|
|
6866
7316
|
import { hasPlatformObjectPrefix as hasPlatformObjectPrefix2, isPlatformProvidedObjectName as isPlatformProvidedObjectName3 } from "@objectstack/spec/system";
|
|
6867
7317
|
var TRANSLATION_TARGET_UNKNOWN = "translation-target-unknown";
|
|
6868
7318
|
var TRANSLATION_OPTION_KEY_UNKNOWN = "translation-option-key-unknown";
|
|
6869
|
-
function
|
|
7319
|
+
function isRec20(v) {
|
|
6870
7320
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
6871
7321
|
}
|
|
6872
|
-
function
|
|
7322
|
+
function asArray35(v) {
|
|
6873
7323
|
if (Array.isArray(v)) return v;
|
|
6874
|
-
if (
|
|
7324
|
+
if (isRec20(v)) return Object.entries(v).map(([name, def]) => ({ name, ...isRec20(def) ? def : {} }));
|
|
6875
7325
|
return [];
|
|
6876
7326
|
}
|
|
6877
|
-
function
|
|
7327
|
+
function strName16(v) {
|
|
6878
7328
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
6879
7329
|
}
|
|
6880
7330
|
function distance5(a, b) {
|
|
@@ -6934,34 +7384,34 @@ function collectViewRecord(view, factsFor) {
|
|
|
6934
7384
|
};
|
|
6935
7385
|
const addSections = (container, binding) => {
|
|
6936
7386
|
if (!binding) return;
|
|
6937
|
-
for (const section of
|
|
6938
|
-
const sectionName =
|
|
7387
|
+
for (const section of asArray35(container.sections)) {
|
|
7388
|
+
const sectionName = strName16(section.name);
|
|
6939
7389
|
if (sectionName) factsFor(binding).sections.add(sectionName);
|
|
6940
7390
|
}
|
|
6941
7391
|
};
|
|
6942
|
-
const listBinding =
|
|
6943
|
-
if (
|
|
6944
|
-
addView(recordObject ?? listBinding,
|
|
7392
|
+
const listBinding = isRec20(view.list) ? bindingOf(view.list) : void 0;
|
|
7393
|
+
if (isRec20(view.list)) addView(listBinding, defaultListViewKey(listBinding, view));
|
|
7394
|
+
addView(recordObject ?? listBinding, strName16(view.name));
|
|
6945
7395
|
const named = namedViewKeys(view);
|
|
6946
7396
|
for (const family of ["listViews", "formViews"]) {
|
|
6947
7397
|
const container = view[family];
|
|
6948
|
-
if (!
|
|
7398
|
+
if (!isRec20(container)) continue;
|
|
6949
7399
|
const registryKeys = family === "listViews" ? named.list : named.form;
|
|
6950
7400
|
let at = 0;
|
|
6951
7401
|
for (const sub of Object.values(container)) {
|
|
6952
7402
|
if (!sub || typeof sub !== "object") continue;
|
|
6953
7403
|
const registryKey = registryKeys[at++];
|
|
6954
|
-
if (!
|
|
7404
|
+
if (!isRec20(sub)) continue;
|
|
6955
7405
|
const binding = bindingOf(sub) ?? listBinding;
|
|
6956
7406
|
addView(binding, registryKey);
|
|
6957
7407
|
addSections(sub, binding);
|
|
6958
7408
|
}
|
|
6959
7409
|
}
|
|
6960
|
-
if (
|
|
7410
|
+
if (isRec20(view.form)) addSections(view.form, bindingOf(view.form) ?? listBinding);
|
|
6961
7411
|
addSections(view, recordObject ?? listBinding);
|
|
6962
7412
|
}
|
|
6963
7413
|
function defaultListViewKey(object, container) {
|
|
6964
|
-
if (!object || !
|
|
7414
|
+
if (!object || !isRec20(container.list)) return void 0;
|
|
6965
7415
|
const item = expandViewContainer(object, container).find(
|
|
6966
7416
|
(i) => i.viewKind === "list" && i.isDefault
|
|
6967
7417
|
);
|
|
@@ -6973,7 +7423,7 @@ function namedViewKeys(container) {
|
|
|
6973
7423
|
const object = "probe";
|
|
6974
7424
|
const prefix = `${object}.`;
|
|
6975
7425
|
const bare = (name) => name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
6976
|
-
const countEntries = (v) =>
|
|
7426
|
+
const countEntries = (v) => isRec20(v) ? Object.values(v).filter((e) => !!e && typeof e === "object").length : 0;
|
|
6977
7427
|
const listCount = countEntries(container.listViews);
|
|
6978
7428
|
const formCount = countEntries(container.formViews);
|
|
6979
7429
|
if (!listCount && !formCount) return { list: [], form: [] };
|
|
@@ -6991,14 +7441,14 @@ function readOptions(field) {
|
|
|
6991
7441
|
values.add(opt);
|
|
6992
7442
|
continue;
|
|
6993
7443
|
}
|
|
6994
|
-
if (!
|
|
6995
|
-
const value =
|
|
7444
|
+
if (!isRec20(opt)) continue;
|
|
7445
|
+
const value = strName16(opt.value);
|
|
6996
7446
|
if (!value) continue;
|
|
6997
7447
|
values.add(value);
|
|
6998
|
-
const label2 =
|
|
7448
|
+
const label2 = strName16(opt.label);
|
|
6999
7449
|
if (label2) byLabel.set(label2.toLowerCase(), value);
|
|
7000
7450
|
}
|
|
7001
|
-
} else if (
|
|
7451
|
+
} else if (isRec20(raw)) {
|
|
7002
7452
|
for (const [value, label2] of Object.entries(raw)) {
|
|
7003
7453
|
values.add(value);
|
|
7004
7454
|
if (typeof label2 === "string" && label2.length > 0) byLabel.set(label2.toLowerCase(), value);
|
|
@@ -7018,48 +7468,48 @@ function buildUniverse(stack) {
|
|
|
7018
7468
|
}
|
|
7019
7469
|
return facts;
|
|
7020
7470
|
};
|
|
7021
|
-
for (const obj of
|
|
7022
|
-
const objectName =
|
|
7471
|
+
for (const obj of asArray35(stack.objects)) {
|
|
7472
|
+
const objectName = strName16(obj.name);
|
|
7023
7473
|
if (!objectName) continue;
|
|
7024
7474
|
const facts = factsFor(objectName);
|
|
7025
|
-
for (const field of
|
|
7026
|
-
const fieldName =
|
|
7475
|
+
for (const field of asArray35(obj.fields)) {
|
|
7476
|
+
const fieldName = strName16(field.name);
|
|
7027
7477
|
if (fieldName) facts.fields.set(fieldName, field);
|
|
7028
7478
|
}
|
|
7029
|
-
for (const action of
|
|
7030
|
-
const actionName =
|
|
7479
|
+
for (const action of asArray35(obj.actions)) {
|
|
7480
|
+
const actionName = strName16(action.name);
|
|
7031
7481
|
if (actionName) facts.actions.set(actionName, action);
|
|
7032
7482
|
}
|
|
7033
|
-
for (const view of
|
|
7034
|
-
collectViewRecord({ ...view, object:
|
|
7483
|
+
for (const view of asArray35(obj.views)) {
|
|
7484
|
+
collectViewRecord({ ...view, object: strName16(view.object) ?? objectName }, factsFor);
|
|
7035
7485
|
}
|
|
7036
7486
|
collectViewRecord({ object: objectName, listViews: obj.listViews }, factsFor);
|
|
7037
|
-
for (const group of
|
|
7038
|
-
const key =
|
|
7487
|
+
for (const group of asArray35(obj.fieldGroups)) {
|
|
7488
|
+
const key = strName16(group.key) ?? strName16(group.name);
|
|
7039
7489
|
if (key) facts.sections.add(key);
|
|
7040
7490
|
}
|
|
7041
7491
|
}
|
|
7042
|
-
for (const view of
|
|
7492
|
+
for (const view of asArray35(stack.views)) {
|
|
7043
7493
|
collectViewRecord(view, factsFor);
|
|
7044
7494
|
}
|
|
7045
|
-
const pages =
|
|
7495
|
+
const pages = asArray35(stack.pages);
|
|
7046
7496
|
for (let pi = 0; pi < pages.length; pi++) {
|
|
7047
7497
|
for (const walked of walkPageComponents(pages[pi], `pages[${pi}]`)) {
|
|
7048
7498
|
if (!walked.objectName) continue;
|
|
7049
|
-
const props =
|
|
7499
|
+
const props = isRec20(walked.component.properties) ? walked.component.properties : void 0;
|
|
7050
7500
|
if (!props) continue;
|
|
7051
|
-
for (const section of
|
|
7052
|
-
const sectionName =
|
|
7501
|
+
for (const section of asArray35(props.sections)) {
|
|
7502
|
+
const sectionName = strName16(section.name);
|
|
7053
7503
|
if (sectionName) factsFor(walked.objectName).sections.add(sectionName);
|
|
7054
7504
|
}
|
|
7055
7505
|
}
|
|
7056
7506
|
}
|
|
7057
7507
|
const globalActions = /* @__PURE__ */ new Map();
|
|
7058
7508
|
const actionOwners = /* @__PURE__ */ new Map();
|
|
7059
|
-
for (const action of
|
|
7060
|
-
const actionName =
|
|
7509
|
+
for (const action of asArray35(stack.actions)) {
|
|
7510
|
+
const actionName = strName16(action.name);
|
|
7061
7511
|
if (!actionName) continue;
|
|
7062
|
-
const owner =
|
|
7512
|
+
const owner = strName16(action.objectName) ?? strName16(action.object);
|
|
7063
7513
|
if (owner) {
|
|
7064
7514
|
factsFor(owner).actions.set(actionName, action);
|
|
7065
7515
|
actionOwners.set(actionName, owner);
|
|
@@ -7073,41 +7523,41 @@ function buildUniverse(stack) {
|
|
|
7073
7523
|
}
|
|
7074
7524
|
}
|
|
7075
7525
|
const apps = /* @__PURE__ */ new Map();
|
|
7076
|
-
for (const app of
|
|
7077
|
-
const appName =
|
|
7526
|
+
for (const app of asArray35(stack.apps)) {
|
|
7527
|
+
const appName = strName16(app.name);
|
|
7078
7528
|
if (!appName) continue;
|
|
7079
7529
|
const navIds = apps.get(appName) ?? /* @__PURE__ */ new Set();
|
|
7080
7530
|
const walkNav = (items) => {
|
|
7081
|
-
for (const item of
|
|
7082
|
-
const id =
|
|
7531
|
+
for (const item of asArray35(items)) {
|
|
7532
|
+
const id = strName16(item.id);
|
|
7083
7533
|
if (id) navIds.add(id);
|
|
7084
7534
|
if (item.children) walkNav(item.children);
|
|
7085
7535
|
}
|
|
7086
7536
|
};
|
|
7087
7537
|
walkNav(app.navigation);
|
|
7088
|
-
for (const area of
|
|
7089
|
-
const areaId =
|
|
7538
|
+
for (const area of asArray35(app.areas)) {
|
|
7539
|
+
const areaId = strName16(area.id);
|
|
7090
7540
|
if (areaId) navIds.add(areaId);
|
|
7091
7541
|
walkNav(area.navigation);
|
|
7092
7542
|
}
|
|
7093
7543
|
apps.set(appName, navIds);
|
|
7094
7544
|
}
|
|
7095
7545
|
const dashboards = /* @__PURE__ */ new Map();
|
|
7096
|
-
for (const dash of
|
|
7097
|
-
const dashName =
|
|
7546
|
+
for (const dash of asArray35(stack.dashboards)) {
|
|
7547
|
+
const dashName = strName16(dash.name);
|
|
7098
7548
|
if (!dashName) continue;
|
|
7099
7549
|
const widgets = /* @__PURE__ */ new Set();
|
|
7100
|
-
for (const widget of
|
|
7101
|
-
const id =
|
|
7550
|
+
for (const widget of asArray35(dash.widgets)) {
|
|
7551
|
+
const id = strName16(widget.id) ?? strName16(widget.name);
|
|
7102
7552
|
if (id) widgets.add(id);
|
|
7103
7553
|
}
|
|
7104
7554
|
const actions = /* @__PURE__ */ new Set();
|
|
7105
7555
|
const headerActions = [
|
|
7106
|
-
...
|
|
7107
|
-
...
|
|
7556
|
+
...asArray35(isRec20(dash.header) ? dash.header.actions : void 0),
|
|
7557
|
+
...asArray35(dash.actions)
|
|
7108
7558
|
];
|
|
7109
7559
|
for (const action of headerActions) {
|
|
7110
|
-
const key =
|
|
7560
|
+
const key = strName16(action.actionUrl) ?? strName16(action.url) ?? strName16(action.name);
|
|
7111
7561
|
if (key) actions.add(key);
|
|
7112
7562
|
}
|
|
7113
7563
|
dashboards.set(dashName, { widgets, actions });
|
|
@@ -7119,7 +7569,7 @@ function localePath(bundleIndex, locale) {
|
|
|
7119
7569
|
}
|
|
7120
7570
|
function validateTranslationReferences(stack) {
|
|
7121
7571
|
const findings = [];
|
|
7122
|
-
if (!
|
|
7572
|
+
if (!isRec20(stack)) return findings;
|
|
7123
7573
|
const bundles = Array.isArray(stack.translations) ? stack.translations : [];
|
|
7124
7574
|
if (bundles.length === 0) return findings;
|
|
7125
7575
|
const universe = buildUniverse(stack);
|
|
@@ -7128,13 +7578,13 @@ function validateTranslationReferences(stack) {
|
|
|
7128
7578
|
};
|
|
7129
7579
|
for (let bi = 0; bi < bundles.length; bi++) {
|
|
7130
7580
|
const bundle = bundles[bi];
|
|
7131
|
-
if (!
|
|
7581
|
+
if (!isRec20(bundle)) continue;
|
|
7132
7582
|
for (const [locale, rawData] of Object.entries(bundle)) {
|
|
7133
|
-
if (!
|
|
7583
|
+
if (!isRec20(rawData)) continue;
|
|
7134
7584
|
const base = localePath(bi, locale);
|
|
7135
7585
|
const inLocale = `locale "${locale}"`;
|
|
7136
7586
|
for (const [objectName, rawNode] of Object.entries(asRecord(rawData.objects))) {
|
|
7137
|
-
if (!
|
|
7587
|
+
if (!isRec20(rawNode)) continue;
|
|
7138
7588
|
const objPath = `${base}.objects.${objectName}`;
|
|
7139
7589
|
const facts = universe.objects.get(objectName);
|
|
7140
7590
|
if (!facts) {
|
|
@@ -7160,7 +7610,7 @@ function validateTranslationReferences(stack) {
|
|
|
7160
7610
|
);
|
|
7161
7611
|
continue;
|
|
7162
7612
|
}
|
|
7163
|
-
if (!
|
|
7613
|
+
if (!isRec20(rawField)) continue;
|
|
7164
7614
|
checkOptionKeys(findings, {
|
|
7165
7615
|
optionMap: rawField.options,
|
|
7166
7616
|
field,
|
|
@@ -7242,7 +7692,7 @@ function validateTranslationReferences(stack) {
|
|
|
7242
7692
|
);
|
|
7243
7693
|
continue;
|
|
7244
7694
|
}
|
|
7245
|
-
if (!
|
|
7695
|
+
if (!isRec20(rawApp)) continue;
|
|
7246
7696
|
for (const navId of Object.keys(asRecord(rawApp.navigation))) {
|
|
7247
7697
|
if (navIds.has(navId)) continue;
|
|
7248
7698
|
orphan(
|
|
@@ -7265,7 +7715,7 @@ function validateTranslationReferences(stack) {
|
|
|
7265
7715
|
);
|
|
7266
7716
|
continue;
|
|
7267
7717
|
}
|
|
7268
|
-
if (!
|
|
7718
|
+
if (!isRec20(rawDash)) continue;
|
|
7269
7719
|
for (const widgetId of Object.keys(asRecord(rawDash.widgets))) {
|
|
7270
7720
|
if (dash.widgets.has(widgetId)) continue;
|
|
7271
7721
|
orphan(
|
|
@@ -7290,7 +7740,7 @@ function validateTranslationReferences(stack) {
|
|
|
7290
7740
|
return findings;
|
|
7291
7741
|
}
|
|
7292
7742
|
function asRecord(v) {
|
|
7293
|
-
return
|
|
7743
|
+
return isRec20(v) ? v : {};
|
|
7294
7744
|
}
|
|
7295
7745
|
function checkOptionKeys(findings, ctx) {
|
|
7296
7746
|
const optionKeys = Object.keys(asRecord(ctx.optionMap));
|
|
@@ -7302,7 +7752,7 @@ function checkOptionKeys(findings, ctx) {
|
|
|
7302
7752
|
rule: TRANSLATION_OPTION_KEY_UNKNOWN,
|
|
7303
7753
|
where: ctx.where,
|
|
7304
7754
|
path: ctx.path,
|
|
7305
|
-
message: `Option translations are keyed under field "${ctx.fieldName}" of object "${ctx.objectName}", which declares no \`options\` at all (field type "${
|
|
7755
|
+
message: `Option translations are keyed under field "${ctx.fieldName}" of object "${ctx.objectName}", which declares no \`options\` at all (field type "${strName16(ctx.field.type) ?? "unknown"}"). Nothing reads this map.`,
|
|
7306
7756
|
hint: `Declare the options on the field, move the translations to the field that owns them, or drop them.`
|
|
7307
7757
|
});
|
|
7308
7758
|
return;
|
|
@@ -7321,11 +7771,11 @@ function checkOptionKeys(findings, ctx) {
|
|
|
7321
7771
|
}
|
|
7322
7772
|
}
|
|
7323
7773
|
function checkActionParams(findings, ctx) {
|
|
7324
|
-
const rawParams = Object.keys(asRecord(
|
|
7774
|
+
const rawParams = Object.keys(asRecord(isRec20(ctx.rawAction) ? ctx.rawAction.params : void 0));
|
|
7325
7775
|
if (rawParams.length === 0) return;
|
|
7326
7776
|
const declared = /* @__PURE__ */ new Set();
|
|
7327
|
-
for (const param of
|
|
7328
|
-
const name =
|
|
7777
|
+
for (const param of asArray35(ctx.action.params)) {
|
|
7778
|
+
const name = strName16(param.name) ?? strName16(param.field);
|
|
7329
7779
|
if (name) declared.add(name);
|
|
7330
7780
|
}
|
|
7331
7781
|
for (const paramName of rawParams) {
|
|
@@ -7343,14 +7793,14 @@ function checkActionParams(findings, ctx) {
|
|
|
7343
7793
|
|
|
7344
7794
|
// src/validate-translatable-sections.ts
|
|
7345
7795
|
var TRANSLATION_SECTION_NAME_MISSING = "translation-section-name-missing";
|
|
7346
|
-
function
|
|
7796
|
+
function isRec21(v) {
|
|
7347
7797
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7348
7798
|
}
|
|
7349
|
-
function
|
|
7799
|
+
function strName17(v) {
|
|
7350
7800
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
7351
7801
|
}
|
|
7352
7802
|
function viewLabel(view) {
|
|
7353
|
-
const name =
|
|
7803
|
+
const name = strName17(view.name);
|
|
7354
7804
|
return name ? `view "${name}"` : "";
|
|
7355
7805
|
}
|
|
7356
7806
|
function joinWhere(...parts) {
|
|
@@ -7358,7 +7808,7 @@ function joinWhere(...parts) {
|
|
|
7358
7808
|
}
|
|
7359
7809
|
function collectViewSites(view, basePath, label2, sites) {
|
|
7360
7810
|
const recordObject = viewObjectName(view);
|
|
7361
|
-
const listBinding =
|
|
7811
|
+
const listBinding = isRec21(view.list) ? viewObjectName(view.list) ?? recordObject : void 0;
|
|
7362
7812
|
for (const site of viewContainerSites(view, basePath)) {
|
|
7363
7813
|
sites.push({
|
|
7364
7814
|
path: `${site.path}.sections`,
|
|
@@ -7372,11 +7822,11 @@ function translatedObjectNames(stack) {
|
|
|
7372
7822
|
const out = /* @__PURE__ */ new Set();
|
|
7373
7823
|
const bundles = Array.isArray(stack.translations) ? stack.translations : [];
|
|
7374
7824
|
for (const bundle of bundles) {
|
|
7375
|
-
if (!
|
|
7825
|
+
if (!isRec21(bundle)) continue;
|
|
7376
7826
|
for (const data of Object.values(bundle)) {
|
|
7377
|
-
if (!
|
|
7827
|
+
if (!isRec21(data) || !isRec21(data.objects)) continue;
|
|
7378
7828
|
for (const [objectName, node] of Object.entries(data.objects)) {
|
|
7379
|
-
if (
|
|
7829
|
+
if (isRec21(node)) out.add(objectName);
|
|
7380
7830
|
}
|
|
7381
7831
|
}
|
|
7382
7832
|
}
|
|
@@ -7388,22 +7838,22 @@ function suggestedName(label2) {
|
|
|
7388
7838
|
}
|
|
7389
7839
|
function validateTranslatableSections(stack) {
|
|
7390
7840
|
const findings = [];
|
|
7391
|
-
if (!
|
|
7841
|
+
if (!isRec21(stack)) return findings;
|
|
7392
7842
|
const translated = translatedObjectNames(stack);
|
|
7393
7843
|
if (translated.size === 0) return findings;
|
|
7394
7844
|
const sites = [];
|
|
7395
7845
|
for (const { rec: obj, path: objPath } of collectionEntries(stack.objects, "objects")) {
|
|
7396
|
-
const objectName =
|
|
7846
|
+
const objectName = strName17(obj.name);
|
|
7397
7847
|
if (!objectName) continue;
|
|
7398
7848
|
for (const { rec: view, path } of collectionEntries(obj.views, `${objPath}.views`)) {
|
|
7399
7849
|
collectViewSites(
|
|
7400
|
-
{ ...view, object:
|
|
7850
|
+
{ ...view, object: strName17(view.object) ?? objectName },
|
|
7401
7851
|
path,
|
|
7402
7852
|
viewLabel(view),
|
|
7403
7853
|
sites
|
|
7404
7854
|
);
|
|
7405
7855
|
}
|
|
7406
|
-
if (
|
|
7856
|
+
if (isRec21(obj.listViews)) {
|
|
7407
7857
|
collectViewSites({ object: objectName, listViews: obj.listViews }, objPath, "", sites);
|
|
7408
7858
|
}
|
|
7409
7859
|
}
|
|
@@ -7411,13 +7861,13 @@ function validateTranslatableSections(stack) {
|
|
|
7411
7861
|
collectViewSites(view, path, viewLabel(view), sites);
|
|
7412
7862
|
}
|
|
7413
7863
|
for (const { rec: page, path: pagePath } of collectionEntries(stack.pages, "pages")) {
|
|
7414
|
-
const pageName =
|
|
7864
|
+
const pageName = strName17(page.name);
|
|
7415
7865
|
const pageLabel = pageName ? `page "${pageName}"` : "";
|
|
7416
7866
|
for (const walked of walkPageComponents(page, pagePath)) {
|
|
7417
7867
|
if (!walked.objectName) continue;
|
|
7418
|
-
const props =
|
|
7868
|
+
const props = isRec21(walked.component.properties) ? walked.component.properties : void 0;
|
|
7419
7869
|
if (!props) continue;
|
|
7420
|
-
const type =
|
|
7870
|
+
const type = strName17(walked.component.type) ?? "component";
|
|
7421
7871
|
sites.push({
|
|
7422
7872
|
path: `${walked.path}.properties.sections`,
|
|
7423
7873
|
surface: joinWhere(pageLabel, type),
|
|
@@ -7432,9 +7882,9 @@ function validateTranslatableSections(stack) {
|
|
|
7432
7882
|
if (!Array.isArray(site.sections)) continue;
|
|
7433
7883
|
for (let i = 0; i < site.sections.length; i++) {
|
|
7434
7884
|
const section = site.sections[i];
|
|
7435
|
-
if (!
|
|
7436
|
-
if (
|
|
7437
|
-
const heading =
|
|
7885
|
+
if (!isRec21(section)) continue;
|
|
7886
|
+
if (strName17(section.name)) continue;
|
|
7887
|
+
const heading = strName17(section.label);
|
|
7438
7888
|
if (!heading) continue;
|
|
7439
7889
|
const slug = suggestedName(heading);
|
|
7440
7890
|
findings.push({
|
|
@@ -7452,14 +7902,14 @@ function validateTranslatableSections(stack) {
|
|
|
7452
7902
|
|
|
7453
7903
|
// src/validate-ai-surface-affinity.ts
|
|
7454
7904
|
var AI_SKILL_SURFACE_MISMATCH = "ai-skill-surface-mismatch";
|
|
7455
|
-
function
|
|
7905
|
+
function asArray36(v) {
|
|
7456
7906
|
if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
|
|
7457
7907
|
if (v && typeof v === "object") {
|
|
7458
7908
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
7459
7909
|
}
|
|
7460
7910
|
return [];
|
|
7461
7911
|
}
|
|
7462
|
-
function
|
|
7912
|
+
function strName18(v) {
|
|
7463
7913
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
7464
7914
|
}
|
|
7465
7915
|
function surfaceOf(v) {
|
|
@@ -7469,18 +7919,18 @@ function validateAiSurfaceAffinity(stack) {
|
|
|
7469
7919
|
const findings = [];
|
|
7470
7920
|
if (!stack || typeof stack !== "object") return findings;
|
|
7471
7921
|
const skillsByName = /* @__PURE__ */ new Map();
|
|
7472
|
-
for (const skill of
|
|
7473
|
-
const n =
|
|
7922
|
+
for (const skill of asArray36(stack.skills)) {
|
|
7923
|
+
const n = strName18(skill.name);
|
|
7474
7924
|
if (n) skillsByName.set(n, skill);
|
|
7475
7925
|
}
|
|
7476
|
-
const agents =
|
|
7926
|
+
const agents = asArray36(stack.agents);
|
|
7477
7927
|
for (let ai = 0; ai < agents.length; ai++) {
|
|
7478
7928
|
const agent = agents[ai];
|
|
7479
|
-
const agentName =
|
|
7929
|
+
const agentName = strName18(agent.name) ?? `#${ai}`;
|
|
7480
7930
|
const agentSurface = surfaceOf(agent.surface);
|
|
7481
7931
|
const skillRefs = Array.isArray(agent.skills) ? agent.skills : [];
|
|
7482
7932
|
for (let si = 0; si < skillRefs.length; si++) {
|
|
7483
|
-
const ref =
|
|
7933
|
+
const ref = strName18(skillRefs[si]);
|
|
7484
7934
|
if (!ref) continue;
|
|
7485
7935
|
const skill = skillsByName.get(ref);
|
|
7486
7936
|
if (!skill) continue;
|
|
@@ -7502,14 +7952,14 @@ function validateAiSurfaceAffinity(stack) {
|
|
|
7502
7952
|
// src/validate-ai-tool-references.ts
|
|
7503
7953
|
import { PLATFORM_PROVIDED_TOOL_NAMES, PLATFORM_TOOL_FAMILY_PREFIXES } from "@objectstack/spec/system";
|
|
7504
7954
|
var AI_SKILL_TOOL_UNRESOLVED = "ai-skill-tool-unresolved";
|
|
7505
|
-
function
|
|
7955
|
+
function asArray37(v) {
|
|
7506
7956
|
if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
|
|
7507
7957
|
if (v && typeof v === "object") {
|
|
7508
7958
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
7509
7959
|
}
|
|
7510
7960
|
return [];
|
|
7511
7961
|
}
|
|
7512
|
-
function
|
|
7962
|
+
function strName19(v) {
|
|
7513
7963
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
7514
7964
|
}
|
|
7515
7965
|
function distance6(a, b) {
|
|
@@ -7550,26 +8000,26 @@ function materialisesAsTool(action) {
|
|
|
7550
8000
|
if (!ai || typeof ai !== "object") return false;
|
|
7551
8001
|
const aiRec = ai;
|
|
7552
8002
|
if (aiRec.exposed !== true) return false;
|
|
7553
|
-
if (!
|
|
7554
|
-
const type =
|
|
8003
|
+
if (!strName19(aiRec.description)) return false;
|
|
8004
|
+
const type = strName19(action.type);
|
|
7555
8005
|
if (!type || !HEADLESS_ACTION_TYPES.has(type)) return false;
|
|
7556
8006
|
if (type === "script") return Boolean(action.target || action.body);
|
|
7557
8007
|
return Boolean(action.target);
|
|
7558
8008
|
}
|
|
7559
8009
|
function collectToolUniverse(stack) {
|
|
7560
8010
|
const universe = new Set(PLATFORM_PROVIDED_TOOL_NAMES);
|
|
7561
|
-
for (const tool of
|
|
7562
|
-
const n =
|
|
8011
|
+
for (const tool of asArray37(stack.tools)) {
|
|
8012
|
+
const n = strName19(tool.name);
|
|
7563
8013
|
if (n) universe.add(n);
|
|
7564
8014
|
}
|
|
7565
8015
|
const addActionFamily = (actions) => {
|
|
7566
|
-
for (const action of
|
|
7567
|
-
const n =
|
|
8016
|
+
for (const action of asArray37(actions)) {
|
|
8017
|
+
const n = strName19(action.name);
|
|
7568
8018
|
if (n && materialisesAsTool(action)) universe.add(`action_${n}`);
|
|
7569
8019
|
}
|
|
7570
8020
|
};
|
|
7571
8021
|
addActionFamily(stack.actions);
|
|
7572
|
-
for (const obj of
|
|
8022
|
+
for (const obj of asArray37(stack.objects)) {
|
|
7573
8023
|
addActionFamily(obj.actions);
|
|
7574
8024
|
}
|
|
7575
8025
|
return universe;
|
|
@@ -7577,13 +8027,13 @@ function collectToolUniverse(stack) {
|
|
|
7577
8027
|
function collectUnexposedActionNames(stack) {
|
|
7578
8028
|
const names = /* @__PURE__ */ new Set();
|
|
7579
8029
|
const scan = (actions) => {
|
|
7580
|
-
for (const action of
|
|
7581
|
-
const n =
|
|
8030
|
+
for (const action of asArray37(actions)) {
|
|
8031
|
+
const n = strName19(action.name);
|
|
7582
8032
|
if (n && !materialisesAsTool(action)) names.add(n);
|
|
7583
8033
|
}
|
|
7584
8034
|
};
|
|
7585
8035
|
scan(stack.actions);
|
|
7586
|
-
for (const obj of
|
|
8036
|
+
for (const obj of asArray37(stack.objects)) scan(obj.actions);
|
|
7587
8037
|
return names;
|
|
7588
8038
|
}
|
|
7589
8039
|
function validateAiToolReferences(stack) {
|
|
@@ -7601,13 +8051,13 @@ function validateAiToolReferences(stack) {
|
|
|
7601
8051
|
}
|
|
7602
8052
|
return universe.has(ref);
|
|
7603
8053
|
};
|
|
7604
|
-
const skills =
|
|
8054
|
+
const skills = asArray37(stack.skills);
|
|
7605
8055
|
for (let si = 0; si < skills.length; si++) {
|
|
7606
8056
|
const skill = skills[si];
|
|
7607
|
-
const skillName =
|
|
8057
|
+
const skillName = strName19(skill.name) ?? `#${si}`;
|
|
7608
8058
|
const refs = Array.isArray(skill.tools) ? skill.tools : [];
|
|
7609
8059
|
for (let ti = 0; ti < refs.length; ti++) {
|
|
7610
|
-
const ref =
|
|
8060
|
+
const ref = strName19(refs[ti]);
|
|
7611
8061
|
if (!ref || resolves(ref)) continue;
|
|
7612
8062
|
const isPattern = ref.endsWith("*");
|
|
7613
8063
|
const unexposed = !isPattern && ref.startsWith("action_") && unexposedActions.has(ref.slice("action_".length)) ? ref.slice("action_".length) : void 0;
|
|
@@ -7627,24 +8077,24 @@ function validateAiToolReferences(stack) {
|
|
|
7627
8077
|
// src/validate-ai-agent-authoring.ts
|
|
7628
8078
|
var AGENT_AUTHORING_WITHDRAWN = "agent-authoring-withdrawn";
|
|
7629
8079
|
var DEFAULT_AGENT_OUTSIDE_ROSTER = "default-agent-outside-roster";
|
|
7630
|
-
function
|
|
8080
|
+
function asArray38(v) {
|
|
7631
8081
|
if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
|
|
7632
8082
|
if (v && typeof v === "object") {
|
|
7633
8083
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
7634
8084
|
}
|
|
7635
8085
|
return [];
|
|
7636
8086
|
}
|
|
7637
|
-
function
|
|
8087
|
+
function strName20(v) {
|
|
7638
8088
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
7639
8089
|
}
|
|
7640
8090
|
var PLATFORM_AGENT_NAMES = /* @__PURE__ */ new Set(["ask", "build", "data_chat", "metadata_assistant"]);
|
|
7641
8091
|
function validateAiAgentAuthoring(stack) {
|
|
7642
8092
|
const findings = [];
|
|
7643
8093
|
if (!stack || typeof stack !== "object") return findings;
|
|
7644
|
-
const agents =
|
|
8094
|
+
const agents = asArray38(stack.agents);
|
|
7645
8095
|
for (let ai = 0; ai < agents.length; ai++) {
|
|
7646
8096
|
const agent = agents[ai];
|
|
7647
|
-
const name =
|
|
8097
|
+
const name = strName20(agent.name) ?? `#${ai}`;
|
|
7648
8098
|
const isPlatformName = PLATFORM_AGENT_NAMES.has(name);
|
|
7649
8099
|
const skillCount = Array.isArray(agent.skills) ? agent.skills.length : 0;
|
|
7650
8100
|
findings.push({
|
|
@@ -7657,12 +8107,12 @@ function validateAiAgentAuthoring(stack) {
|
|
|
7657
8107
|
});
|
|
7658
8108
|
}
|
|
7659
8109
|
const roster = [...PLATFORM_AGENT_NAMES].join(", ");
|
|
7660
|
-
const apps =
|
|
8110
|
+
const apps = asArray38(stack.apps);
|
|
7661
8111
|
for (let appIdx = 0; appIdx < apps.length; appIdx++) {
|
|
7662
8112
|
const app = apps[appIdx];
|
|
7663
|
-
const defaultAgent =
|
|
8113
|
+
const defaultAgent = strName20(app.defaultAgent);
|
|
7664
8114
|
if (!defaultAgent || PLATFORM_AGENT_NAMES.has(defaultAgent)) continue;
|
|
7665
|
-
const appName =
|
|
8115
|
+
const appName = strName20(app.name) ?? `#${appIdx}`;
|
|
7666
8116
|
findings.push({
|
|
7667
8117
|
severity: "warning",
|
|
7668
8118
|
rule: DEFAULT_AGENT_OUTSIDE_ROSTER,
|
|
@@ -7764,24 +8214,24 @@ var IMPLICIT_FIELDS2 = /* @__PURE__ */ new Set([
|
|
|
7764
8214
|
"owner",
|
|
7765
8215
|
"record_type"
|
|
7766
8216
|
]);
|
|
7767
|
-
var
|
|
7768
|
-
function
|
|
7769
|
-
if (Array.isArray(v)) return v.filter((x) =>
|
|
7770
|
-
if (
|
|
8217
|
+
var isRec22 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
8218
|
+
function asArray39(v) {
|
|
8219
|
+
if (Array.isArray(v)) return v.filter((x) => isRec22(x));
|
|
8220
|
+
if (isRec22(v)) {
|
|
7771
8221
|
return Object.entries(v).map(([name, def]) => ({
|
|
7772
8222
|
name,
|
|
7773
|
-
...
|
|
8223
|
+
...isRec22(def) ? def : {}
|
|
7774
8224
|
}));
|
|
7775
8225
|
}
|
|
7776
8226
|
return [];
|
|
7777
8227
|
}
|
|
7778
8228
|
function indexObjectFields2(stack) {
|
|
7779
8229
|
const out = /* @__PURE__ */ new Map();
|
|
7780
|
-
for (const obj of
|
|
8230
|
+
for (const obj of asArray39(stack.objects)) {
|
|
7781
8231
|
const name = typeof obj.name === "string" ? obj.name : void 0;
|
|
7782
8232
|
if (!name) continue;
|
|
7783
8233
|
const names = /* @__PURE__ */ new Set();
|
|
7784
|
-
for (const f of
|
|
8234
|
+
for (const f of asArray39(obj.fields)) {
|
|
7785
8235
|
if (typeof f.name === "string" && f.name) names.add(f.name);
|
|
7786
8236
|
}
|
|
7787
8237
|
out.set(name, names);
|
|
@@ -7911,12 +8361,12 @@ ${source}
|
|
|
7911
8361
|
}
|
|
7912
8362
|
function validateHookBodyWrites(stack) {
|
|
7913
8363
|
const findings = [];
|
|
7914
|
-
const hooks =
|
|
8364
|
+
const hooks = asArray39(stack.hooks);
|
|
7915
8365
|
if (hooks.length === 0) return findings;
|
|
7916
8366
|
let objectFields = null;
|
|
7917
8367
|
hooks.forEach((hook, hookIndex) => {
|
|
7918
8368
|
const body = hook.body;
|
|
7919
|
-
if (!
|
|
8369
|
+
if (!isRec22(body) || body.language !== "js") return;
|
|
7920
8370
|
const source = body.source;
|
|
7921
8371
|
if (typeof source !== "string" || source.trim() === "") return;
|
|
7922
8372
|
const writes = extractHookBodyWrites(source).filter((w) => HOOK_APPLICABLE_IDS.has(w.patternId));
|
|
@@ -7997,13 +8447,13 @@ var ACTION_BODY_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_B
|
|
|
7997
8447
|
var ACTION_RECORD_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_RECORD_WRITE_PATTERN_IDS.includes(p.id));
|
|
7998
8448
|
var APPLICABLE_IDS = new Set(ACTION_BODY_WRITE_PATTERN_IDS);
|
|
7999
8449
|
var RECORD_WRITE_IDS = new Set(ACTION_RECORD_WRITE_PATTERN_IDS);
|
|
8000
|
-
var
|
|
8001
|
-
function
|
|
8002
|
-
if (Array.isArray(v)) return v.filter((x) =>
|
|
8003
|
-
if (
|
|
8450
|
+
var isRec23 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
8451
|
+
function asArray40(v) {
|
|
8452
|
+
if (Array.isArray(v)) return v.filter((x) => isRec23(x));
|
|
8453
|
+
if (isRec23(v)) {
|
|
8004
8454
|
return Object.entries(v).map(([name, def]) => ({
|
|
8005
8455
|
name,
|
|
8006
|
-
...
|
|
8456
|
+
...isRec23(def) ? def : {}
|
|
8007
8457
|
}));
|
|
8008
8458
|
}
|
|
8009
8459
|
return [];
|
|
@@ -8017,11 +8467,11 @@ function collectActionBodies(stack) {
|
|
|
8017
8467
|
const sites = [];
|
|
8018
8468
|
const seen = /* @__PURE__ */ new Set();
|
|
8019
8469
|
const collect = (actions, pathPrefix, parentObject) => {
|
|
8020
|
-
|
|
8470
|
+
asArray40(actions).forEach((action, index) => {
|
|
8021
8471
|
const type = typeof action.type === "string" ? action.type : "script";
|
|
8022
8472
|
if (type !== "script") return;
|
|
8023
8473
|
const body = action.body;
|
|
8024
|
-
if (!
|
|
8474
|
+
if (!isRec23(body) || body.language !== "js") return;
|
|
8025
8475
|
const source = body.source;
|
|
8026
8476
|
if (typeof source !== "string" || source.trim() === "") return;
|
|
8027
8477
|
const name = typeof action.name === "string" && action.name ? action.name : `#${index}`;
|
|
@@ -8032,7 +8482,7 @@ function collectActionBodies(stack) {
|
|
|
8032
8482
|
});
|
|
8033
8483
|
};
|
|
8034
8484
|
collect(stack.actions, "actions");
|
|
8035
|
-
|
|
8485
|
+
asArray40(stack.objects).forEach((obj, objIndex) => {
|
|
8036
8486
|
const parentObject = typeof obj.name === "string" && obj.name ? obj.name : void 0;
|
|
8037
8487
|
collect(obj.actions, `objects[${objIndex}].actions`, parentObject);
|
|
8038
8488
|
});
|
|
@@ -8040,7 +8490,7 @@ function collectActionBodies(stack) {
|
|
|
8040
8490
|
}
|
|
8041
8491
|
function validateActionBodyWrites(stack) {
|
|
8042
8492
|
const findings = [];
|
|
8043
|
-
if (!
|
|
8493
|
+
if (!isRec23(stack)) return findings;
|
|
8044
8494
|
const sites = collectActionBodies(stack);
|
|
8045
8495
|
if (sites.length === 0) return findings;
|
|
8046
8496
|
let objectFields = null;
|
|
@@ -8099,13 +8549,13 @@ import { findClosestMatches as findClosestMatches4, formatSuggestion as formatSu
|
|
|
8099
8549
|
var FLOW_NODE_WRITE_UNKNOWN_FIELD = "flow-node-write-unknown-field";
|
|
8100
8550
|
var FLOW_WRITE_NODE_TYPES = ["update_record", "create_record"];
|
|
8101
8551
|
var FLOW_WRITE_NODE_TYPES_DEFERRED = [];
|
|
8102
|
-
var
|
|
8103
|
-
function
|
|
8104
|
-
if (Array.isArray(v)) return v.filter((x) =>
|
|
8105
|
-
if (
|
|
8552
|
+
var isRec24 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
8553
|
+
function asArray41(v) {
|
|
8554
|
+
if (Array.isArray(v)) return v.filter((x) => isRec24(x));
|
|
8555
|
+
if (isRec24(v)) {
|
|
8106
8556
|
return Object.entries(v).map(([name, def]) => ({
|
|
8107
8557
|
name,
|
|
8108
|
-
...
|
|
8558
|
+
...isRec24(def) ? def : {}
|
|
8109
8559
|
}));
|
|
8110
8560
|
}
|
|
8111
8561
|
return [];
|
|
@@ -8118,8 +8568,8 @@ function readLiteralObjectName2(config) {
|
|
|
8118
8568
|
var COVERED_TYPES = new Set(FLOW_WRITE_NODE_TYPES);
|
|
8119
8569
|
function validateFlowNodeWrites(stack) {
|
|
8120
8570
|
const findings = [];
|
|
8121
|
-
if (!
|
|
8122
|
-
const flows =
|
|
8571
|
+
if (!isRec24(stack)) return findings;
|
|
8572
|
+
const flows = asArray41(stack.flows);
|
|
8123
8573
|
if (flows.length === 0) return findings;
|
|
8124
8574
|
let objectFields = null;
|
|
8125
8575
|
flows.forEach((flow, flowIndex) => {
|
|
@@ -8127,10 +8577,10 @@ function validateFlowNodeWrites(stack) {
|
|
|
8127
8577
|
const walked = walkFlowNodes(flow, `flows[${flowIndex}]`);
|
|
8128
8578
|
walked.forEach(({ node, path: nodePath, regionTrail }, walkIndex) => {
|
|
8129
8579
|
if (typeof node.type !== "string" || !COVERED_TYPES.has(node.type)) return;
|
|
8130
|
-
const config =
|
|
8580
|
+
const config = isRec24(node.config) ? node.config : void 0;
|
|
8131
8581
|
if (!config) return;
|
|
8132
8582
|
const fields = config.fields;
|
|
8133
|
-
if (!
|
|
8583
|
+
if (!isRec24(fields)) return;
|
|
8134
8584
|
const written = Object.keys(fields);
|
|
8135
8585
|
if (written.length === 0) return;
|
|
8136
8586
|
const objectName = readLiteralObjectName2(config);
|
|
@@ -8176,6 +8626,15 @@ var REFERENCE_INTEGRITY_RULES = [
|
|
|
8176
8626
|
// `action` is deliberately absent (validateActionNameRefs owns it) and so is
|
|
8177
8627
|
// `component` (an unregistered ref renders a named diagnostic, not silence).
|
|
8178
8628
|
{ name: "validateNavTargetRefs", run: validateNavTargetRefs },
|
|
8629
|
+
// [#7912] The THIRD question about a nav entry, after "does the target
|
|
8630
|
+
// resolve?" (above) and "is it granted?" (`validateNavAccess`): can the
|
|
8631
|
+
// destination serve at all? An object's own `enable` block can make its list
|
|
8632
|
+
// answer 404/405 for every persona, and no gate authorable on the entry
|
|
8633
|
+
// expresses that — which is how #7544's dead row survived review for a year.
|
|
8634
|
+
// The server now prunes such an entry from the `/meta` payload; the
|
|
8635
|
+
// maintainer ruling of 2026-08-12 makes THIS the mandatory companion, so the
|
|
8636
|
+
// prune is never silent to the author who wrote the row.
|
|
8637
|
+
{ name: "validateNavObjectServability", run: validateNavObjectServability },
|
|
8179
8638
|
{ name: "validateTranslationReferences", run: validateTranslationReferences },
|
|
8180
8639
|
// The same family from the other end (#5417). Its sibling above asks "does
|
|
8181
8640
|
// this bundle key resolve?"; this one asks "is there a key at all?" — a form
|
|
@@ -8276,7 +8735,7 @@ import {
|
|
|
8276
8735
|
collectFlowGraphs as collectFlowGraphs2
|
|
8277
8736
|
} from "@objectstack/spec/automation";
|
|
8278
8737
|
import { reduceFilterVerdict as reduceFilterVerdict2 } from "@objectstack/spec/data";
|
|
8279
|
-
function
|
|
8738
|
+
function asArray42(v) {
|
|
8280
8739
|
if (Array.isArray(v)) return v;
|
|
8281
8740
|
if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
8282
8741
|
return [];
|
|
@@ -8660,7 +9119,7 @@ function scanApprovalReviseLoops(at, nodes, edges, findings) {
|
|
|
8660
9119
|
}
|
|
8661
9120
|
function lintFlowPatterns(stack) {
|
|
8662
9121
|
const findings = [];
|
|
8663
|
-
for (const flow of
|
|
9122
|
+
for (const flow of asArray42(stack.flows)) {
|
|
8664
9123
|
const flowName = typeof flow.name === "string" ? flow.name : "(unnamed flow)";
|
|
8665
9124
|
const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
|
|
8666
9125
|
const edges = Array.isArray(flow.edges) ? flow.edges : [];
|
|
@@ -8757,7 +9216,7 @@ import { dirname, join } from "path";
|
|
|
8757
9216
|
import { existsSync, readFileSync } from "fs";
|
|
8758
9217
|
var LIVENESS_DEAD_PROPERTY = "liveness-dead-property";
|
|
8759
9218
|
var LIVENESS_EXPERIMENTAL_PROPERTY = "liveness-experimental-property";
|
|
8760
|
-
function
|
|
9219
|
+
function asArray43(v) {
|
|
8761
9220
|
if (Array.isArray(v)) return v;
|
|
8762
9221
|
if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
8763
9222
|
return [];
|
|
@@ -8894,11 +9353,11 @@ function lintLivenessProperties(stack) {
|
|
|
8894
9353
|
const findings = [];
|
|
8895
9354
|
const objectWarn = loadWarnMap(dir, "object");
|
|
8896
9355
|
const fieldWarn = loadWarnMap(dir, "field");
|
|
8897
|
-
for (const obj of
|
|
9356
|
+
for (const obj of asArray43(stack.objects)) {
|
|
8898
9357
|
const objName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
|
|
8899
9358
|
if (objectWarn.size > 0) checkItem("object", obj, `object '${objName}'`, objectWarn, findings);
|
|
8900
9359
|
if (fieldWarn.size > 0) {
|
|
8901
|
-
for (const field of
|
|
9360
|
+
for (const field of asArray43(obj.fields)) {
|
|
8902
9361
|
const fieldName = typeof field.name === "string" ? field.name : "(unnamed field)";
|
|
8903
9362
|
checkItem("field", field, `object '${objName}' \xB7 field '${fieldName}'`, fieldWarn, findings);
|
|
8904
9363
|
}
|
|
@@ -8907,7 +9366,7 @@ function lintLivenessProperties(stack) {
|
|
|
8907
9366
|
for (const { type, key } of TYPE_COLLECTIONS) {
|
|
8908
9367
|
const warnMap = loadWarnMap(dir, type);
|
|
8909
9368
|
if (warnMap.size === 0) continue;
|
|
8910
|
-
for (const item of
|
|
9369
|
+
for (const item of asArray43(stack[key])) {
|
|
8911
9370
|
const name = typeof item.name === "string" ? item.name : typeof item.object === "string" ? item.object : `(unnamed ${type})`;
|
|
8912
9371
|
checkItem(type, item, `${type} '${name}'`, warnMap, findings);
|
|
8913
9372
|
}
|
|
@@ -8921,7 +9380,7 @@ var AUTONUMBER_UNKNOWN_FIELD = "autonumber-references-unknown-field";
|
|
|
8921
9380
|
var AUTONUMBER_OPTIONAL_FIELD = "autonumber-references-optional-field";
|
|
8922
9381
|
var AUTONUMBER_SELF_REFERENCE = "autonumber-references-self";
|
|
8923
9382
|
var AUTONUMBER_LITERAL_TOKEN = "autonumber-unrecognized-token";
|
|
8924
|
-
function
|
|
9383
|
+
function asArray44(v) {
|
|
8925
9384
|
if (Array.isArray(v)) return v;
|
|
8926
9385
|
if (v && typeof v === "object") {
|
|
8927
9386
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -8930,9 +9389,9 @@ function asArray43(v) {
|
|
|
8930
9389
|
}
|
|
8931
9390
|
function lintAutonumberFormats(stack) {
|
|
8932
9391
|
const findings = [];
|
|
8933
|
-
for (const obj of
|
|
9392
|
+
for (const obj of asArray44(stack.objects)) {
|
|
8934
9393
|
const objectName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
|
|
8935
|
-
const fields =
|
|
9394
|
+
const fields = asArray44(obj.fields);
|
|
8936
9395
|
const fieldMeta = /* @__PURE__ */ new Map();
|
|
8937
9396
|
for (const f of fields) {
|
|
8938
9397
|
if (typeof f.name === "string") fieldMeta.set(f.name, { required: f.required === true });
|
|
@@ -8998,7 +9457,7 @@ function lintAutonumberFormats(stack) {
|
|
|
8998
9457
|
|
|
8999
9458
|
// src/lint-view-refs.ts
|
|
9000
9459
|
import { expandViewContainerWithDiagnostics, isAggregatedViewContainer } from "@objectstack/spec";
|
|
9001
|
-
function
|
|
9460
|
+
function asArray45(v) {
|
|
9002
9461
|
if (Array.isArray(v)) return v;
|
|
9003
9462
|
if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
9004
9463
|
return [];
|
|
@@ -9026,7 +9485,7 @@ function lintViewRefs(stack) {
|
|
|
9026
9485
|
s.add(kind);
|
|
9027
9486
|
};
|
|
9028
9487
|
const containers = [];
|
|
9029
|
-
for (const v of
|
|
9488
|
+
for (const v of asArray45(stack.views)) {
|
|
9030
9489
|
if (v.viewKind) {
|
|
9031
9490
|
if (typeof v.name === "string") indexKind(v.name, v.viewKind === "form" ? "form" : "list");
|
|
9032
9491
|
continue;
|
|
@@ -9035,7 +9494,7 @@ function lintViewRefs(stack) {
|
|
|
9035
9494
|
const object = viewContainerObjectName(v);
|
|
9036
9495
|
if (object) containers.push({ object, container: v });
|
|
9037
9496
|
}
|
|
9038
|
-
for (const obj of
|
|
9497
|
+
for (const obj of asArray45(stack.objects)) {
|
|
9039
9498
|
const object = typeof obj.name === "string" ? obj.name : void 0;
|
|
9040
9499
|
if (!object) continue;
|
|
9041
9500
|
if (obj.list || obj.form || obj.listViews || obj.formViews) {
|
|
@@ -9089,11 +9548,11 @@ function lintViewRefs(stack) {
|
|
|
9089
9548
|
});
|
|
9090
9549
|
}
|
|
9091
9550
|
};
|
|
9092
|
-
for (const obj of
|
|
9551
|
+
for (const obj of asArray45(stack.objects)) {
|
|
9093
9552
|
const object = typeof obj.name === "string" ? obj.name : void 0;
|
|
9094
|
-
for (const action of
|
|
9553
|
+
for (const action of asArray45(obj.actions)) checkAction(action, object);
|
|
9095
9554
|
}
|
|
9096
|
-
for (const action of
|
|
9555
|
+
for (const action of asArray45(stack.actions)) checkAction(action);
|
|
9097
9556
|
return findings;
|
|
9098
9557
|
}
|
|
9099
9558
|
|
|
@@ -9397,7 +9856,6 @@ var CLI_ONLY = ["cli"];
|
|
|
9397
9856
|
var CLI_AND_RUNTIME = ["cli", "runtime-publish"];
|
|
9398
9857
|
var RUNTIME_NEEDS_FULL_SNAPSHOT = "P2 (#4463): reads a stack-wide collection the per-write snapshot does not carry, so running it now would report the rest of the tenant's metadata as missing rather than judging this write.";
|
|
9399
9858
|
var RUNTIME_HEAVY_SOURCE_PARSE = "Not runtime-safe: parses authored source through typescript/sucrase, the two dependencies the kernel boot path must never load (lazy-deps.test.ts). Studio compiles page source on its own path.";
|
|
9400
|
-
var RUNTIME_VISIBILITY_FAMILY_IS_CLI_ONLY = "Deliberate, and not a snapshot limitation: this rule needs only the written item, but every other rule on the `views[]` visibility-predicate surface (validate-visibility-predicates.ts) is CLI-only. Gating one of three sibling verdicts about the same predicate at the Studio door is less predictable than gating none; move the family together, as one measured edit.";
|
|
9401
9859
|
var RUNTIME_OBJECT_WRITES_P2 = "P2 (#4463): judges an object/field declaration. Object writes are the hottest metadata path in the product, so P1 gates `flow` first and widens once the gate has real traffic behind it.";
|
|
9402
9860
|
var EXPRESSION_INVALID = "expression-invalid";
|
|
9403
9861
|
var AUTHORING_RULES = [
|
|
@@ -9463,6 +9921,31 @@ var AUTHORING_RULES = [
|
|
|
9463
9921
|
surfaceReason: RUNTIME_OBJECT_WRITES_P2,
|
|
9464
9922
|
run: (stack) => validateFunctionalCompleteness(stack)
|
|
9465
9923
|
},
|
|
9924
|
+
// [#7521, via cloud#1225] A managed object advertising a generic write verb
|
|
9925
|
+
// in `enable.apiMethods` that its own resolved affordances refuse. Every key
|
|
9926
|
+
// is one we know and each is individually valid, so #4001's unknown-key
|
|
9927
|
+
// rejection and the Zod parse both pass it; the contradiction is only visible
|
|
9928
|
+
// when the two keys are read TOGETHER, which nothing did at authoring time.
|
|
9929
|
+
//
|
|
9930
|
+
// `gating` because the declaration is already false when it ships: objectql's
|
|
9931
|
+
// registry strips the verb at registration, so the metadata advertises an API
|
|
9932
|
+
// the product does not serve. That strip has been correct and silent — a
|
|
9933
|
+
// `console.warn` on every control-plane boot that went unread for the life of
|
|
9934
|
+
// a real divergence (`sys_environment`/`sys_package`). This entry is the
|
|
9935
|
+
// ruling's "close it where the author is"; boot stays warn-and-strip.
|
|
9936
|
+
//
|
|
9937
|
+
// Pre-parse: the predicate reads only authored keys, and the finding must
|
|
9938
|
+
// survive an unrelated schema error elsewhere in the stack.
|
|
9939
|
+
{
|
|
9940
|
+
name: "validateManagedApiMethods",
|
|
9941
|
+
tier: "gating",
|
|
9942
|
+
input: "normalized",
|
|
9943
|
+
commands: ALL,
|
|
9944
|
+
source: "packages/lint/src/validate-managed-api-methods.ts",
|
|
9945
|
+
surfaces: CLI_ONLY,
|
|
9946
|
+
surfaceReason: RUNTIME_OBJECT_WRITES_P2,
|
|
9947
|
+
run: (stack) => validateManagedApiMethods(stack)
|
|
9948
|
+
},
|
|
9466
9949
|
// A view container in `views: []` that registers zero views: nothing appears
|
|
9467
9950
|
// in the Console, and the schema step cannot tell it from an intentionally
|
|
9468
9951
|
// empty one. The FLAT-list-view arm no longer needs this tier — `ViewSchema`
|
|
@@ -9842,14 +10325,47 @@ var AUTHORING_RULES = [
|
|
|
9842
10325
|
// what decides whether any given diagnostic gates, exactly as `lintFlowPatterns`
|
|
9843
10326
|
// has worked since #3760. The promotion follows the #5762 precedent: a family
|
|
9844
10327
|
// that gains an `error` finding moves its registry tier in the same edit.
|
|
10328
|
+
//
|
|
10329
|
+
// ─── The `views[]` visibility-predicate FAMILY at the runtime door (#7220) ───
|
|
10330
|
+
//
|
|
10331
|
+
// This entry and `validatePredicatePathRefs` below moved to `runtime-publish`
|
|
10332
|
+
// in ONE edit, on the maintainer's 2026-08-10 ruling, sequenced after #4717's
|
|
10333
|
+
// `advisories` channel landed (PR #7435). Before that move a `view` written
|
|
10334
|
+
// through Studio / REST `/meta` / MCP — the only door most tenants have, and
|
|
10335
|
+
// the door AI authors use — was judged by NONE of the family's rule ids (six
|
|
10336
|
+
// at the time of the move; seven since #7659 added
|
|
10337
|
+
// `predicate-rhs-path-shaped` inside the second entry).
|
|
10338
|
+
//
|
|
10339
|
+
// They move together on purpose, and the two entries carry one comment because
|
|
10340
|
+
// they are one wall: #7214's implementer wired its own rule here alone and then
|
|
10341
|
+
// REVERTED it, because a `view` refused for an unresolvable predicate PATH
|
|
10342
|
+
// while a predicate that does not parse at all walks through the same door is
|
|
10343
|
+
// less predictable than refusing neither. A half-wired wall is worse than an
|
|
10344
|
+
// unwired one, so `authoring-rule-wiring.test.ts` now pins the family property
|
|
10345
|
+
// directly: every id on this surface is gated at the runtime door, or none is.
|
|
10346
|
+
//
|
|
10347
|
+
// The previous `surfaceReason` on THIS entry was `RUNTIME_NEEDS_FULL_SNAPSHOT`,
|
|
10348
|
+
// and re-measuring it at move time found it false: both rule functions read
|
|
10349
|
+
// `stack.views` and `stack.pages` and NO other collection — never `objects` —
|
|
10350
|
+
// so the per-write snapshot the gate builds is not partial for them, it is
|
|
10351
|
+
// complete. (`pages` is simply absent on a `view` write, so the page half
|
|
10352
|
+
// contributes zero findings to both differential passes rather than inventing
|
|
10353
|
+
// any.) The reason was not describing this rule; it was the default a rule got
|
|
10354
|
+
// when nobody measured, which is the #4409/#4463 defect one layer in.
|
|
10355
|
+
//
|
|
10356
|
+
// Runtime input tier: the gate hands the rules the body as persisted, without
|
|
10357
|
+
// `normalizeStackInput`, so the ADR-0087 D2 alias fold does NOT run at this
|
|
10358
|
+
// door. That costs the family nothing — `validateVisibilityPredicates` reads
|
|
10359
|
+
// `visibleWhen ?? visibleOn ?? visibility` itself, canonical-first, precisely
|
|
10360
|
+
// so a caller handing it a raw authored object still gets a verdict.
|
|
9845
10361
|
{
|
|
9846
10362
|
name: "validateVisibilityPredicates",
|
|
9847
10363
|
tier: "gating",
|
|
9848
10364
|
input: "normalized",
|
|
9849
10365
|
commands: ALL,
|
|
9850
10366
|
source: "packages/lint/src/validate-visibility-predicates.ts",
|
|
9851
|
-
surfaces:
|
|
9852
|
-
|
|
10367
|
+
surfaces: CLI_AND_RUNTIME,
|
|
10368
|
+
runtimeTypes: ["view"],
|
|
9853
10369
|
run: (stack) => validateVisibilityPredicates(stack)
|
|
9854
10370
|
},
|
|
9855
10371
|
// #7010 — the same predicate surface, one question further in. The three
|
|
@@ -9866,14 +10382,28 @@ var AUTHORING_RULES = [
|
|
|
9866
10382
|
// object's addressable path set is NOT closed (lookup traversal, system
|
|
9867
10383
|
// columns, formula outputs), and an `error` gate over an open set generates
|
|
9868
10384
|
// false build errors. See the rule's module note.
|
|
10385
|
+
//
|
|
10386
|
+
// #7659 adds a THIRD id here, `predicate-rhs-path-shaped`, which is not a
|
|
10387
|
+
// resolution question at all: the metadata-admin renderer resolves paths only
|
|
10388
|
+
// on the LEFT of `==` / `!=` and hands the right side to its literal parser,
|
|
10389
|
+
// so `data.a == data.b` resolves both sides cleanly, passes the two rules
|
|
10390
|
+
// above, and still compares against the string "data.b" — a constant verdict.
|
|
10391
|
+
// It carries `error` on a dotted chain (no reading under which it worked) and
|
|
10392
|
+
// `warning` on a bare word (`status == active` compares as the text today, so
|
|
10393
|
+
// refusing it would fail a build over metadata that renders correctly). The
|
|
10394
|
+
// per-finding severity is what gates, exactly as `lintFlowPatterns` has worked
|
|
10395
|
+
// since #3760; the entry's `gating` tier is unchanged because it already was.
|
|
9869
10396
|
{
|
|
9870
10397
|
name: "validatePredicatePathRefs",
|
|
9871
10398
|
tier: "gating",
|
|
9872
10399
|
input: "normalized",
|
|
9873
10400
|
commands: ALL,
|
|
9874
10401
|
source: "packages/lint/src/validate-predicate-path-refs.ts",
|
|
9875
|
-
|
|
9876
|
-
|
|
10402
|
+
// The second half of the #7220 family move — see the block above the
|
|
10403
|
+
// `validateVisibilityPredicates` entry. This is the rule whose solo wiring
|
|
10404
|
+
// was reverted; it is wired now because its siblings are.
|
|
10405
|
+
surfaces: CLI_AND_RUNTIME,
|
|
10406
|
+
runtimeTypes: ["view"],
|
|
9877
10407
|
run: (stack) => validatePredicatePathRefs(stack)
|
|
9878
10408
|
},
|
|
9879
10409
|
// #1874 — flow authoring anti-patterns. Advisory by default; a finding marked
|
|
@@ -10033,16 +10563,110 @@ var AUTHORING_RULES = [
|
|
|
10033
10563
|
// a runtime enforcement point (fail-closed OWD default, canonical enum, anchor
|
|
10034
10564
|
// binding gate, vocabulary freeze), moving the failure from a runtime deny to
|
|
10035
10565
|
// an author-time fix-it. Per ADR-0049 this is not advisory security.
|
|
10566
|
+
//
|
|
10567
|
+
// [#7576] The `surfaceReason` below is MEASURED. Its predecessor was not, and
|
|
10568
|
+
// was false in both halves — it read: "Already gated at this surface by a
|
|
10569
|
+
// DIFFERENT mechanism: plugin-security registers an ADR-0094 authoring gate on
|
|
10570
|
+
// `object` (`registerAuthoringGate`) that enforces the same OWD posture rules
|
|
10571
|
+
// on every runtime write. Running the linter here as well would double-report
|
|
10572
|
+
// one refusal in two vocabularies."
|
|
10573
|
+
//
|
|
10574
|
+
// - COVERAGE. `object-posture-gate.ts` reads exactly `sharingModel` and
|
|
10575
|
+
// `externalSharingModel` through a local `OWD_WIDTH`, and never touches
|
|
10576
|
+
// `fields`, `permissions`, `books` or `data`. Of the THIRTEEN rule ids this
|
|
10577
|
+
// block carries it covers ONE — `security-external-wider-than-internal`
|
|
10578
|
+
// (its R2). The gate's other half, R1 (env-tighten-only, ADR-0086 D1),
|
|
10579
|
+
// corresponds to no lint rule at all, so it is not coverage in the other
|
|
10580
|
+
// direction either. Twelve rules were enforced at no runtime door while
|
|
10581
|
+
// this field said they were.
|
|
10582
|
+
// - DOUBLE-REPORTING. It cannot happen, and not by luck: `saveMetaItem` runs
|
|
10583
|
+
// `assertRuntimeAuthoringRules` (this table, 422 `invalid_metadata`) BEFORE
|
|
10584
|
+
// `runAuthoringGate` (the ADR-0094 gate, 403 `owd_external_wider`), and
|
|
10585
|
+
// both refuse by THROWING. The first to fire ends the write, so an author
|
|
10586
|
+
// sees one refusal, never two. The stated cost of moving was imaginary; the
|
|
10587
|
+
// reason it has not moved is the measured one below.
|
|
10588
|
+
//
|
|
10589
|
+
// The move IS taken now — the #7891 programme's three slices, in order:
|
|
10590
|
+
//
|
|
10591
|
+
// - #8307: the ADR-0091 seed pair crossed (`runtimeTypes: ['seed']`), with
|
|
10592
|
+
// the isolation proof that the differential cancels every finding this
|
|
10593
|
+
// function derives from the sibling collections.
|
|
10594
|
+
// - #8309: the snapshot repair. The gate used to carry `objects` and
|
|
10595
|
+
// nothing else, so the three cross-collection rules judged a universe
|
|
10596
|
+
// missing the collection they compare against (measured: 38 phantom
|
|
10597
|
+
// `security-master-detail-ungranted` per-write vs 4 whole-stack,
|
|
10598
|
+
// PR #7886). `RuntimeStackContext` now carries `permissions`/`books` in
|
|
10599
|
+
// BOTH differential passes and `TYPE_TO_STACK_KEY` maps both types.
|
|
10600
|
+
// - #8310 slice 1: `runtimeTypes` gains `permission` + `book` (PR #8546).
|
|
10601
|
+
// `object` measured DIRTY on that tree and was escalated, not forced.
|
|
10602
|
+
// - #8310 slice 2 (this state): `object` crosses under the maintainer
|
|
10603
|
+
// ruling recorded on #8310 (2026-08-13, 「接受你的全部建议」): an
|
|
10604
|
+
// authored OWD is REQUIRED at the runtime object door — an object
|
|
10605
|
+
// publish with no authored `sharingModel` is refused with the 422 lint
|
|
10606
|
+
// envelope (`security-owd-unset`); absence is not a decision. The ~16
|
|
10607
|
+
// objectql/rest suite files that relied on OWD-less publishes were
|
|
10608
|
+
// repaired honestly (fixtures author their posture), and
|
|
10609
|
+
// `meta-object-owd-gate.test.ts` re-pins the door ORDER: this table
|
|
10610
|
+
// answers first (`saveMetaItem` runs it before `runAuthoringGate`), the
|
|
10611
|
+
// ADR-0094-seam 403 doors answer for what passes lint. The same ruling
|
|
10612
|
+
// retired the plugin gate's R2 `owd_external_wider` arm as a duplicate
|
|
10613
|
+
// of this door (R1 env-tighten-only STAYS — no lint rule covers it);
|
|
10614
|
+
// see `object-posture-gate.ts` and the ADR-0094 amendment.
|
|
10615
|
+
//
|
|
10616
|
+
// `security-role-word` is NOT in this entry any more — that is what the
|
|
10617
|
+
// `validateSecurityRoleWord` entry below records. It judges six collections
|
|
10618
|
+
// (objects, fields, actions, permission sets, positions, apps — plus books),
|
|
10619
|
+
// and `positions`/`apps` are neither carried by the per-write snapshot nor
|
|
10620
|
+
// mapped in `TYPE_TO_STACK_KEY`, so declaring `permission`/`book` on a
|
|
10621
|
+
// function that still contained it would have enforced ONE rule id for a
|
|
10622
|
+
// strict subset of its collections: a door where a permission set named
|
|
10623
|
+
// `role_manager` is refused and a position named `sales_role` walks through
|
|
10624
|
+
// — the #7220 failure this table refuses to build, in either direction. The
|
|
10625
|
+
// rule therefore stays behind WHOLE (#8310's explicit call), as its own
|
|
10626
|
+
// entry.
|
|
10627
|
+
//
|
|
10628
|
+
// This entry remains the rest of the D7 block (12 rule ids) as ONE
|
|
10629
|
+
// registration, not a per-rule split: the baseline/candidate differential is
|
|
10630
|
+
// what keeps a write of one declared type from leaking the other rules'
|
|
10631
|
+
// whole-stack findings — every finding derived from a sibling collection is
|
|
10632
|
+
// produced byte-identically in both passes and cancels in the diff. Only
|
|
10633
|
+
// findings the written item itself adds are attributed to the write.
|
|
10036
10634
|
{
|
|
10037
10635
|
name: "validateSecurityPosture",
|
|
10038
10636
|
tier: "gating",
|
|
10039
10637
|
input: "parsed",
|
|
10040
10638
|
commands: ALL,
|
|
10041
10639
|
source: "packages/lint/src/validate-security-posture.ts",
|
|
10042
|
-
surfaces:
|
|
10043
|
-
|
|
10640
|
+
surfaces: CLI_AND_RUNTIME,
|
|
10641
|
+
runtimeTypes: ["seed", "permission", "book", "object"],
|
|
10044
10642
|
run: (stack) => validateSecurityPosture(stack)
|
|
10045
10643
|
},
|
|
10644
|
+
// [ADR-0090 D3 / #8310] The vocabulary freeze, split out of
|
|
10645
|
+
// `validateSecurityPosture` the day the rest of that block crossed the
|
|
10646
|
+
// runtime wall — so that it could stay behind WHOLE rather than cross for
|
|
10647
|
+
// three of the six collections it judges (#7220: one rule id must sit on ONE
|
|
10648
|
+
// side of the wall). The split is a surface boundary, not taste: the rule's
|
|
10649
|
+
// verdict and findings are byte-identical to before on every CLI command
|
|
10650
|
+
// (both entries run on all three), and the runtime door does not run it for
|
|
10651
|
+
// ANY type.
|
|
10652
|
+
//
|
|
10653
|
+
// The road to crossing is concrete and short, recorded here so the next
|
|
10654
|
+
// seat prices it correctly: carry `positions`/`apps` in
|
|
10655
|
+
// `RuntimeStackContext` + `CONTEXT_STACK_KEYS`, map both types in
|
|
10656
|
+
// `TYPE_TO_STACK_KEY` (both are `allowRuntimeCreate: true`, so the writes
|
|
10657
|
+
// are real), then declare `runtimeTypes: ['object', 'permission', 'book',
|
|
10658
|
+
// 'position', 'app']` on THIS entry — all six collections in one edit, the
|
|
10659
|
+
// #7220 discipline satisfied.
|
|
10660
|
+
{
|
|
10661
|
+
name: "validateSecurityRoleWord",
|
|
10662
|
+
tier: "gating",
|
|
10663
|
+
input: "parsed",
|
|
10664
|
+
commands: ALL,
|
|
10665
|
+
source: "packages/lint/src/validate-security-posture.ts",
|
|
10666
|
+
surfaces: CLI_ONLY,
|
|
10667
|
+
surfaceReason: "P2 (#4463)/#8310: judges six collections (objects, fields, actions, permission sets, positions, apps \u2014 plus books), and the per-write snapshot neither carries nor maps positions/apps. Wiring it for the mapped types alone would enforce one rule id for three of its six collections \u2014 the #7220 split (an object named sales_role refused while a position named sales_role walks through). It crosses whole \u2014 positions/apps carried, mapped and declared \u2014 or stays behind; it stays behind until that wiring exists.",
|
|
10668
|
+
run: (stack) => validateSecurityRoleWord(stack)
|
|
10669
|
+
},
|
|
10046
10670
|
// ADR-0105 D6 — the org tree is a REPORTING dimension. An RLS policy or
|
|
10047
10671
|
// sharing rule that walks it builds a second permission hierarchy (the
|
|
10048
10672
|
// dual-hierarchy mistake ADR-0057 D5 retired) and cannot widen Layer 0 anyway,
|
|
@@ -10093,7 +10717,7 @@ var AUTHORING_RULES = [
|
|
|
10093
10717
|
commands: ALL,
|
|
10094
10718
|
source: "packages/lint/src/validate-rls-predicate-enforceability.ts",
|
|
10095
10719
|
surfaces: CLI_ONLY,
|
|
10096
|
-
surfaceReason: "
|
|
10720
|
+
surfaceReason: "The rule reads `stack.permissions[]`, which the per-write snapshot DOES carry since #8309 \u2014 the remaining gap is only the declaration: no `runtimeTypes` names `permission` here, and that flip is a rollout decision on #8310's axis, not a wiring fix. Recorded as pending rather than done, because a rule that has never run at a door should not claim it.",
|
|
10097
10721
|
run: (stack) => validateRlsPredicateEnforceability(stack)
|
|
10098
10722
|
},
|
|
10099
10723
|
// #4762 — the same "declared but enforces nothing" question, for the two
|
|
@@ -10169,8 +10793,34 @@ var TYPE_TO_STACK_KEY = {
|
|
|
10169
10793
|
dashboard: "dashboards",
|
|
10170
10794
|
agent: "agents",
|
|
10171
10795
|
hook: "hooks",
|
|
10172
|
-
seed
|
|
10796
|
+
// [#7576] `data`, NOT `seeds`. The metadata TYPE is `seed`; the stack KEY that
|
|
10797
|
+
// holds seeds is `data` (`ObjectStackDefinitionSchema.data: z.array(SeedSchema)`)
|
|
10798
|
+
// — a stack has no `seeds` key at all, and `PLURAL_TO_SINGULAR` declares no
|
|
10799
|
+
// mapping onto one either.
|
|
10800
|
+
//
|
|
10801
|
+
// The wrong spelling was INERT rather than harmless, and it is the #4449 shape
|
|
10802
|
+
// one surface over: the wiring guard asks only that a declared type HAS a
|
|
10803
|
+
// mapping, never that the mapping names a key some rule reads. So it would
|
|
10804
|
+
// have stayed green while the gate built `{ objects, seeds: [item] }` for
|
|
10805
|
+
// every seed write and every rule reading `stack.data` saw nothing — wired,
|
|
10806
|
+
// and running on nothing, with `rulesRun` reporting the rules as having run.
|
|
10807
|
+
// Nothing declared `seed` in `runtimeTypes` at the time, so correcting it
|
|
10808
|
+
// changed no behaviour then; it was corrected here, with the measurement
|
|
10809
|
+
// that found it (#7576), rather than left for the rollout card to trip
|
|
10810
|
+
// over. The ADR-0091 seed pair now DOES declare `seed` (#8307), so this
|
|
10811
|
+
// mapping is load-bearing today, not merely inert-and-correct.
|
|
10812
|
+
seed: "data",
|
|
10813
|
+
// [#8309] `permission`/`book` map ahead of their registration (#8310), the
|
|
10814
|
+
// same order `seed` arrived in: the mapping plus the enriched snapshot below
|
|
10815
|
+
// are this card's halves, and the `runtimeTypes` flip is deliberately NOT —
|
|
10816
|
+
// a mapping without a declaring rule is inert by construction (the gate
|
|
10817
|
+
// filters by `runtimeTypes` before it ever consults this table), while a
|
|
10818
|
+
// declaration without the mapping is the wired-onto-nothing state the wiring
|
|
10819
|
+
// guard refuses. Landing the mapping first keeps #8310 a registry data edit.
|
|
10820
|
+
permission: "permissions",
|
|
10821
|
+
book: "books"
|
|
10173
10822
|
};
|
|
10823
|
+
var CONTEXT_STACK_KEYS = ["objects", "permissions", "books"];
|
|
10174
10824
|
function runtimeAuthoringRulesFor(type) {
|
|
10175
10825
|
return AUTHORING_RULES.filter(
|
|
10176
10826
|
(r) => r.surfaces.includes("runtime-publish") && (r.runtimeTypes ?? []).includes(type)
|
|
@@ -10188,6 +10838,23 @@ function stackKeyForType(type) {
|
|
|
10188
10838
|
return TYPE_TO_STACK_KEY[type] ?? null;
|
|
10189
10839
|
}
|
|
10190
10840
|
var fingerprint = (f) => `${f.rule}\0${f.where}\0${f.path}\0${f.message}`;
|
|
10841
|
+
function buildRuntimeWriteSnapshots(args) {
|
|
10842
|
+
const stackKey = stackKeyForType(args.type);
|
|
10843
|
+
if (!stackKey) return null;
|
|
10844
|
+
if (!args.item || typeof args.item !== "object") return null;
|
|
10845
|
+
const item = args.item;
|
|
10846
|
+
const itemName = typeof item.name === "string" ? item.name : void 0;
|
|
10847
|
+
const baseline = {};
|
|
10848
|
+
for (const key of CONTEXT_STACK_KEYS) {
|
|
10849
|
+
const collection = args.context?.[key] ?? [];
|
|
10850
|
+
baseline[key] = key === stackKey ? collection.filter((o) => !itemName || o?.name !== itemName) : collection;
|
|
10851
|
+
}
|
|
10852
|
+
const candidate = {
|
|
10853
|
+
...baseline,
|
|
10854
|
+
[stackKey]: [...baseline[stackKey] ?? [], item]
|
|
10855
|
+
};
|
|
10856
|
+
return { baseline, candidate };
|
|
10857
|
+
}
|
|
10191
10858
|
function runRules(rules, stack, ctx) {
|
|
10192
10859
|
const findings = [];
|
|
10193
10860
|
for (const rule of rules) {
|
|
@@ -10210,19 +10877,15 @@ function runRuntimeAuthoringRules(args) {
|
|
|
10210
10877
|
const rules = runtimeAuthoringRulesFor(args.type);
|
|
10211
10878
|
const empty = { errors: [], advisories: [], rulesRun: [] };
|
|
10212
10879
|
if (rules.length === 0) return empty;
|
|
10213
|
-
const
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10880
|
+
const snapshots = buildRuntimeWriteSnapshots({
|
|
10881
|
+
type: args.type,
|
|
10882
|
+
item: args.item,
|
|
10883
|
+
...args.context !== void 0 ? { context: args.context } : {}
|
|
10884
|
+
});
|
|
10885
|
+
if (!snapshots) return empty;
|
|
10219
10886
|
const ctx = { sduiManifest: args.sduiManifest };
|
|
10220
|
-
const
|
|
10221
|
-
const
|
|
10222
|
-
const baseline = { objects: baselineObjects };
|
|
10223
|
-
const candidate = writesIntoContext ? { objects: [...baselineObjects, item] } : { objects: baselineObjects, [stackKey]: [item] };
|
|
10224
|
-
const before = new Set(runRules(rules, baseline, ctx).map(fingerprint));
|
|
10225
|
-
const added = runRules(rules, candidate, ctx).filter((f) => !before.has(fingerprint(f)));
|
|
10887
|
+
const before = new Set(runRules(rules, snapshots.baseline, ctx).map(fingerprint));
|
|
10888
|
+
const added = runRules(rules, snapshots.candidate, ctx).filter((f) => !before.has(fingerprint(f)));
|
|
10226
10889
|
return {
|
|
10227
10890
|
errors: added.filter((f) => f.severity === "error"),
|
|
10228
10891
|
advisories: added.filter((f) => f.severity !== "error"),
|
|
@@ -10271,6 +10934,7 @@ export {
|
|
|
10271
10934
|
DASHBOARD_ACTION_ROUTE_UNRESOLVED,
|
|
10272
10935
|
DASHBOARD_ACTION_TARGET_UNDEFINED,
|
|
10273
10936
|
DASHBOARD_FILTER_FIELD_UNKNOWN,
|
|
10937
|
+
DASHBOARD_FILTER_FIELD_UNPROVISIONED,
|
|
10274
10938
|
DEFAULT_AGENT_OUTSIDE_ROSTER,
|
|
10275
10939
|
EXPRESSION_INVALID,
|
|
10276
10940
|
FIELD_GROUP_EMPTY,
|
|
@@ -10297,6 +10961,7 @@ export {
|
|
|
10297
10961
|
FLOW_NODE_WRITE_UNKNOWN_FIELD,
|
|
10298
10962
|
FLOW_PHANTOM_AGGREGATION,
|
|
10299
10963
|
FLOW_RUNAS_UNSCOPED,
|
|
10964
|
+
FLOW_TEMPLATE_FIELD_UNPROVISIONED,
|
|
10300
10965
|
FLOW_TEMPLATE_LOOKUP_TRAVERSAL,
|
|
10301
10966
|
FLOW_TEMPLATE_UNKNOWN_FIELD,
|
|
10302
10967
|
FLOW_TIME_RELATIVE_ANTIPATTERN,
|
|
@@ -10318,9 +10983,11 @@ export {
|
|
|
10318
10983
|
LIST_VIEW_FILTERS_IN_VIEWS_MODE,
|
|
10319
10984
|
LIVENESS_DEAD_PROPERTY,
|
|
10320
10985
|
LIVENESS_EXPERIMENTAL_PROPERTY,
|
|
10986
|
+
MANAGED_API_METHOD_UNAFFORDABLE,
|
|
10321
10987
|
MAX_SCHEMA_WALK_DEPTH,
|
|
10322
10988
|
MEASURE_AGGREGATE_INCOHERENT,
|
|
10323
10989
|
NAV_OBJECT_UNGRANTED,
|
|
10990
|
+
NAV_OBJECT_UNSERVABLE,
|
|
10324
10991
|
NAV_TARGET_UNRESOLVED,
|
|
10325
10992
|
NULL_GUARD_HINT,
|
|
10326
10993
|
OBJECT_REFERENCE_UNKNOWN,
|
|
@@ -10329,15 +10996,18 @@ export {
|
|
|
10329
10996
|
ORG_AXIS_CROSS_ORG_BU_GRANT,
|
|
10330
10997
|
ORG_AXIS_PERMISSION_INHERITANCE,
|
|
10331
10998
|
PAGE_FIELD_UNKNOWN,
|
|
10999
|
+
PAGE_FIELD_UNPROVISIONED,
|
|
10332
11000
|
PAGE_SOURCE_CLASSNAME,
|
|
10333
11001
|
PREDICATE_PATH_UNRESOLVED,
|
|
10334
11002
|
PREDICATE_PATH_UNROOTED,
|
|
11003
|
+
PREDICATE_RHS_PATH_SHAPED,
|
|
10335
11004
|
PRE_SEAL_PHASES,
|
|
10336
11005
|
REACT_BLOCK_NEEDS_RECORD_CONTEXT,
|
|
10337
11006
|
REACT_CHART_AGGREGATE_INVALID,
|
|
10338
11007
|
REACT_CHART_AXIS_UNKNOWN,
|
|
10339
11008
|
REACT_CHART_DRILLDOWN_INVALID,
|
|
10340
11009
|
REACT_CHART_FIELD_UNKNOWN,
|
|
11010
|
+
REACT_CHART_FIELD_UNPROVISIONED,
|
|
10341
11011
|
REFERENCE_INTEGRITY_RULES,
|
|
10342
11012
|
RLS_PREDICATE_OVER_BUDGET,
|
|
10343
11013
|
RLS_PREDICATE_UNENFORCEABLE,
|
|
@@ -10348,6 +11018,7 @@ export {
|
|
|
10348
11018
|
SEARCHABLE_FIELD_UNSEARCHABLE,
|
|
10349
11019
|
SECURITY_ANCHOR_HIGH_PRIVILEGE,
|
|
10350
11020
|
SECURITY_BOOK_AUDIENCE_UNKNOWN_SET,
|
|
11021
|
+
SECURITY_CBP_NO_RELATION,
|
|
10351
11022
|
SECURITY_DELEGATION_MISSING_REASON,
|
|
10352
11023
|
SECURITY_EXTERNAL_WIDER,
|
|
10353
11024
|
SECURITY_FLS_UNQUALIFIED_KEY,
|
|
@@ -10361,6 +11032,7 @@ export {
|
|
|
10361
11032
|
SEED_INSERT_MODE_DUPLICATES_ON_REPLAY,
|
|
10362
11033
|
SEED_VALUE_OUTSIDE_STATE_MACHINE,
|
|
10363
11034
|
SEMANTIC_ROLE_FIELD_UNKNOWN,
|
|
11035
|
+
SEMANTIC_ROLE_FIELD_UNPROVISIONED,
|
|
10364
11036
|
SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
|
|
10365
11037
|
SHARING_RULE_UNLOWERABLE_CONDITION,
|
|
10366
11038
|
STARTUP_OPEN_VOCABULARY_VERDICT,
|
|
@@ -10398,6 +11070,7 @@ export {
|
|
|
10398
11070
|
WIDGET_MEASURE_UNKNOWN,
|
|
10399
11071
|
authoringRulesFor,
|
|
10400
11072
|
buildAccessMatrix,
|
|
11073
|
+
buildRuntimeWriteSnapshots,
|
|
10401
11074
|
diffAccessMatrix,
|
|
10402
11075
|
extractHookBodyWriteSet,
|
|
10403
11076
|
extractHookBodyWrites,
|
|
@@ -10441,7 +11114,9 @@ export {
|
|
|
10441
11114
|
validateHookBodyWrites,
|
|
10442
11115
|
validateJsxPages,
|
|
10443
11116
|
validateListViewMode,
|
|
11117
|
+
validateManagedApiMethods,
|
|
10444
11118
|
validateNavAccess,
|
|
11119
|
+
validateNavObjectServability,
|
|
10445
11120
|
validateNavTargetRefs,
|
|
10446
11121
|
validateObjectReferences,
|
|
10447
11122
|
validateOrgAxisRedLines,
|
|
@@ -10459,6 +11134,7 @@ export {
|
|
|
10459
11134
|
validateRuleSchemaFormats,
|
|
10460
11135
|
validateSearchableFields,
|
|
10461
11136
|
validateSecurityPosture,
|
|
11137
|
+
validateSecurityRoleWord,
|
|
10462
11138
|
validateSeedReplaySafety,
|
|
10463
11139
|
validateSeedStateMachine,
|
|
10464
11140
|
validateSemanticRoles,
|