@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/dist/runtime.js CHANGED
@@ -1,9 +1,13 @@
1
1
  // src/validate-expressions.ts
2
- import { validateExpression, collectCelRootIdentifiers, SCOPE_ROOTS } from "@objectstack/formula";
2
+ import { validateExpression, collectCelRootIdentifiers, parseCelToAst as parseCelToAst2, SCOPE_ROOTS } from "@objectstack/formula";
3
3
  import { collectFlowGraphs, resolveFlowNodeExpressions } from "@objectstack/spec/automation";
4
4
 
5
5
  // src/system-fields.ts
6
- import { FIELD_GROUP_SYSTEM_FIELDS, resolveInjectedSystemColumns } from "@objectstack/spec/data";
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-null-guards.ts
17
49
  import { parseCelToAst } from "@objectstack/formula";
@@ -245,6 +277,37 @@ function buildFieldIndex(objects) {
245
277
  }
246
278
  return idx;
247
279
  }
280
+ var BOUND_RECORD_ROOTS = ["record", "previous"];
281
+ function isCelNode(v) {
282
+ return !!v && typeof v === "object" && typeof v.op === "string";
283
+ }
284
+ function collectBoundRecordReads(source) {
285
+ const out = /* @__PURE__ */ new Map();
286
+ const ast = parseCelToAst2(source);
287
+ if (!ast) return out;
288
+ const pending = [ast];
289
+ while (pending.length > 0) {
290
+ const celNode = pending.pop();
291
+ if (!isCelNode(celNode)) continue;
292
+ if ((celNode.op === "." || celNode.op === ".?") && Array.isArray(celNode.args) && celNode.args.length >= 2) {
293
+ const [celRecv, seg] = celNode.args;
294
+ if (typeof seg === "string" && isCelNode(celRecv) && celRecv.op === "id" && typeof celRecv.args === "string" && BOUND_RECORD_ROOTS.includes(celRecv.args)) {
295
+ if (!out.has(seg)) out.set(seg, `${celRecv.args}.${seg}`);
296
+ }
297
+ }
298
+ const celArgs = celNode.args;
299
+ if (isCelNode(celArgs)) pending.push(celArgs);
300
+ else if (Array.isArray(celArgs)) {
301
+ for (const a of celArgs) {
302
+ if (isCelNode(a)) pending.push(a);
303
+ else if (Array.isArray(a)) {
304
+ for (const b of a) if (isCelNode(b)) pending.push(b);
305
+ }
306
+ }
307
+ }
308
+ }
309
+ return out;
310
+ }
248
311
  function buildFieldTypeIndex(objects) {
249
312
  const idx = /* @__PURE__ */ new Map();
250
313
  for (const obj of objects) {
@@ -344,6 +407,29 @@ function validateStackExpressions(stack) {
344
407
  const fieldIndex = buildFieldIndex(objects);
345
408
  const fieldTypeIndex = buildFieldTypeIndex(objects);
346
409
  const nullableIndex = buildNullableFieldIndex(objects);
410
+ const unprovisionedIndex = /* @__PURE__ */ new Map();
411
+ for (const obj of objects) {
412
+ const name = typeof obj.name === "string" ? obj.name : void 0;
413
+ if (!name) continue;
414
+ const anchors = unprovisionedInjectedColumnsFor(obj);
415
+ if (anchors.size > 0) unprovisionedIndex.set(name, anchors);
416
+ }
417
+ const warnUnprovisionedAnchors = (where, raw, objectName) => {
418
+ if (!objectName) return;
419
+ const anchors = unprovisionedIndex.get(objectName);
420
+ if (!anchors) return;
421
+ const source = celSourceOf(raw);
422
+ if (!source) return;
423
+ for (const [field, operand] of collectBoundRecordReads(source)) {
424
+ if (!anchors.has(field)) continue;
425
+ issues.push({
426
+ where,
427
+ 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).`,
428
+ source,
429
+ severity: "warning"
430
+ });
431
+ }
432
+ };
347
433
  const checkNullGuards = (where, subject, raw, objectName, outcome = "fail-closed") => {
348
434
  if (!objectName) return;
349
435
  const nullableFields = nullableIndex.get(objectName);
@@ -370,6 +456,7 @@ function validateStackExpressions(stack) {
370
456
  );
371
457
  for (const e of res.errors) issues.push({ where, message: e.message, source: e.source, severity: "error" });
372
458
  for (const w of res.warnings) issues.push({ where, message: w.message, source: w.source, severity: "warning" });
459
+ warnUnprovisionedAnchors(where, raw, objectName);
373
460
  };
374
461
  const FIELD_RULE_BOUND_ROOTS = ["record", "previous", "parent"];
375
462
  const FIELD_RULE_USER_ROOTS = ["current_user", "user", "ctx", "os"];
@@ -516,6 +603,7 @@ function validateStackExpressions(stack) {
516
603
  const fieldWhere = `object '${objectName}' \xB7 field '${fname}' expression`;
517
604
  for (const e of res.errors) issues.push({ where: fieldWhere, message: e.message, source: e.source, severity: "error" });
518
605
  for (const w of res.warnings) issues.push({ where: fieldWhere, message: w.message, source: w.source, severity: "warning" });
606
+ warnUnprovisionedAnchors(fieldWhere, f.expression, objectName);
519
607
  }
520
608
  }
521
609
  }
@@ -725,6 +813,46 @@ function validateFunctionalCompleteness(stack) {
725
813
  return out;
726
814
  }
727
815
 
816
+ // src/validate-managed-api-methods.ts
817
+ import {
818
+ checkManagedApiMethodAffordances,
819
+ describeManagedApiMethodConflicts
820
+ } from "@objectstack/spec/data";
821
+ var MANAGED_API_METHOD_UNAFFORDABLE = "object/managed-api-method-unaffordable";
822
+ var isRec2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
823
+ function entriesOf2(v) {
824
+ if (Array.isArray(v)) {
825
+ return v.flatMap(
826
+ (def, i) => isRec2(def) ? [{ name: String(def.name ?? i), def, key: `[${i}]` }] : []
827
+ );
828
+ }
829
+ if (isRec2(v)) {
830
+ return Object.entries(v).flatMap(
831
+ ([name, def]) => isRec2(def) ? [{ name, def: { name, ...def }, key: `.${name}` }] : []
832
+ );
833
+ }
834
+ return [];
835
+ }
836
+ function validateManagedApiMethods(stack) {
837
+ const out = [];
838
+ if (!isRec2(stack)) return out;
839
+ for (const [oi, obj] of entriesOf2(stack.objects).entries()) {
840
+ const conflicts = checkManagedApiMethodAffordances(obj.def);
841
+ if (conflicts.length === 0) continue;
842
+ const verbs = conflicts.map((c) => c.verb).join(", ");
843
+ const flags = [...new Set(conflicts.map((c) => c.needs))];
844
+ out.push({
845
+ severity: "error",
846
+ rule: MANAGED_API_METHOD_UNAFFORDABLE,
847
+ where: `object "${obj.name}"`,
848
+ path: `objects[${oi}].enable.apiMethods`,
849
+ 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.`,
850
+ 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.`
851
+ });
852
+ }
853
+ return out;
854
+ }
855
+
728
856
  // src/validate-view-containers.ts
729
857
  var VIEW_CONTAINER_SHAPE = "view-container-shape";
730
858
  var CONTAINER_SLOT_KEYS = ["list", "form", "listViews", "formViews"];
@@ -742,10 +870,32 @@ function containerViewCount(rec) {
742
870
  function validateViewContainers(stack) {
743
871
  const out = [];
744
872
  if (!stack || typeof stack !== "object") return out;
873
+ const viewItems = stack.viewItems;
874
+ if (viewItems != null && asEntries(viewItems).length > 0) {
875
+ out.push({
876
+ severity: "error",
877
+ rule: VIEW_CONTAINER_SHAPE,
878
+ where: "viewItems",
879
+ path: "viewItems",
880
+ 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.",
881
+ 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."
882
+ });
883
+ }
745
884
  for (const { key, value } of asEntries(stack.views)) {
746
885
  if (!value || typeof value !== "object" || Array.isArray(value)) continue;
747
886
  const rec = value;
748
- if (rec.viewKind != null) continue;
887
+ if (rec.viewKind != null) {
888
+ const label3 = typeof rec.name === "string" ? ` ("${rec.name}")` : "";
889
+ out.push({
890
+ severity: "error",
891
+ rule: VIEW_CONTAINER_SHAPE,
892
+ where: `views${key}${label3}`,
893
+ path: `views${key}`,
894
+ 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).",
895
+ 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:`."
896
+ });
897
+ continue;
898
+ }
749
899
  if (containerViewCount(rec) > 0) continue;
750
900
  const label2 = typeof rec.name === "string" ? ` ("${rec.name}")` : "";
751
901
  const hasContainerSlot = CONTAINER_SLOT_KEYS.some((k) => k in rec);
@@ -775,6 +925,7 @@ var MEASURE_AGGREGATE_INCOHERENT = "measure-aggregate-incoherent";
775
925
  var WIDGET_LEGACY_ANALYTICS_SHAPE = "widget-legacy-analytics-shape";
776
926
  var WIDGET_LEGACY_ANALYTICS_UNRENDERABLE = "widget-legacy-analytics-unrenderable";
777
927
  var DASHBOARD_FILTER_FIELD_UNKNOWN = "dashboard-filter-field-unknown";
928
+ var DASHBOARD_FILTER_FIELD_UNPROVISIONED = "dashboard-filter-field-unprovisioned";
778
929
  var LEGACY_ANALYTICS_KEYS = [
779
930
  "categoryField",
780
931
  "valueField",
@@ -894,6 +1045,7 @@ function validateWidgetBindings(stack) {
894
1045
  }
895
1046
  objectFieldTypes.set(o.name, fm);
896
1047
  }
1048
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
897
1049
  const datasetList = asArray3(stack.datasets);
898
1050
  for (let i = 0; i < datasetList.length; i++) {
899
1051
  const ds = datasetList[i];
@@ -980,13 +1132,24 @@ function validateWidgetBindings(stack) {
980
1132
  if (dashFilterDefs.length > 0) {
981
1133
  const datasetObject = typeof dataset.object === "string" ? dataset.object : void 0;
982
1134
  const objectFields = datasetObject ? objectFieldTypes.get(datasetObject) : void 0;
983
- if (objectFields) {
1135
+ const anchors = datasetObject ? unprovisionedAnchors.get(datasetObject) : void 0;
1136
+ if (objectFields && datasetObject) {
984
1137
  for (const def of dashFilterDefs) {
985
1138
  const eff = effectiveFilterField(w, def);
986
1139
  if (!eff) continue;
987
1140
  const field = eff.field;
988
1141
  if (field.includes(".")) continue;
989
- if (objectFields.has(field) || SYSTEM_FIELDS.has(field)) continue;
1142
+ if (objectFields.has(field) || SYSTEM_FIELDS.has(field)) {
1143
+ if (anchors?.has(field)) {
1144
+ push2({
1145
+ severity: "warning",
1146
+ rule: DASHBOARD_FILTER_FIELD_UNPROVISIONED,
1147
+ 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).`,
1148
+ 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.`
1149
+ });
1150
+ }
1151
+ continue;
1152
+ }
990
1153
  push2({
991
1154
  severity: "error",
992
1155
  rule: DASHBOARD_FILTER_FIELD_UNKNOWN,
@@ -1660,7 +1823,7 @@ function asArray7(v) {
1660
1823
  }
1661
1824
  return [];
1662
1825
  }
1663
- function isRec2(v) {
1826
+ function isRec3(v) {
1664
1827
  return !!v && typeof v === "object" && !Array.isArray(v);
1665
1828
  }
1666
1829
  function strName3(v) {
@@ -1736,7 +1899,7 @@ function distance2(a, b) {
1736
1899
  }
1737
1900
  function indexObjectSearchTargets(stack) {
1738
1901
  const fieldsByObject = /* @__PURE__ */ new Map();
1739
- if (!isRec2(stack)) return fieldsByObject;
1902
+ if (!isRec3(stack)) return fieldsByObject;
1740
1903
  for (const obj of asArray7(stack.objects)) {
1741
1904
  const name = strName3(obj.name);
1742
1905
  if (name) fieldsByObject.set(name, declaredFieldTarget(obj));
@@ -1818,7 +1981,7 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
1818
1981
  }
1819
1982
  function validateSearchableFields(stack) {
1820
1983
  const findings = [];
1821
- if (!isRec2(stack)) return findings;
1984
+ if (!isRec3(stack)) return findings;
1822
1985
  const objects = asArray7(stack.objects);
1823
1986
  const fieldsByObject = indexObjectSearchTargets(stack);
1824
1987
  const check = (declared, objectName, where, path, subject, role) => {
@@ -1828,7 +1991,7 @@ function validateSearchableFields(stack) {
1828
1991
  };
1829
1992
  for (let oi = 0; oi < objects.length; oi++) {
1830
1993
  const obj = objects[oi];
1831
- if (!isRec2(obj)) continue;
1994
+ if (!isRec3(obj)) continue;
1832
1995
  const objName = strName3(obj.name);
1833
1996
  const label2 = objName ? `object "${objName}"` : `objects[${oi}]`;
1834
1997
  check(
@@ -1839,9 +2002,9 @@ function validateSearchableFields(stack) {
1839
2002
  "searchableFields",
1840
2003
  "canonical"
1841
2004
  );
1842
- if (isRec2(obj.listViews)) {
2005
+ if (isRec3(obj.listViews)) {
1843
2006
  for (const [key, lv] of Object.entries(obj.listViews)) {
1844
- if (!isRec2(lv)) continue;
2007
+ if (!isRec3(lv)) continue;
1845
2008
  check(
1846
2009
  lv.searchableFields,
1847
2010
  // A built-in list view belongs to its object; an inline `data.object`
@@ -1858,10 +2021,10 @@ function validateSearchableFields(stack) {
1858
2021
  const views = asArray7(stack.views);
1859
2022
  for (let vi = 0; vi < views.length; vi++) {
1860
2023
  const view = views[vi];
1861
- if (!isRec2(view)) continue;
2024
+ if (!isRec3(view)) continue;
1862
2025
  const viewLabel2 = strName3(view.name) ?? strName3(view.objectName) ?? `#${vi}`;
1863
2026
  const viewObject = strName3(view.objectName) ?? strName3(view.object);
1864
- if (isRec2(view.list)) {
2027
+ if (isRec3(view.list)) {
1865
2028
  check(
1866
2029
  view.list.searchableFields,
1867
2030
  listViewObject(view.list) ?? viewObject,
@@ -1871,9 +2034,9 @@ function validateSearchableFields(stack) {
1871
2034
  "narrowing"
1872
2035
  );
1873
2036
  }
1874
- if (isRec2(view.listViews)) {
2037
+ if (isRec3(view.listViews)) {
1875
2038
  for (const [key, lv] of Object.entries(view.listViews)) {
1876
- if (!isRec2(lv)) continue;
2039
+ if (!isRec3(lv)) continue;
1877
2040
  check(
1878
2041
  lv.searchableFields,
1879
2042
  listViewObject(lv) ?? viewObject,
@@ -1889,11 +2052,11 @@ function validateSearchableFields(stack) {
1889
2052
  }
1890
2053
  function listViewObject(listView) {
1891
2054
  const data = listView.data;
1892
- return isRec2(data) ? strName3(data.object) : void 0;
2055
+ return isRec3(data) ? strName3(data.object) : void 0;
1893
2056
  }
1894
2057
 
1895
2058
  // src/page-walk.ts
1896
- function isRec3(v) {
2059
+ function isRec4(v) {
1897
2060
  return !!v && typeof v === "object" && !Array.isArray(v);
1898
2061
  }
1899
2062
  function strName4(v) {
@@ -1906,19 +2069,19 @@ function isSourceAuthoredPage(page) {
1906
2069
  }
1907
2070
  function walkPageComponents(page, pagePath) {
1908
2071
  const out = [];
1909
- if (!isRec3(page) || isSourceAuthoredPage(page)) return out;
2072
+ if (!isRec4(page) || isSourceAuthoredPage(page)) return out;
1910
2073
  const pageObject = strName4(page.object);
1911
2074
  const visit = (node, path, inheritedObject) => {
1912
- if (!isRec3(node)) return;
1913
- const props = isRec3(node.properties) ? node.properties : void 0;
1914
- const dataSource = isRec3(node.dataSource) ? node.dataSource : void 0;
2075
+ if (!isRec4(node)) return;
2076
+ const props = isRec4(node.properties) ? node.properties : void 0;
2077
+ const dataSource = isRec4(node.dataSource) ? node.dataSource : void 0;
1915
2078
  const objectName = strName4(dataSource?.object) ?? strName4(props?.object) ?? inheritedObject;
1916
2079
  out.push({ component: node, path, objectName });
1917
2080
  if (!props) return;
1918
2081
  if (Array.isArray(props.items)) {
1919
2082
  for (let i = 0; i < props.items.length; i++) {
1920
2083
  const item = props.items[i];
1921
- if (!isRec3(item) || !Array.isArray(item.children)) continue;
2084
+ if (!isRec4(item) || !Array.isArray(item.children)) continue;
1922
2085
  for (let c = 0; c < item.children.length; c++) {
1923
2086
  visit(item.children[c], `${path}.properties.items[${i}].children[${c}]`, objectName);
1924
2087
  }
@@ -1940,12 +2103,12 @@ function walkPageComponents(page, pagePath) {
1940
2103
  const regions = Array.isArray(page.regions) ? page.regions : [];
1941
2104
  for (let r = 0; r < regions.length; r++) {
1942
2105
  const region = regions[r];
1943
- if (!isRec3(region) || !Array.isArray(region.components)) continue;
2106
+ if (!isRec4(region) || !Array.isArray(region.components)) continue;
1944
2107
  for (let c = 0; c < region.components.length; c++) {
1945
2108
  visit(region.components[c], `${pagePath}.regions[${r}].components[${c}]`, pageObject);
1946
2109
  }
1947
2110
  }
1948
- const slots = isRec3(page.slots) ? page.slots : void 0;
2111
+ const slots = isRec4(page.slots) ? page.slots : void 0;
1949
2112
  if (slots) {
1950
2113
  for (const [slot, value] of Object.entries(slots)) {
1951
2114
  const list3 = Array.isArray(value) ? value : [value];
@@ -2153,6 +2316,7 @@ function validateActionNameRefs(stack) {
2153
2316
 
2154
2317
  // src/validate-page-field-bindings.ts
2155
2318
  var PAGE_FIELD_UNKNOWN = "page-field-unknown";
2319
+ var PAGE_FIELD_UNPROVISIONED = "page-field-unprovisioned";
2156
2320
  function asArray9(v) {
2157
2321
  if (Array.isArray(v)) return v;
2158
2322
  if (v && typeof v === "object") {
@@ -2163,7 +2327,7 @@ function asArray9(v) {
2163
2327
  function strName6(v) {
2164
2328
  return typeof v === "string" && v.length > 0 ? v : void 0;
2165
2329
  }
2166
- function isRec4(v) {
2330
+ function isRec5(v) {
2167
2331
  return !!v && typeof v === "object" && !Array.isArray(v);
2168
2332
  }
2169
2333
  function fieldRefsFrom(value, basePath) {
@@ -2174,7 +2338,7 @@ function fieldRefsFrom(value, basePath) {
2174
2338
  out.push({ name: bare, path });
2175
2339
  return;
2176
2340
  }
2177
- if (!isRec4(v)) return;
2341
+ if (!isRec5(v)) return;
2178
2342
  const named = strName6(v.field) ?? strName6(v.name);
2179
2343
  if (named) out.push({ name: named, path: `${path}.${strName6(v.field) ? "field" : "name"}` });
2180
2344
  };
@@ -2232,15 +2396,15 @@ function componentFieldRefs(type, props, basePath, sep = ".") {
2232
2396
  const sections = Array.isArray(props[key]) ? props[key] : [];
2233
2397
  for (let si = 0; si < sections.length; si++) {
2234
2398
  const section = sections[si];
2235
- if (!isRec4(section)) continue;
2399
+ if (!isRec5(section)) continue;
2236
2400
  refs.push(...fieldRefsFrom(section.fields, `${basePath}${sep}${key}[${si}].fields`));
2237
2401
  }
2238
2402
  }
2239
2403
  return refs;
2240
2404
  }
2241
2405
  function relatedListFieldRefs(props, basePath, sep = ".") {
2242
- const add = isRec4(props.add) ? props.add : void 0;
2243
- const picker = add && isRec4(add.picker) ? add.picker : void 0;
2406
+ const add = isRec5(props.add) ? props.add : void 0;
2407
+ const picker = add && isRec5(add.picker) ? add.picker : void 0;
2244
2408
  const at = (key) => `${basePath}${sep}${key}`;
2245
2409
  return {
2246
2410
  relatedObject: strName6(props.objectName),
@@ -2261,7 +2425,7 @@ function relatedListFieldRefs(props, basePath, sep = ".") {
2261
2425
  }
2262
2426
  function indexObjectFields(stack) {
2263
2427
  const objectFields = /* @__PURE__ */ new Map();
2264
- if (!isRec4(stack)) return objectFields;
2428
+ if (!isRec5(stack)) return objectFields;
2265
2429
  for (const obj of asArray9(stack.objects)) {
2266
2430
  const name = strName6(obj.name);
2267
2431
  if (!name) continue;
@@ -2274,14 +2438,27 @@ function indexObjectFields(stack) {
2274
2438
  }
2275
2439
  return objectFields;
2276
2440
  }
2277
- function checkFieldRefs(refs, objectName, objectFields, where, consequence2 = "skipped") {
2441
+ function checkFieldRefs(refs, objectName, objectFields, where, consequence2 = "skipped", unprovisionedAnchors) {
2278
2442
  const findings = [];
2279
2443
  if (!objectName) return findings;
2280
2444
  const known = objectFields.get(objectName);
2281
2445
  if (!known) return findings;
2446
+ const anchors = unprovisionedAnchors?.get(objectName);
2282
2447
  for (const ref of refs) {
2283
2448
  if (ref.name.includes(".")) continue;
2284
- if (known.has(ref.name) || SYSTEM_FIELDS.has(ref.name)) continue;
2449
+ if (known.has(ref.name) || SYSTEM_FIELDS.has(ref.name)) {
2450
+ if (anchors?.has(ref.name)) {
2451
+ findings.push({
2452
+ severity: "warning",
2453
+ rule: PAGE_FIELD_UNPROVISIONED,
2454
+ where,
2455
+ path: ref.path,
2456
+ 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."),
2457
+ hint: unprovisionedAnchorHint(objectName, ref.name)
2458
+ });
2459
+ }
2460
+ continue;
2461
+ }
2285
2462
  findings.push({
2286
2463
  severity: consequence2 === "queried" ? "error" : "warning",
2287
2464
  rule: PAGE_FIELD_UNKNOWN,
@@ -2297,17 +2474,20 @@ function validatePageFieldBindings(stack) {
2297
2474
  const findings = [];
2298
2475
  if (!stack || typeof stack !== "object") return findings;
2299
2476
  const objectFields = indexObjectFields(stack);
2477
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
2300
2478
  const pages = asArray9(stack.pages);
2301
2479
  for (let pi = 0; pi < pages.length; pi++) {
2302
2480
  const page = pages[pi];
2303
2481
  if (!page || typeof page !== "object") continue;
2304
2482
  const pageName = strName6(page.name) ?? `#${pi}`;
2305
2483
  const checkRefs = (refs, objectName, where) => {
2306
- findings.push(...checkFieldRefs(refs, objectName, objectFields, where));
2484
+ findings.push(
2485
+ ...checkFieldRefs(refs, objectName, objectFields, where, "skipped", unprovisionedAnchors)
2486
+ );
2307
2487
  };
2308
2488
  for (const { component, path, objectName } of walkPageComponents(page, `pages[${pi}]`)) {
2309
2489
  const type = strName6(component.type);
2310
- const props = isRec4(component.properties) ? component.properties : void 0;
2490
+ const props = isRec5(component.properties) ? component.properties : void 0;
2311
2491
  if (!type || !props) continue;
2312
2492
  const where = `page "${pageName}" \xB7 ${type}`;
2313
2493
  const base = `${path}.properties`;
@@ -2322,7 +2502,7 @@ function validatePageFieldBindings(stack) {
2322
2502
  if (!refs) continue;
2323
2503
  checkRefs(refs, objectName, where);
2324
2504
  }
2325
- const cfg = isRec4(page.interfaceConfig) ? page.interfaceConfig : void 0;
2505
+ const cfg = isRec5(page.interfaceConfig) ? page.interfaceConfig : void 0;
2326
2506
  if (cfg) {
2327
2507
  const cfgObject = strName6(cfg.source) ?? strName6(page.object);
2328
2508
  const base = `pages[${pi}].interfaceConfig`;
@@ -2331,7 +2511,7 @@ function validatePageFieldBindings(stack) {
2331
2511
  ...sortFieldRefs(cfg.sort, `${base}.sort`),
2332
2512
  ...fieldRefsFrom(cfg.filterBy, `${base}.filterBy`)
2333
2513
  ];
2334
- const userFilters = isRec4(cfg.userFilters) ? cfg.userFilters : void 0;
2514
+ const userFilters = isRec5(cfg.userFilters) ? cfg.userFilters : void 0;
2335
2515
  if (userFilters) {
2336
2516
  refs.push(...fieldRefsFrom(userFilters.fields, `${base}.userFilters.fields`));
2337
2517
  }
@@ -2359,7 +2539,7 @@ function strName7(v) {
2359
2539
  function strList2(v) {
2360
2540
  return Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.length > 0) : [];
2361
2541
  }
2362
- function isRec5(v) {
2542
+ function isRec6(v) {
2363
2543
  return !!v && typeof v === "object" && !Array.isArray(v);
2364
2544
  }
2365
2545
  function distance4(a, b) {
@@ -2488,10 +2668,10 @@ function validateChartBindings(stack) {
2488
2668
  const reports = asArray10(stack.reports);
2489
2669
  for (let ri = 0; ri < reports.length; ri++) {
2490
2670
  const report = reports[ri];
2491
- if (!isRec5(report)) continue;
2671
+ if (!isRec6(report)) continue;
2492
2672
  const reportName = strName7(report.name) ?? `#${ri}`;
2493
2673
  const checkReportChart = (chart, dataset, values, where, path) => {
2494
- if (!isRec5(chart)) return;
2674
+ if (!isRec6(chart)) return;
2495
2675
  check({
2496
2676
  dataset,
2497
2677
  // `values` is the report's measure SELECTION, not a chart ref; feeding
@@ -2515,7 +2695,7 @@ function validateChartBindings(stack) {
2515
2695
  const blocks = Array.isArray(report.blocks) ? report.blocks : [];
2516
2696
  for (let bi = 0; bi < blocks.length; bi++) {
2517
2697
  const block = blocks[bi];
2518
- if (!isRec5(block)) continue;
2698
+ if (!isRec6(block)) continue;
2519
2699
  checkReportChart(
2520
2700
  block.chart,
2521
2701
  strName7(block.dataset),
@@ -2526,9 +2706,9 @@ function validateChartBindings(stack) {
2526
2706
  }
2527
2707
  }
2528
2708
  const checkListChart = (container, where, path) => {
2529
- if (!isRec5(container)) return;
2709
+ if (!isRec6(container)) return;
2530
2710
  const chart = container.chart;
2531
- if (!isRec5(chart)) return;
2711
+ if (!isRec6(chart)) return;
2532
2712
  check({
2533
2713
  dataset: strName7(chart.dataset),
2534
2714
  dimensions: { names: strList2(chart.dimensions), path: `${path}.chart.dimensions` },
@@ -2540,10 +2720,10 @@ function validateChartBindings(stack) {
2540
2720
  const views = asArray10(stack.views);
2541
2721
  for (let vi = 0; vi < views.length; vi++) {
2542
2722
  const view = views[vi];
2543
- if (!isRec5(view)) continue;
2723
+ if (!isRec6(view)) continue;
2544
2724
  const viewName = strName7(view.name) ?? strName7(view.objectName) ?? `#${vi}`;
2545
2725
  checkListChart(view.list, `view "${viewName}" \xB7 list chart`, `views[${vi}].list`);
2546
- if (isRec5(view.listViews)) {
2726
+ if (isRec6(view.listViews)) {
2547
2727
  for (const [key, lv] of Object.entries(view.listViews)) {
2548
2728
  checkListChart(lv, `view "${viewName}" \xB7 listViews.${key} chart`, `views[${vi}].listViews.${key}`);
2549
2729
  }
@@ -2552,7 +2732,7 @@ function validateChartBindings(stack) {
2552
2732
  const objects = asArray10(stack.objects);
2553
2733
  for (let oi = 0; oi < objects.length; oi++) {
2554
2734
  const obj = objects[oi];
2555
- if (!isRec5(obj) || !isRec5(obj.listViews)) continue;
2735
+ if (!isRec6(obj) || !isRec6(obj.listViews)) continue;
2556
2736
  const objName = strName7(obj.name) ?? `#${oi}`;
2557
2737
  for (const [key, lv] of Object.entries(obj.listViews)) {
2558
2738
  checkListChart(
@@ -2565,10 +2745,10 @@ function validateChartBindings(stack) {
2565
2745
  const pages = asArray10(stack.pages);
2566
2746
  for (let pi = 0; pi < pages.length; pi++) {
2567
2747
  const page = pages[pi];
2568
- if (!isRec5(page)) continue;
2748
+ if (!isRec6(page)) continue;
2569
2749
  const pageName = strName7(page.name) ?? `#${pi}`;
2570
2750
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
2571
- const props = isRec5(component.properties) ? component.properties : void 0;
2751
+ const props = isRec6(component.properties) ? component.properties : void 0;
2572
2752
  if (!props || !strName7(props.dataset)) continue;
2573
2753
  const axisRefs = asArray10(props.yAxis).map((a, ai) => ({ name: strName7(a.field), path: `${path}.properties.yAxis[${ai}].field` })).filter((a) => !!a.name);
2574
2754
  const seriesRefs = asArray10(props.series).map((s, si) => ({ name: strName7(s.name), path: `${path}.properties.series[${si}].name` })).filter((s) => !!s.name);
@@ -2718,10 +2898,10 @@ function validateNavAccess(stack) {
2718
2898
 
2719
2899
  // src/validate-nav-target-refs.ts
2720
2900
  var NAV_TARGET_UNRESOLVED = "nav-target-unresolved";
2721
- var isRec6 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
2901
+ var isRec7 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
2722
2902
  function asArray13(v) {
2723
- if (Array.isArray(v)) return v.filter(isRec6);
2724
- if (isRec6(v)) return Object.entries(v).map(([name, def]) => isRec6(def) ? { name, ...def } : { name });
2903
+ if (Array.isArray(v)) return v.filter(isRec7);
2904
+ if (isRec7(v)) return Object.entries(v).map(([name, def]) => isRec7(def) ? { name, ...def } : { name });
2725
2905
  return [];
2726
2906
  }
2727
2907
  function strName9(v) {
@@ -2743,7 +2923,7 @@ function namesOf(collection) {
2743
2923
  }
2744
2924
  function validateNavTargetRefs(stack) {
2745
2925
  const findings = [];
2746
- if (!isRec6(stack)) return findings;
2926
+ if (!isRec7(stack)) return findings;
2747
2927
  const apps = asArray13(stack.apps);
2748
2928
  if (apps.length === 0) return findings;
2749
2929
  const declared = /* @__PURE__ */ new Map();
@@ -2755,7 +2935,7 @@ function validateNavTargetRefs(stack) {
2755
2935
  const walk = (items, basePath) => {
2756
2936
  if (!Array.isArray(items)) return;
2757
2937
  for (const [ni, raw] of items.entries()) {
2758
- if (!isRec6(raw)) continue;
2938
+ if (!isRec7(raw)) continue;
2759
2939
  const nav = raw;
2760
2940
  const navPath = `${basePath}[${ni}]`;
2761
2941
  for (const [type, prop, collection, noun] of NAV_TARGETS) {
@@ -2786,32 +2966,100 @@ function validateNavTargetRefs(stack) {
2786
2966
  return findings;
2787
2967
  }
2788
2968
 
2969
+ // src/validate-nav-object-servability.ts
2970
+ import { canServeApiOperation } from "@objectstack/spec/data";
2971
+ var NAV_OBJECT_UNSERVABLE = "nav-object-unservable";
2972
+ var isRec8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
2973
+ function asArray14(v) {
2974
+ if (Array.isArray(v)) return v.filter(isRec8);
2975
+ if (isRec8(v)) return Object.entries(v).map(([name, def]) => isRec8(def) ? { name, ...def } : { name });
2976
+ return [];
2977
+ }
2978
+ function strName10(v) {
2979
+ return typeof v === "string" && v.length > 0 ? v : void 0;
2980
+ }
2981
+ var isInterpolated3 = (s) => s.includes("${") || s.includes("{");
2982
+ function validateNavObjectServability(stack) {
2983
+ const findings = [];
2984
+ if (!isRec8(stack)) return findings;
2985
+ const apps = asArray14(stack.apps);
2986
+ if (apps.length === 0) return findings;
2987
+ const ownEnable = /* @__PURE__ */ new Map();
2988
+ const objects = asArray14(stack.objects);
2989
+ for (const [oi, obj] of objects.entries()) {
2990
+ const n = strName10(obj.name);
2991
+ if (!n) continue;
2992
+ ownEnable.set(n, { enable: obj.enable, path: `objects[${oi}].enable` });
2993
+ }
2994
+ if (ownEnable.size === 0) return findings;
2995
+ for (const [ai, app] of apps.entries()) {
2996
+ const appName = strName10(app.name) ?? `#${ai}`;
2997
+ const walk = (items, basePath) => {
2998
+ if (!Array.isArray(items)) return;
2999
+ for (const [ni, raw] of items.entries()) {
3000
+ if (!isRec8(raw)) continue;
3001
+ const nav = raw;
3002
+ const navPath = `${basePath}[${ni}]`;
3003
+ if (nav.type === "object") {
3004
+ const target = strName10(nav.objectName);
3005
+ const declared = target && !isInterpolated3(target) ? ownEnable.get(target) : void 0;
3006
+ if (target && declared && !canServeApiOperation(declared.enable, "list")) {
3007
+ const enable = isRec8(declared.enable) ? declared.enable : {};
3008
+ const apiDisabled = enable.apiEnabled === false;
3009
+ 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(", ")})` : "");
3010
+ const answer = apiDisabled ? "404 `OBJECT_API_DISABLED`" : "405 `OBJECT_API_METHOD_NOT_ALLOWED`";
3011
+ const offendingKey = apiDisabled ? `${declared.path}.apiEnabled` : `${declared.path}.apiMethods`;
3012
+ findings.push({
3013
+ severity: "error",
3014
+ rule: NAV_OBJECT_UNSERVABLE,
3015
+ where: `app "${appName}" \xB7 nav "${strName10(nav.id) ?? strName10(nav.label) ?? `#${ni}`}"`,
3016
+ // The nav entry is where the dead row is authored; the `enable`
3017
+ // key that condemns it is named in the message, because the fix
3018
+ // may belong at either end.
3019
+ path: `${navPath}.objectName`,
3020
+ 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.`,
3021
+ 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.`
3022
+ });
3023
+ }
3024
+ }
3025
+ if (Array.isArray(nav.children)) walk(nav.children, `${navPath}.children`);
3026
+ }
3027
+ };
3028
+ walk(app.navigation, `apps[${ai}].navigation`);
3029
+ for (const [ari, area] of asArray14(app.areas).entries()) {
3030
+ walk(area.items, `apps[${ai}].areas[${ari}].items`);
3031
+ walk(area.navigation, `apps[${ai}].areas[${ari}].navigation`);
3032
+ }
3033
+ }
3034
+ return findings;
3035
+ }
3036
+
2789
3037
  // src/validate-translation-references.ts
2790
3038
  import { expandViewContainer } from "@objectstack/spec";
2791
3039
  import { hasPlatformObjectPrefix as hasPlatformObjectPrefix2, isPlatformProvidedObjectName as isPlatformProvidedObjectName3 } from "@objectstack/spec/system";
2792
3040
 
2793
3041
  // src/view-walk.ts
2794
- function isRec7(v) {
3042
+ function isRec9(v) {
2795
3043
  return !!v && typeof v === "object" && !Array.isArray(v);
2796
3044
  }
2797
- function strName10(v) {
3045
+ function strName11(v) {
2798
3046
  return typeof v === "string" && v.length > 0 ? v : void 0;
2799
3047
  }
2800
3048
  function viewObjectName(view) {
2801
- return strName10(view.objectName) ?? strName10(view.object) ?? (isRec7(view.data) ? strName10(view.data.object) : void 0);
3049
+ return strName11(view.objectName) ?? strName11(view.object) ?? (isRec9(view.data) ? strName11(view.data.object) : void 0);
2802
3050
  }
2803
3051
  function viewContainerSites(view, basePath) {
2804
- if (!isRec7(view)) return [];
3052
+ if (!isRec9(view)) return [];
2805
3053
  const sites = [{ view, path: basePath, surface: "", kind: "self" }];
2806
- if (isRec7(view.form)) {
3054
+ if (isRec9(view.form)) {
2807
3055
  sites.push({ view: view.form, path: `${basePath}.form`, surface: "form", kind: "form" });
2808
3056
  }
2809
3057
  for (const key of ["listViews", "formViews"]) {
2810
3058
  const container = view[key];
2811
- if (!isRec7(container)) continue;
3059
+ if (!isRec9(container)) continue;
2812
3060
  const kind = key === "listViews" ? "listView" : "formView";
2813
3061
  for (const [subKey, sub] of Object.entries(container)) {
2814
- if (!isRec7(sub)) continue;
3062
+ if (!isRec9(sub)) continue;
2815
3063
  sites.push({
2816
3064
  view: sub,
2817
3065
  path: `${basePath}.${key}.${subKey}`,
@@ -2829,15 +3077,15 @@ function formViewSites(view, basePath) {
2829
3077
  // src/validate-translation-references.ts
2830
3078
  var TRANSLATION_TARGET_UNKNOWN = "translation-target-unknown";
2831
3079
  var TRANSLATION_OPTION_KEY_UNKNOWN = "translation-option-key-unknown";
2832
- function isRec8(v) {
3080
+ function isRec10(v) {
2833
3081
  return !!v && typeof v === "object" && !Array.isArray(v);
2834
3082
  }
2835
- function asArray14(v) {
3083
+ function asArray15(v) {
2836
3084
  if (Array.isArray(v)) return v;
2837
- if (isRec8(v)) return Object.entries(v).map(([name, def]) => ({ name, ...isRec8(def) ? def : {} }));
3085
+ if (isRec10(v)) return Object.entries(v).map(([name, def]) => ({ name, ...isRec10(def) ? def : {} }));
2838
3086
  return [];
2839
3087
  }
2840
- function strName11(v) {
3088
+ function strName12(v) {
2841
3089
  return typeof v === "string" && v.length > 0 ? v : void 0;
2842
3090
  }
2843
3091
  function distance5(a, b) {
@@ -2897,34 +3145,34 @@ function collectViewRecord(view, factsFor) {
2897
3145
  };
2898
3146
  const addSections = (container, binding) => {
2899
3147
  if (!binding) return;
2900
- for (const section of asArray14(container.sections)) {
2901
- const sectionName = strName11(section.name);
3148
+ for (const section of asArray15(container.sections)) {
3149
+ const sectionName = strName12(section.name);
2902
3150
  if (sectionName) factsFor(binding).sections.add(sectionName);
2903
3151
  }
2904
3152
  };
2905
- const listBinding = isRec8(view.list) ? bindingOf(view.list) : void 0;
2906
- if (isRec8(view.list)) addView(listBinding, defaultListViewKey(listBinding, view));
2907
- addView(recordObject ?? listBinding, strName11(view.name));
3153
+ const listBinding = isRec10(view.list) ? bindingOf(view.list) : void 0;
3154
+ if (isRec10(view.list)) addView(listBinding, defaultListViewKey(listBinding, view));
3155
+ addView(recordObject ?? listBinding, strName12(view.name));
2908
3156
  const named = namedViewKeys(view);
2909
3157
  for (const family of ["listViews", "formViews"]) {
2910
3158
  const container = view[family];
2911
- if (!isRec8(container)) continue;
3159
+ if (!isRec10(container)) continue;
2912
3160
  const registryKeys = family === "listViews" ? named.list : named.form;
2913
3161
  let at = 0;
2914
3162
  for (const sub of Object.values(container)) {
2915
3163
  if (!sub || typeof sub !== "object") continue;
2916
3164
  const registryKey = registryKeys[at++];
2917
- if (!isRec8(sub)) continue;
3165
+ if (!isRec10(sub)) continue;
2918
3166
  const binding = bindingOf(sub) ?? listBinding;
2919
3167
  addView(binding, registryKey);
2920
3168
  addSections(sub, binding);
2921
3169
  }
2922
3170
  }
2923
- if (isRec8(view.form)) addSections(view.form, bindingOf(view.form) ?? listBinding);
3171
+ if (isRec10(view.form)) addSections(view.form, bindingOf(view.form) ?? listBinding);
2924
3172
  addSections(view, recordObject ?? listBinding);
2925
3173
  }
2926
3174
  function defaultListViewKey(object, container) {
2927
- if (!object || !isRec8(container.list)) return void 0;
3175
+ if (!object || !isRec10(container.list)) return void 0;
2928
3176
  const item = expandViewContainer(object, container).find(
2929
3177
  (i) => i.viewKind === "list" && i.isDefault
2930
3178
  );
@@ -2936,7 +3184,7 @@ function namedViewKeys(container) {
2936
3184
  const object = "probe";
2937
3185
  const prefix = `${object}.`;
2938
3186
  const bare = (name) => name.startsWith(prefix) ? name.slice(prefix.length) : name;
2939
- const countEntries = (v) => isRec8(v) ? Object.values(v).filter((e) => !!e && typeof e === "object").length : 0;
3187
+ const countEntries = (v) => isRec10(v) ? Object.values(v).filter((e) => !!e && typeof e === "object").length : 0;
2940
3188
  const listCount = countEntries(container.listViews);
2941
3189
  const formCount = countEntries(container.formViews);
2942
3190
  if (!listCount && !formCount) return { list: [], form: [] };
@@ -2954,14 +3202,14 @@ function readOptions(field) {
2954
3202
  values.add(opt);
2955
3203
  continue;
2956
3204
  }
2957
- if (!isRec8(opt)) continue;
2958
- const value = strName11(opt.value);
3205
+ if (!isRec10(opt)) continue;
3206
+ const value = strName12(opt.value);
2959
3207
  if (!value) continue;
2960
3208
  values.add(value);
2961
- const label2 = strName11(opt.label);
3209
+ const label2 = strName12(opt.label);
2962
3210
  if (label2) byLabel.set(label2.toLowerCase(), value);
2963
3211
  }
2964
- } else if (isRec8(raw)) {
3212
+ } else if (isRec10(raw)) {
2965
3213
  for (const [value, label2] of Object.entries(raw)) {
2966
3214
  values.add(value);
2967
3215
  if (typeof label2 === "string" && label2.length > 0) byLabel.set(label2.toLowerCase(), value);
@@ -2981,48 +3229,48 @@ function buildUniverse(stack) {
2981
3229
  }
2982
3230
  return facts;
2983
3231
  };
2984
- for (const obj of asArray14(stack.objects)) {
2985
- const objectName = strName11(obj.name);
3232
+ for (const obj of asArray15(stack.objects)) {
3233
+ const objectName = strName12(obj.name);
2986
3234
  if (!objectName) continue;
2987
3235
  const facts = factsFor(objectName);
2988
- for (const field of asArray14(obj.fields)) {
2989
- const fieldName = strName11(field.name);
3236
+ for (const field of asArray15(obj.fields)) {
3237
+ const fieldName = strName12(field.name);
2990
3238
  if (fieldName) facts.fields.set(fieldName, field);
2991
3239
  }
2992
- for (const action of asArray14(obj.actions)) {
2993
- const actionName = strName11(action.name);
3240
+ for (const action of asArray15(obj.actions)) {
3241
+ const actionName = strName12(action.name);
2994
3242
  if (actionName) facts.actions.set(actionName, action);
2995
3243
  }
2996
- for (const view of asArray14(obj.views)) {
2997
- collectViewRecord({ ...view, object: strName11(view.object) ?? objectName }, factsFor);
3244
+ for (const view of asArray15(obj.views)) {
3245
+ collectViewRecord({ ...view, object: strName12(view.object) ?? objectName }, factsFor);
2998
3246
  }
2999
3247
  collectViewRecord({ object: objectName, listViews: obj.listViews }, factsFor);
3000
- for (const group of asArray14(obj.fieldGroups)) {
3001
- const key = strName11(group.key) ?? strName11(group.name);
3248
+ for (const group of asArray15(obj.fieldGroups)) {
3249
+ const key = strName12(group.key) ?? strName12(group.name);
3002
3250
  if (key) facts.sections.add(key);
3003
3251
  }
3004
3252
  }
3005
- for (const view of asArray14(stack.views)) {
3253
+ for (const view of asArray15(stack.views)) {
3006
3254
  collectViewRecord(view, factsFor);
3007
3255
  }
3008
- const pages = asArray14(stack.pages);
3256
+ const pages = asArray15(stack.pages);
3009
3257
  for (let pi = 0; pi < pages.length; pi++) {
3010
3258
  for (const walked of walkPageComponents(pages[pi], `pages[${pi}]`)) {
3011
3259
  if (!walked.objectName) continue;
3012
- const props = isRec8(walked.component.properties) ? walked.component.properties : void 0;
3260
+ const props = isRec10(walked.component.properties) ? walked.component.properties : void 0;
3013
3261
  if (!props) continue;
3014
- for (const section of asArray14(props.sections)) {
3015
- const sectionName = strName11(section.name);
3262
+ for (const section of asArray15(props.sections)) {
3263
+ const sectionName = strName12(section.name);
3016
3264
  if (sectionName) factsFor(walked.objectName).sections.add(sectionName);
3017
3265
  }
3018
3266
  }
3019
3267
  }
3020
3268
  const globalActions = /* @__PURE__ */ new Map();
3021
3269
  const actionOwners = /* @__PURE__ */ new Map();
3022
- for (const action of asArray14(stack.actions)) {
3023
- const actionName = strName11(action.name);
3270
+ for (const action of asArray15(stack.actions)) {
3271
+ const actionName = strName12(action.name);
3024
3272
  if (!actionName) continue;
3025
- const owner = strName11(action.objectName) ?? strName11(action.object);
3273
+ const owner = strName12(action.objectName) ?? strName12(action.object);
3026
3274
  if (owner) {
3027
3275
  factsFor(owner).actions.set(actionName, action);
3028
3276
  actionOwners.set(actionName, owner);
@@ -3036,41 +3284,41 @@ function buildUniverse(stack) {
3036
3284
  }
3037
3285
  }
3038
3286
  const apps = /* @__PURE__ */ new Map();
3039
- for (const app of asArray14(stack.apps)) {
3040
- const appName = strName11(app.name);
3287
+ for (const app of asArray15(stack.apps)) {
3288
+ const appName = strName12(app.name);
3041
3289
  if (!appName) continue;
3042
3290
  const navIds = apps.get(appName) ?? /* @__PURE__ */ new Set();
3043
3291
  const walkNav = (items) => {
3044
- for (const item of asArray14(items)) {
3045
- const id = strName11(item.id);
3292
+ for (const item of asArray15(items)) {
3293
+ const id = strName12(item.id);
3046
3294
  if (id) navIds.add(id);
3047
3295
  if (item.children) walkNav(item.children);
3048
3296
  }
3049
3297
  };
3050
3298
  walkNav(app.navigation);
3051
- for (const area of asArray14(app.areas)) {
3052
- const areaId = strName11(area.id);
3299
+ for (const area of asArray15(app.areas)) {
3300
+ const areaId = strName12(area.id);
3053
3301
  if (areaId) navIds.add(areaId);
3054
3302
  walkNav(area.navigation);
3055
3303
  }
3056
3304
  apps.set(appName, navIds);
3057
3305
  }
3058
3306
  const dashboards = /* @__PURE__ */ new Map();
3059
- for (const dash of asArray14(stack.dashboards)) {
3060
- const dashName = strName11(dash.name);
3307
+ for (const dash of asArray15(stack.dashboards)) {
3308
+ const dashName = strName12(dash.name);
3061
3309
  if (!dashName) continue;
3062
3310
  const widgets = /* @__PURE__ */ new Set();
3063
- for (const widget of asArray14(dash.widgets)) {
3064
- const id = strName11(widget.id) ?? strName11(widget.name);
3311
+ for (const widget of asArray15(dash.widgets)) {
3312
+ const id = strName12(widget.id) ?? strName12(widget.name);
3065
3313
  if (id) widgets.add(id);
3066
3314
  }
3067
3315
  const actions = /* @__PURE__ */ new Set();
3068
3316
  const headerActions = [
3069
- ...asArray14(isRec8(dash.header) ? dash.header.actions : void 0),
3070
- ...asArray14(dash.actions)
3317
+ ...asArray15(isRec10(dash.header) ? dash.header.actions : void 0),
3318
+ ...asArray15(dash.actions)
3071
3319
  ];
3072
3320
  for (const action of headerActions) {
3073
- const key = strName11(action.actionUrl) ?? strName11(action.url) ?? strName11(action.name);
3321
+ const key = strName12(action.actionUrl) ?? strName12(action.url) ?? strName12(action.name);
3074
3322
  if (key) actions.add(key);
3075
3323
  }
3076
3324
  dashboards.set(dashName, { widgets, actions });
@@ -3082,7 +3330,7 @@ function localePath(bundleIndex, locale) {
3082
3330
  }
3083
3331
  function validateTranslationReferences(stack) {
3084
3332
  const findings = [];
3085
- if (!isRec8(stack)) return findings;
3333
+ if (!isRec10(stack)) return findings;
3086
3334
  const bundles = Array.isArray(stack.translations) ? stack.translations : [];
3087
3335
  if (bundles.length === 0) return findings;
3088
3336
  const universe = buildUniverse(stack);
@@ -3091,13 +3339,13 @@ function validateTranslationReferences(stack) {
3091
3339
  };
3092
3340
  for (let bi = 0; bi < bundles.length; bi++) {
3093
3341
  const bundle = bundles[bi];
3094
- if (!isRec8(bundle)) continue;
3342
+ if (!isRec10(bundle)) continue;
3095
3343
  for (const [locale, rawData] of Object.entries(bundle)) {
3096
- if (!isRec8(rawData)) continue;
3344
+ if (!isRec10(rawData)) continue;
3097
3345
  const base = localePath(bi, locale);
3098
3346
  const inLocale = `locale "${locale}"`;
3099
3347
  for (const [objectName, rawNode] of Object.entries(asRecord(rawData.objects))) {
3100
- if (!isRec8(rawNode)) continue;
3348
+ if (!isRec10(rawNode)) continue;
3101
3349
  const objPath = `${base}.objects.${objectName}`;
3102
3350
  const facts = universe.objects.get(objectName);
3103
3351
  if (!facts) {
@@ -3123,7 +3371,7 @@ function validateTranslationReferences(stack) {
3123
3371
  );
3124
3372
  continue;
3125
3373
  }
3126
- if (!isRec8(rawField)) continue;
3374
+ if (!isRec10(rawField)) continue;
3127
3375
  checkOptionKeys(findings, {
3128
3376
  optionMap: rawField.options,
3129
3377
  field,
@@ -3205,7 +3453,7 @@ function validateTranslationReferences(stack) {
3205
3453
  );
3206
3454
  continue;
3207
3455
  }
3208
- if (!isRec8(rawApp)) continue;
3456
+ if (!isRec10(rawApp)) continue;
3209
3457
  for (const navId of Object.keys(asRecord(rawApp.navigation))) {
3210
3458
  if (navIds.has(navId)) continue;
3211
3459
  orphan(
@@ -3228,7 +3476,7 @@ function validateTranslationReferences(stack) {
3228
3476
  );
3229
3477
  continue;
3230
3478
  }
3231
- if (!isRec8(rawDash)) continue;
3479
+ if (!isRec10(rawDash)) continue;
3232
3480
  for (const widgetId of Object.keys(asRecord(rawDash.widgets))) {
3233
3481
  if (dash.widgets.has(widgetId)) continue;
3234
3482
  orphan(
@@ -3253,7 +3501,7 @@ function validateTranslationReferences(stack) {
3253
3501
  return findings;
3254
3502
  }
3255
3503
  function asRecord(v) {
3256
- return isRec8(v) ? v : {};
3504
+ return isRec10(v) ? v : {};
3257
3505
  }
3258
3506
  function checkOptionKeys(findings, ctx) {
3259
3507
  const optionKeys = Object.keys(asRecord(ctx.optionMap));
@@ -3265,7 +3513,7 @@ function checkOptionKeys(findings, ctx) {
3265
3513
  rule: TRANSLATION_OPTION_KEY_UNKNOWN,
3266
3514
  where: ctx.where,
3267
3515
  path: ctx.path,
3268
- message: `Option translations are keyed under field "${ctx.fieldName}" of object "${ctx.objectName}", which declares no \`options\` at all (field type "${strName11(ctx.field.type) ?? "unknown"}"). Nothing reads this map.`,
3516
+ message: `Option translations are keyed under field "${ctx.fieldName}" of object "${ctx.objectName}", which declares no \`options\` at all (field type "${strName12(ctx.field.type) ?? "unknown"}"). Nothing reads this map.`,
3269
3517
  hint: `Declare the options on the field, move the translations to the field that owns them, or drop them.`
3270
3518
  });
3271
3519
  return;
@@ -3284,11 +3532,11 @@ function checkOptionKeys(findings, ctx) {
3284
3532
  }
3285
3533
  }
3286
3534
  function checkActionParams(findings, ctx) {
3287
- const rawParams = Object.keys(asRecord(isRec8(ctx.rawAction) ? ctx.rawAction.params : void 0));
3535
+ const rawParams = Object.keys(asRecord(isRec10(ctx.rawAction) ? ctx.rawAction.params : void 0));
3288
3536
  if (rawParams.length === 0) return;
3289
3537
  const declared = /* @__PURE__ */ new Set();
3290
- for (const param of asArray14(ctx.action.params)) {
3291
- const name = strName11(param.name) ?? strName11(param.field);
3538
+ for (const param of asArray15(ctx.action.params)) {
3539
+ const name = strName12(param.name) ?? strName12(param.field);
3292
3540
  if (name) declared.add(name);
3293
3541
  }
3294
3542
  for (const paramName of rawParams) {
@@ -3305,33 +3553,33 @@ function checkActionParams(findings, ctx) {
3305
3553
  }
3306
3554
 
3307
3555
  // src/collection-entries.ts
3308
- function isRec9(v) {
3556
+ function isRec11(v) {
3309
3557
  return !!v && typeof v === "object" && !Array.isArray(v);
3310
3558
  }
3311
3559
  function collectionEntries(v, base) {
3312
3560
  if (Array.isArray(v)) {
3313
3561
  const out = [];
3314
3562
  for (let i = 0; i < v.length; i++) {
3315
- if (isRec9(v[i])) out.push({ rec: v[i], path: `${base}[${i}]` });
3563
+ if (isRec11(v[i])) out.push({ rec: v[i], path: `${base}[${i}]` });
3316
3564
  }
3317
3565
  return out;
3318
3566
  }
3319
- if (isRec9(v)) {
3320
- return Object.entries(v).filter(([, def]) => isRec9(def)).map(([name, def]) => ({ rec: { name, ...def }, path: `${base}.${name}` }));
3567
+ if (isRec11(v)) {
3568
+ return Object.entries(v).filter(([, def]) => isRec11(def)).map(([name, def]) => ({ rec: { name, ...def }, path: `${base}.${name}` }));
3321
3569
  }
3322
3570
  return [];
3323
3571
  }
3324
3572
 
3325
3573
  // src/validate-translatable-sections.ts
3326
3574
  var TRANSLATION_SECTION_NAME_MISSING = "translation-section-name-missing";
3327
- function isRec10(v) {
3575
+ function isRec12(v) {
3328
3576
  return !!v && typeof v === "object" && !Array.isArray(v);
3329
3577
  }
3330
- function strName12(v) {
3578
+ function strName13(v) {
3331
3579
  return typeof v === "string" && v.length > 0 ? v : void 0;
3332
3580
  }
3333
3581
  function viewLabel(view) {
3334
- const name = strName12(view.name);
3582
+ const name = strName13(view.name);
3335
3583
  return name ? `view "${name}"` : "";
3336
3584
  }
3337
3585
  function joinWhere(...parts) {
@@ -3339,7 +3587,7 @@ function joinWhere(...parts) {
3339
3587
  }
3340
3588
  function collectViewSites(view, basePath, label2, sites) {
3341
3589
  const recordObject = viewObjectName(view);
3342
- const listBinding = isRec10(view.list) ? viewObjectName(view.list) ?? recordObject : void 0;
3590
+ const listBinding = isRec12(view.list) ? viewObjectName(view.list) ?? recordObject : void 0;
3343
3591
  for (const site of viewContainerSites(view, basePath)) {
3344
3592
  sites.push({
3345
3593
  path: `${site.path}.sections`,
@@ -3353,11 +3601,11 @@ function translatedObjectNames(stack) {
3353
3601
  const out = /* @__PURE__ */ new Set();
3354
3602
  const bundles = Array.isArray(stack.translations) ? stack.translations : [];
3355
3603
  for (const bundle of bundles) {
3356
- if (!isRec10(bundle)) continue;
3604
+ if (!isRec12(bundle)) continue;
3357
3605
  for (const data of Object.values(bundle)) {
3358
- if (!isRec10(data) || !isRec10(data.objects)) continue;
3606
+ if (!isRec12(data) || !isRec12(data.objects)) continue;
3359
3607
  for (const [objectName, node] of Object.entries(data.objects)) {
3360
- if (isRec10(node)) out.add(objectName);
3608
+ if (isRec12(node)) out.add(objectName);
3361
3609
  }
3362
3610
  }
3363
3611
  }
@@ -3369,22 +3617,22 @@ function suggestedName(label2) {
3369
3617
  }
3370
3618
  function validateTranslatableSections(stack) {
3371
3619
  const findings = [];
3372
- if (!isRec10(stack)) return findings;
3620
+ if (!isRec12(stack)) return findings;
3373
3621
  const translated = translatedObjectNames(stack);
3374
3622
  if (translated.size === 0) return findings;
3375
3623
  const sites = [];
3376
3624
  for (const { rec: obj, path: objPath } of collectionEntries(stack.objects, "objects")) {
3377
- const objectName = strName12(obj.name);
3625
+ const objectName = strName13(obj.name);
3378
3626
  if (!objectName) continue;
3379
3627
  for (const { rec: view, path } of collectionEntries(obj.views, `${objPath}.views`)) {
3380
3628
  collectViewSites(
3381
- { ...view, object: strName12(view.object) ?? objectName },
3629
+ { ...view, object: strName13(view.object) ?? objectName },
3382
3630
  path,
3383
3631
  viewLabel(view),
3384
3632
  sites
3385
3633
  );
3386
3634
  }
3387
- if (isRec10(obj.listViews)) {
3635
+ if (isRec12(obj.listViews)) {
3388
3636
  collectViewSites({ object: objectName, listViews: obj.listViews }, objPath, "", sites);
3389
3637
  }
3390
3638
  }
@@ -3392,13 +3640,13 @@ function validateTranslatableSections(stack) {
3392
3640
  collectViewSites(view, path, viewLabel(view), sites);
3393
3641
  }
3394
3642
  for (const { rec: page, path: pagePath } of collectionEntries(stack.pages, "pages")) {
3395
- const pageName = strName12(page.name);
3643
+ const pageName = strName13(page.name);
3396
3644
  const pageLabel = pageName ? `page "${pageName}"` : "";
3397
3645
  for (const walked of walkPageComponents(page, pagePath)) {
3398
3646
  if (!walked.objectName) continue;
3399
- const props = isRec10(walked.component.properties) ? walked.component.properties : void 0;
3647
+ const props = isRec12(walked.component.properties) ? walked.component.properties : void 0;
3400
3648
  if (!props) continue;
3401
- const type = strName12(walked.component.type) ?? "component";
3649
+ const type = strName13(walked.component.type) ?? "component";
3402
3650
  sites.push({
3403
3651
  path: `${walked.path}.properties.sections`,
3404
3652
  surface: joinWhere(pageLabel, type),
@@ -3413,9 +3661,9 @@ function validateTranslatableSections(stack) {
3413
3661
  if (!Array.isArray(site.sections)) continue;
3414
3662
  for (let i = 0; i < site.sections.length; i++) {
3415
3663
  const section = site.sections[i];
3416
- if (!isRec10(section)) continue;
3417
- if (strName12(section.name)) continue;
3418
- const heading = strName12(section.label);
3664
+ if (!isRec12(section)) continue;
3665
+ if (strName13(section.name)) continue;
3666
+ const heading = strName13(section.label);
3419
3667
  if (!heading) continue;
3420
3668
  const slug = suggestedName(heading);
3421
3669
  findings.push({
@@ -3433,10 +3681,10 @@ function validateTranslatableSections(stack) {
3433
3681
 
3434
3682
  // src/flow-walk.ts
3435
3683
  import { FLOW_REGION_SLOTS_BY_TYPE, FLOW_REGION_CONFIG_KEYS } from "@objectstack/spec/automation";
3436
- function isRec11(v) {
3684
+ function isRec13(v) {
3437
3685
  return !!v && typeof v === "object" && !Array.isArray(v);
3438
3686
  }
3439
- function strName13(v) {
3687
+ function strName14(v) {
3440
3688
  return typeof v === "string" && v.length > 0 ? v : void 0;
3441
3689
  }
3442
3690
  var REGION_SLOTS = new Map(
@@ -3445,10 +3693,10 @@ var REGION_SLOTS = new Map(
3445
3693
  var REGION_CONFIG_KEYS = FLOW_REGION_CONFIG_KEYS;
3446
3694
  var MAX_REGION_DEPTH = 16;
3447
3695
  function flowNodeLabel(node, index) {
3448
- return strName13(node.label) ?? strName13(node.id) ?? `#${index}`;
3696
+ return strName14(node.label) ?? strName14(node.id) ?? `#${index}`;
3449
3697
  }
3450
3698
  function stripRegions(config) {
3451
- if (!isRec11(config)) return void 0;
3699
+ if (!isRec13(config)) return void 0;
3452
3700
  let out;
3453
3701
  for (const key of Object.keys(config)) {
3454
3702
  if (!REGION_CONFIG_KEYS.has(key)) continue;
@@ -3459,11 +3707,11 @@ function stripRegions(config) {
3459
3707
  }
3460
3708
  function walkFlowNodes(flow, flowPath) {
3461
3709
  const out = [];
3462
- if (!isRec11(flow)) return out;
3710
+ if (!isRec13(flow)) return out;
3463
3711
  const visitList = (nodes, basePath, trail, depth) => {
3464
3712
  if (!Array.isArray(nodes) || depth > MAX_REGION_DEPTH) return;
3465
3713
  nodes.forEach((raw, index) => {
3466
- if (!isRec11(raw)) return;
3714
+ if (!isRec13(raw)) return;
3467
3715
  const path = `${basePath}[${index}]`;
3468
3716
  out.push({
3469
3717
  node: raw,
@@ -3472,9 +3720,9 @@ function walkFlowNodes(flow, flowPath) {
3472
3720
  regionTrail: trail,
3473
3721
  depth
3474
3722
  });
3475
- const type = strName13(raw.type);
3723
+ const type = strName14(raw.type);
3476
3724
  const slots = type ? REGION_SLOTS.get(type) : void 0;
3477
- if (!slots || !isRec11(raw.config)) return;
3725
+ if (!slots || !isRec13(raw.config)) return;
3478
3726
  const config = raw.config;
3479
3727
  const here = `${type} "${flowNodeLabel(raw, index)}"`;
3480
3728
  for (const slot of slots) {
@@ -3482,8 +3730,8 @@ function walkFlowNodes(flow, flowPath) {
3482
3730
  if (slot === "branches") {
3483
3731
  if (!Array.isArray(value)) continue;
3484
3732
  value.forEach((branch, b) => {
3485
- if (!isRec11(branch)) return;
3486
- const branchName = strName13(branch.name) ?? `#${b}`;
3733
+ if (!isRec13(branch)) return;
3734
+ const branchName = strName14(branch.name) ?? `#${b}`;
3487
3735
  visitList(
3488
3736
  branch.nodes,
3489
3737
  `${path}.config.branches[${b}].nodes`,
@@ -3493,7 +3741,7 @@ function walkFlowNodes(flow, flowPath) {
3493
3741
  });
3494
3742
  continue;
3495
3743
  }
3496
- if (!isRec11(value)) continue;
3744
+ if (!isRec13(value)) continue;
3497
3745
  visitList(
3498
3746
  value.nodes,
3499
3747
  `${path}.config.${slot}.nodes`,
@@ -3513,7 +3761,8 @@ function joinTrail(trail, segment) {
3513
3761
  // src/validate-flow-template-paths.ts
3514
3762
  var FLOW_TEMPLATE_UNKNOWN_FIELD = "flow-template-unknown-field";
3515
3763
  var FLOW_TEMPLATE_LOOKUP_TRAVERSAL = "flow-template-lookup-traversal";
3516
- function asArray15(v) {
3764
+ var FLOW_TEMPLATE_FIELD_UNPROVISIONED = "flow-template-field-unprovisioned";
3765
+ function asArray16(v) {
3517
3766
  if (Array.isArray(v)) return v;
3518
3767
  if (v && typeof v === "object") {
3519
3768
  return Object.entries(v).map(([name, def]) => ({
@@ -3542,7 +3791,7 @@ var FILTER_GUARDED_NODE_TYPES = /* @__PURE__ */ new Set([
3542
3791
  ]);
3543
3792
  function fieldTypesOf(obj) {
3544
3793
  const types = /* @__PURE__ */ new Map();
3545
- for (const f of asArray15(obj.fields)) {
3794
+ for (const f of asArray16(obj.fields)) {
3546
3795
  if (typeof f.name === "string") {
3547
3796
  types.set(f.name, typeof f.type === "string" ? f.type : "");
3548
3797
  }
@@ -3639,10 +3888,10 @@ function declaredExpandOf(flow) {
3639
3888
  }
3640
3889
  function validateFlowTemplatePaths(stack) {
3641
3890
  const findings = [];
3642
- const flows = asArray15(stack.flows);
3891
+ const flows = asArray16(stack.flows);
3643
3892
  if (flows.length === 0) return findings;
3644
3893
  const objectsByName = /* @__PURE__ */ new Map();
3645
- for (const obj of asArray15(stack.objects)) {
3894
+ for (const obj of asArray16(stack.objects)) {
3646
3895
  if (typeof obj.name === "string") objectsByName.set(obj.name, obj);
3647
3896
  }
3648
3897
  flows.forEach((flow, flowIndex) => {
@@ -3655,6 +3904,7 @@ function validateFlowTemplatePaths(stack) {
3655
3904
  const obj = objectsByName.get(objectName);
3656
3905
  if (!obj) return;
3657
3906
  const fieldTypes = fieldTypesOf(obj);
3907
+ const unprovisionedAnchors = unprovisionedInjectedColumnsFor(obj);
3658
3908
  const expandSet = declaredExpandOf(flow);
3659
3909
  walkFlowNodes(flow, `flows[${flowIndex}]`).forEach(({ node, path: nodePath, regionTrail, localConfig }, walkIndex) => {
3660
3910
  const nodeLabel = typeof node.type === "string" ? node.type : typeof node.id === "string" ? node.id : `#${walkIndex}`;
@@ -3666,6 +3916,7 @@ function validateFlowTemplatePaths(stack) {
3666
3916
  if (leaves.length === 0) return;
3667
3917
  const seenUnknown = /* @__PURE__ */ new Set();
3668
3918
  const seenTraversal = /* @__PURE__ */ new Set();
3919
+ const seenUnprovisioned = /* @__PURE__ */ new Set();
3669
3920
  for (const leaf of leaves) {
3670
3921
  const inFilter = leaf.inFilter;
3671
3922
  for (const rest of recordRefsIn(leaf.text)) {
@@ -3673,6 +3924,19 @@ function validateFlowTemplatePaths(stack) {
3673
3924
  const hasSubPath = rest.length > 1;
3674
3925
  const nextIsIdentifier = hasSubPath && !/^\d+$/.test(rest[1]);
3675
3926
  const isKnown = fieldTypes.has(head) || IMPLICIT_HEADS.has(head);
3927
+ if (unprovisionedAnchors.has(head)) {
3928
+ if (!seenUnprovisioned.has(head)) {
3929
+ seenUnprovisioned.add(head);
3930
+ findings.push({
3931
+ severity: "warning",
3932
+ rule: FLOW_TEMPLATE_FIELD_UNPROVISIONED,
3933
+ where,
3934
+ path: nodePath,
3935
+ 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).`),
3936
+ hint: unprovisionedAnchorHint(objectName, head)
3937
+ });
3938
+ }
3939
+ }
3676
3940
  if (!isKnown) {
3677
3941
  if (seenUnknown.has(head)) continue;
3678
3942
  seenUnknown.add(head);
@@ -3711,14 +3975,14 @@ function validateFlowTemplatePaths(stack) {
3711
3975
 
3712
3976
  // src/validate-ai-surface-affinity.ts
3713
3977
  var AI_SKILL_SURFACE_MISMATCH = "ai-skill-surface-mismatch";
3714
- function asArray16(v) {
3978
+ function asArray17(v) {
3715
3979
  if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
3716
3980
  if (v && typeof v === "object") {
3717
3981
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
3718
3982
  }
3719
3983
  return [];
3720
3984
  }
3721
- function strName14(v) {
3985
+ function strName15(v) {
3722
3986
  return typeof v === "string" && v.length > 0 ? v : void 0;
3723
3987
  }
3724
3988
  function surfaceOf(v) {
@@ -3728,18 +3992,18 @@ function validateAiSurfaceAffinity(stack) {
3728
3992
  const findings = [];
3729
3993
  if (!stack || typeof stack !== "object") return findings;
3730
3994
  const skillsByName = /* @__PURE__ */ new Map();
3731
- for (const skill of asArray16(stack.skills)) {
3732
- const n = strName14(skill.name);
3995
+ for (const skill of asArray17(stack.skills)) {
3996
+ const n = strName15(skill.name);
3733
3997
  if (n) skillsByName.set(n, skill);
3734
3998
  }
3735
- const agents = asArray16(stack.agents);
3999
+ const agents = asArray17(stack.agents);
3736
4000
  for (let ai = 0; ai < agents.length; ai++) {
3737
4001
  const agent = agents[ai];
3738
- const agentName = strName14(agent.name) ?? `#${ai}`;
4002
+ const agentName = strName15(agent.name) ?? `#${ai}`;
3739
4003
  const agentSurface = surfaceOf(agent.surface);
3740
4004
  const skillRefs = Array.isArray(agent.skills) ? agent.skills : [];
3741
4005
  for (let si = 0; si < skillRefs.length; si++) {
3742
- const ref = strName14(skillRefs[si]);
4006
+ const ref = strName15(skillRefs[si]);
3743
4007
  if (!ref) continue;
3744
4008
  const skill = skillsByName.get(ref);
3745
4009
  if (!skill) continue;
@@ -3761,14 +4025,14 @@ function validateAiSurfaceAffinity(stack) {
3761
4025
  // src/validate-ai-tool-references.ts
3762
4026
  import { PLATFORM_PROVIDED_TOOL_NAMES, PLATFORM_TOOL_FAMILY_PREFIXES } from "@objectstack/spec/system";
3763
4027
  var AI_SKILL_TOOL_UNRESOLVED = "ai-skill-tool-unresolved";
3764
- function asArray17(v) {
4028
+ function asArray18(v) {
3765
4029
  if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
3766
4030
  if (v && typeof v === "object") {
3767
4031
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
3768
4032
  }
3769
4033
  return [];
3770
4034
  }
3771
- function strName15(v) {
4035
+ function strName16(v) {
3772
4036
  return typeof v === "string" && v.length > 0 ? v : void 0;
3773
4037
  }
3774
4038
  function distance6(a, b) {
@@ -3809,26 +4073,26 @@ function materialisesAsTool(action) {
3809
4073
  if (!ai || typeof ai !== "object") return false;
3810
4074
  const aiRec = ai;
3811
4075
  if (aiRec.exposed !== true) return false;
3812
- if (!strName15(aiRec.description)) return false;
3813
- const type = strName15(action.type);
4076
+ if (!strName16(aiRec.description)) return false;
4077
+ const type = strName16(action.type);
3814
4078
  if (!type || !HEADLESS_ACTION_TYPES.has(type)) return false;
3815
4079
  if (type === "script") return Boolean(action.target || action.body);
3816
4080
  return Boolean(action.target);
3817
4081
  }
3818
4082
  function collectToolUniverse(stack) {
3819
4083
  const universe = new Set(PLATFORM_PROVIDED_TOOL_NAMES);
3820
- for (const tool of asArray17(stack.tools)) {
3821
- const n = strName15(tool.name);
4084
+ for (const tool of asArray18(stack.tools)) {
4085
+ const n = strName16(tool.name);
3822
4086
  if (n) universe.add(n);
3823
4087
  }
3824
4088
  const addActionFamily = (actions) => {
3825
- for (const action of asArray17(actions)) {
3826
- const n = strName15(action.name);
4089
+ for (const action of asArray18(actions)) {
4090
+ const n = strName16(action.name);
3827
4091
  if (n && materialisesAsTool(action)) universe.add(`action_${n}`);
3828
4092
  }
3829
4093
  };
3830
4094
  addActionFamily(stack.actions);
3831
- for (const obj of asArray17(stack.objects)) {
4095
+ for (const obj of asArray18(stack.objects)) {
3832
4096
  addActionFamily(obj.actions);
3833
4097
  }
3834
4098
  return universe;
@@ -3836,13 +4100,13 @@ function collectToolUniverse(stack) {
3836
4100
  function collectUnexposedActionNames(stack) {
3837
4101
  const names = /* @__PURE__ */ new Set();
3838
4102
  const scan = (actions) => {
3839
- for (const action of asArray17(actions)) {
3840
- const n = strName15(action.name);
4103
+ for (const action of asArray18(actions)) {
4104
+ const n = strName16(action.name);
3841
4105
  if (n && !materialisesAsTool(action)) names.add(n);
3842
4106
  }
3843
4107
  };
3844
4108
  scan(stack.actions);
3845
- for (const obj of asArray17(stack.objects)) scan(obj.actions);
4109
+ for (const obj of asArray18(stack.objects)) scan(obj.actions);
3846
4110
  return names;
3847
4111
  }
3848
4112
  function validateAiToolReferences(stack) {
@@ -3860,13 +4124,13 @@ function validateAiToolReferences(stack) {
3860
4124
  }
3861
4125
  return universe.has(ref);
3862
4126
  };
3863
- const skills = asArray17(stack.skills);
4127
+ const skills = asArray18(stack.skills);
3864
4128
  for (let si = 0; si < skills.length; si++) {
3865
4129
  const skill = skills[si];
3866
- const skillName = strName15(skill.name) ?? `#${si}`;
4130
+ const skillName = strName16(skill.name) ?? `#${si}`;
3867
4131
  const refs = Array.isArray(skill.tools) ? skill.tools : [];
3868
4132
  for (let ti = 0; ti < refs.length; ti++) {
3869
- const ref = strName15(refs[ti]);
4133
+ const ref = strName16(refs[ti]);
3870
4134
  if (!ref || resolves(ref)) continue;
3871
4135
  const isPattern = ref.endsWith("*");
3872
4136
  const unexposed = !isPattern && ref.startsWith("action_") && unexposedActions.has(ref.slice("action_".length)) ? ref.slice("action_".length) : void 0;
@@ -3886,24 +4150,24 @@ function validateAiToolReferences(stack) {
3886
4150
  // src/validate-ai-agent-authoring.ts
3887
4151
  var AGENT_AUTHORING_WITHDRAWN = "agent-authoring-withdrawn";
3888
4152
  var DEFAULT_AGENT_OUTSIDE_ROSTER = "default-agent-outside-roster";
3889
- function asArray18(v) {
4153
+ function asArray19(v) {
3890
4154
  if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
3891
4155
  if (v && typeof v === "object") {
3892
4156
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
3893
4157
  }
3894
4158
  return [];
3895
4159
  }
3896
- function strName16(v) {
4160
+ function strName17(v) {
3897
4161
  return typeof v === "string" && v.length > 0 ? v : void 0;
3898
4162
  }
3899
4163
  var PLATFORM_AGENT_NAMES = /* @__PURE__ */ new Set(["ask", "build", "data_chat", "metadata_assistant"]);
3900
4164
  function validateAiAgentAuthoring(stack) {
3901
4165
  const findings = [];
3902
4166
  if (!stack || typeof stack !== "object") return findings;
3903
- const agents = asArray18(stack.agents);
4167
+ const agents = asArray19(stack.agents);
3904
4168
  for (let ai = 0; ai < agents.length; ai++) {
3905
4169
  const agent = agents[ai];
3906
- const name = strName16(agent.name) ?? `#${ai}`;
4170
+ const name = strName17(agent.name) ?? `#${ai}`;
3907
4171
  const isPlatformName = PLATFORM_AGENT_NAMES.has(name);
3908
4172
  const skillCount = Array.isArray(agent.skills) ? agent.skills.length : 0;
3909
4173
  findings.push({
@@ -3916,12 +4180,12 @@ function validateAiAgentAuthoring(stack) {
3916
4180
  });
3917
4181
  }
3918
4182
  const roster = [...PLATFORM_AGENT_NAMES].join(", ");
3919
- const apps = asArray18(stack.apps);
4183
+ const apps = asArray19(stack.apps);
3920
4184
  for (let appIdx = 0; appIdx < apps.length; appIdx++) {
3921
4185
  const app = apps[appIdx];
3922
- const defaultAgent = strName16(app.defaultAgent);
4186
+ const defaultAgent = strName17(app.defaultAgent);
3923
4187
  if (!defaultAgent || PLATFORM_AGENT_NAMES.has(defaultAgent)) continue;
3924
- const appName = strName16(app.name) ?? `#${appIdx}`;
4188
+ const appName = strName17(app.name) ?? `#${appIdx}`;
3925
4189
  findings.push({
3926
4190
  severity: "warning",
3927
4191
  rule: DEFAULT_AGENT_OUTSIDE_ROSTER,
@@ -4017,24 +4281,24 @@ var IMPLICIT_FIELDS2 = /* @__PURE__ */ new Set([
4017
4281
  "owner",
4018
4282
  "record_type"
4019
4283
  ]);
4020
- var isRec12 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4021
- function asArray19(v) {
4022
- if (Array.isArray(v)) return v.filter((x) => isRec12(x));
4023
- if (isRec12(v)) {
4284
+ var isRec14 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4285
+ function asArray20(v) {
4286
+ if (Array.isArray(v)) return v.filter((x) => isRec14(x));
4287
+ if (isRec14(v)) {
4024
4288
  return Object.entries(v).map(([name, def]) => ({
4025
4289
  name,
4026
- ...isRec12(def) ? def : {}
4290
+ ...isRec14(def) ? def : {}
4027
4291
  }));
4028
4292
  }
4029
4293
  return [];
4030
4294
  }
4031
4295
  function indexObjectFields2(stack) {
4032
4296
  const out = /* @__PURE__ */ new Map();
4033
- for (const obj of asArray19(stack.objects)) {
4297
+ for (const obj of asArray20(stack.objects)) {
4034
4298
  const name = typeof obj.name === "string" ? obj.name : void 0;
4035
4299
  if (!name) continue;
4036
4300
  const names = /* @__PURE__ */ new Set();
4037
- for (const f of asArray19(obj.fields)) {
4301
+ for (const f of asArray20(obj.fields)) {
4038
4302
  if (typeof f.name === "string" && f.name) names.add(f.name);
4039
4303
  }
4040
4304
  out.set(name, names);
@@ -4164,12 +4428,12 @@ ${source}
4164
4428
  }
4165
4429
  function validateHookBodyWrites(stack) {
4166
4430
  const findings = [];
4167
- const hooks = asArray19(stack.hooks);
4431
+ const hooks = asArray20(stack.hooks);
4168
4432
  if (hooks.length === 0) return findings;
4169
4433
  let objectFields = null;
4170
4434
  hooks.forEach((hook, hookIndex) => {
4171
4435
  const body = hook.body;
4172
- if (!isRec12(body) || body.language !== "js") return;
4436
+ if (!isRec14(body) || body.language !== "js") return;
4173
4437
  const source = body.source;
4174
4438
  if (typeof source !== "string" || source.trim() === "") return;
4175
4439
  const writes = extractHookBodyWrites(source).filter((w) => HOOK_APPLICABLE_IDS.has(w.patternId));
@@ -4240,13 +4504,13 @@ var ACTION_BODY_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_B
4240
4504
  var ACTION_RECORD_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_RECORD_WRITE_PATTERN_IDS.includes(p.id));
4241
4505
  var APPLICABLE_IDS = new Set(ACTION_BODY_WRITE_PATTERN_IDS);
4242
4506
  var RECORD_WRITE_IDS = new Set(ACTION_RECORD_WRITE_PATTERN_IDS);
4243
- var isRec13 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4244
- function asArray20(v) {
4245
- if (Array.isArray(v)) return v.filter((x) => isRec13(x));
4246
- if (isRec13(v)) {
4507
+ var isRec15 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4508
+ function asArray21(v) {
4509
+ if (Array.isArray(v)) return v.filter((x) => isRec15(x));
4510
+ if (isRec15(v)) {
4247
4511
  return Object.entries(v).map(([name, def]) => ({
4248
4512
  name,
4249
- ...isRec13(def) ? def : {}
4513
+ ...isRec15(def) ? def : {}
4250
4514
  }));
4251
4515
  }
4252
4516
  return [];
@@ -4260,11 +4524,11 @@ function collectActionBodies(stack) {
4260
4524
  const sites = [];
4261
4525
  const seen = /* @__PURE__ */ new Set();
4262
4526
  const collect = (actions, pathPrefix, parentObject) => {
4263
- asArray20(actions).forEach((action, index) => {
4527
+ asArray21(actions).forEach((action, index) => {
4264
4528
  const type = typeof action.type === "string" ? action.type : "script";
4265
4529
  if (type !== "script") return;
4266
4530
  const body = action.body;
4267
- if (!isRec13(body) || body.language !== "js") return;
4531
+ if (!isRec15(body) || body.language !== "js") return;
4268
4532
  const source = body.source;
4269
4533
  if (typeof source !== "string" || source.trim() === "") return;
4270
4534
  const name = typeof action.name === "string" && action.name ? action.name : `#${index}`;
@@ -4275,7 +4539,7 @@ function collectActionBodies(stack) {
4275
4539
  });
4276
4540
  };
4277
4541
  collect(stack.actions, "actions");
4278
- asArray20(stack.objects).forEach((obj, objIndex) => {
4542
+ asArray21(stack.objects).forEach((obj, objIndex) => {
4279
4543
  const parentObject = typeof obj.name === "string" && obj.name ? obj.name : void 0;
4280
4544
  collect(obj.actions, `objects[${objIndex}].actions`, parentObject);
4281
4545
  });
@@ -4283,7 +4547,7 @@ function collectActionBodies(stack) {
4283
4547
  }
4284
4548
  function validateActionBodyWrites(stack) {
4285
4549
  const findings = [];
4286
- if (!isRec13(stack)) return findings;
4550
+ if (!isRec15(stack)) return findings;
4287
4551
  const sites = collectActionBodies(stack);
4288
4552
  if (sites.length === 0) return findings;
4289
4553
  let objectFields = null;
@@ -4341,13 +4605,13 @@ function fixHint2(field, declared) {
4341
4605
  import { findClosestMatches as findClosestMatches3, formatSuggestion as formatSuggestion3 } from "@objectstack/spec/shared";
4342
4606
  var FLOW_NODE_WRITE_UNKNOWN_FIELD = "flow-node-write-unknown-field";
4343
4607
  var FLOW_WRITE_NODE_TYPES = ["update_record", "create_record"];
4344
- var isRec14 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4345
- function asArray21(v) {
4346
- if (Array.isArray(v)) return v.filter((x) => isRec14(x));
4347
- if (isRec14(v)) {
4608
+ var isRec16 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4609
+ function asArray22(v) {
4610
+ if (Array.isArray(v)) return v.filter((x) => isRec16(x));
4611
+ if (isRec16(v)) {
4348
4612
  return Object.entries(v).map(([name, def]) => ({
4349
4613
  name,
4350
- ...isRec14(def) ? def : {}
4614
+ ...isRec16(def) ? def : {}
4351
4615
  }));
4352
4616
  }
4353
4617
  return [];
@@ -4360,8 +4624,8 @@ function readLiteralObjectName(config) {
4360
4624
  var COVERED_TYPES = new Set(FLOW_WRITE_NODE_TYPES);
4361
4625
  function validateFlowNodeWrites(stack) {
4362
4626
  const findings = [];
4363
- if (!isRec14(stack)) return findings;
4364
- const flows = asArray21(stack.flows);
4627
+ if (!isRec16(stack)) return findings;
4628
+ const flows = asArray22(stack.flows);
4365
4629
  if (flows.length === 0) return findings;
4366
4630
  let objectFields = null;
4367
4631
  flows.forEach((flow, flowIndex) => {
@@ -4369,10 +4633,10 @@ function validateFlowNodeWrites(stack) {
4369
4633
  const walked = walkFlowNodes(flow, `flows[${flowIndex}]`);
4370
4634
  walked.forEach(({ node, path: nodePath, regionTrail }, walkIndex) => {
4371
4635
  if (typeof node.type !== "string" || !COVERED_TYPES.has(node.type)) return;
4372
- const config = isRec14(node.config) ? node.config : void 0;
4636
+ const config = isRec16(node.config) ? node.config : void 0;
4373
4637
  if (!config) return;
4374
4638
  const fields = config.fields;
4375
- if (!isRec14(fields)) return;
4639
+ if (!isRec16(fields)) return;
4376
4640
  const written = Object.keys(fields);
4377
4641
  if (written.length === 0) return;
4378
4642
  const objectName = readLiteralObjectName(config);
@@ -4406,7 +4670,7 @@ function fixHint3(field, declared) {
4406
4670
  // src/validate-readonly-flow-writes.ts
4407
4671
  var FLOW_UPDATE_READONLY_FIELD = "flow-update-readonly-field";
4408
4672
  var FLOW_UPDATE_READONLY_WHEN_FIELD = "flow-update-readonly-when-field";
4409
- function asArray22(v) {
4673
+ function asArray23(v) {
4410
4674
  if (Array.isArray(v)) return v;
4411
4675
  if (v && typeof v === "object") {
4412
4676
  return Object.entries(v).map(([name, def]) => ({
@@ -4447,9 +4711,9 @@ function readLiteralObjectName2(config) {
4447
4711
  }
4448
4712
  function validateReadonlyFlowWrites(stack) {
4449
4713
  const findings = [];
4450
- const flows = asArray22(stack.flows);
4714
+ const flows = asArray23(stack.flows);
4451
4715
  if (flows.length === 0) return findings;
4452
- const roIndex = buildReadonlyIndex(asArray22(stack.objects));
4716
+ const roIndex = buildReadonlyIndex(asArray23(stack.objects));
4453
4717
  flows.forEach((flow, flowIndex) => {
4454
4718
  if (flow.runAs === "system") return;
4455
4719
  const runAs = flow.runAs === "user" || flow.runAs === "system" ? flow.runAs : "user";
@@ -4508,11 +4772,11 @@ import {
4508
4772
  import { VALID_AST_OPERATORS } from "@objectstack/spec/data";
4509
4773
 
4510
4774
  // src/zod-issue-format.ts
4511
- var isRec15 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4775
+ var isRec17 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4512
4776
  var valueAtPath = (root, path) => {
4513
4777
  let cur = root;
4514
4778
  for (const key of path) {
4515
- if (!isRec15(cur) && !Array.isArray(cur)) return void 0;
4779
+ if (!isRec17(cur) && !Array.isArray(cur)) return void 0;
4516
4780
  cur = cur[key];
4517
4781
  }
4518
4782
  return cur;
@@ -4557,7 +4821,7 @@ function loadTypeScript2() {
4557
4821
  }
4558
4822
  return cachedTs2;
4559
4823
  }
4560
- var asArray23 = (v) => Array.isArray(v) ? v : [];
4824
+ var asArray24 = (v) => Array.isArray(v) ? v : [];
4561
4825
  var BLOCKS = new Map(
4562
4826
  REACT_BLOCKS.map((b) => [
4563
4827
  b.tag,
@@ -4645,12 +4909,13 @@ function filterAttrValue(tsc, sf, attr) {
4645
4909
  return perPosition(init.expression);
4646
4910
  }
4647
4911
  var REACT_CHART_FIELD_UNKNOWN = "react-chart-field-unknown";
4912
+ var REACT_CHART_FIELD_UNPROVISIONED = "react-chart-field-unprovisioned";
4648
4913
  var REACT_CHART_AGGREGATE_INVALID = "react-chart-aggregate-invalid";
4649
4914
  var REACT_CHART_AXIS_UNKNOWN = "react-chart-axis-unknown";
4650
4915
  var REACT_CHART_DRILLDOWN_INVALID = "react-chart-drilldown-invalid";
4651
4916
  function checkChartDrillDown(raw, push2) {
4652
4917
  if (raw === void 0 || raw === NOT_STATIC) return;
4653
- if (!isRec16(raw)) {
4918
+ if (!isRec18(raw)) {
4654
4919
  push2(
4655
4920
  "error",
4656
4921
  REACT_CHART_DRILLDOWN_INVALID,
@@ -4673,7 +4938,7 @@ function checkChartDrillDown(raw, push2) {
4673
4938
  }
4674
4939
  function checkChartAggregate(raw, push2) {
4675
4940
  if (raw === void 0 || raw === NOT_STATIC) return;
4676
- if (!isRec16(raw)) {
4941
+ if (!isRec18(raw)) {
4677
4942
  push2(
4678
4943
  "error",
4679
4944
  REACT_CHART_AGGREGATE_INVALID,
@@ -4704,9 +4969,9 @@ function checkChartAggregate(raw, push2) {
4704
4969
  );
4705
4970
  }
4706
4971
  }
4707
- var isRec16 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4972
+ var isRec18 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4708
4973
  var strOf = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
4709
- function checkObjectChart(attrs, objectFields, findings) {
4974
+ function checkObjectChart(attrs, objectFields, findings, unprovisionedAnchors = /* @__PURE__ */ new Map()) {
4710
4975
  const { values, where, path } = attrs;
4711
4976
  const push2 = (severity, rule, message, hint) => findings.push({ severity, rule, where, path, message, hint });
4712
4977
  checkChartDrillDown(values.get("drillDown"), push2);
@@ -4714,18 +4979,29 @@ function checkObjectChart(attrs, objectFields, findings) {
4714
4979
  const aggregate = values.get("aggregate");
4715
4980
  checkChartAggregate(aggregate, push2);
4716
4981
  if (aggregate === void 0 || aggregate === NOT_STATIC) return;
4717
- if (!isRec16(aggregate)) return;
4982
+ if (!isRec18(aggregate)) return;
4718
4983
  const fn = strOf(aggregate.function);
4719
4984
  const field = strOf(aggregate.field);
4720
4985
  const groupBy = aggregate.groupBy;
4721
- const groupByField = strOf(groupBy) ?? (isRec16(groupBy) ? strOf(groupBy.field) : void 0);
4986
+ const groupByField = strOf(groupBy) ?? (isRec18(groupBy) ? strOf(groupBy.field) : void 0);
4722
4987
  const objectName = strOf(values.get("objectName"));
4723
4988
  const known = objectName ? objectFields.get(objectName) : void 0;
4724
4989
  if (objectName && known) {
4990
+ const anchors = unprovisionedAnchors.get(objectName);
4725
4991
  const fieldRef = (name, prop) => {
4726
4992
  if (!name) return;
4727
4993
  if (name.includes(".")) return;
4728
- if (known.has(name) || SYSTEM_FIELDS.has(name)) return;
4994
+ if (known.has(name) || SYSTEM_FIELDS.has(name)) {
4995
+ if (anchors?.has(name)) {
4996
+ push2(
4997
+ "warning",
4998
+ REACT_CHART_FIELD_UNPROVISIONED,
4999
+ `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.`,
5000
+ unprovisionedAnchorHint(objectName, name)
5001
+ );
5002
+ }
5003
+ return;
5004
+ }
4729
5005
  push2(
4730
5006
  "error",
4731
5007
  REACT_CHART_FIELD_UNKNOWN,
@@ -4751,18 +5027,18 @@ function checkObjectChart(attrs, objectFields, findings) {
4751
5027
  );
4752
5028
  };
4753
5029
  const xAxisRaw = values.get("xAxis");
4754
- const categoryAxis = strOf(values.get("xAxisKey")) ?? strOf(xAxisRaw) ?? (isRec16(xAxisRaw) ? strOf(xAxisRaw.field) : void 0);
5030
+ const categoryAxis = strOf(values.get("xAxisKey")) ?? strOf(xAxisRaw) ?? (isRec18(xAxisRaw) ? strOf(xAxisRaw.field) : void 0);
4755
5031
  const categoryProp = values.has("xAxisKey") ? "xAxisKey" : "xAxis.field";
4756
5032
  axisRef(categoryAxis, categoryProp);
4757
5033
  const yAxisRaw = values.get("yAxis");
4758
5034
  const yAxisList = Array.isArray(yAxisRaw) ? yAxisRaw : yAxisRaw !== void 0 ? [yAxisRaw] : [];
4759
5035
  for (const a of yAxisList) {
4760
- axisRef(strOf(a) ?? (isRec16(a) ? strOf(a.field) : void 0), "yAxis[].field");
5036
+ axisRef(strOf(a) ?? (isRec18(a) ? strOf(a.field) : void 0), "yAxis[].field");
4761
5037
  }
4762
5038
  const series = values.get("series");
4763
5039
  if (Array.isArray(series)) {
4764
5040
  for (const s of series) {
4765
- if (!isRec16(s)) continue;
5041
+ if (!isRec18(s)) continue;
4766
5042
  const dataKey = strOf(s.dataKey);
4767
5043
  axisRef(dataKey ?? strOf(s.name), dataKey ? "series[].dataKey" : "series[].name");
4768
5044
  }
@@ -4818,7 +5094,7 @@ function subformFieldRefs(value, basePath) {
4818
5094
  if (!Array.isArray(value)) return { child, parent };
4819
5095
  for (let i = 0; i < value.length; i++) {
4820
5096
  const sub = value[i];
4821
- if (!isRec16(sub)) continue;
5097
+ if (!isRec18(sub)) continue;
4822
5098
  const at = (key) => `${basePath}[${i}].${key}`;
4823
5099
  child.push({
4824
5100
  objectName: strOf(sub.childObject),
@@ -4863,20 +5139,20 @@ function reactFieldRefs(spec, values, basePath) {
4863
5139
  }
4864
5140
  for (const key of spec.nestedFields ?? []) {
4865
5141
  const v = readable(key);
4866
- if (isRec16(v)) own.push(...fieldRefsFrom(v.fields, at(`${key}.fields`)));
5142
+ if (isRec18(v)) own.push(...fieldRefsFrom(v.fields, at(`${key}.fields`)));
4867
5143
  }
4868
5144
  for (const key of spec.sections ?? []) {
4869
5145
  const v = readable(key);
4870
5146
  if (!Array.isArray(v)) continue;
4871
5147
  for (let i = 0; i < v.length; i++) {
4872
5148
  const section = v[i];
4873
- if (!isRec16(section)) continue;
5149
+ if (!isRec18(section)) continue;
4874
5150
  own.push(...fieldRefsFrom(section.fields, at(`${key}[${i}].fields`)));
4875
5151
  }
4876
5152
  }
4877
5153
  for (const key of spec.keyedByField ?? []) {
4878
5154
  const v = readable(key);
4879
- if (!isRec16(v)) continue;
5155
+ if (!isRec18(v)) continue;
4880
5156
  for (const k of Object.keys(v)) own.push({ name: k, path: at(`${key}.${k}`) });
4881
5157
  }
4882
5158
  for (const key of spec.filterArrays ?? []) {
@@ -4884,22 +5160,30 @@ function reactFieldRefs(spec, values, basePath) {
4884
5160
  }
4885
5161
  return { own, queried };
4886
5162
  }
4887
- function checkBlockFieldProps(tag, values, objectFields, where, path) {
5163
+ function checkBlockFieldProps(tag, values, objectFields, where, path, unprovisionedAnchors) {
4888
5164
  const objectName = strOf(values.get("objectName"));
4889
5165
  const out = [];
4890
5166
  const spec = REACT_FIELD_SPECS[tag];
4891
5167
  if (spec) {
4892
5168
  const { own, queried } = reactFieldRefs(spec, values, path);
4893
- out.push(...checkFieldRefs(own, objectName, objectFields, where));
4894
- out.push(...checkFieldRefs(queried, objectName, objectFields, where, "queried"));
5169
+ out.push(
5170
+ ...checkFieldRefs(own, objectName, objectFields, where, "skipped", unprovisionedAnchors)
5171
+ );
5172
+ out.push(
5173
+ ...checkFieldRefs(queried, objectName, objectFields, where, "queried", unprovisionedAnchors)
5174
+ );
4895
5175
  }
4896
5176
  if (tag === "ObjectForm") {
4897
5177
  const raw = values.get("subforms");
4898
5178
  const subs = subformFieldRefs(raw === NOT_STATIC ? void 0 : raw, `${path}${PATH_SEP}subforms`);
4899
5179
  for (const sub of subs.child) {
4900
- out.push(...checkFieldRefs(sub.refs, sub.objectName, objectFields, where));
5180
+ out.push(
5181
+ ...checkFieldRefs(sub.refs, sub.objectName, objectFields, where, "skipped", unprovisionedAnchors)
5182
+ );
4901
5183
  }
4902
- out.push(...checkFieldRefs(subs.parent, objectName, objectFields, where));
5184
+ out.push(
5185
+ ...checkFieldRefs(subs.parent, objectName, objectFields, where, "skipped", unprovisionedAnchors)
5186
+ );
4903
5187
  }
4904
5188
  const schemaType = tag === "Block" ? strOf(values.get("type")) : SCHEMA_TYPE_BY_TAG.get(tag);
4905
5189
  if (schemaType && COMPONENT_FIELD_SPECS[schemaType]) {
@@ -4908,7 +5192,9 @@ function checkBlockFieldProps(tag, values, objectFields, where, path) {
4908
5192
  componentFieldRefs(schemaType, readableProps(values), path, PATH_SEP) ?? [],
4909
5193
  objectName,
4910
5194
  objectFields,
4911
- where
5195
+ where,
5196
+ "skipped",
5197
+ unprovisionedAnchors
4912
5198
  )
4913
5199
  );
4914
5200
  }
@@ -4940,8 +5226,9 @@ function localComponentNames(tsc, sf) {
4940
5226
  function validateReactPageProps(stack) {
4941
5227
  const findings = [];
4942
5228
  const objectFields = indexObjectFields(stack);
5229
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
4943
5230
  const searchTargets = indexObjectSearchTargets(stack);
4944
- const pages = asArray23(stack.pages);
5231
+ const pages = asArray24(stack.pages);
4945
5232
  for (let p = 0; p < pages.length; p++) {
4946
5233
  const page = pages[p];
4947
5234
  if (!page || page.kind !== "react") continue;
@@ -5022,7 +5309,7 @@ function validateReactPageProps(stack) {
5022
5309
  }
5023
5310
  }
5024
5311
  if (tag === "ObjectChart" && !hasSpread) {
5025
- checkObjectChart({ values, where, path }, objectFields, findings);
5312
+ checkObjectChart({ values, where, path }, objectFields, findings, unprovisionedAnchors);
5026
5313
  }
5027
5314
  if (tag === "ListView" && !hasSpread) {
5028
5315
  findings.push(
@@ -5038,7 +5325,7 @@ function validateReactPageProps(stack) {
5038
5325
  }
5039
5326
  if (!hasSpread) {
5040
5327
  findings.push(
5041
- ...checkBlockFieldProps(tag, values, objectFields, where, path)
5328
+ ...checkBlockFieldProps(tag, values, objectFields, where, path, unprovisionedAnchors)
5042
5329
  );
5043
5330
  }
5044
5331
  }
@@ -5065,6 +5352,15 @@ var REFERENCE_INTEGRITY_RULES = [
5065
5352
  // `action` is deliberately absent (validateActionNameRefs owns it) and so is
5066
5353
  // `component` (an unregistered ref renders a named diagnostic, not silence).
5067
5354
  { name: "validateNavTargetRefs", run: validateNavTargetRefs },
5355
+ // [#7912] The THIRD question about a nav entry, after "does the target
5356
+ // resolve?" (above) and "is it granted?" (`validateNavAccess`): can the
5357
+ // destination serve at all? An object's own `enable` block can make its list
5358
+ // answer 404/405 for every persona, and no gate authorable on the entry
5359
+ // expresses that — which is how #7544's dead row survived review for a year.
5360
+ // The server now prunes such an entry from the `/meta` payload; the
5361
+ // maintainer ruling of 2026-08-12 makes THIS the mandatory companion, so the
5362
+ // prune is never silent to the author who wrote the row.
5363
+ { name: "validateNavObjectServability", run: validateNavObjectServability },
5068
5364
  { name: "validateTranslationReferences", run: validateTranslationReferences },
5069
5365
  // The same family from the other end (#5417). Its sibling above asks "does
5070
5366
  // this bundle key resolve?"; this one asks "is there a key at all?" — a form
@@ -5163,13 +5459,13 @@ import { ComponentPropsMap } from "@objectstack/spec/ui";
5163
5459
  import { lintUnknownKeysAgainstSchema } from "@objectstack/spec";
5164
5460
  var COMPONENT_PROPS_UNKNOWN_KEY = "component-props-unknown-key";
5165
5461
  var COMPONENT_PROPS_INVALID = "component-props-invalid";
5166
- function isRec17(v) {
5462
+ function isRec19(v) {
5167
5463
  return !!v && typeof v === "object" && !Array.isArray(v);
5168
5464
  }
5169
- function strName17(v) {
5465
+ function strName18(v) {
5170
5466
  return typeof v === "string" && v.length > 0 ? v : void 0;
5171
5467
  }
5172
- function asArray24(v) {
5468
+ function asArray25(v) {
5173
5469
  if (Array.isArray(v)) return v;
5174
5470
  if (v && typeof v === "object") {
5175
5471
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -5180,23 +5476,36 @@ var PROPS_SCHEMAS = ComponentPropsMap;
5180
5476
  var DATASOURCE_SUPPLIED_PROP = "object";
5181
5477
  function suppliedByDataSource(issue, component) {
5182
5478
  if (issue.path.length !== 1 || issue.path[0] !== DATASOURCE_SUPPLIED_PROP) return false;
5183
- const dataSource = isRec17(component.dataSource) ? component.dataSource : void 0;
5184
- return strName17(dataSource?.object) !== void 0;
5479
+ const dataSource = isRec19(component.dataSource) ? component.dataSource : void 0;
5480
+ return strName18(dataSource?.object) !== void 0;
5481
+ }
5482
+ function unrecognizedKeysFromUnionArm(issue) {
5483
+ if (issue.code !== "invalid_union") return void 0;
5484
+ const arms = issue.errors;
5485
+ if (!arms || arms.length === 0) return void 0;
5486
+ let found;
5487
+ for (const arm of arms) {
5488
+ const keyIssues = arm.filter((inner) => inner.code === "unrecognized_keys");
5489
+ if (keyIssues.length === 0) continue;
5490
+ if (keyIssues.length !== arm.length || found) return void 0;
5491
+ found = keyIssues[0];
5492
+ }
5493
+ return found;
5185
5494
  }
5186
5495
  function validateComponentProps(stack) {
5187
5496
  const findings = [];
5188
- if (!isRec17(stack)) return findings;
5189
- const pages = asArray24(stack.pages);
5497
+ if (!isRec19(stack)) return findings;
5498
+ const pages = asArray25(stack.pages);
5190
5499
  for (let pi = 0; pi < pages.length; pi++) {
5191
5500
  const page = pages[pi];
5192
- if (!isRec17(page)) continue;
5193
- const pageName = strName17(page.name) ?? `#${pi}`;
5501
+ if (!isRec19(page)) continue;
5502
+ const pageName = strName18(page.name) ?? `#${pi}`;
5194
5503
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
5195
- const type = strName17(component.type);
5504
+ const type = strName18(component.type);
5196
5505
  if (!type) continue;
5197
5506
  const schema = PROPS_SCHEMAS[type];
5198
5507
  if (!schema) continue;
5199
- const props = isRec17(component.properties) ? component.properties : void 0;
5508
+ const props = isRec19(component.properties) ? component.properties : void 0;
5200
5509
  if (!props) continue;
5201
5510
  const where = `page "${pageName}" \xB7 ${type}`;
5202
5511
  const base = `${path}.properties`;
@@ -5215,6 +5524,20 @@ function validateComponentProps(stack) {
5215
5524
  for (const issue of parsed.error?.issues ?? []) {
5216
5525
  if (suppliedByDataSource(issue, component)) continue;
5217
5526
  const at = issue.path.length ? `${base}.${issue.path.join(".")}` : base;
5527
+ const armIssue = unrecognizedKeysFromUnionArm(issue);
5528
+ if (armIssue) {
5529
+ for (const key of armIssue.keys ?? []) {
5530
+ findings.push({
5531
+ severity: "warning",
5532
+ rule: COMPONENT_PROPS_UNKNOWN_KEY,
5533
+ where,
5534
+ path: `${at}.${key}`,
5535
+ message: `\`${key}\` is not a prop \`${type}\` declares (ComponentPropsMap, @objectstack/spec/ui): ${armIssue.message}`,
5536
+ hint: `Remove \`${key}\`, or declare it on \`${type}\`'s props schema if the component honours it.`
5537
+ });
5538
+ }
5539
+ continue;
5540
+ }
5218
5541
  if (issue.code === "unrecognized_keys") {
5219
5542
  for (const key of issue.keys ?? []) {
5220
5543
  findings.push({
@@ -5445,7 +5768,7 @@ function looksLikeTailwind(className) {
5445
5768
  return false;
5446
5769
  });
5447
5770
  }
5448
- function asArray25(v) {
5771
+ function asArray26(v) {
5449
5772
  if (Array.isArray(v)) return v;
5450
5773
  if (v && typeof v === "object") {
5451
5774
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -5538,13 +5861,13 @@ function checkNode(node, pageName, path, findings) {
5538
5861
  }
5539
5862
  function validateResponsiveStyles(stack) {
5540
5863
  const findings = [];
5541
- const pages = asArray25(stack.pages);
5864
+ const pages = asArray26(stack.pages);
5542
5865
  for (let p = 0; p < pages.length; p++) {
5543
5866
  const page = pages[p];
5544
5867
  const pageName = typeof page.name === "string" ? page.name : `pages[${p}]`;
5545
- const regions = asArray25(page.regions);
5868
+ const regions = asArray26(page.regions);
5546
5869
  for (let r = 0; r < regions.length; r++) {
5547
- const components = asArray25(regions[r].components);
5870
+ const components = asArray26(regions[r].components);
5548
5871
  for (let c = 0; c < components.length; c++) {
5549
5872
  checkNode(components[c], pageName, `pages[${p}].regions[${r}].components[${c}]`, findings);
5550
5873
  }
@@ -5555,10 +5878,10 @@ function validateResponsiveStyles(stack) {
5555
5878
 
5556
5879
  // src/validate-jsx-pages.ts
5557
5880
  import { parseJsx, compile } from "@objectstack/sdui-parser";
5558
- var asArray26 = (v) => Array.isArray(v) ? v : [];
5881
+ var asArray27 = (v) => Array.isArray(v) ? v : [];
5559
5882
  function validateJsxPages(stack, opts = {}) {
5560
5883
  const findings = [];
5561
- const pages = asArray26(stack.pages);
5884
+ const pages = asArray27(stack.pages);
5562
5885
  for (let p = 0; p < pages.length; p++) {
5563
5886
  const page = pages[p];
5564
5887
  if (!page || page.kind !== "html" && page.kind !== "jsx") continue;
@@ -5605,10 +5928,10 @@ function loadSucraseTransform() {
5605
5928
  }
5606
5929
  return cachedTransform;
5607
5930
  }
5608
- var asArray27 = (v) => Array.isArray(v) ? v : [];
5931
+ var asArray28 = (v) => Array.isArray(v) ? v : [];
5609
5932
  function validateReactPages(stack) {
5610
5933
  const findings = [];
5611
- const pages = asArray27(stack.pages);
5934
+ const pages = asArray28(stack.pages);
5612
5935
  for (let p = 0; p < pages.length; p++) {
5613
5936
  const page = pages[p];
5614
5937
  if (!page || page.kind !== "react") continue;
@@ -5645,11 +5968,11 @@ function validateReactPages(stack) {
5645
5968
 
5646
5969
  // src/validate-page-source-styling.ts
5647
5970
  var PAGE_SOURCE_CLASSNAME = "page-source-className-tailwind";
5648
- var asArray28 = (v) => Array.isArray(v) ? v : [];
5971
+ var asArray29 = (v) => Array.isArray(v) ? v : [];
5649
5972
  var CLASSNAME_ATTR = /\bclassName\s*=\s*["'{]/g;
5650
5973
  function validatePageSourceStyling(stack) {
5651
5974
  const findings = [];
5652
- const pages = asArray28(stack.pages);
5975
+ const pages = asArray29(stack.pages);
5653
5976
  for (let p = 0; p < pages.length; p++) {
5654
5977
  const page = pages[p];
5655
5978
  if (!page) continue;
@@ -5677,7 +6000,7 @@ function validatePageSourceStyling(stack) {
5677
6000
  // src/validate-capability-references.ts
5678
6001
  import { PLATFORM_CAPABILITY_NAMES } from "@objectstack/spec/security";
5679
6002
  var CAPABILITY_REFERENCE_UNKNOWN = "capability-reference-unknown";
5680
- function asArray29(v) {
6003
+ function asArray30(v) {
5681
6004
  if (Array.isArray(v)) return v;
5682
6005
  if (v && typeof v === "object") {
5683
6006
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -5702,13 +6025,13 @@ function validateCapabilityReferences(stack) {
5702
6025
  const findings = [];
5703
6026
  if (!stack || typeof stack !== "object") return findings;
5704
6027
  const known = new Set(PLATFORM_CAPABILITY_NAMES);
5705
- for (const cap of asArray29(stack.capabilities)) {
6028
+ for (const cap of asArray30(stack.capabilities)) {
5706
6029
  if (typeof cap.name === "string" && cap.name.length > 0) known.add(cap.name);
5707
6030
  }
5708
- for (const ps of asArray29(stack.permissions)) {
6031
+ for (const ps of asArray30(stack.permissions)) {
5709
6032
  for (const cap of asCapArray(ps.systemPermissions)) known.add(cap);
5710
6033
  }
5711
- for (const seed of asArray29(stack.data)) {
6034
+ for (const seed of asArray30(stack.data)) {
5712
6035
  if (seed.object !== "sys_capability") continue;
5713
6036
  for (const rec of Array.isArray(seed.records) ? seed.records : []) {
5714
6037
  const name = rec?.name;
@@ -5727,7 +6050,7 @@ function validateCapabilityReferences(stack) {
5727
6050
  hint
5728
6051
  });
5729
6052
  };
5730
- const objects = asArray29(stack.objects);
6053
+ const objects = asArray30(stack.objects);
5731
6054
  for (let i = 0; i < objects.length; i++) {
5732
6055
  const obj = objects[i];
5733
6056
  if (!obj || typeof obj !== "object") continue;
@@ -5736,27 +6059,27 @@ function validateCapabilityReferences(stack) {
5736
6059
  for (const { cap, key } of flattenObjectRequired(obj.requiredPermissions)) {
5737
6060
  flag(cap, `object "${objName}"`, `${objPath}.requiredPermissions${key ? `.${key}` : ""}`);
5738
6061
  }
5739
- const fields = asArray29(obj.fields);
6062
+ const fields = asArray30(obj.fields);
5740
6063
  for (const f of fields) {
5741
6064
  const fname = typeof f.name === "string" ? f.name : "(field)";
5742
6065
  for (const cap of asCapArray(f.requiredPermissions)) {
5743
6066
  flag(cap, `field "${objName}.${fname}"`, `${objPath}.fields.${fname}.requiredPermissions`);
5744
6067
  }
5745
6068
  }
5746
- for (const [ai, action] of asArray29(obj.actions).entries()) {
6069
+ for (const [ai, action] of asArray30(obj.actions).entries()) {
5747
6070
  const aName = typeof action.name === "string" ? action.name : `(action ${ai})`;
5748
6071
  for (const cap of asCapArray(action.requiredPermissions)) {
5749
6072
  flag(cap, `action "${objName}.${aName}"`, `${objPath}.actions[${ai}].requiredPermissions`);
5750
6073
  }
5751
6074
  }
5752
6075
  }
5753
- for (const [i, action] of asArray29(stack.actions).entries()) {
6076
+ for (const [i, action] of asArray30(stack.actions).entries()) {
5754
6077
  const aName = typeof action.name === "string" ? action.name : `(action ${i})`;
5755
6078
  for (const cap of asCapArray(action.requiredPermissions)) {
5756
6079
  flag(cap, `action "${aName}"`, `actions[${i}].requiredPermissions`);
5757
6080
  }
5758
6081
  }
5759
- const apps = asArray29(stack.apps);
6082
+ const apps = asArray30(stack.apps);
5760
6083
  for (let i = 0; i < apps.length; i++) {
5761
6084
  const app = apps[i];
5762
6085
  if (!app || typeof app !== "object") continue;
@@ -5791,7 +6114,7 @@ var FLOW_TIME_RELATIVE_DESCRIPTOR_INVALID = "flow-time-relative-descriptor-inval
5791
6114
  var FLOW_TIME_RELATIVE_DESCRIPTOR_UNROUTABLE = "flow-time-relative-descriptor-unroutable";
5792
6115
  var FLOW_TRIGGER_UNROUTABLE = "flow-trigger-unroutable";
5793
6116
  var VALID_RECORD_TRIGGER = /^record-(?:before|after)-(?:create|insert|update|delete|write)$/;
5794
- function asArray30(v) {
6117
+ function asArray31(v) {
5795
6118
  if (Array.isArray(v)) return v;
5796
6119
  if (v && typeof v === "object") {
5797
6120
  return Object.entries(v).map(([name, def]) => ({
@@ -5819,10 +6142,10 @@ function startNodeOf(flow) {
5819
6142
  }
5820
6143
  function validateFlowTriggerReadiness(stack) {
5821
6144
  const findings = [];
5822
- const flows = asArray30(stack.flows);
6145
+ const flows = asArray31(stack.flows);
5823
6146
  if (flows.length === 0) return findings;
5824
6147
  const objectNames = new Set(
5825
- asArray30(stack.objects).map((o) => typeof o.name === "string" ? o.name : void 0).filter((n) => !!n)
6148
+ asArray31(stack.objects).map((o) => typeof o.name === "string" ? o.name : void 0).filter((n) => !!n)
5826
6149
  );
5827
6150
  flows.forEach((flow, flowIndex) => {
5828
6151
  const flowName = typeof flow.name === "string" ? flow.name : `#${flowIndex}`;
@@ -5995,7 +6318,7 @@ var TYPE_FIX = {
5995
6318
  business_unit: "department",
5996
6319
  bu: "department"
5997
6320
  };
5998
- function asArray31(v) {
6321
+ function asArray32(v) {
5999
6322
  if (Array.isArray(v)) return v;
6000
6323
  if (v && typeof v === "object") {
6001
6324
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -6005,7 +6328,7 @@ function asArray31(v) {
6005
6328
  function validateApprovalApprovers(stack) {
6006
6329
  const findings = [];
6007
6330
  if (!stack || typeof stack !== "object") return findings;
6008
- const flows = asArray31(stack.flows);
6331
+ const flows = asArray32(stack.flows);
6009
6332
  const validTypes = new Set(ApproverType.options);
6010
6333
  for (let fi = 0; fi < flows.length; fi++) {
6011
6334
  const flow = flows[fi];
@@ -6188,7 +6511,7 @@ function validateApprovalApprovers(stack) {
6188
6511
  import { objectTitleCompleteness } from "@objectstack/spec/data";
6189
6512
  var TITLE_FORMAT_RETIRED = "title-format-retired";
6190
6513
  var TITLE_UNRESOLVABLE = "title-unresolvable";
6191
- function asArray32(v) {
6514
+ function asArray33(v) {
6192
6515
  if (Array.isArray(v)) return v;
6193
6516
  if (v && typeof v === "object") {
6194
6517
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -6197,7 +6520,7 @@ function asArray32(v) {
6197
6520
  }
6198
6521
  function validateRecordTitle(stack) {
6199
6522
  const findings = [];
6200
- const objects = asArray32(stack.objects);
6523
+ const objects = asArray33(stack.objects);
6201
6524
  for (let i = 0; i < objects.length; i++) {
6202
6525
  const obj = objects[i];
6203
6526
  const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
@@ -6233,7 +6556,8 @@ var FIELD_GROUP_UNDECLARED = "field-group-undeclared";
6233
6556
  var FIELD_GROUP_EMPTY = "field-group-empty";
6234
6557
  var FIELD_GROUP_SHADOWED = "field-group-shadowed";
6235
6558
  var SEMANTIC_ROLE_FIELD_UNKNOWN = "semantic-role-field-unknown";
6236
- function asArray33(v) {
6559
+ var SEMANTIC_ROLE_FIELD_UNPROVISIONED = "semantic-role-field-unprovisioned";
6560
+ function asArray34(v) {
6237
6561
  if (Array.isArray(v)) return v;
6238
6562
  if (v && typeof v === "object") {
6239
6563
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -6242,7 +6566,7 @@ function asArray33(v) {
6242
6566
  }
6243
6567
  function validateSemanticRoles(stack) {
6244
6568
  const findings = [];
6245
- const objects = asArray33(stack.objects);
6569
+ const objects = asArray34(stack.objects);
6246
6570
  for (let i = 0; i < objects.length; i++) {
6247
6571
  const obj = objects[i];
6248
6572
  if (!obj || typeof obj !== "object") continue;
@@ -6251,6 +6575,15 @@ function validateSemanticRoles(stack) {
6251
6575
  const path = `objects[${i}]`;
6252
6576
  const fields = obj.fields && typeof obj.fields === "object" && !Array.isArray(obj.fields) ? obj.fields : {};
6253
6577
  const fieldNames = /* @__PURE__ */ new Set([...Object.keys(fields), ...injectedColumnsFor(obj)]);
6578
+ const unprovisioned = unprovisionedInjectedColumnsFor(obj);
6579
+ const unprovisionedPointer = (slot, entry) => ({
6580
+ severity: "warning",
6581
+ rule: SEMANTIC_ROLE_FIELD_UNPROVISIONED,
6582
+ where,
6583
+ path: `${path}.${slot}`,
6584
+ 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.`,
6585
+ 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.`
6586
+ });
6254
6587
  const declaredGroups = new Set(
6255
6588
  (Array.isArray(obj.fieldGroups) ? obj.fieldGroups : []).filter((g) => !!g && typeof g === "object").map((g) => g.key).filter((k) => typeof k === "string" && k.length > 0)
6256
6589
  );
@@ -6292,10 +6625,16 @@ function validateSemanticRoles(stack) {
6292
6625
  message: `${objName}: stageField "${stage}" is not a field on this object \u2014 consumers fall back to heuristic stage detection`,
6293
6626
  hint: `Point stageField at an existing select/status field, or set stageField: false to declare the object has no linear lifecycle.`
6294
6627
  });
6628
+ } else if (typeof stage === "string" && unprovisioned.has(stage)) {
6629
+ findings.push(unprovisionedPointer("stageField", stage));
6295
6630
  }
6296
6631
  const highlights = Array.isArray(obj.highlightFields) ? obj.highlightFields : Array.isArray(obj.compactLayout) ? obj.compactLayout : [];
6297
6632
  for (const entry of highlights) {
6298
- if (typeof entry !== "string" || entry.length === 0 || fieldNames.has(entry)) continue;
6633
+ if (typeof entry !== "string" || entry.length === 0) continue;
6634
+ if (fieldNames.has(entry)) {
6635
+ if (unprovisioned.has(entry)) findings.push(unprovisionedPointer("highlightFields", entry));
6636
+ continue;
6637
+ }
6299
6638
  findings.push({
6300
6639
  severity: "warning",
6301
6640
  rule: SEMANTIC_ROLE_FIELD_UNKNOWN,
@@ -6337,17 +6676,17 @@ function validateSemanticRoles(stack) {
6337
6676
  // src/validate-form-layout.ts
6338
6677
  var FORM_FIELD_UNKNOWN = "form-field-unknown";
6339
6678
  var FORM_COLSPAN_ABSOLUTE = "absolute-colspan-discouraged";
6340
- function asArray34(v) {
6679
+ function asArray35(v) {
6341
6680
  if (Array.isArray(v)) return v;
6342
6681
  if (v && typeof v === "object") {
6343
6682
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
6344
6683
  }
6345
6684
  return [];
6346
6685
  }
6347
- function isRec18(v) {
6686
+ function isRec20(v) {
6348
6687
  return !!v && typeof v === "object" && !Array.isArray(v);
6349
6688
  }
6350
- function strName18(v) {
6689
+ function strName19(v) {
6351
6690
  return typeof v === "string" && v.length > 0 ? v : void 0;
6352
6691
  }
6353
6692
  function fieldNameOf(entry) {
@@ -6361,14 +6700,14 @@ function fieldNameOf(entry) {
6361
6700
  function validateFormLayout(stack) {
6362
6701
  const findings = [];
6363
6702
  const objectFields = /* @__PURE__ */ new Map();
6364
- for (const obj of asArray34(stack.objects)) {
6703
+ for (const obj of asArray35(stack.objects)) {
6365
6704
  const name = typeof obj.name === "string" ? obj.name : void 0;
6366
6705
  if (!name) continue;
6367
6706
  const fields = obj.fields && typeof obj.fields === "object" && !Array.isArray(obj.fields) ? Object.keys(obj.fields) : [];
6368
6707
  objectFields.set(name, new Set(fields));
6369
6708
  }
6370
6709
  for (const { rec: view, path: viewPath } of collectionEntries(stack.views, "views")) {
6371
- const viewName = strName18(view.name) ?? strName18(view.object) ?? viewPath;
6710
+ const viewName = strName19(view.name) ?? strName19(view.object) ?? viewPath;
6372
6711
  const containerObject = viewObjectName(view);
6373
6712
  for (const site of formViewSites(view, viewPath)) {
6374
6713
  const objName = viewObjectName(site.view) ?? containerObject;
@@ -6378,7 +6717,7 @@ function validateFormLayout(stack) {
6378
6717
  const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
6379
6718
  for (let s = 0; s < sections.length; s++) {
6380
6719
  const sec = sections[s];
6381
- const secFields = isRec18(sec) && Array.isArray(sec.fields) ? sec.fields : [];
6720
+ const secFields = isRec20(sec) && Array.isArray(sec.fields) ? sec.fields : [];
6382
6721
  for (let f = 0; f < secFields.length; f++) {
6383
6722
  const entry = secFields[f];
6384
6723
  const fname = fieldNameOf(entry);
@@ -6393,7 +6732,7 @@ function validateFormLayout(stack) {
6393
6732
  hint: `Fix the field name, or add "${fname}" to ${objName}. Section field references must match the object's field names exactly.`
6394
6733
  });
6395
6734
  }
6396
- const colSpan = isRec18(entry) ? entry.colSpan : void 0;
6735
+ const colSpan = isRec20(entry) ? entry.colSpan : void 0;
6397
6736
  if (colSpan != null) {
6398
6737
  findings.push({
6399
6738
  severity: "warning",
@@ -6506,9 +6845,67 @@ function validateSeedStateMachine(stack) {
6506
6845
  import {
6507
6846
  collectCelRootIdentifiers as collectCelRootIdentifiers3,
6508
6847
  firstUndeclaredReference,
6509
- parseCelToAst as parseCelToAst2,
6848
+ parseCelToAst as parseCelToAst3,
6510
6849
  parseCelToAstWithReason
6511
6850
  } from "@objectstack/formula";
6851
+
6852
+ // src/predicate-rhs-position.ts
6853
+ function isNode2(v) {
6854
+ return !!v && typeof v === "object" && typeof v.op === "string";
6855
+ }
6856
+ var EQUALITY_OPS = /* @__PURE__ */ new Set(["==", "!="]);
6857
+ var COMPREHENSION_MACROS = /* @__PURE__ */ new Set(["all", "exists", "exists_one", "map", "filter"]);
6858
+ function bareId(node) {
6859
+ if (!isNode2(node)) return null;
6860
+ return node.op === "id" && typeof node.args === "string" ? node.args : null;
6861
+ }
6862
+ function bareRhsOnlyIdentifiers(ast) {
6863
+ const rhs = /* @__PURE__ */ new Set();
6864
+ const elsewhere = /* @__PURE__ */ new Set();
6865
+ const walk = (node, suppressible) => {
6866
+ if (Array.isArray(node)) {
6867
+ for (const child of node) walk(child, suppressible);
6868
+ return;
6869
+ }
6870
+ if (!isNode2(node)) return;
6871
+ const args = node.args;
6872
+ if (node.op === "rcall" && Array.isArray(args) && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
6873
+ walk(args[1], suppressible);
6874
+ walk(args[2], false);
6875
+ return;
6876
+ }
6877
+ if (typeof node.op === "string" && EQUALITY_OPS.has(node.op) && Array.isArray(args) && args.length === 2) {
6878
+ const right = suppressible ? bareId(args[1]) : null;
6879
+ walk(args[0], suppressible);
6880
+ if (right !== null) {
6881
+ rhs.add(right);
6882
+ return;
6883
+ }
6884
+ walk(args[1], suppressible);
6885
+ return;
6886
+ }
6887
+ const name = bareId(node);
6888
+ if (name !== null) {
6889
+ elsewhere.add(name);
6890
+ return;
6891
+ }
6892
+ walk(args, suppressible);
6893
+ };
6894
+ walk(ast, true);
6895
+ for (const name of elsewhere) rhs.delete(name);
6896
+ return rhs;
6897
+ }
6898
+ function isRec21(v) {
6899
+ return !!v && typeof v === "object" && !Array.isArray(v);
6900
+ }
6901
+ function schemaIdOf(view) {
6902
+ const data = view.data;
6903
+ if (!isRec21(data)) return void 0;
6904
+ if (data.provider !== "schema") return void 0;
6905
+ return typeof data.schemaId === "string" ? data.schemaId : void 0;
6906
+ }
6907
+
6908
+ // src/validate-visibility-predicates.ts
6512
6909
  var VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
6513
6910
  var VISIBILITY_BARE_IDENTIFIER = "visibility-bare-identifier";
6514
6911
  var VISIBILITY_PREDICATE_SYNTAX = "visibility-predicate-syntax";
@@ -6556,7 +6953,7 @@ function boundName(overrun) {
6556
6953
  return overrun.limit && overrun.limitValue !== null ? `the \`${overrun.limit}\` budget (platform limit ${overrun.limitValue})` : "one of the platform's parse budgets";
6557
6954
  }
6558
6955
  var VIEW_PAGE_EXTRA_ROOTS = ["current_user", "page"];
6559
- function isNode2(v) {
6956
+ function isNode3(v) {
6560
6957
  return !!v && typeof v === "object" && typeof v.op === "string";
6561
6958
  }
6562
6959
  function namespaceRoots(node, out) {
@@ -6564,22 +6961,27 @@ function namespaceRoots(node, out) {
6564
6961
  for (const child of node) namespaceRoots(child, out);
6565
6962
  return;
6566
6963
  }
6567
- if (!isNode2(node)) return;
6964
+ if (!isNode3(node)) return;
6568
6965
  const args = node.args;
6569
6966
  if (Array.isArray(args)) {
6570
6967
  const receiver = node.op === "rcall" ? args[1] : args[0];
6571
- if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode2(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
6968
+ if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode3(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
6572
6969
  out.add(receiver.args);
6573
6970
  }
6574
6971
  }
6575
6972
  namespaceRoots(args, out);
6576
6973
  }
6577
- function firstBareIdentifier(source) {
6578
- const ast = parseCelToAst2(source);
6974
+ function firstBareIdentifier(source, literalRhs) {
6975
+ const ast = parseCelToAst3(source);
6579
6976
  if (!ast) return null;
6580
6977
  const rooted = /* @__PURE__ */ new Set();
6581
6978
  namespaceRoots(ast, rooted);
6582
- return firstUndeclaredReference(source, [...VIEW_PAGE_EXTRA_ROOTS, ...rooted]);
6979
+ const literalSlot = literalRhs ? bareRhsOnlyIdentifiers(ast) : [];
6980
+ return firstUndeclaredReference(source, [
6981
+ ...VIEW_PAGE_EXTRA_ROOTS,
6982
+ ...rooted,
6983
+ ...literalSlot
6984
+ ]);
6583
6985
  }
6584
6986
  var CANONICAL_ROOT_BY_LAYER = {
6585
6987
  runtime: "record",
@@ -6588,16 +6990,16 @@ var CANONICAL_ROOT_BY_LAYER = {
6588
6990
  var MISLAYER_BY_LAYER = {
6589
6991
  runtime: {
6590
6992
  forbiddenRoot: "data",
6591
- message: "visibility predicate is rooted at `data.` \u2014 that is the metadata-editing-form root (a `*.form.ts` row under edit), not a runtime surface. A runtime view/page predicate that binds `data.` never matches and the element renders unconditionally (ADR-0089).",
6993
+ 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).",
6592
6994
  hint: "Runtime record surfaces bind `record` + `current_user` (pages also expose `page.<var>`). Use e.g. `record.status == 'open'` instead of `data.status == 'open'`."
6593
6995
  },
6594
6996
  metadata: {
6595
6997
  forbiddenRoot: "record",
6596
- message: "visibility predicate is rooted at `record.` \u2014 that is the runtime record-surface root (a `*.view.ts` / `*.page.ts` live record), not a metadata-editing form. A `*.form.ts` predicate that binds `record.` never matches and the element renders unconditionally (ADR-0089).",
6998
+ 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).",
6597
6999
  hint: "Metadata-editing forms bind `data` (the row under edit). Use e.g. `data.type == 'grid'` instead of `record.type == 'grid'`."
6598
7000
  }
6599
7001
  };
6600
- function checkElement(el, where, path, layer, findings) {
7002
+ function checkElement(el, where, path, layer, findings, literalRhs = false) {
6601
7003
  const raw = el[CANONICAL] ?? el.visibleOn ?? el.visibility;
6602
7004
  const source = predicateSource(raw);
6603
7005
  const rule = MISLAYER_BY_LAYER[layer];
@@ -6635,7 +7037,7 @@ function checkElement(el, where, path, layer, findings) {
6635
7037
  });
6636
7038
  }
6637
7039
  if (source && !refusal) {
6638
- const bare = firstBareIdentifier(source);
7040
+ const bare = firstBareIdentifier(source, literalRhs);
6639
7041
  if (bare) {
6640
7042
  const root = CANONICAL_ROOT_BY_LAYER[layer];
6641
7043
  findings.push({
@@ -6644,7 +7046,7 @@ function checkElement(el, where, path, layer, findings) {
6644
7046
  where,
6645
7047
  path,
6646
7048
  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).`,
6647
- 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 `*.form.ts` metadata-editing form binds the row under edit as `data`).")
7049
+ 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`).")
6648
7050
  });
6649
7051
  }
6650
7052
  }
@@ -6653,24 +7055,27 @@ function isFieldObject(entry) {
6653
7055
  return !!entry && typeof entry === "object" && !Array.isArray(entry);
6654
7056
  }
6655
7057
  function validateVisibilityPredicates(stack, opts = {}) {
6656
- const layer = opts.layer ?? "runtime";
7058
+ const declaredLayer = opts.layer ?? "runtime";
6657
7059
  const findings = [];
6658
7060
  for (const { rec: view, path: viewPath } of collectionEntries(stack.views, "views")) {
6659
7061
  const viewName = typeof view.name === "string" ? view.name : typeof view.object === "string" ? view.object : viewPath;
6660
7062
  for (const site of formViewSites(view, viewPath)) {
6661
7063
  const where = site.surface ? `view "${viewName}" \xB7 ${site.surface}` : `view "${viewName}"`;
7064
+ const schemaBound = schemaIdOf(site.view) !== void 0;
7065
+ const literalRhs = schemaBound;
7066
+ const layer = schemaBound ? "metadata" : declaredLayer;
6662
7067
  for (const bucket of ["sections", "groups"]) {
6663
7068
  const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
6664
7069
  for (let s = 0; s < sections.length; s++) {
6665
7070
  const sec = sections[s];
6666
7071
  if (!sec || typeof sec !== "object") continue;
6667
7072
  const secPath = `${site.path}.${bucket}[${s}]`;
6668
- checkElement(sec, where, secPath, layer, findings);
7073
+ checkElement(sec, where, secPath, layer, findings, literalRhs);
6669
7074
  const secFields = Array.isArray(sec.fields) ? sec.fields : [];
6670
7075
  for (let f = 0; f < secFields.length; f++) {
6671
7076
  const entry = secFields[f];
6672
7077
  if (isFieldObject(entry)) {
6673
- checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings);
7078
+ checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings, literalRhs);
6674
7079
  }
6675
7080
  }
6676
7081
  }
@@ -6681,21 +7086,21 @@ function validateVisibilityPredicates(stack, opts = {}) {
6681
7086
  const pageName = typeof page.name === "string" ? page.name : void 0;
6682
7087
  const where = `page "${pageName ?? pagePath}"`;
6683
7088
  for (const walked of walkPageComponents(page, pagePath)) {
6684
- checkElement(walked.component, where, walked.path, layer, findings);
7089
+ checkElement(walked.component, where, walked.path, declaredLayer, findings);
6685
7090
  }
6686
7091
  }
6687
7092
  return findings;
6688
7093
  }
6689
7094
 
6690
7095
  // src/validate-predicate-path-refs.ts
6691
- import { parseCelToAst as parseCelToAst3 } from "@objectstack/formula";
7096
+ import { parseCelToAst as parseCelToAst4 } from "@objectstack/formula";
6692
7097
  import { getMetadataTypeSchema } from "@objectstack/spec/kernel";
6693
7098
  import { findClosestMatches as findClosestMatches4, formatSuggestion as formatSuggestion4 } from "@objectstack/spec";
6694
7099
  var PREDICATE_PATH_UNRESOLVED = "predicate-path-unresolved";
6695
7100
  var PREDICATE_PATH_UNROOTED = "predicate-path-unrooted";
7101
+ var PREDICATE_RHS_PATH_SHAPED = "predicate-rhs-path-shaped";
6696
7102
  var PREDICATE_KEYS = ["visibleWhen", "visibleOn"];
6697
7103
  var ROOT = "data";
6698
- var COMPREHENSION_MACROS = /* @__PURE__ */ new Set(["all", "exists", "exists_one", "map", "filter"]);
6699
7104
  function defOf(schema) {
6700
7105
  if (!schema || typeof schema !== "object" && typeof schema !== "function") return void 0;
6701
7106
  const s = schema;
@@ -6788,11 +7193,11 @@ function stepInto(scope, segment) {
6788
7193
  if (!declared.includes(segment)) return { kind: "undeclared", declared };
6789
7194
  return { kind: "declared", next: propertyOf(u, segment) };
6790
7195
  }
6791
- function isNode3(v) {
7196
+ function isNode4(v) {
6792
7197
  return !!v && typeof v === "object" && typeof v.op === "string";
6793
7198
  }
6794
7199
  function memberChain(node) {
6795
- if (!isNode3(node)) return null;
7200
+ if (!isNode4(node)) return null;
6796
7201
  if (node.op === "id" && typeof node.args === "string") return [node.args];
6797
7202
  if (node.op === "." && Array.isArray(node.args) && typeof node.args[1] === "string") {
6798
7203
  const head = memberChain(node.args[0]);
@@ -6805,7 +7210,7 @@ function rootedPaths(node, out) {
6805
7210
  for (const child of node) rootedPaths(child, out);
6806
7211
  return;
6807
7212
  }
6808
- if (!isNode3(node)) return;
7213
+ if (!isNode4(node)) return;
6809
7214
  if (node.op === ".") {
6810
7215
  const chain = memberChain(node);
6811
7216
  if (chain && chain[0] === ROOT && chain.length > 1) {
@@ -6815,23 +7220,40 @@ function rootedPaths(node, out) {
6815
7220
  }
6816
7221
  rootedPaths(node.args, out);
6817
7222
  }
7223
+ var PATH_SHAPED_RHS = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/;
7224
+ function equalitySites(node, out) {
7225
+ if (Array.isArray(node)) {
7226
+ for (const child of node) equalitySites(child, out);
7227
+ return;
7228
+ }
7229
+ if (!isNode4(node)) return;
7230
+ const args = node.args;
7231
+ if (node.op === "rcall" && Array.isArray(args) && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
7232
+ equalitySites(args[1], out);
7233
+ return;
7234
+ }
7235
+ if (typeof node.op === "string" && EQUALITY_OPS.has(node.op) && Array.isArray(args) && args.length === 2) {
7236
+ out.push({ op: node.op, right: args[1] });
7237
+ }
7238
+ equalitySites(args, out);
7239
+ }
6818
7240
  function classifyIdentifiers(node, values, excluded) {
6819
7241
  if (Array.isArray(node)) {
6820
7242
  for (const child of node) classifyIdentifiers(child, values, excluded);
6821
7243
  return;
6822
7244
  }
6823
- if (!isNode3(node)) return;
7245
+ if (!isNode4(node)) return;
6824
7246
  const args = node.args;
6825
7247
  if (Array.isArray(args)) {
6826
7248
  const receiver = node.op === "rcall" ? args[1] : args[0];
6827
- if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode3(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
7249
+ if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode4(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
6828
7250
  excluded.add(receiver.args);
6829
7251
  }
6830
7252
  if (node.op === "rcall" && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
6831
7253
  const macroArgs = args[2];
6832
7254
  if (Array.isArray(macroArgs) && macroArgs.length >= 2) {
6833
7255
  const bound = macroArgs[0];
6834
- if (isNode3(bound) && bound.op === "id" && typeof bound.args === "string") {
7256
+ if (isNode4(bound) && bound.op === "id" && typeof bound.args === "string") {
6835
7257
  excluded.add(bound.args);
6836
7258
  }
6837
7259
  }
@@ -6850,17 +7272,11 @@ function predicateSource2(v) {
6850
7272
  }
6851
7273
  return void 0;
6852
7274
  }
6853
- function isRec19(v) {
7275
+ function isRec22(v) {
6854
7276
  return !!v && typeof v === "object" && !Array.isArray(v);
6855
7277
  }
6856
- function schemaIdOf(view) {
6857
- const data = view.data;
6858
- if (!isRec19(data)) return void 0;
6859
- if (data.provider !== "schema") return void 0;
6860
- return typeof data.schemaId === "string" ? data.schemaId : void 0;
6861
- }
6862
7278
  function checkPredicate(source, scope, where, path, findings) {
6863
- const ast = parseCelToAst3(source);
7279
+ const ast = parseCelToAst4(source);
6864
7280
  if (!ast) return;
6865
7281
  const paths = [];
6866
7282
  rootedPaths(ast, paths);
@@ -6888,19 +7304,38 @@ function checkPredicate(source, scope, where, path, findings) {
6888
7304
  }
6889
7305
  }
6890
7306
  const declaredHere = keysOf(scope);
6891
- if (!declaredHere) return;
6892
- const values = /* @__PURE__ */ new Set();
6893
- const excluded = /* @__PURE__ */ new Set();
6894
- classifyIdentifiers(ast, values, excluded);
6895
- for (const id of values) {
6896
- if (excluded.has(id) || !declaredHere.includes(id)) continue;
7307
+ const rhsOnly = bareRhsOnlyIdentifiers(ast);
7308
+ if (declaredHere) {
7309
+ const values = /* @__PURE__ */ new Set();
7310
+ const excluded = /* @__PURE__ */ new Set();
7311
+ classifyIdentifiers(ast, values, excluded);
7312
+ for (const id of values) {
7313
+ if (excluded.has(id) || rhsOnly.has(id) || !declaredHere.includes(id)) continue;
7314
+ findings.push({
7315
+ severity: "error",
7316
+ rule: PREDICATE_PATH_UNROOTED,
7317
+ where,
7318
+ path,
7319
+ 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).`,
7320
+ 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).`
7321
+ });
7322
+ }
7323
+ }
7324
+ const sites = [];
7325
+ equalitySites(ast, sites);
7326
+ for (const { op, right } of sites) {
7327
+ const chain = memberChain(right);
7328
+ if (!chain) continue;
7329
+ const text = chain.join(".");
7330
+ if (!PATH_SHAPED_RHS.test(text)) continue;
7331
+ const dotted = chain.length > 1;
6897
7332
  findings.push({
6898
- severity: "error",
6899
- rule: PREDICATE_PATH_UNROOTED,
7333
+ severity: dotted ? "error" : "warning",
7334
+ rule: PREDICATE_RHS_PATH_SHAPED,
6900
7335
  where,
6901
7336
  path,
6902
- 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).`,
6903
- 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).`
7337
+ 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).`,
7338
+ 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.`
6904
7339
  });
6905
7340
  }
6906
7341
  }
@@ -6908,7 +7343,7 @@ function walkFields(entries, scope, where, base, findings, depth) {
6908
7343
  if (!Array.isArray(entries) || depth > 12) return;
6909
7344
  for (let i = 0; i < entries.length; i++) {
6910
7345
  const entry = entries[i];
6911
- if (!isRec19(entry)) continue;
7346
+ if (!isRec22(entry)) continue;
6912
7347
  const path = `${base}[${i}]`;
6913
7348
  for (const key of PREDICATE_KEYS) {
6914
7349
  const source = predicateSource2(entry[key]);
@@ -6935,15 +7370,14 @@ function validatePredicatePathRefs(stack, opts = {}) {
6935
7370
  try {
6936
7371
  root = resolveSchema(schemaId);
6937
7372
  } catch {
6938
- continue;
7373
+ root = void 0;
6939
7374
  }
6940
- if (!root) continue;
6941
7375
  const where = site.surface ? `view "${viewName}" \xB7 ${site.surface} (schema "${schemaId}")` : `view "${viewName}" (schema "${schemaId}")`;
6942
7376
  for (const bucket of ["sections", "groups"]) {
6943
7377
  const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
6944
7378
  for (let s = 0; s < sections.length; s++) {
6945
7379
  const section = sections[s];
6946
- if (!isRec19(section)) continue;
7380
+ if (!isRec22(section)) continue;
6947
7381
  const sectionPath = `${site.path}.${bucket}[${s}]`;
6948
7382
  for (const key of PREDICATE_KEYS) {
6949
7383
  const source = predicateSource2(section[key]);
@@ -6974,6 +7408,7 @@ var SECURITY_MASTER_DETAIL_UNGRANTED = "security-master-detail-ungranted";
6974
7408
  var SECURITY_FLS_UNQUALIFIED_KEY = "security-fls-unqualified-key";
6975
7409
  var SECURITY_GRANT_EXPIRED_AT_AUTHORING = "security-grant-expired-at-authoring";
6976
7410
  var SECURITY_DELEGATION_MISSING_REASON = "security-delegation-missing-reason";
7411
+ var SECURITY_CBP_NO_RELATION = "security-controlled-by-parent-no-relation";
6977
7412
  var CANONICAL_OWD = ["private", "public_read", "public_read_write", "controlled_by_parent"];
6978
7413
  var OWD_ALIAS_FIX = {
6979
7414
  read: "public_read",
@@ -6986,7 +7421,7 @@ var OWD_WIDTH = {
6986
7421
  public_read: 1,
6987
7422
  public_read_write: 2
6988
7423
  };
6989
- function asArray35(v) {
7424
+ function asArray36(v) {
6990
7425
  if (Array.isArray(v)) return v;
6991
7426
  if (v && typeof v === "object") {
6992
7427
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -7012,21 +7447,28 @@ function refOf(def) {
7012
7447
  return typeof r === "string" && r ? r : void 0;
7013
7448
  }
7014
7449
  function firstMasterDetailField(obj) {
7015
- for (const f of asArray35(obj.fields)) {
7450
+ for (const f of asArray36(obj.fields)) {
7016
7451
  if (f.type === "master_detail") {
7017
7452
  return { name: String(f.name ?? "?"), parent: refOf(f) };
7018
7453
  }
7019
7454
  }
7020
7455
  return void 0;
7021
7456
  }
7457
+ function resolveCbpRelation(obj) {
7458
+ const entries = asArray36(obj.fields);
7459
+ const pick = (pred) => entries.find((f) => pred(f) && refOf(f));
7460
+ const found = pick((f) => f.type === "master_detail" && !!f.required) ?? pick((f) => f.type === "master_detail") ?? pick((f) => f.type === "lookup" && !!f.required);
7461
+ if (!found) return void 0;
7462
+ return { field: String(found.name ?? "?"), type: String(found.type), master: refOf(found) };
7463
+ }
7022
7464
  function grantsObjectAccess(p) {
7023
7465
  return p.allowRead === true || p.allowCreate === true || p.allowEdit === true || p.allowDelete === true || p.viewAllRecords === true || p.modifyAllRecords === true;
7024
7466
  }
7025
7467
  function validateSecurityPosture(stack, opts) {
7026
7468
  const findings = [];
7027
7469
  if (!stack || typeof stack !== "object") return findings;
7028
- const objects = asArray35(stack.objects);
7029
- const permissionSets = asArray35(stack.permissions);
7470
+ const objects = asArray36(stack.objects);
7471
+ const permissionSets = asArray36(stack.permissions);
7030
7472
  for (let i = 0; i < objects.length; i++) {
7031
7473
  const obj = objects[i];
7032
7474
  if (!obj || typeof obj !== "object") continue;
@@ -7064,6 +7506,16 @@ function validateSecurityPosture(stack, opts) {
7064
7506
  });
7065
7507
  }
7066
7508
  }
7509
+ if (owd === "controlled_by_parent" && !resolveCbpRelation(obj)) {
7510
+ findings.push({
7511
+ severity: "error",
7512
+ rule: SECURITY_CBP_NO_RELATION,
7513
+ where: `object "${objName}"`,
7514
+ path: `${objPath}.sharingModel`,
7515
+ 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.`,
7516
+ 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'.`
7517
+ });
7518
+ }
7067
7519
  if (typeof external === "string") {
7068
7520
  if (OWD_ALIAS_FIX[external]) {
7069
7521
  findings.push({
@@ -7129,57 +7581,10 @@ function validateSecurityPosture(stack, opts) {
7129
7581
  }
7130
7582
  }
7131
7583
  }
7132
- const flagRole = (kind, name, label2, where, path) => {
7133
- if (identifierHasRoleToken(name)) {
7134
- findings.push({
7135
- severity: "error",
7136
- rule: SECURITY_ROLE_WORD,
7137
- where,
7138
- path,
7139
- 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).`,
7140
- hint: `Rename using 'position' for distribution groups or a domain word (e.g. 'function', 'duty').`
7141
- });
7142
- } else if (labelHasRoleWord(label2)) {
7143
- findings.push({
7144
- severity: "error",
7145
- rule: SECURITY_ROLE_WORD,
7146
- where,
7147
- path: `${path.replace(/\.name$/, "")}.label`,
7148
- message: `${kind} label "${String(label2)}" uses the reserved word "role" (ADR-0090 D3).`,
7149
- hint: `Relabel with 'Position' (distribution) or a domain word \u2014 admins must meet ONE vocabulary.`
7150
- });
7151
- }
7152
- };
7153
- for (let i = 0; i < objects.length; i++) {
7154
- const obj = objects[i];
7155
- if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
7156
- const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
7157
- flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
7158
- for (const f of asArray35(obj.fields)) {
7159
- flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
7160
- }
7161
- for (const [ai, action] of asArray35(obj.actions).entries()) {
7162
- flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
7163
- }
7164
- }
7165
- for (let i = 0; i < permissionSets.length; i++) {
7166
- const ps = permissionSets[i];
7167
- if (!ps || typeof ps !== "object") continue;
7168
- flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
7169
- }
7170
- for (const [i, pos] of asArray35(stack.positions).entries()) {
7171
- flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
7172
- }
7173
- for (const [i, app] of asArray35(stack.apps).entries()) {
7174
- flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
7175
- }
7176
- for (const [i, book] of asArray35(stack.books).entries()) {
7177
- flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
7178
- }
7179
7584
  const stackSetNames = new Set(
7180
7585
  permissionSets.map((ps) => typeof ps.name === "string" ? ps.name : void 0).filter((n) => !!n)
7181
7586
  );
7182
- for (const [i, book] of asArray35(stack.books).entries()) {
7587
+ for (const [i, book] of asArray36(stack.books).entries()) {
7183
7588
  const audience = book.audience;
7184
7589
  if (!audience || typeof audience !== "object") continue;
7185
7590
  const setName = audience.permissionSet;
@@ -7257,7 +7662,7 @@ function validateSecurityPosture(stack, opts) {
7257
7662
  }
7258
7663
  const GRANT_SEED_OBJECTS = /* @__PURE__ */ new Set(["sys_user_position", "sys_user_permission_set"]);
7259
7664
  const nowMs = opts?.nowMs ?? Date.now();
7260
- for (const [i, seed] of asArray35(stack.data).entries()) {
7665
+ for (const [i, seed] of asArray36(stack.data).entries()) {
7261
7666
  const seedObject = typeof seed.object === "string" ? seed.object : "";
7262
7667
  if (!GRANT_SEED_OBJECTS.has(seedObject)) continue;
7263
7668
  const records = Array.isArray(seed.records) ? seed.records : [];
@@ -7296,13 +7701,67 @@ function validateSecurityPosture(stack, opts) {
7296
7701
  }
7297
7702
  return findings;
7298
7703
  }
7704
+ function validateSecurityRoleWord(stack) {
7705
+ const findings = [];
7706
+ if (!stack || typeof stack !== "object") return findings;
7707
+ const objects = asArray36(stack.objects);
7708
+ const permissionSets = asArray36(stack.permissions);
7709
+ const flagRole = (kind, name, label2, where, path) => {
7710
+ if (identifierHasRoleToken(name)) {
7711
+ findings.push({
7712
+ severity: "error",
7713
+ rule: SECURITY_ROLE_WORD,
7714
+ where,
7715
+ path,
7716
+ 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).`,
7717
+ hint: `Rename using 'position' for distribution groups or a domain word (e.g. 'function', 'duty').`
7718
+ });
7719
+ } else if (labelHasRoleWord(label2)) {
7720
+ findings.push({
7721
+ severity: "error",
7722
+ rule: SECURITY_ROLE_WORD,
7723
+ where,
7724
+ path: `${path.replace(/\.name$/, "")}.label`,
7725
+ message: `${kind} label "${String(label2)}" uses the reserved word "role" (ADR-0090 D3).`,
7726
+ hint: `Relabel with 'Position' (distribution) or a domain word \u2014 admins must meet ONE vocabulary.`
7727
+ });
7728
+ }
7729
+ };
7730
+ for (let i = 0; i < objects.length; i++) {
7731
+ const obj = objects[i];
7732
+ if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
7733
+ const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
7734
+ flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
7735
+ for (const f of asArray36(obj.fields)) {
7736
+ flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
7737
+ }
7738
+ for (const [ai, action] of asArray36(obj.actions).entries()) {
7739
+ flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
7740
+ }
7741
+ }
7742
+ for (let i = 0; i < permissionSets.length; i++) {
7743
+ const ps = permissionSets[i];
7744
+ if (!ps || typeof ps !== "object") continue;
7745
+ flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
7746
+ }
7747
+ for (const [i, pos] of asArray36(stack.positions).entries()) {
7748
+ flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
7749
+ }
7750
+ for (const [i, app] of asArray36(stack.apps).entries()) {
7751
+ flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
7752
+ }
7753
+ for (const [i, book] of asArray36(stack.books).entries()) {
7754
+ flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
7755
+ }
7756
+ return findings;
7757
+ }
7299
7758
 
7300
7759
  // src/validate-org-axis-red-lines.ts
7301
7760
  var ORG_AXIS_PERMISSION_INHERITANCE = "org-axis-permission-inheritance";
7302
7761
  var ORG_AXIS_CROSS_ORG_BU_GRANT = "org-axis-cross-org-bu-grant";
7303
7762
  var ORG_PARENT_FIELD = "parent_organization_id";
7304
7763
  var BU_TREE_RECIPIENT_TYPES = /* @__PURE__ */ new Set(["business_unit", "unit_and_subordinates"]);
7305
- function asArray36(v) {
7764
+ function asArray37(v) {
7306
7765
  if (Array.isArray(v)) return v;
7307
7766
  if (v && typeof v === "object") {
7308
7767
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -7332,9 +7791,9 @@ var INHERITANCE_HINT = `Remove the ${ORG_PARENT_FIELD} reference. Cross-organiza
7332
7791
  function validateOrgAxisRedLines(stack) {
7333
7792
  const findings = [];
7334
7793
  const cfg = stack ?? {};
7335
- const permissionSets = asArray36(cfg.permissions);
7794
+ const permissionSets = asArray37(cfg.permissions);
7336
7795
  permissionSets.forEach((ps, psIndex) => {
7337
- asArray36(ps.rowLevelSecurity).forEach((policy, pIndex) => {
7796
+ asArray37(ps.rowLevelSecurity).forEach((policy, pIndex) => {
7338
7797
  for (const clause of ["using", "check"]) {
7339
7798
  if (!str(policy[clause]).includes(ORG_PARENT_FIELD)) continue;
7340
7799
  findings.push({
@@ -7348,7 +7807,7 @@ function validateOrgAxisRedLines(stack) {
7348
7807
  }
7349
7808
  });
7350
7809
  });
7351
- asArray36(cfg.sharingRules).forEach((rule, rIndex) => {
7810
+ asArray37(cfg.sharingRules).forEach((rule, rIndex) => {
7352
7811
  const slots = [
7353
7812
  { key: "condition", text: expressionText(rule.condition) },
7354
7813
  { key: "sharedWith", text: JSON.stringify(rule.sharedWith ?? "") ?? "" }
@@ -7366,9 +7825,9 @@ function validateOrgAxisRedLines(stack) {
7366
7825
  }
7367
7826
  });
7368
7827
  const tenancyDisabledObjects = new Set(
7369
- asArray36(cfg.objects).filter((o) => isTenancyDisabled(o)).map((o) => str(o.name)).filter(Boolean)
7828
+ asArray37(cfg.objects).filter((o) => isTenancyDisabled(o)).map((o) => str(o.name)).filter(Boolean)
7370
7829
  );
7371
- asArray36(cfg.sharingRules).forEach((rule, rIndex) => {
7830
+ asArray37(cfg.sharingRules).forEach((rule, rIndex) => {
7372
7831
  const target = str(rule.object);
7373
7832
  if (!target || !tenancyDisabledObjects.has(target)) return;
7374
7833
  const sharedWith = rule.sharedWith;
@@ -7391,7 +7850,7 @@ function validateOrgAxisRedLines(stack) {
7391
7850
  import { compileCelToFilter } from "@objectstack/formula";
7392
7851
  var SHARING_RULE_UNLOWERABLE_CONDITION = "sharing-rule-unlowerable-condition";
7393
7852
  var SHARING_RULE_RUNTIME_VARIABLE_CONDITION = "sharing-rule-runtime-variable-condition";
7394
- function asArray37(v) {
7853
+ function asArray38(v) {
7395
7854
  if (Array.isArray(v)) return v;
7396
7855
  if (v && typeof v === "object") {
7397
7856
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -7418,7 +7877,7 @@ var PUSHDOWN_SUBSET = "The lowerable subset is: `==` `!=` `>` `<` `>=` `<=`, `in
7418
7877
  function validateSharingRuleEnforceability(stack) {
7419
7878
  const findings = [];
7420
7879
  const cfg = stack ?? {};
7421
- asArray37(cfg.sharingRules).forEach((rule, index) => {
7880
+ asArray38(cfg.sharingRules).forEach((rule, index) => {
7422
7881
  const input = toCompilerInput(rule.condition);
7423
7882
  if (input === null) return;
7424
7883
  const result = compileCelToFilter(input, { variables: {} });
@@ -7463,7 +7922,7 @@ import {
7463
7922
  var RLS_PREDICATE_UNENFORCEABLE = "rls-predicate-unenforceable";
7464
7923
  var RLS_PREDICATE_UNPARSEABLE = "rls-predicate-unparseable";
7465
7924
  var RLS_PREDICATE_OVER_BUDGET = "rls-predicate-over-budget";
7466
- function asArray38(v) {
7925
+ function asArray39(v) {
7467
7926
  if (Array.isArray(v)) return v;
7468
7927
  if (v && typeof v === "object") {
7469
7928
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -7488,8 +7947,8 @@ function consequence(clause) {
7488
7947
  function validateRlsPredicateEnforceability(stack) {
7489
7948
  const findings = [];
7490
7949
  const cfg = stack ?? {};
7491
- asArray38(cfg.permissions).forEach((ps, psIndex) => {
7492
- asArray38(ps.rowLevelSecurity).forEach((policy, pIndex) => {
7950
+ asArray39(cfg.permissions).forEach((ps, psIndex) => {
7951
+ asArray39(ps.rowLevelSecurity).forEach((policy, pIndex) => {
7493
7952
  for (const clause of ["using", "check"]) {
7494
7953
  const source = str3(policy[clause]);
7495
7954
  if (!source.trim()) continue;
@@ -7548,11 +8007,11 @@ import { createRequire as createRequire4 } from "module";
7548
8007
  var VALIDATION_RULE_REGEX_UNCOMPILABLE = "validation-rule-regex-uncompilable";
7549
8008
  var VALIDATION_RULE_SCHEMA_UNCOMPILABLE = "validation-rule-json-schema-uncompilable";
7550
8009
  var RUNTIME_AJV_OPTIONS = { allErrors: true, strict: false };
7551
- var isRec20 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7552
- function asArray39(v) {
7553
- if (Array.isArray(v)) return v.filter(isRec20);
7554
- if (isRec20(v)) {
7555
- return Object.entries(v).filter(([, def]) => isRec20(def)).map(([name, def]) => ({ name, ...def }));
8010
+ var isRec23 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8011
+ function asArray40(v) {
8012
+ if (Array.isArray(v)) return v.filter(isRec23);
8013
+ if (isRec23(v)) {
8014
+ return Object.entries(v).filter(([, def]) => isRec23(def)).map(([name, def]) => ({ name, ...def }));
7556
8015
  }
7557
8016
  return [];
7558
8017
  }
@@ -7569,7 +8028,7 @@ function loadAjv() {
7569
8028
  `@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.`
7570
8029
  );
7571
8030
  }
7572
- const ctor = isRec20(mod) && "default" in mod ? mod.default : mod;
8031
+ const ctor = isRec23(mod) && "default" in mod ? mod.default : mod;
7573
8032
  cachedAjv = ctor;
7574
8033
  return ctor;
7575
8034
  }
@@ -7584,7 +8043,7 @@ function loadAddFormats() {
7584
8043
  `@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.`
7585
8044
  );
7586
8045
  }
7587
- const plugin = isRec20(mod) && "default" in mod ? mod.default : mod;
8046
+ const plugin = isRec23(mod) && "default" in mod ? mod.default : mod;
7588
8047
  cachedAddFormats = plugin;
7589
8048
  return plugin;
7590
8049
  }
@@ -7614,17 +8073,17 @@ function flattenRules(rule, labelTrail, pathTrail, depth = 0) {
7614
8073
  if (depth >= MAX_RULE_NESTING_DEPTH) return out;
7615
8074
  for (const branch of ["then", "otherwise"]) {
7616
8075
  const nested = rule[branch];
7617
- if (isRec20(nested)) out.push(...flattenRules(nested, label2, `${path}.${branch}`, depth + 1));
8076
+ if (isRec23(nested)) out.push(...flattenRules(nested, label2, `${path}.${branch}`, depth + 1));
7618
8077
  }
7619
8078
  return out;
7620
8079
  }
7621
8080
  function walkObjectValidationRules(stack) {
7622
8081
  const walked = [];
7623
- if (!isRec20(stack)) return walked;
7624
- for (const obj of asArray39(stack.objects)) {
8082
+ if (!isRec23(stack)) return walked;
8083
+ for (const obj of asArray40(stack.objects)) {
7625
8084
  const objectName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
7626
8085
  const validations = obj.validations;
7627
- for (const authored of asArray39(validations)) {
8086
+ for (const authored of asArray40(validations)) {
7628
8087
  for (const { rule, label: label2, path } of flattenRules(authored, "", "")) {
7629
8088
  walked.push({
7630
8089
  rule,
@@ -7655,7 +8114,7 @@ function validateRuleCompilability(stack) {
7655
8114
  });
7656
8115
  }
7657
8116
  }
7658
- if (rule.type === "json_schema" && isRec20(rule.schema)) {
8117
+ if (rule.type === "json_schema" && isRec23(rule.schema)) {
7659
8118
  try {
7660
8119
  createRuntimeAjv().compile(rule.schema);
7661
8120
  } catch (err) {
@@ -7675,7 +8134,7 @@ function validateRuleCompilability(stack) {
7675
8134
 
7676
8135
  // src/validate-rule-schema-formats.ts
7677
8136
  var VALIDATION_RULE_SCHEMA_UNKNOWN_FORMAT = "validation-rule-json-schema-unknown-format";
7678
- var isRec21 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8137
+ var isRec24 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7679
8138
  var SUBSCHEMA_KEYS = [
7680
8139
  "additionalItems",
7681
8140
  "additionalProperties",
@@ -7699,7 +8158,7 @@ var SUBSCHEMA_MAP_KEYS = [
7699
8158
  var MAX_SCHEMA_WALK_DEPTH = 32;
7700
8159
  var escapePointerSegment = (segment) => segment.replace(/~/g, "~0").replace(/\//g, "~1");
7701
8160
  function collectFormatUses(schema, pointer, out, depth) {
7702
- if (!isRec21(schema)) return;
8161
+ if (!isRec24(schema)) return;
7703
8162
  if (typeof schema.format === "string") {
7704
8163
  out.push({ pointer: `${pointer}/format`, name: schema.format });
7705
8164
  }
@@ -7718,7 +8177,7 @@ function collectFormatUses(schema, pointer, out, depth) {
7718
8177
  }
7719
8178
  for (const key of SUBSCHEMA_MAP_KEYS) {
7720
8179
  const value = schema[key];
7721
- if (!isRec21(value)) continue;
8180
+ if (!isRec24(value)) continue;
7722
8181
  for (const [name, entry] of Object.entries(value)) {
7723
8182
  collectFormatUses(entry, `${pointer}/${escapePointerSegment(key)}/${escapePointerSegment(name)}`, out, depth + 1);
7724
8183
  }
@@ -7726,13 +8185,13 @@ function collectFormatUses(schema, pointer, out, depth) {
7726
8185
  const items = schema.items;
7727
8186
  if (Array.isArray(items)) {
7728
8187
  items.forEach((entry, index) => collectFormatUses(entry, `${pointer}/items/${index}`, out, depth + 1));
7729
- } else if (isRec21(items)) {
8188
+ } else if (isRec24(items)) {
7730
8189
  collectFormatUses(items, `${pointer}/items`, out, depth + 1);
7731
8190
  }
7732
8191
  const dependencies = schema.dependencies;
7733
- if (isRec21(dependencies)) {
8192
+ if (isRec24(dependencies)) {
7734
8193
  for (const [name, entry] of Object.entries(dependencies)) {
7735
- if (!isRec21(entry)) continue;
8194
+ if (!isRec24(entry)) continue;
7736
8195
  collectFormatUses(entry, `${pointer}/dependencies/${escapePointerSegment(name)}`, out, depth + 1);
7737
8196
  }
7738
8197
  }
@@ -7771,7 +8230,7 @@ function validateRuleSchemaFormats(stack) {
7771
8230
  const findings = [];
7772
8231
  const pending = [];
7773
8232
  for (const { rule, objectName, label: label2, where, basePath } of walkObjectValidationRules(stack)) {
7774
- if (rule.type !== "json_schema" || !isRec21(rule.schema)) continue;
8233
+ if (rule.type !== "json_schema" || !isRec24(rule.schema)) continue;
7775
8234
  const uses = [];
7776
8235
  collectFormatUses(rule.schema, "", uses, 0);
7777
8236
  for (const use of uses) pending.push({ use, where, label: label2, objectName, basePath });
@@ -7797,14 +8256,14 @@ function validateRuleSchemaFormats(stack) {
7797
8256
 
7798
8257
  // src/validate-action-locations.ts
7799
8258
  var ACTION_NO_PLACEMENT = "action-no-placement";
7800
- function asArray40(v) {
8259
+ function asArray41(v) {
7801
8260
  if (Array.isArray(v)) return v;
7802
8261
  if (v && typeof v === "object") {
7803
8262
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
7804
8263
  }
7805
8264
  return [];
7806
8265
  }
7807
- function strName19(v) {
8266
+ function strName20(v) {
7808
8267
  return typeof v === "string" && v.length > 0 ? v : void 0;
7809
8268
  }
7810
8269
  function strList3(v) {
@@ -7818,8 +8277,8 @@ function collectNamePlacedActions(stack) {
7818
8277
  for (const key of ["rowActions", "bulkActions"]) {
7819
8278
  for (const n of strList3(list3[key])) placed.add(n);
7820
8279
  }
7821
- for (const def of asArray40(list3.bulkActionDefs)) {
7822
- const n = strName19(def?.name);
8280
+ for (const def of asArray41(list3.bulkActionDefs)) {
8281
+ const n = strName20(def?.name);
7823
8282
  if (n) placed.add(n);
7824
8283
  }
7825
8284
  };
@@ -7827,12 +8286,12 @@ function collectNamePlacedActions(stack) {
7827
8286
  if (!listViews || typeof listViews !== "object" || Array.isArray(listViews)) return;
7828
8287
  for (const lv of Object.values(listViews)) harvest(lv);
7829
8288
  };
7830
- for (const view of asArray40(stack.views)) {
8289
+ for (const view of asArray41(stack.views)) {
7831
8290
  if (!view || typeof view !== "object") continue;
7832
8291
  harvest(view.list);
7833
8292
  harvestListViews(view.listViews);
7834
8293
  }
7835
- for (const obj of asArray40(stack.objects)) {
8294
+ for (const obj of asArray41(stack.objects)) {
7836
8295
  if (!obj || typeof obj !== "object") continue;
7837
8296
  harvestListViews(obj.listViews);
7838
8297
  }
@@ -7845,7 +8304,7 @@ function validateActionLocations(stack) {
7845
8304
  const check = (action, path) => {
7846
8305
  if (!action || typeof action !== "object") return;
7847
8306
  if ("locations" in action) return;
7848
- const name = strName19(action.name);
8307
+ const name = strName20(action.name);
7849
8308
  if (!name) return;
7850
8309
  if (namePlaced.has(name)) return;
7851
8310
  findings.push({
@@ -7857,13 +8316,13 @@ function validateActionLocations(stack) {
7857
8316
  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."
7858
8317
  });
7859
8318
  };
7860
- const actions = asArray40(stack.actions);
8319
+ const actions = asArray41(stack.actions);
7861
8320
  for (let i = 0; i < actions.length; i++) check(actions[i], `actions[${i}]`);
7862
- const objects = asArray40(stack.objects);
8321
+ const objects = asArray41(stack.objects);
7863
8322
  for (let oi = 0; oi < objects.length; oi++) {
7864
8323
  const obj = objects[oi];
7865
8324
  if (!obj || typeof obj !== "object") continue;
7866
- const own = asArray40(obj.actions);
8325
+ const own = asArray41(obj.actions);
7867
8326
  for (let ai = 0; ai < own.length; ai++) check(own[ai], `objects[${oi}].actions[${ai}]`);
7868
8327
  }
7869
8328
  return findings;
@@ -7876,7 +8335,7 @@ import {
7876
8335
  collectFlowGraphs as collectFlowGraphs2
7877
8336
  } from "@objectstack/spec/automation";
7878
8337
  import { reduceFilterVerdict as reduceFilterVerdict2 } from "@objectstack/spec/data";
7879
- function asArray41(v) {
8338
+ function asArray42(v) {
7880
8339
  if (Array.isArray(v)) return v;
7881
8340
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
7882
8341
  return [];
@@ -8260,7 +8719,7 @@ function scanApprovalReviseLoops(at, nodes, edges, findings) {
8260
8719
  }
8261
8720
  function lintFlowPatterns(stack) {
8262
8721
  const findings = [];
8263
- for (const flow of asArray41(stack.flows)) {
8722
+ for (const flow of asArray42(stack.flows)) {
8264
8723
  const flowName = typeof flow.name === "string" ? flow.name : "(unnamed flow)";
8265
8724
  const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
8266
8725
  const edges = Array.isArray(flow.edges) ? flow.edges : [];
@@ -8357,7 +8816,7 @@ import { dirname, join } from "path";
8357
8816
  import { existsSync, readFileSync } from "fs";
8358
8817
  var LIVENESS_DEAD_PROPERTY = "liveness-dead-property";
8359
8818
  var LIVENESS_EXPERIMENTAL_PROPERTY = "liveness-experimental-property";
8360
- function asArray42(v) {
8819
+ function asArray43(v) {
8361
8820
  if (Array.isArray(v)) return v;
8362
8821
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
8363
8822
  return [];
@@ -8494,11 +8953,11 @@ function lintLivenessProperties(stack) {
8494
8953
  const findings = [];
8495
8954
  const objectWarn = loadWarnMap(dir, "object");
8496
8955
  const fieldWarn = loadWarnMap(dir, "field");
8497
- for (const obj of asArray42(stack.objects)) {
8956
+ for (const obj of asArray43(stack.objects)) {
8498
8957
  const objName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
8499
8958
  if (objectWarn.size > 0) checkItem("object", obj, `object '${objName}'`, objectWarn, findings);
8500
8959
  if (fieldWarn.size > 0) {
8501
- for (const field of asArray42(obj.fields)) {
8960
+ for (const field of asArray43(obj.fields)) {
8502
8961
  const fieldName = typeof field.name === "string" ? field.name : "(unnamed field)";
8503
8962
  checkItem("field", field, `object '${objName}' \xB7 field '${fieldName}'`, fieldWarn, findings);
8504
8963
  }
@@ -8507,7 +8966,7 @@ function lintLivenessProperties(stack) {
8507
8966
  for (const { type, key } of TYPE_COLLECTIONS) {
8508
8967
  const warnMap = loadWarnMap(dir, type);
8509
8968
  if (warnMap.size === 0) continue;
8510
- for (const item of asArray42(stack[key])) {
8969
+ for (const item of asArray43(stack[key])) {
8511
8970
  const name = typeof item.name === "string" ? item.name : typeof item.object === "string" ? item.object : `(unnamed ${type})`;
8512
8971
  checkItem(type, item, `${type} '${name}'`, warnMap, findings);
8513
8972
  }
@@ -8521,7 +8980,7 @@ var AUTONUMBER_UNKNOWN_FIELD = "autonumber-references-unknown-field";
8521
8980
  var AUTONUMBER_OPTIONAL_FIELD = "autonumber-references-optional-field";
8522
8981
  var AUTONUMBER_SELF_REFERENCE = "autonumber-references-self";
8523
8982
  var AUTONUMBER_LITERAL_TOKEN = "autonumber-unrecognized-token";
8524
- function asArray43(v) {
8983
+ function asArray44(v) {
8525
8984
  if (Array.isArray(v)) return v;
8526
8985
  if (v && typeof v === "object") {
8527
8986
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -8530,9 +8989,9 @@ function asArray43(v) {
8530
8989
  }
8531
8990
  function lintAutonumberFormats(stack) {
8532
8991
  const findings = [];
8533
- for (const obj of asArray43(stack.objects)) {
8992
+ for (const obj of asArray44(stack.objects)) {
8534
8993
  const objectName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
8535
- const fields = asArray43(obj.fields);
8994
+ const fields = asArray44(obj.fields);
8536
8995
  const fieldMeta = /* @__PURE__ */ new Map();
8537
8996
  for (const f of fields) {
8538
8997
  if (typeof f.name === "string") fieldMeta.set(f.name, { required: f.required === true });
@@ -8598,7 +9057,7 @@ function lintAutonumberFormats(stack) {
8598
9057
 
8599
9058
  // src/lint-view-refs.ts
8600
9059
  import { expandViewContainerWithDiagnostics, isAggregatedViewContainer } from "@objectstack/spec";
8601
- function asArray44(v) {
9060
+ function asArray45(v) {
8602
9061
  if (Array.isArray(v)) return v;
8603
9062
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
8604
9063
  return [];
@@ -8626,7 +9085,7 @@ function lintViewRefs(stack) {
8626
9085
  s.add(kind);
8627
9086
  };
8628
9087
  const containers = [];
8629
- for (const v of asArray44(stack.views)) {
9088
+ for (const v of asArray45(stack.views)) {
8630
9089
  if (v.viewKind) {
8631
9090
  if (typeof v.name === "string") indexKind(v.name, v.viewKind === "form" ? "form" : "list");
8632
9091
  continue;
@@ -8635,7 +9094,7 @@ function lintViewRefs(stack) {
8635
9094
  const object = viewContainerObjectName(v);
8636
9095
  if (object) containers.push({ object, container: v });
8637
9096
  }
8638
- for (const obj of asArray44(stack.objects)) {
9097
+ for (const obj of asArray45(stack.objects)) {
8639
9098
  const object = typeof obj.name === "string" ? obj.name : void 0;
8640
9099
  if (!object) continue;
8641
9100
  if (obj.list || obj.form || obj.listViews || obj.formViews) {
@@ -8689,11 +9148,11 @@ function lintViewRefs(stack) {
8689
9148
  });
8690
9149
  }
8691
9150
  };
8692
- for (const obj of asArray44(stack.objects)) {
9151
+ for (const obj of asArray45(stack.objects)) {
8693
9152
  const object = typeof obj.name === "string" ? obj.name : void 0;
8694
- for (const action of asArray44(obj.actions)) checkAction(action, object);
9153
+ for (const action of asArray45(obj.actions)) checkAction(action, object);
8695
9154
  }
8696
- for (const action of asArray44(stack.actions)) checkAction(action);
9155
+ for (const action of asArray45(stack.actions)) checkAction(action);
8697
9156
  return findings;
8698
9157
  }
8699
9158
 
@@ -8828,7 +9287,6 @@ var CLI_ONLY = ["cli"];
8828
9287
  var CLI_AND_RUNTIME = ["cli", "runtime-publish"];
8829
9288
  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.";
8830
9289
  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.";
8831
- 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.";
8832
9290
  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.";
8833
9291
  var EXPRESSION_INVALID = "expression-invalid";
8834
9292
  var AUTHORING_RULES = [
@@ -8894,6 +9352,31 @@ var AUTHORING_RULES = [
8894
9352
  surfaceReason: RUNTIME_OBJECT_WRITES_P2,
8895
9353
  run: (stack) => validateFunctionalCompleteness(stack)
8896
9354
  },
9355
+ // [#7521, via cloud#1225] A managed object advertising a generic write verb
9356
+ // in `enable.apiMethods` that its own resolved affordances refuse. Every key
9357
+ // is one we know and each is individually valid, so #4001's unknown-key
9358
+ // rejection and the Zod parse both pass it; the contradiction is only visible
9359
+ // when the two keys are read TOGETHER, which nothing did at authoring time.
9360
+ //
9361
+ // `gating` because the declaration is already false when it ships: objectql's
9362
+ // registry strips the verb at registration, so the metadata advertises an API
9363
+ // the product does not serve. That strip has been correct and silent — a
9364
+ // `console.warn` on every control-plane boot that went unread for the life of
9365
+ // a real divergence (`sys_environment`/`sys_package`). This entry is the
9366
+ // ruling's "close it where the author is"; boot stays warn-and-strip.
9367
+ //
9368
+ // Pre-parse: the predicate reads only authored keys, and the finding must
9369
+ // survive an unrelated schema error elsewhere in the stack.
9370
+ {
9371
+ name: "validateManagedApiMethods",
9372
+ tier: "gating",
9373
+ input: "normalized",
9374
+ commands: ALL,
9375
+ source: "packages/lint/src/validate-managed-api-methods.ts",
9376
+ surfaces: CLI_ONLY,
9377
+ surfaceReason: RUNTIME_OBJECT_WRITES_P2,
9378
+ run: (stack) => validateManagedApiMethods(stack)
9379
+ },
8897
9380
  // A view container in `views: []` that registers zero views: nothing appears
8898
9381
  // in the Console, and the schema step cannot tell it from an intentionally
8899
9382
  // empty one. The FLAT-list-view arm no longer needs this tier — `ViewSchema`
@@ -9273,14 +9756,47 @@ var AUTHORING_RULES = [
9273
9756
  // what decides whether any given diagnostic gates, exactly as `lintFlowPatterns`
9274
9757
  // has worked since #3760. The promotion follows the #5762 precedent: a family
9275
9758
  // that gains an `error` finding moves its registry tier in the same edit.
9759
+ //
9760
+ // ─── The `views[]` visibility-predicate FAMILY at the runtime door (#7220) ───
9761
+ //
9762
+ // This entry and `validatePredicatePathRefs` below moved to `runtime-publish`
9763
+ // in ONE edit, on the maintainer's 2026-08-10 ruling, sequenced after #4717's
9764
+ // `advisories` channel landed (PR #7435). Before that move a `view` written
9765
+ // through Studio / REST `/meta` / MCP — the only door most tenants have, and
9766
+ // the door AI authors use — was judged by NONE of the family's rule ids (six
9767
+ // at the time of the move; seven since #7659 added
9768
+ // `predicate-rhs-path-shaped` inside the second entry).
9769
+ //
9770
+ // They move together on purpose, and the two entries carry one comment because
9771
+ // they are one wall: #7214's implementer wired its own rule here alone and then
9772
+ // REVERTED it, because a `view` refused for an unresolvable predicate PATH
9773
+ // while a predicate that does not parse at all walks through the same door is
9774
+ // less predictable than refusing neither. A half-wired wall is worse than an
9775
+ // unwired one, so `authoring-rule-wiring.test.ts` now pins the family property
9776
+ // directly: every id on this surface is gated at the runtime door, or none is.
9777
+ //
9778
+ // The previous `surfaceReason` on THIS entry was `RUNTIME_NEEDS_FULL_SNAPSHOT`,
9779
+ // and re-measuring it at move time found it false: both rule functions read
9780
+ // `stack.views` and `stack.pages` and NO other collection — never `objects` —
9781
+ // so the per-write snapshot the gate builds is not partial for them, it is
9782
+ // complete. (`pages` is simply absent on a `view` write, so the page half
9783
+ // contributes zero findings to both differential passes rather than inventing
9784
+ // any.) The reason was not describing this rule; it was the default a rule got
9785
+ // when nobody measured, which is the #4409/#4463 defect one layer in.
9786
+ //
9787
+ // Runtime input tier: the gate hands the rules the body as persisted, without
9788
+ // `normalizeStackInput`, so the ADR-0087 D2 alias fold does NOT run at this
9789
+ // door. That costs the family nothing — `validateVisibilityPredicates` reads
9790
+ // `visibleWhen ?? visibleOn ?? visibility` itself, canonical-first, precisely
9791
+ // so a caller handing it a raw authored object still gets a verdict.
9276
9792
  {
9277
9793
  name: "validateVisibilityPredicates",
9278
9794
  tier: "gating",
9279
9795
  input: "normalized",
9280
9796
  commands: ALL,
9281
9797
  source: "packages/lint/src/validate-visibility-predicates.ts",
9282
- surfaces: CLI_ONLY,
9283
- surfaceReason: RUNTIME_NEEDS_FULL_SNAPSHOT,
9798
+ surfaces: CLI_AND_RUNTIME,
9799
+ runtimeTypes: ["view"],
9284
9800
  run: (stack) => validateVisibilityPredicates(stack)
9285
9801
  },
9286
9802
  // #7010 — the same predicate surface, one question further in. The three
@@ -9297,14 +9813,28 @@ var AUTHORING_RULES = [
9297
9813
  // object's addressable path set is NOT closed (lookup traversal, system
9298
9814
  // columns, formula outputs), and an `error` gate over an open set generates
9299
9815
  // false build errors. See the rule's module note.
9816
+ //
9817
+ // #7659 adds a THIRD id here, `predicate-rhs-path-shaped`, which is not a
9818
+ // resolution question at all: the metadata-admin renderer resolves paths only
9819
+ // on the LEFT of `==` / `!=` and hands the right side to its literal parser,
9820
+ // so `data.a == data.b` resolves both sides cleanly, passes the two rules
9821
+ // above, and still compares against the string "data.b" — a constant verdict.
9822
+ // It carries `error` on a dotted chain (no reading under which it worked) and
9823
+ // `warning` on a bare word (`status == active` compares as the text today, so
9824
+ // refusing it would fail a build over metadata that renders correctly). The
9825
+ // per-finding severity is what gates, exactly as `lintFlowPatterns` has worked
9826
+ // since #3760; the entry's `gating` tier is unchanged because it already was.
9300
9827
  {
9301
9828
  name: "validatePredicatePathRefs",
9302
9829
  tier: "gating",
9303
9830
  input: "normalized",
9304
9831
  commands: ALL,
9305
9832
  source: "packages/lint/src/validate-predicate-path-refs.ts",
9306
- surfaces: CLI_ONLY,
9307
- surfaceReason: RUNTIME_VISIBILITY_FAMILY_IS_CLI_ONLY,
9833
+ // The second half of the #7220 family move — see the block above the
9834
+ // `validateVisibilityPredicates` entry. This is the rule whose solo wiring
9835
+ // was reverted; it is wired now because its siblings are.
9836
+ surfaces: CLI_AND_RUNTIME,
9837
+ runtimeTypes: ["view"],
9308
9838
  run: (stack) => validatePredicatePathRefs(stack)
9309
9839
  },
9310
9840
  // #1874 — flow authoring anti-patterns. Advisory by default; a finding marked
@@ -9464,16 +9994,110 @@ var AUTHORING_RULES = [
9464
9994
  // a runtime enforcement point (fail-closed OWD default, canonical enum, anchor
9465
9995
  // binding gate, vocabulary freeze), moving the failure from a runtime deny to
9466
9996
  // an author-time fix-it. Per ADR-0049 this is not advisory security.
9997
+ //
9998
+ // [#7576] The `surfaceReason` below is MEASURED. Its predecessor was not, and
9999
+ // was false in both halves — it read: "Already gated at this surface by a
10000
+ // DIFFERENT mechanism: plugin-security registers an ADR-0094 authoring gate on
10001
+ // `object` (`registerAuthoringGate`) that enforces the same OWD posture rules
10002
+ // on every runtime write. Running the linter here as well would double-report
10003
+ // one refusal in two vocabularies."
10004
+ //
10005
+ // - COVERAGE. `object-posture-gate.ts` reads exactly `sharingModel` and
10006
+ // `externalSharingModel` through a local `OWD_WIDTH`, and never touches
10007
+ // `fields`, `permissions`, `books` or `data`. Of the THIRTEEN rule ids this
10008
+ // block carries it covers ONE — `security-external-wider-than-internal`
10009
+ // (its R2). The gate's other half, R1 (env-tighten-only, ADR-0086 D1),
10010
+ // corresponds to no lint rule at all, so it is not coverage in the other
10011
+ // direction either. Twelve rules were enforced at no runtime door while
10012
+ // this field said they were.
10013
+ // - DOUBLE-REPORTING. It cannot happen, and not by luck: `saveMetaItem` runs
10014
+ // `assertRuntimeAuthoringRules` (this table, 422 `invalid_metadata`) BEFORE
10015
+ // `runAuthoringGate` (the ADR-0094 gate, 403 `owd_external_wider`), and
10016
+ // both refuse by THROWING. The first to fire ends the write, so an author
10017
+ // sees one refusal, never two. The stated cost of moving was imaginary; the
10018
+ // reason it has not moved is the measured one below.
10019
+ //
10020
+ // The move IS taken now — the #7891 programme's three slices, in order:
10021
+ //
10022
+ // - #8307: the ADR-0091 seed pair crossed (`runtimeTypes: ['seed']`), with
10023
+ // the isolation proof that the differential cancels every finding this
10024
+ // function derives from the sibling collections.
10025
+ // - #8309: the snapshot repair. The gate used to carry `objects` and
10026
+ // nothing else, so the three cross-collection rules judged a universe
10027
+ // missing the collection they compare against (measured: 38 phantom
10028
+ // `security-master-detail-ungranted` per-write vs 4 whole-stack,
10029
+ // PR #7886). `RuntimeStackContext` now carries `permissions`/`books` in
10030
+ // BOTH differential passes and `TYPE_TO_STACK_KEY` maps both types.
10031
+ // - #8310 slice 1: `runtimeTypes` gains `permission` + `book` (PR #8546).
10032
+ // `object` measured DIRTY on that tree and was escalated, not forced.
10033
+ // - #8310 slice 2 (this state): `object` crosses under the maintainer
10034
+ // ruling recorded on #8310 (2026-08-13, 「接受你的全部建议」): an
10035
+ // authored OWD is REQUIRED at the runtime object door — an object
10036
+ // publish with no authored `sharingModel` is refused with the 422 lint
10037
+ // envelope (`security-owd-unset`); absence is not a decision. The ~16
10038
+ // objectql/rest suite files that relied on OWD-less publishes were
10039
+ // repaired honestly (fixtures author their posture), and
10040
+ // `meta-object-owd-gate.test.ts` re-pins the door ORDER: this table
10041
+ // answers first (`saveMetaItem` runs it before `runAuthoringGate`), the
10042
+ // ADR-0094-seam 403 doors answer for what passes lint. The same ruling
10043
+ // retired the plugin gate's R2 `owd_external_wider` arm as a duplicate
10044
+ // of this door (R1 env-tighten-only STAYS — no lint rule covers it);
10045
+ // see `object-posture-gate.ts` and the ADR-0094 amendment.
10046
+ //
10047
+ // `security-role-word` is NOT in this entry any more — that is what the
10048
+ // `validateSecurityRoleWord` entry below records. It judges six collections
10049
+ // (objects, fields, actions, permission sets, positions, apps — plus books),
10050
+ // and `positions`/`apps` are neither carried by the per-write snapshot nor
10051
+ // mapped in `TYPE_TO_STACK_KEY`, so declaring `permission`/`book` on a
10052
+ // function that still contained it would have enforced ONE rule id for a
10053
+ // strict subset of its collections: a door where a permission set named
10054
+ // `role_manager` is refused and a position named `sales_role` walks through
10055
+ // — the #7220 failure this table refuses to build, in either direction. The
10056
+ // rule therefore stays behind WHOLE (#8310's explicit call), as its own
10057
+ // entry.
10058
+ //
10059
+ // This entry remains the rest of the D7 block (12 rule ids) as ONE
10060
+ // registration, not a per-rule split: the baseline/candidate differential is
10061
+ // what keeps a write of one declared type from leaking the other rules'
10062
+ // whole-stack findings — every finding derived from a sibling collection is
10063
+ // produced byte-identically in both passes and cancels in the diff. Only
10064
+ // findings the written item itself adds are attributed to the write.
9467
10065
  {
9468
10066
  name: "validateSecurityPosture",
9469
10067
  tier: "gating",
9470
10068
  input: "parsed",
9471
10069
  commands: ALL,
9472
10070
  source: "packages/lint/src/validate-security-posture.ts",
9473
- surfaces: CLI_ONLY,
9474
- surfaceReason: "Already gated at this surface by a DIFFERENT mechanism: plugin-security registers an ADR-0094 authoring gate on `object` (`registerAuthoringGate`) that enforces the same OWD posture rules on every runtime write. Running the linter here as well would double-report one refusal in two vocabularies. Consolidating the two onto this table is P2 (#4463), and is a merge, not a hole.",
10071
+ surfaces: CLI_AND_RUNTIME,
10072
+ runtimeTypes: ["seed", "permission", "book", "object"],
9475
10073
  run: (stack) => validateSecurityPosture(stack)
9476
10074
  },
10075
+ // [ADR-0090 D3 / #8310] The vocabulary freeze, split out of
10076
+ // `validateSecurityPosture` the day the rest of that block crossed the
10077
+ // runtime wall — so that it could stay behind WHOLE rather than cross for
10078
+ // three of the six collections it judges (#7220: one rule id must sit on ONE
10079
+ // side of the wall). The split is a surface boundary, not taste: the rule's
10080
+ // verdict and findings are byte-identical to before on every CLI command
10081
+ // (both entries run on all three), and the runtime door does not run it for
10082
+ // ANY type.
10083
+ //
10084
+ // The road to crossing is concrete and short, recorded here so the next
10085
+ // seat prices it correctly: carry `positions`/`apps` in
10086
+ // `RuntimeStackContext` + `CONTEXT_STACK_KEYS`, map both types in
10087
+ // `TYPE_TO_STACK_KEY` (both are `allowRuntimeCreate: true`, so the writes
10088
+ // are real), then declare `runtimeTypes: ['object', 'permission', 'book',
10089
+ // 'position', 'app']` on THIS entry — all six collections in one edit, the
10090
+ // #7220 discipline satisfied.
10091
+ {
10092
+ name: "validateSecurityRoleWord",
10093
+ tier: "gating",
10094
+ input: "parsed",
10095
+ commands: ALL,
10096
+ source: "packages/lint/src/validate-security-posture.ts",
10097
+ surfaces: CLI_ONLY,
10098
+ 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.",
10099
+ run: (stack) => validateSecurityRoleWord(stack)
10100
+ },
9477
10101
  // ADR-0105 D6 — the org tree is a REPORTING dimension. An RLS policy or
9478
10102
  // sharing rule that walks it builds a second permission hierarchy (the
9479
10103
  // dual-hierarchy mistake ADR-0057 D5 retired) and cannot widen Layer 0 anyway,
@@ -9524,7 +10148,7 @@ var AUTHORING_RULES = [
9524
10148
  commands: ALL,
9525
10149
  source: "packages/lint/src/validate-rls-predicate-enforceability.ts",
9526
10150
  surfaces: CLI_ONLY,
9527
- surfaceReason: "P2 (#4463): the rule reads `stack.permissions[]`, a stack-wide collection the per-write snapshot does not carry, and P1 gates `flow` alone. It is otherwise snapshot-ready \u2014 it needs no other collection \u2014 so widening it is a `runtimeTypes: ['permission_set']` edit once the gate builds that snapshot, not new wiring. Recorded as pending rather than done, because a rule that has never run at a door should not claim it.",
10151
+ 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.",
9528
10152
  run: (stack) => validateRlsPredicateEnforceability(stack)
9529
10153
  },
9530
10154
  // #4762 — the same "declared but enforces nothing" question, for the two
@@ -9582,8 +10206,34 @@ var TYPE_TO_STACK_KEY = {
9582
10206
  dashboard: "dashboards",
9583
10207
  agent: "agents",
9584
10208
  hook: "hooks",
9585
- seed: "seeds"
10209
+ // [#7576] `data`, NOT `seeds`. The metadata TYPE is `seed`; the stack KEY that
10210
+ // holds seeds is `data` (`ObjectStackDefinitionSchema.data: z.array(SeedSchema)`)
10211
+ // — a stack has no `seeds` key at all, and `PLURAL_TO_SINGULAR` declares no
10212
+ // mapping onto one either.
10213
+ //
10214
+ // The wrong spelling was INERT rather than harmless, and it is the #4449 shape
10215
+ // one surface over: the wiring guard asks only that a declared type HAS a
10216
+ // mapping, never that the mapping names a key some rule reads. So it would
10217
+ // have stayed green while the gate built `{ objects, seeds: [item] }` for
10218
+ // every seed write and every rule reading `stack.data` saw nothing — wired,
10219
+ // and running on nothing, with `rulesRun` reporting the rules as having run.
10220
+ // Nothing declared `seed` in `runtimeTypes` at the time, so correcting it
10221
+ // changed no behaviour then; it was corrected here, with the measurement
10222
+ // that found it (#7576), rather than left for the rollout card to trip
10223
+ // over. The ADR-0091 seed pair now DOES declare `seed` (#8307), so this
10224
+ // mapping is load-bearing today, not merely inert-and-correct.
10225
+ seed: "data",
10226
+ // [#8309] `permission`/`book` map ahead of their registration (#8310), the
10227
+ // same order `seed` arrived in: the mapping plus the enriched snapshot below
10228
+ // are this card's halves, and the `runtimeTypes` flip is deliberately NOT —
10229
+ // a mapping without a declaring rule is inert by construction (the gate
10230
+ // filters by `runtimeTypes` before it ever consults this table), while a
10231
+ // declaration without the mapping is the wired-onto-nothing state the wiring
10232
+ // guard refuses. Landing the mapping first keeps #8310 a registry data edit.
10233
+ permission: "permissions",
10234
+ book: "books"
9586
10235
  };
10236
+ var CONTEXT_STACK_KEYS = ["objects", "permissions", "books"];
9587
10237
  function runtimeAuthoringRulesFor(type) {
9588
10238
  return AUTHORING_RULES.filter(
9589
10239
  (r) => r.surfaces.includes("runtime-publish") && (r.runtimeTypes ?? []).includes(type)
@@ -9601,6 +10251,23 @@ function stackKeyForType(type) {
9601
10251
  return TYPE_TO_STACK_KEY[type] ?? null;
9602
10252
  }
9603
10253
  var fingerprint = (f) => `${f.rule}\0${f.where}\0${f.path}\0${f.message}`;
10254
+ function buildRuntimeWriteSnapshots(args) {
10255
+ const stackKey = stackKeyForType(args.type);
10256
+ if (!stackKey) return null;
10257
+ if (!args.item || typeof args.item !== "object") return null;
10258
+ const item = args.item;
10259
+ const itemName = typeof item.name === "string" ? item.name : void 0;
10260
+ const baseline = {};
10261
+ for (const key of CONTEXT_STACK_KEYS) {
10262
+ const collection = args.context?.[key] ?? [];
10263
+ baseline[key] = key === stackKey ? collection.filter((o) => !itemName || o?.name !== itemName) : collection;
10264
+ }
10265
+ const candidate = {
10266
+ ...baseline,
10267
+ [stackKey]: [...baseline[stackKey] ?? [], item]
10268
+ };
10269
+ return { baseline, candidate };
10270
+ }
9604
10271
  function runRules(rules, stack, ctx) {
9605
10272
  const findings = [];
9606
10273
  for (const rule of rules) {
@@ -9623,19 +10290,15 @@ function runRuntimeAuthoringRules(args) {
9623
10290
  const rules = runtimeAuthoringRulesFor(args.type);
9624
10291
  const empty = { errors: [], advisories: [], rulesRun: [] };
9625
10292
  if (rules.length === 0) return empty;
9626
- const stackKey = stackKeyForType(args.type);
9627
- if (!stackKey) return empty;
9628
- if (!args.item || typeof args.item !== "object") return empty;
9629
- const item = args.item;
9630
- const itemName = typeof item.name === "string" ? item.name : void 0;
9631
- const contextObjects = args.context?.objects ?? [];
10293
+ const snapshots = buildRuntimeWriteSnapshots({
10294
+ type: args.type,
10295
+ item: args.item,
10296
+ ...args.context !== void 0 ? { context: args.context } : {}
10297
+ });
10298
+ if (!snapshots) return empty;
9632
10299
  const ctx = { sduiManifest: args.sduiManifest };
9633
- const writesIntoContext = stackKey === "objects";
9634
- const baselineObjects = writesIntoContext ? contextObjects.filter((o) => !itemName || o?.name !== itemName) : contextObjects;
9635
- const baseline = { objects: baselineObjects };
9636
- const candidate = writesIntoContext ? { objects: [...baselineObjects, item] } : { objects: baselineObjects, [stackKey]: [item] };
9637
- const before = new Set(runRules(rules, baseline, ctx).map(fingerprint));
9638
- const added = runRules(rules, candidate, ctx).filter((f) => !before.has(fingerprint(f)));
10300
+ const before = new Set(runRules(rules, snapshots.baseline, ctx).map(fingerprint));
10301
+ const added = runRules(rules, snapshots.candidate, ctx).filter((f) => !before.has(fingerprint(f)));
9639
10302
  return {
9640
10303
  errors: added.filter((f) => f.severity === "error"),
9641
10304
  advisories: added.filter((f) => f.severity !== "error"),
@@ -9643,6 +10306,7 @@ function runRuntimeAuthoringRules(args) {
9643
10306
  };
9644
10307
  }
9645
10308
  export {
10309
+ buildRuntimeWriteSnapshots,
9646
10310
  runRuntimeAuthoringRules,
9647
10311
  runtimeAuthoringRulesFor,
9648
10312
  runtimeGatedTypes,