@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/index.cjs CHANGED
@@ -61,6 +61,7 @@ __export(index_exports, {
61
61
  DASHBOARD_ACTION_ROUTE_UNRESOLVED: () => DASHBOARD_ACTION_ROUTE_UNRESOLVED,
62
62
  DASHBOARD_ACTION_TARGET_UNDEFINED: () => DASHBOARD_ACTION_TARGET_UNDEFINED,
63
63
  DASHBOARD_FILTER_FIELD_UNKNOWN: () => DASHBOARD_FILTER_FIELD_UNKNOWN,
64
+ DASHBOARD_FILTER_FIELD_UNPROVISIONED: () => DASHBOARD_FILTER_FIELD_UNPROVISIONED,
64
65
  DEFAULT_AGENT_OUTSIDE_ROSTER: () => DEFAULT_AGENT_OUTSIDE_ROSTER,
65
66
  EXPRESSION_INVALID: () => EXPRESSION_INVALID,
66
67
  FIELD_GROUP_EMPTY: () => FIELD_GROUP_EMPTY,
@@ -87,6 +88,7 @@ __export(index_exports, {
87
88
  FLOW_NODE_WRITE_UNKNOWN_FIELD: () => FLOW_NODE_WRITE_UNKNOWN_FIELD,
88
89
  FLOW_PHANTOM_AGGREGATION: () => FLOW_PHANTOM_AGGREGATION,
89
90
  FLOW_RUNAS_UNSCOPED: () => FLOW_RUNAS_UNSCOPED,
91
+ FLOW_TEMPLATE_FIELD_UNPROVISIONED: () => FLOW_TEMPLATE_FIELD_UNPROVISIONED,
90
92
  FLOW_TEMPLATE_LOOKUP_TRAVERSAL: () => FLOW_TEMPLATE_LOOKUP_TRAVERSAL,
91
93
  FLOW_TEMPLATE_UNKNOWN_FIELD: () => FLOW_TEMPLATE_UNKNOWN_FIELD,
92
94
  FLOW_TIME_RELATIVE_ANTIPATTERN: () => FLOW_TIME_RELATIVE_ANTIPATTERN,
@@ -108,9 +110,11 @@ __export(index_exports, {
108
110
  LIST_VIEW_FILTERS_IN_VIEWS_MODE: () => LIST_VIEW_FILTERS_IN_VIEWS_MODE,
109
111
  LIVENESS_DEAD_PROPERTY: () => LIVENESS_DEAD_PROPERTY,
110
112
  LIVENESS_EXPERIMENTAL_PROPERTY: () => LIVENESS_EXPERIMENTAL_PROPERTY,
113
+ MANAGED_API_METHOD_UNAFFORDABLE: () => MANAGED_API_METHOD_UNAFFORDABLE,
111
114
  MAX_SCHEMA_WALK_DEPTH: () => MAX_SCHEMA_WALK_DEPTH,
112
115
  MEASURE_AGGREGATE_INCOHERENT: () => MEASURE_AGGREGATE_INCOHERENT,
113
116
  NAV_OBJECT_UNGRANTED: () => NAV_OBJECT_UNGRANTED,
117
+ NAV_OBJECT_UNSERVABLE: () => NAV_OBJECT_UNSERVABLE,
114
118
  NAV_TARGET_UNRESOLVED: () => NAV_TARGET_UNRESOLVED,
115
119
  NULL_GUARD_HINT: () => NULL_GUARD_HINT,
116
120
  OBJECT_REFERENCE_UNKNOWN: () => OBJECT_REFERENCE_UNKNOWN,
@@ -119,15 +123,18 @@ __export(index_exports, {
119
123
  ORG_AXIS_CROSS_ORG_BU_GRANT: () => ORG_AXIS_CROSS_ORG_BU_GRANT,
120
124
  ORG_AXIS_PERMISSION_INHERITANCE: () => ORG_AXIS_PERMISSION_INHERITANCE,
121
125
  PAGE_FIELD_UNKNOWN: () => PAGE_FIELD_UNKNOWN,
126
+ PAGE_FIELD_UNPROVISIONED: () => PAGE_FIELD_UNPROVISIONED,
122
127
  PAGE_SOURCE_CLASSNAME: () => PAGE_SOURCE_CLASSNAME,
123
128
  PREDICATE_PATH_UNRESOLVED: () => PREDICATE_PATH_UNRESOLVED,
124
129
  PREDICATE_PATH_UNROOTED: () => PREDICATE_PATH_UNROOTED,
130
+ PREDICATE_RHS_PATH_SHAPED: () => PREDICATE_RHS_PATH_SHAPED,
125
131
  PRE_SEAL_PHASES: () => PRE_SEAL_PHASES,
126
132
  REACT_BLOCK_NEEDS_RECORD_CONTEXT: () => REACT_BLOCK_NEEDS_RECORD_CONTEXT,
127
133
  REACT_CHART_AGGREGATE_INVALID: () => REACT_CHART_AGGREGATE_INVALID,
128
134
  REACT_CHART_AXIS_UNKNOWN: () => REACT_CHART_AXIS_UNKNOWN,
129
135
  REACT_CHART_DRILLDOWN_INVALID: () => REACT_CHART_DRILLDOWN_INVALID,
130
136
  REACT_CHART_FIELD_UNKNOWN: () => REACT_CHART_FIELD_UNKNOWN,
137
+ REACT_CHART_FIELD_UNPROVISIONED: () => REACT_CHART_FIELD_UNPROVISIONED,
131
138
  REFERENCE_INTEGRITY_RULES: () => REFERENCE_INTEGRITY_RULES,
132
139
  RLS_PREDICATE_OVER_BUDGET: () => RLS_PREDICATE_OVER_BUDGET,
133
140
  RLS_PREDICATE_UNENFORCEABLE: () => RLS_PREDICATE_UNENFORCEABLE,
@@ -138,6 +145,7 @@ __export(index_exports, {
138
145
  SEARCHABLE_FIELD_UNSEARCHABLE: () => SEARCHABLE_FIELD_UNSEARCHABLE,
139
146
  SECURITY_ANCHOR_HIGH_PRIVILEGE: () => SECURITY_ANCHOR_HIGH_PRIVILEGE,
140
147
  SECURITY_BOOK_AUDIENCE_UNKNOWN_SET: () => SECURITY_BOOK_AUDIENCE_UNKNOWN_SET,
148
+ SECURITY_CBP_NO_RELATION: () => SECURITY_CBP_NO_RELATION,
141
149
  SECURITY_DELEGATION_MISSING_REASON: () => SECURITY_DELEGATION_MISSING_REASON,
142
150
  SECURITY_EXTERNAL_WIDER: () => SECURITY_EXTERNAL_WIDER,
143
151
  SECURITY_FLS_UNQUALIFIED_KEY: () => SECURITY_FLS_UNQUALIFIED_KEY,
@@ -151,6 +159,7 @@ __export(index_exports, {
151
159
  SEED_INSERT_MODE_DUPLICATES_ON_REPLAY: () => SEED_INSERT_MODE_DUPLICATES_ON_REPLAY,
152
160
  SEED_VALUE_OUTSIDE_STATE_MACHINE: () => SEED_VALUE_OUTSIDE_STATE_MACHINE,
153
161
  SEMANTIC_ROLE_FIELD_UNKNOWN: () => SEMANTIC_ROLE_FIELD_UNKNOWN,
162
+ SEMANTIC_ROLE_FIELD_UNPROVISIONED: () => SEMANTIC_ROLE_FIELD_UNPROVISIONED,
154
163
  SHARING_RULE_RUNTIME_VARIABLE_CONDITION: () => SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
155
164
  SHARING_RULE_UNLOWERABLE_CONDITION: () => SHARING_RULE_UNLOWERABLE_CONDITION,
156
165
  STARTUP_OPEN_VOCABULARY_VERDICT: () => STARTUP_OPEN_VOCABULARY_VERDICT,
@@ -188,6 +197,7 @@ __export(index_exports, {
188
197
  WIDGET_MEASURE_UNKNOWN: () => WIDGET_MEASURE_UNKNOWN,
189
198
  authoringRulesFor: () => authoringRulesFor,
190
199
  buildAccessMatrix: () => buildAccessMatrix,
200
+ buildRuntimeWriteSnapshots: () => buildRuntimeWriteSnapshots,
191
201
  diffAccessMatrix: () => diffAccessMatrix,
192
202
  extractHookBodyWriteSet: () => extractHookBodyWriteSet,
193
203
  extractHookBodyWrites: () => extractHookBodyWrites,
@@ -231,7 +241,9 @@ __export(index_exports, {
231
241
  validateHookBodyWrites: () => validateHookBodyWrites,
232
242
  validateJsxPages: () => validateJsxPages,
233
243
  validateListViewMode: () => validateListViewMode,
244
+ validateManagedApiMethods: () => validateManagedApiMethods,
234
245
  validateNavAccess: () => validateNavAccess,
246
+ validateNavObjectServability: () => validateNavObjectServability,
235
247
  validateNavTargetRefs: () => validateNavTargetRefs,
236
248
  validateObjectReferences: () => validateObjectReferences,
237
249
  validateOrgAxisRedLines: () => validateOrgAxisRedLines,
@@ -249,6 +261,7 @@ __export(index_exports, {
249
261
  validateRuleSchemaFormats: () => validateRuleSchemaFormats,
250
262
  validateSearchableFields: () => validateSearchableFields,
251
263
  validateSecurityPosture: () => validateSecurityPosture,
264
+ validateSecurityRoleWord: () => validateSecurityRoleWord,
252
265
  validateSeedReplaySafety: () => validateSeedReplaySafety,
253
266
  validateSeedStateMachine: () => validateSeedStateMachine,
254
267
  validateSemanticRoles: () => validateSemanticRoles,
@@ -277,6 +290,34 @@ var SYSTEM_FIELDS = /* @__PURE__ */ new Set([
277
290
  function injectedColumnsFor(objectDef) {
278
291
  return (0, import_data.resolveInjectedSystemColumns)(objectDef).names;
279
292
  }
293
+ function unprovisionedInjectedColumnsFor(objectDef) {
294
+ return new Set((0, import_data.unprovisionedInjectedColumns)(objectDef));
295
+ }
296
+ function objectDefsOf(stack) {
297
+ if (!stack || typeof stack !== "object") return [];
298
+ const objects = stack.objects;
299
+ if (Array.isArray(objects)) return objects.filter((o) => !!o && typeof o === "object");
300
+ if (objects && typeof objects === "object") {
301
+ return Object.entries(objects).filter(([, def]) => !!def && typeof def === "object").map(([name, def]) => ({ name, ...def }));
302
+ }
303
+ return [];
304
+ }
305
+ function indexUnprovisionedAnchors(stack) {
306
+ const index = /* @__PURE__ */ new Map();
307
+ for (const obj of objectDefsOf(stack)) {
308
+ const name = typeof obj.name === "string" && obj.name.length > 0 ? obj.name : void 0;
309
+ if (!name) continue;
310
+ const anchors = unprovisionedInjectedColumnsFor(obj);
311
+ if (anchors.size > 0) index.set(name, anchors);
312
+ }
313
+ return index;
314
+ }
315
+ function unprovisionedAnchorCause(objectName, field) {
316
+ 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`;
317
+ }
318
+ function unprovisionedAnchorHint(objectName, field) {
319
+ 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).`;
320
+ }
280
321
 
281
322
  // src/validate-widget-bindings.ts
282
323
  var WIDGET_DATASET_UNKNOWN = "widget-dataset-unknown";
@@ -289,6 +330,7 @@ var MEASURE_AGGREGATE_INCOHERENT = "measure-aggregate-incoherent";
289
330
  var WIDGET_LEGACY_ANALYTICS_SHAPE = "widget-legacy-analytics-shape";
290
331
  var WIDGET_LEGACY_ANALYTICS_UNRENDERABLE = "widget-legacy-analytics-unrenderable";
291
332
  var DASHBOARD_FILTER_FIELD_UNKNOWN = "dashboard-filter-field-unknown";
333
+ var DASHBOARD_FILTER_FIELD_UNPROVISIONED = "dashboard-filter-field-unprovisioned";
292
334
  var LEGACY_ANALYTICS_KEYS = [
293
335
  "categoryField",
294
336
  "valueField",
@@ -408,6 +450,7 @@ function validateWidgetBindings(stack) {
408
450
  }
409
451
  objectFieldTypes.set(o.name, fm);
410
452
  }
453
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
411
454
  const datasetList = asArray(stack.datasets);
412
455
  for (let i = 0; i < datasetList.length; i++) {
413
456
  const ds = datasetList[i];
@@ -494,13 +537,24 @@ function validateWidgetBindings(stack) {
494
537
  if (dashFilterDefs.length > 0) {
495
538
  const datasetObject = typeof dataset.object === "string" ? dataset.object : void 0;
496
539
  const objectFields = datasetObject ? objectFieldTypes.get(datasetObject) : void 0;
497
- if (objectFields) {
540
+ const anchors = datasetObject ? unprovisionedAnchors.get(datasetObject) : void 0;
541
+ if (objectFields && datasetObject) {
498
542
  for (const def of dashFilterDefs) {
499
543
  const eff = effectiveFilterField(w, def);
500
544
  if (!eff) continue;
501
545
  const field = eff.field;
502
546
  if (field.includes(".")) continue;
503
- if (objectFields.has(field) || SYSTEM_FIELDS.has(field)) continue;
547
+ if (objectFields.has(field) || SYSTEM_FIELDS.has(field)) {
548
+ if (anchors?.has(field)) {
549
+ push2({
550
+ severity: "warning",
551
+ rule: DASHBOARD_FILTER_FIELD_UNPROVISIONED,
552
+ 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).`,
553
+ 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.`
554
+ });
555
+ }
556
+ continue;
557
+ }
504
558
  push2({
505
559
  severity: "error",
506
560
  rule: DASHBOARD_FILTER_FIELD_UNKNOWN,
@@ -833,6 +887,37 @@ function buildFieldIndex(objects) {
833
887
  }
834
888
  return idx;
835
889
  }
890
+ var BOUND_RECORD_ROOTS = ["record", "previous"];
891
+ function isCelNode(v) {
892
+ return !!v && typeof v === "object" && typeof v.op === "string";
893
+ }
894
+ function collectBoundRecordReads(source) {
895
+ const out = /* @__PURE__ */ new Map();
896
+ const ast = (0, import_formula2.parseCelToAst)(source);
897
+ if (!ast) return out;
898
+ const pending = [ast];
899
+ while (pending.length > 0) {
900
+ const celNode = pending.pop();
901
+ if (!isCelNode(celNode)) continue;
902
+ if ((celNode.op === "." || celNode.op === ".?") && Array.isArray(celNode.args) && celNode.args.length >= 2) {
903
+ const [celRecv, seg] = celNode.args;
904
+ if (typeof seg === "string" && isCelNode(celRecv) && celRecv.op === "id" && typeof celRecv.args === "string" && BOUND_RECORD_ROOTS.includes(celRecv.args)) {
905
+ if (!out.has(seg)) out.set(seg, `${celRecv.args}.${seg}`);
906
+ }
907
+ }
908
+ const celArgs = celNode.args;
909
+ if (isCelNode(celArgs)) pending.push(celArgs);
910
+ else if (Array.isArray(celArgs)) {
911
+ for (const a of celArgs) {
912
+ if (isCelNode(a)) pending.push(a);
913
+ else if (Array.isArray(a)) {
914
+ for (const b of a) if (isCelNode(b)) pending.push(b);
915
+ }
916
+ }
917
+ }
918
+ }
919
+ return out;
920
+ }
836
921
  function buildFieldTypeIndex(objects) {
837
922
  const idx = /* @__PURE__ */ new Map();
838
923
  for (const obj of objects) {
@@ -932,6 +1017,29 @@ function validateStackExpressions(stack) {
932
1017
  const fieldIndex = buildFieldIndex(objects);
933
1018
  const fieldTypeIndex = buildFieldTypeIndex(objects);
934
1019
  const nullableIndex = buildNullableFieldIndex(objects);
1020
+ const unprovisionedIndex = /* @__PURE__ */ new Map();
1021
+ for (const obj of objects) {
1022
+ const name = typeof obj.name === "string" ? obj.name : void 0;
1023
+ if (!name) continue;
1024
+ const anchors = unprovisionedInjectedColumnsFor(obj);
1025
+ if (anchors.size > 0) unprovisionedIndex.set(name, anchors);
1026
+ }
1027
+ const warnUnprovisionedAnchors = (where, raw, objectName) => {
1028
+ if (!objectName) return;
1029
+ const anchors = unprovisionedIndex.get(objectName);
1030
+ if (!anchors) return;
1031
+ const source = celSourceOf(raw);
1032
+ if (!source) return;
1033
+ for (const [field, operand] of collectBoundRecordReads(source)) {
1034
+ if (!anchors.has(field)) continue;
1035
+ issues.push({
1036
+ where,
1037
+ 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).`,
1038
+ source,
1039
+ severity: "warning"
1040
+ });
1041
+ }
1042
+ };
935
1043
  const checkNullGuards = (where, subject, raw, objectName, outcome = "fail-closed") => {
936
1044
  if (!objectName) return;
937
1045
  const nullableFields = nullableIndex.get(objectName);
@@ -958,6 +1066,7 @@ function validateStackExpressions(stack) {
958
1066
  );
959
1067
  for (const e of res.errors) issues.push({ where, message: e.message, source: e.source, severity: "error" });
960
1068
  for (const w of res.warnings) issues.push({ where, message: w.message, source: w.source, severity: "warning" });
1069
+ warnUnprovisionedAnchors(where, raw, objectName);
961
1070
  };
962
1071
  const FIELD_RULE_BOUND_ROOTS = ["record", "previous", "parent"];
963
1072
  const FIELD_RULE_USER_ROOTS = ["current_user", "user", "ctx", "os"];
@@ -1104,6 +1213,7 @@ function validateStackExpressions(stack) {
1104
1213
  const fieldWhere = `object '${objectName}' \xB7 field '${fname}' expression`;
1105
1214
  for (const e of res.errors) issues.push({ where: fieldWhere, message: e.message, source: e.source, severity: "error" });
1106
1215
  for (const w of res.warnings) issues.push({ where: fieldWhere, message: w.message, source: w.source, severity: "warning" });
1216
+ warnUnprovisionedAnchors(fieldWhere, f.expression, objectName);
1107
1217
  }
1108
1218
  }
1109
1219
  }
@@ -1651,6 +1761,43 @@ function validateFunctionalCompleteness(stack) {
1651
1761
  return out;
1652
1762
  }
1653
1763
 
1764
+ // src/validate-managed-api-methods.ts
1765
+ var import_data3 = require("@objectstack/spec/data");
1766
+ var MANAGED_API_METHOD_UNAFFORDABLE = "object/managed-api-method-unaffordable";
1767
+ var isRec2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1768
+ function entriesOf2(v) {
1769
+ if (Array.isArray(v)) {
1770
+ return v.flatMap(
1771
+ (def, i) => isRec2(def) ? [{ name: String(def.name ?? i), def, key: `[${i}]` }] : []
1772
+ );
1773
+ }
1774
+ if (isRec2(v)) {
1775
+ return Object.entries(v).flatMap(
1776
+ ([name, def]) => isRec2(def) ? [{ name, def: { name, ...def }, key: `.${name}` }] : []
1777
+ );
1778
+ }
1779
+ return [];
1780
+ }
1781
+ function validateManagedApiMethods(stack) {
1782
+ const out = [];
1783
+ if (!isRec2(stack)) return out;
1784
+ for (const [oi, obj] of entriesOf2(stack.objects).entries()) {
1785
+ const conflicts = (0, import_data3.checkManagedApiMethodAffordances)(obj.def);
1786
+ if (conflicts.length === 0) continue;
1787
+ const verbs = conflicts.map((c) => c.verb).join(", ");
1788
+ const flags = [...new Set(conflicts.map((c) => c.needs))];
1789
+ out.push({
1790
+ severity: "error",
1791
+ rule: MANAGED_API_METHOD_UNAFFORDABLE,
1792
+ where: `object "${obj.name}"`,
1793
+ path: `objects[${oi}].enable.apiMethods`,
1794
+ message: `\`managedBy: '${String(obj.def.managedBy)}'\` object "${obj.name}" ` + (0, import_data3.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.`,
1795
+ 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.`
1796
+ });
1797
+ }
1798
+ return out;
1799
+ }
1800
+
1654
1801
  // src/validate-flow-trigger-readiness.ts
1655
1802
  var import_automation2 = require("@objectstack/spec/automation");
1656
1803
  var FLOW_TRIGGER_UNKNOWN_OBJECT = "flow-trigger-unknown-object";
@@ -1835,7 +1982,7 @@ function validateFlowTriggerReadiness(stack) {
1835
1982
 
1836
1983
  // src/flow-walk.ts
1837
1984
  var import_automation3 = require("@objectstack/spec/automation");
1838
- function isRec2(v) {
1985
+ function isRec3(v) {
1839
1986
  return !!v && typeof v === "object" && !Array.isArray(v);
1840
1987
  }
1841
1988
  function strName(v) {
@@ -1850,7 +1997,7 @@ function flowNodeLabel(node, index) {
1850
1997
  return strName(node.label) ?? strName(node.id) ?? `#${index}`;
1851
1998
  }
1852
1999
  function stripRegions(config) {
1853
- if (!isRec2(config)) return void 0;
2000
+ if (!isRec3(config)) return void 0;
1854
2001
  let out;
1855
2002
  for (const key of Object.keys(config)) {
1856
2003
  if (!REGION_CONFIG_KEYS.has(key)) continue;
@@ -1861,11 +2008,11 @@ function stripRegions(config) {
1861
2008
  }
1862
2009
  function walkFlowNodes(flow, flowPath) {
1863
2010
  const out = [];
1864
- if (!isRec2(flow)) return out;
2011
+ if (!isRec3(flow)) return out;
1865
2012
  const visitList = (nodes, basePath, trail, depth) => {
1866
2013
  if (!Array.isArray(nodes) || depth > MAX_REGION_DEPTH) return;
1867
2014
  nodes.forEach((raw, index) => {
1868
- if (!isRec2(raw)) return;
2015
+ if (!isRec3(raw)) return;
1869
2016
  const path = `${basePath}[${index}]`;
1870
2017
  out.push({
1871
2018
  node: raw,
@@ -1876,7 +2023,7 @@ function walkFlowNodes(flow, flowPath) {
1876
2023
  });
1877
2024
  const type = strName(raw.type);
1878
2025
  const slots = type ? REGION_SLOTS.get(type) : void 0;
1879
- if (!slots || !isRec2(raw.config)) return;
2026
+ if (!slots || !isRec3(raw.config)) return;
1880
2027
  const config = raw.config;
1881
2028
  const here = `${type} "${flowNodeLabel(raw, index)}"`;
1882
2029
  for (const slot of slots) {
@@ -1884,7 +2031,7 @@ function walkFlowNodes(flow, flowPath) {
1884
2031
  if (slot === "branches") {
1885
2032
  if (!Array.isArray(value)) continue;
1886
2033
  value.forEach((branch, b) => {
1887
- if (!isRec2(branch)) return;
2034
+ if (!isRec3(branch)) return;
1888
2035
  const branchName = strName(branch.name) ?? `#${b}`;
1889
2036
  visitList(
1890
2037
  branch.nodes,
@@ -1895,7 +2042,7 @@ function walkFlowNodes(flow, flowPath) {
1895
2042
  });
1896
2043
  continue;
1897
2044
  }
1898
- if (!isRec2(value)) continue;
2045
+ if (!isRec3(value)) continue;
1899
2046
  visitList(
1900
2047
  value.nodes,
1901
2048
  `${path}.config.${slot}.nodes`,
@@ -1915,6 +2062,7 @@ function joinTrail(trail, segment) {
1915
2062
  // src/validate-flow-template-paths.ts
1916
2063
  var FLOW_TEMPLATE_UNKNOWN_FIELD = "flow-template-unknown-field";
1917
2064
  var FLOW_TEMPLATE_LOOKUP_TRAVERSAL = "flow-template-lookup-traversal";
2065
+ var FLOW_TEMPLATE_FIELD_UNPROVISIONED = "flow-template-field-unprovisioned";
1918
2066
  function asArray5(v) {
1919
2067
  if (Array.isArray(v)) return v;
1920
2068
  if (v && typeof v === "object") {
@@ -2057,6 +2205,7 @@ function validateFlowTemplatePaths(stack) {
2057
2205
  const obj = objectsByName.get(objectName);
2058
2206
  if (!obj) return;
2059
2207
  const fieldTypes = fieldTypesOf(obj);
2208
+ const unprovisionedAnchors = unprovisionedInjectedColumnsFor(obj);
2060
2209
  const expandSet = declaredExpandOf(flow);
2061
2210
  walkFlowNodes(flow, `flows[${flowIndex}]`).forEach(({ node, path: nodePath, regionTrail, localConfig }, walkIndex) => {
2062
2211
  const nodeLabel = typeof node.type === "string" ? node.type : typeof node.id === "string" ? node.id : `#${walkIndex}`;
@@ -2068,6 +2217,7 @@ function validateFlowTemplatePaths(stack) {
2068
2217
  if (leaves.length === 0) return;
2069
2218
  const seenUnknown = /* @__PURE__ */ new Set();
2070
2219
  const seenTraversal = /* @__PURE__ */ new Set();
2220
+ const seenUnprovisioned = /* @__PURE__ */ new Set();
2071
2221
  for (const leaf of leaves) {
2072
2222
  const inFilter = leaf.inFilter;
2073
2223
  for (const rest of recordRefsIn(leaf.text)) {
@@ -2075,6 +2225,19 @@ function validateFlowTemplatePaths(stack) {
2075
2225
  const hasSubPath = rest.length > 1;
2076
2226
  const nextIsIdentifier = hasSubPath && !/^\d+$/.test(rest[1]);
2077
2227
  const isKnown = fieldTypes.has(head) || IMPLICIT_HEADS.has(head);
2228
+ if (unprovisionedAnchors.has(head)) {
2229
+ if (!seenUnprovisioned.has(head)) {
2230
+ seenUnprovisioned.add(head);
2231
+ findings.push({
2232
+ severity: "warning",
2233
+ rule: FLOW_TEMPLATE_FIELD_UNPROVISIONED,
2234
+ where,
2235
+ path: nodePath,
2236
+ 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).`),
2237
+ hint: unprovisionedAnchorHint(objectName, head)
2238
+ });
2239
+ }
2240
+ }
2078
2241
  if (!isKnown) {
2079
2242
  if (seenUnknown.has(head)) continue;
2080
2243
  seenUnknown.add(head);
@@ -2219,10 +2382,32 @@ function containerViewCount(rec) {
2219
2382
  function validateViewContainers(stack) {
2220
2383
  const out = [];
2221
2384
  if (!stack || typeof stack !== "object") return out;
2385
+ const viewItems = stack.viewItems;
2386
+ if (viewItems != null && asEntries(viewItems).length > 0) {
2387
+ out.push({
2388
+ severity: "error",
2389
+ rule: VIEW_CONTAINER_SHAPE,
2390
+ where: "viewItems",
2391
+ path: "viewItems",
2392
+ 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.",
2393
+ 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."
2394
+ });
2395
+ }
2222
2396
  for (const { key, value } of asEntries(stack.views)) {
2223
2397
  if (!value || typeof value !== "object" || Array.isArray(value)) continue;
2224
2398
  const rec = value;
2225
- if (rec.viewKind != null) continue;
2399
+ if (rec.viewKind != null) {
2400
+ const label3 = typeof rec.name === "string" ? ` ("${rec.name}")` : "";
2401
+ out.push({
2402
+ severity: "error",
2403
+ rule: VIEW_CONTAINER_SHAPE,
2404
+ where: `views${key}${label3}`,
2405
+ path: `views${key}`,
2406
+ 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).",
2407
+ 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:`."
2408
+ });
2409
+ continue;
2410
+ }
2226
2411
  if (containerViewCount(rec) > 0) continue;
2227
2412
  const label2 = typeof rec.name === "string" ? ` ("${rec.name}")` : "";
2228
2413
  const hasContainerSlot = CONTAINER_SLOT_KEYS.some((k) => k in rec);
@@ -2644,10 +2829,10 @@ function validateReactPages(stack) {
2644
2829
  // src/validate-react-page-props.ts
2645
2830
  var import_node_module3 = require("module");
2646
2831
  var import_ui2 = require("@objectstack/spec/ui");
2647
- var import_data4 = require("@objectstack/spec/data");
2832
+ var import_data5 = require("@objectstack/spec/data");
2648
2833
 
2649
2834
  // src/validate-searchable-fields.ts
2650
- var import_data3 = require("@objectstack/spec/data");
2835
+ var import_data4 = require("@objectstack/spec/data");
2651
2836
  var SEARCHABLE_FIELD_UNKNOWN = "searchable-field-unknown";
2652
2837
  var SEARCHABLE_FIELD_UNSEARCHABLE = "searchable-field-unsearchable";
2653
2838
  function asArray10(v) {
@@ -2657,7 +2842,7 @@ function asArray10(v) {
2657
2842
  }
2658
2843
  return [];
2659
2844
  }
2660
- function isRec3(v) {
2845
+ function isRec4(v) {
2661
2846
  return !!v && typeof v === "object" && !Array.isArray(v);
2662
2847
  }
2663
2848
  function strName2(v) {
@@ -2695,7 +2880,7 @@ function resolveAllowedSet(target) {
2695
2880
  fields = { ...fields };
2696
2881
  for (const f of systemDeclared) fields[f] = {};
2697
2882
  }
2698
- const { allowed, source } = (0, import_data3.resolveSearchFieldResolution)({
2883
+ const { allowed, source } = (0, import_data4.resolveSearchFieldResolution)({
2699
2884
  fields,
2700
2885
  searchableFields: target.searchableFields,
2701
2886
  displayField: target.displayField
@@ -2733,7 +2918,7 @@ function distance(a, b) {
2733
2918
  }
2734
2919
  function indexObjectSearchTargets(stack) {
2735
2920
  const fieldsByObject = /* @__PURE__ */ new Map();
2736
- if (!isRec3(stack)) return fieldsByObject;
2921
+ if (!isRec4(stack)) return fieldsByObject;
2737
2922
  for (const obj of asArray10(stack.objects)) {
2738
2923
  const name = strName2(obj.name);
2739
2924
  if (name) fieldsByObject.set(name, declaredFieldTarget(obj));
@@ -2765,7 +2950,7 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
2765
2950
  });
2766
2951
  continue;
2767
2952
  }
2768
- if ((0, import_data3.isVirtualSearchField)(target.fields[name])) {
2953
+ if ((0, import_data4.isVirtualSearchField)(target.fields[name])) {
2769
2954
  const vtype = target.fields[name]?.type;
2770
2955
  findings.push({
2771
2956
  severity: "error",
@@ -2793,7 +2978,7 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
2793
2978
  }
2794
2979
  const isReference = meta?.type === "lookup" || meta?.type === "master_detail";
2795
2980
  let why;
2796
- if (import_data3.SEARCH_AUTO_EXCLUDED_FIELDS.has(name)) {
2981
+ if (import_data4.SEARCH_AUTO_EXCLUDED_FIELDS.has(name)) {
2797
2982
  why = "a system/audit column, which the auto-default set never includes";
2798
2983
  } else if (meta?.hidden) {
2799
2984
  why = "hidden";
@@ -2807,7 +2992,7 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
2807
2992
  rule: SEARCHABLE_FIELD_UNSEARCHABLE,
2808
2993
  where,
2809
2994
  path: `${path}[${i}]`,
2810
- message: `${subject} entry "${name}" on object "${objectName}" is ${why}. With no 'searchableFields' declared on the object, 'search' scans its text-like columns (${[...import_data3.SEARCHABLE_TEXTUAL_TYPES, ...import_data3.SEARCHABLE_ENUM_TYPES].join(" / ")}). Clients echo this declaration verbatim as the '$searchFields' override, and the runtime refuses it: every toolbar search on this list returns 400 INVALID_FIELD (#4254).`,
2995
+ message: `${subject} entry "${name}" on object "${objectName}" is ${why}. With no 'searchableFields' declared on the object, 'search' scans its text-like columns (${[...import_data4.SEARCHABLE_TEXTUAL_TYPES, ...import_data4.SEARCHABLE_ENUM_TYPES].join(" / ")}). Clients echo this declaration verbatim as the '$searchFields' override, and the runtime refuses it: every toolbar search on this list returns 400 INVALID_FIELD (#4254).`,
2811
2996
  hint: (isReference ? `A ${meta?.type} column stores only the referenced record's id, so it cannot be a keyword target \u2014 drop "${name}" from this view and, to search by the related record's title, mirror it onto a stored text field here and declare that instead. ` : `Drop "${name}" from this view, or target a text-like field instead. `) + `Declaring 'searchableFields' on object "${objectName}" chooses the searchable set explicitly.`
2812
2997
  });
2813
2998
  }
@@ -2815,7 +3000,7 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
2815
3000
  }
2816
3001
  function validateSearchableFields(stack) {
2817
3002
  const findings = [];
2818
- if (!isRec3(stack)) return findings;
3003
+ if (!isRec4(stack)) return findings;
2819
3004
  const objects = asArray10(stack.objects);
2820
3005
  const fieldsByObject = indexObjectSearchTargets(stack);
2821
3006
  const check = (declared, objectName, where, path, subject, role) => {
@@ -2825,7 +3010,7 @@ function validateSearchableFields(stack) {
2825
3010
  };
2826
3011
  for (let oi = 0; oi < objects.length; oi++) {
2827
3012
  const obj = objects[oi];
2828
- if (!isRec3(obj)) continue;
3013
+ if (!isRec4(obj)) continue;
2829
3014
  const objName = strName2(obj.name);
2830
3015
  const label2 = objName ? `object "${objName}"` : `objects[${oi}]`;
2831
3016
  check(
@@ -2836,9 +3021,9 @@ function validateSearchableFields(stack) {
2836
3021
  "searchableFields",
2837
3022
  "canonical"
2838
3023
  );
2839
- if (isRec3(obj.listViews)) {
3024
+ if (isRec4(obj.listViews)) {
2840
3025
  for (const [key, lv] of Object.entries(obj.listViews)) {
2841
- if (!isRec3(lv)) continue;
3026
+ if (!isRec4(lv)) continue;
2842
3027
  check(
2843
3028
  lv.searchableFields,
2844
3029
  // A built-in list view belongs to its object; an inline `data.object`
@@ -2855,10 +3040,10 @@ function validateSearchableFields(stack) {
2855
3040
  const views = asArray10(stack.views);
2856
3041
  for (let vi = 0; vi < views.length; vi++) {
2857
3042
  const view = views[vi];
2858
- if (!isRec3(view)) continue;
3043
+ if (!isRec4(view)) continue;
2859
3044
  const viewLabel2 = strName2(view.name) ?? strName2(view.objectName) ?? `#${vi}`;
2860
3045
  const viewObject = strName2(view.objectName) ?? strName2(view.object);
2861
- if (isRec3(view.list)) {
3046
+ if (isRec4(view.list)) {
2862
3047
  check(
2863
3048
  view.list.searchableFields,
2864
3049
  listViewObject(view.list) ?? viewObject,
@@ -2868,9 +3053,9 @@ function validateSearchableFields(stack) {
2868
3053
  "narrowing"
2869
3054
  );
2870
3055
  }
2871
- if (isRec3(view.listViews)) {
3056
+ if (isRec4(view.listViews)) {
2872
3057
  for (const [key, lv] of Object.entries(view.listViews)) {
2873
- if (!isRec3(lv)) continue;
3058
+ if (!isRec4(lv)) continue;
2874
3059
  check(
2875
3060
  lv.searchableFields,
2876
3061
  listViewObject(lv) ?? viewObject,
@@ -2886,11 +3071,11 @@ function validateSearchableFields(stack) {
2886
3071
  }
2887
3072
  function listViewObject(listView) {
2888
3073
  const data = listView.data;
2889
- return isRec3(data) ? strName2(data.object) : void 0;
3074
+ return isRec4(data) ? strName2(data.object) : void 0;
2890
3075
  }
2891
3076
 
2892
3077
  // src/page-walk.ts
2893
- function isRec4(v) {
3078
+ function isRec5(v) {
2894
3079
  return !!v && typeof v === "object" && !Array.isArray(v);
2895
3080
  }
2896
3081
  function strName3(v) {
@@ -2903,19 +3088,19 @@ function isSourceAuthoredPage(page) {
2903
3088
  }
2904
3089
  function walkPageComponents(page, pagePath) {
2905
3090
  const out = [];
2906
- if (!isRec4(page) || isSourceAuthoredPage(page)) return out;
3091
+ if (!isRec5(page) || isSourceAuthoredPage(page)) return out;
2907
3092
  const pageObject = strName3(page.object);
2908
3093
  const visit = (node, path, inheritedObject) => {
2909
- if (!isRec4(node)) return;
2910
- const props = isRec4(node.properties) ? node.properties : void 0;
2911
- const dataSource = isRec4(node.dataSource) ? node.dataSource : void 0;
3094
+ if (!isRec5(node)) return;
3095
+ const props = isRec5(node.properties) ? node.properties : void 0;
3096
+ const dataSource = isRec5(node.dataSource) ? node.dataSource : void 0;
2912
3097
  const objectName = strName3(dataSource?.object) ?? strName3(props?.object) ?? inheritedObject;
2913
3098
  out.push({ component: node, path, objectName });
2914
3099
  if (!props) return;
2915
3100
  if (Array.isArray(props.items)) {
2916
3101
  for (let i = 0; i < props.items.length; i++) {
2917
3102
  const item = props.items[i];
2918
- if (!isRec4(item) || !Array.isArray(item.children)) continue;
3103
+ if (!isRec5(item) || !Array.isArray(item.children)) continue;
2919
3104
  for (let c = 0; c < item.children.length; c++) {
2920
3105
  visit(item.children[c], `${path}.properties.items[${i}].children[${c}]`, objectName);
2921
3106
  }
@@ -2937,12 +3122,12 @@ function walkPageComponents(page, pagePath) {
2937
3122
  const regions = Array.isArray(page.regions) ? page.regions : [];
2938
3123
  for (let r = 0; r < regions.length; r++) {
2939
3124
  const region = regions[r];
2940
- if (!isRec4(region) || !Array.isArray(region.components)) continue;
3125
+ if (!isRec5(region) || !Array.isArray(region.components)) continue;
2941
3126
  for (let c = 0; c < region.components.length; c++) {
2942
3127
  visit(region.components[c], `${pagePath}.regions[${r}].components[${c}]`, pageObject);
2943
3128
  }
2944
3129
  }
2945
- const slots = isRec4(page.slots) ? page.slots : void 0;
3130
+ const slots = isRec5(page.slots) ? page.slots : void 0;
2946
3131
  if (slots) {
2947
3132
  for (const [slot, value] of Object.entries(slots)) {
2948
3133
  const list3 = Array.isArray(value) ? value : [value];
@@ -2957,6 +3142,7 @@ function walkPageComponents(page, pagePath) {
2957
3142
 
2958
3143
  // src/validate-page-field-bindings.ts
2959
3144
  var PAGE_FIELD_UNKNOWN = "page-field-unknown";
3145
+ var PAGE_FIELD_UNPROVISIONED = "page-field-unprovisioned";
2960
3146
  function asArray11(v) {
2961
3147
  if (Array.isArray(v)) return v;
2962
3148
  if (v && typeof v === "object") {
@@ -2967,7 +3153,7 @@ function asArray11(v) {
2967
3153
  function strName4(v) {
2968
3154
  return typeof v === "string" && v.length > 0 ? v : void 0;
2969
3155
  }
2970
- function isRec5(v) {
3156
+ function isRec6(v) {
2971
3157
  return !!v && typeof v === "object" && !Array.isArray(v);
2972
3158
  }
2973
3159
  function fieldRefsFrom(value, basePath) {
@@ -2978,7 +3164,7 @@ function fieldRefsFrom(value, basePath) {
2978
3164
  out.push({ name: bare, path });
2979
3165
  return;
2980
3166
  }
2981
- if (!isRec5(v)) return;
3167
+ if (!isRec6(v)) return;
2982
3168
  const named = strName4(v.field) ?? strName4(v.name);
2983
3169
  if (named) out.push({ name: named, path: `${path}.${strName4(v.field) ? "field" : "name"}` });
2984
3170
  };
@@ -3036,15 +3222,15 @@ function componentFieldRefs(type, props, basePath, sep = ".") {
3036
3222
  const sections = Array.isArray(props[key]) ? props[key] : [];
3037
3223
  for (let si = 0; si < sections.length; si++) {
3038
3224
  const section = sections[si];
3039
- if (!isRec5(section)) continue;
3225
+ if (!isRec6(section)) continue;
3040
3226
  refs.push(...fieldRefsFrom(section.fields, `${basePath}${sep}${key}[${si}].fields`));
3041
3227
  }
3042
3228
  }
3043
3229
  return refs;
3044
3230
  }
3045
3231
  function relatedListFieldRefs(props, basePath, sep = ".") {
3046
- const add = isRec5(props.add) ? props.add : void 0;
3047
- const picker = add && isRec5(add.picker) ? add.picker : void 0;
3232
+ const add = isRec6(props.add) ? props.add : void 0;
3233
+ const picker = add && isRec6(add.picker) ? add.picker : void 0;
3048
3234
  const at = (key) => `${basePath}${sep}${key}`;
3049
3235
  return {
3050
3236
  relatedObject: strName4(props.objectName),
@@ -3065,7 +3251,7 @@ function relatedListFieldRefs(props, basePath, sep = ".") {
3065
3251
  }
3066
3252
  function indexObjectFields(stack) {
3067
3253
  const objectFields = /* @__PURE__ */ new Map();
3068
- if (!isRec5(stack)) return objectFields;
3254
+ if (!isRec6(stack)) return objectFields;
3069
3255
  for (const obj of asArray11(stack.objects)) {
3070
3256
  const name = strName4(obj.name);
3071
3257
  if (!name) continue;
@@ -3078,14 +3264,27 @@ function indexObjectFields(stack) {
3078
3264
  }
3079
3265
  return objectFields;
3080
3266
  }
3081
- function checkFieldRefs(refs, objectName, objectFields, where, consequence2 = "skipped") {
3267
+ function checkFieldRefs(refs, objectName, objectFields, where, consequence2 = "skipped", unprovisionedAnchors) {
3082
3268
  const findings = [];
3083
3269
  if (!objectName) return findings;
3084
3270
  const known = objectFields.get(objectName);
3085
3271
  if (!known) return findings;
3272
+ const anchors = unprovisionedAnchors?.get(objectName);
3086
3273
  for (const ref of refs) {
3087
3274
  if (ref.name.includes(".")) continue;
3088
- if (known.has(ref.name) || SYSTEM_FIELDS.has(ref.name)) continue;
3275
+ if (known.has(ref.name) || SYSTEM_FIELDS.has(ref.name)) {
3276
+ if (anchors?.has(ref.name)) {
3277
+ findings.push({
3278
+ severity: "warning",
3279
+ rule: PAGE_FIELD_UNPROVISIONED,
3280
+ where,
3281
+ path: ref.path,
3282
+ 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."),
3283
+ hint: unprovisionedAnchorHint(objectName, ref.name)
3284
+ });
3285
+ }
3286
+ continue;
3287
+ }
3089
3288
  findings.push({
3090
3289
  severity: consequence2 === "queried" ? "error" : "warning",
3091
3290
  rule: PAGE_FIELD_UNKNOWN,
@@ -3101,17 +3300,20 @@ function validatePageFieldBindings(stack) {
3101
3300
  const findings = [];
3102
3301
  if (!stack || typeof stack !== "object") return findings;
3103
3302
  const objectFields = indexObjectFields(stack);
3303
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
3104
3304
  const pages = asArray11(stack.pages);
3105
3305
  for (let pi = 0; pi < pages.length; pi++) {
3106
3306
  const page = pages[pi];
3107
3307
  if (!page || typeof page !== "object") continue;
3108
3308
  const pageName = strName4(page.name) ?? `#${pi}`;
3109
3309
  const checkRefs = (refs, objectName, where) => {
3110
- findings.push(...checkFieldRefs(refs, objectName, objectFields, where));
3310
+ findings.push(
3311
+ ...checkFieldRefs(refs, objectName, objectFields, where, "skipped", unprovisionedAnchors)
3312
+ );
3111
3313
  };
3112
3314
  for (const { component, path, objectName } of walkPageComponents(page, `pages[${pi}]`)) {
3113
3315
  const type = strName4(component.type);
3114
- const props = isRec5(component.properties) ? component.properties : void 0;
3316
+ const props = isRec6(component.properties) ? component.properties : void 0;
3115
3317
  if (!type || !props) continue;
3116
3318
  const where = `page "${pageName}" \xB7 ${type}`;
3117
3319
  const base = `${path}.properties`;
@@ -3126,7 +3328,7 @@ function validatePageFieldBindings(stack) {
3126
3328
  if (!refs) continue;
3127
3329
  checkRefs(refs, objectName, where);
3128
3330
  }
3129
- const cfg = isRec5(page.interfaceConfig) ? page.interfaceConfig : void 0;
3331
+ const cfg = isRec6(page.interfaceConfig) ? page.interfaceConfig : void 0;
3130
3332
  if (cfg) {
3131
3333
  const cfgObject = strName4(cfg.source) ?? strName4(page.object);
3132
3334
  const base = `pages[${pi}].interfaceConfig`;
@@ -3135,7 +3337,7 @@ function validatePageFieldBindings(stack) {
3135
3337
  ...sortFieldRefs(cfg.sort, `${base}.sort`),
3136
3338
  ...fieldRefsFrom(cfg.filterBy, `${base}.filterBy`)
3137
3339
  ];
3138
- const userFilters = isRec5(cfg.userFilters) ? cfg.userFilters : void 0;
3340
+ const userFilters = isRec6(cfg.userFilters) ? cfg.userFilters : void 0;
3139
3341
  if (userFilters) {
3140
3342
  refs.push(...fieldRefsFrom(userFilters.fields, `${base}.userFilters.fields`));
3141
3343
  }
@@ -3146,11 +3348,11 @@ function validatePageFieldBindings(stack) {
3146
3348
  }
3147
3349
 
3148
3350
  // src/zod-issue-format.ts
3149
- var isRec6 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
3351
+ var isRec7 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
3150
3352
  var valueAtPath = (root, path) => {
3151
3353
  let cur = root;
3152
3354
  for (const key of path) {
3153
- if (!isRec6(cur) && !Array.isArray(cur)) return void 0;
3355
+ if (!isRec7(cur) && !Array.isArray(cur)) return void 0;
3154
3356
  cur = cur[key];
3155
3357
  }
3156
3358
  return cur;
@@ -3284,12 +3486,13 @@ function filterAttrValue(tsc, sf, attr) {
3284
3486
  return perPosition(init.expression);
3285
3487
  }
3286
3488
  var REACT_CHART_FIELD_UNKNOWN = "react-chart-field-unknown";
3489
+ var REACT_CHART_FIELD_UNPROVISIONED = "react-chart-field-unprovisioned";
3287
3490
  var REACT_CHART_AGGREGATE_INVALID = "react-chart-aggregate-invalid";
3288
3491
  var REACT_CHART_AXIS_UNKNOWN = "react-chart-axis-unknown";
3289
3492
  var REACT_CHART_DRILLDOWN_INVALID = "react-chart-drilldown-invalid";
3290
3493
  function checkChartDrillDown(raw, push2) {
3291
3494
  if (raw === void 0 || raw === NOT_STATIC) return;
3292
- if (!isRec7(raw)) {
3495
+ if (!isRec8(raw)) {
3293
3496
  push2(
3294
3497
  "error",
3295
3498
  REACT_CHART_DRILLDOWN_INVALID,
@@ -3312,7 +3515,7 @@ function checkChartDrillDown(raw, push2) {
3312
3515
  }
3313
3516
  function checkChartAggregate(raw, push2) {
3314
3517
  if (raw === void 0 || raw === NOT_STATIC) return;
3315
- if (!isRec7(raw)) {
3518
+ if (!isRec8(raw)) {
3316
3519
  push2(
3317
3520
  "error",
3318
3521
  REACT_CHART_AGGREGATE_INVALID,
@@ -3343,9 +3546,9 @@ function checkChartAggregate(raw, push2) {
3343
3546
  );
3344
3547
  }
3345
3548
  }
3346
- var isRec7 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
3549
+ var isRec8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
3347
3550
  var strOf = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
3348
- function checkObjectChart(attrs, objectFields, findings) {
3551
+ function checkObjectChart(attrs, objectFields, findings, unprovisionedAnchors = /* @__PURE__ */ new Map()) {
3349
3552
  const { values, where, path } = attrs;
3350
3553
  const push2 = (severity, rule, message, hint) => findings.push({ severity, rule, where, path, message, hint });
3351
3554
  checkChartDrillDown(values.get("drillDown"), push2);
@@ -3353,18 +3556,29 @@ function checkObjectChart(attrs, objectFields, findings) {
3353
3556
  const aggregate = values.get("aggregate");
3354
3557
  checkChartAggregate(aggregate, push2);
3355
3558
  if (aggregate === void 0 || aggregate === NOT_STATIC) return;
3356
- if (!isRec7(aggregate)) return;
3559
+ if (!isRec8(aggregate)) return;
3357
3560
  const fn = strOf(aggregate.function);
3358
3561
  const field = strOf(aggregate.field);
3359
3562
  const groupBy = aggregate.groupBy;
3360
- const groupByField = strOf(groupBy) ?? (isRec7(groupBy) ? strOf(groupBy.field) : void 0);
3563
+ const groupByField = strOf(groupBy) ?? (isRec8(groupBy) ? strOf(groupBy.field) : void 0);
3361
3564
  const objectName = strOf(values.get("objectName"));
3362
3565
  const known = objectName ? objectFields.get(objectName) : void 0;
3363
3566
  if (objectName && known) {
3567
+ const anchors = unprovisionedAnchors.get(objectName);
3364
3568
  const fieldRef = (name, prop) => {
3365
3569
  if (!name) return;
3366
3570
  if (name.includes(".")) return;
3367
- if (known.has(name) || SYSTEM_FIELDS.has(name)) return;
3571
+ if (known.has(name) || SYSTEM_FIELDS.has(name)) {
3572
+ if (anchors?.has(name)) {
3573
+ push2(
3574
+ "warning",
3575
+ REACT_CHART_FIELD_UNPROVISIONED,
3576
+ `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.`,
3577
+ unprovisionedAnchorHint(objectName, name)
3578
+ );
3579
+ }
3580
+ return;
3581
+ }
3368
3582
  push2(
3369
3583
  "error",
3370
3584
  REACT_CHART_FIELD_UNKNOWN,
@@ -3390,18 +3604,18 @@ function checkObjectChart(attrs, objectFields, findings) {
3390
3604
  );
3391
3605
  };
3392
3606
  const xAxisRaw = values.get("xAxis");
3393
- const categoryAxis = strOf(values.get("xAxisKey")) ?? strOf(xAxisRaw) ?? (isRec7(xAxisRaw) ? strOf(xAxisRaw.field) : void 0);
3607
+ const categoryAxis = strOf(values.get("xAxisKey")) ?? strOf(xAxisRaw) ?? (isRec8(xAxisRaw) ? strOf(xAxisRaw.field) : void 0);
3394
3608
  const categoryProp = values.has("xAxisKey") ? "xAxisKey" : "xAxis.field";
3395
3609
  axisRef(categoryAxis, categoryProp);
3396
3610
  const yAxisRaw = values.get("yAxis");
3397
3611
  const yAxisList = Array.isArray(yAxisRaw) ? yAxisRaw : yAxisRaw !== void 0 ? [yAxisRaw] : [];
3398
3612
  for (const a of yAxisList) {
3399
- axisRef(strOf(a) ?? (isRec7(a) ? strOf(a.field) : void 0), "yAxis[].field");
3613
+ axisRef(strOf(a) ?? (isRec8(a) ? strOf(a.field) : void 0), "yAxis[].field");
3400
3614
  }
3401
3615
  const series = values.get("series");
3402
3616
  if (Array.isArray(series)) {
3403
3617
  for (const s of series) {
3404
- if (!isRec7(s)) continue;
3618
+ if (!isRec8(s)) continue;
3405
3619
  const dataKey = strOf(s.dataKey);
3406
3620
  axisRef(dataKey ?? strOf(s.name), dataKey ? "series[].dataKey" : "series[].name");
3407
3621
  }
@@ -3457,7 +3671,7 @@ function subformFieldRefs(value, basePath) {
3457
3671
  if (!Array.isArray(value)) return { child, parent };
3458
3672
  for (let i = 0; i < value.length; i++) {
3459
3673
  const sub = value[i];
3460
- if (!isRec7(sub)) continue;
3674
+ if (!isRec8(sub)) continue;
3461
3675
  const at = (key) => `${basePath}[${i}].${key}`;
3462
3676
  child.push({
3463
3677
  objectName: strOf(sub.childObject),
@@ -3482,7 +3696,7 @@ function filterFieldRefs(node, basePath, out) {
3482
3696
  for (let i = 0; i < node.length; i++) filterFieldRefs(node[i], `${basePath}[${i}]`, out);
3483
3697
  return;
3484
3698
  }
3485
- if (typeof head === "string" && head.length > 0 && node.length >= 2 && typeof node[1] === "string" && import_data4.VALID_AST_OPERATORS.has(node[1].toLowerCase())) {
3699
+ if (typeof head === "string" && head.length > 0 && node.length >= 2 && typeof node[1] === "string" && import_data5.VALID_AST_OPERATORS.has(node[1].toLowerCase())) {
3486
3700
  out.push({ name: head, path: `${basePath}[0]` });
3487
3701
  }
3488
3702
  }
@@ -3502,20 +3716,20 @@ function reactFieldRefs(spec, values, basePath) {
3502
3716
  }
3503
3717
  for (const key of spec.nestedFields ?? []) {
3504
3718
  const v = readable(key);
3505
- if (isRec7(v)) own.push(...fieldRefsFrom(v.fields, at(`${key}.fields`)));
3719
+ if (isRec8(v)) own.push(...fieldRefsFrom(v.fields, at(`${key}.fields`)));
3506
3720
  }
3507
3721
  for (const key of spec.sections ?? []) {
3508
3722
  const v = readable(key);
3509
3723
  if (!Array.isArray(v)) continue;
3510
3724
  for (let i = 0; i < v.length; i++) {
3511
3725
  const section = v[i];
3512
- if (!isRec7(section)) continue;
3726
+ if (!isRec8(section)) continue;
3513
3727
  own.push(...fieldRefsFrom(section.fields, at(`${key}[${i}].fields`)));
3514
3728
  }
3515
3729
  }
3516
3730
  for (const key of spec.keyedByField ?? []) {
3517
3731
  const v = readable(key);
3518
- if (!isRec7(v)) continue;
3732
+ if (!isRec8(v)) continue;
3519
3733
  for (const k of Object.keys(v)) own.push({ name: k, path: at(`${key}.${k}`) });
3520
3734
  }
3521
3735
  for (const key of spec.filterArrays ?? []) {
@@ -3523,22 +3737,30 @@ function reactFieldRefs(spec, values, basePath) {
3523
3737
  }
3524
3738
  return { own, queried };
3525
3739
  }
3526
- function checkBlockFieldProps(tag, values, objectFields, where, path) {
3740
+ function checkBlockFieldProps(tag, values, objectFields, where, path, unprovisionedAnchors) {
3527
3741
  const objectName = strOf(values.get("objectName"));
3528
3742
  const out = [];
3529
3743
  const spec = REACT_FIELD_SPECS[tag];
3530
3744
  if (spec) {
3531
3745
  const { own, queried } = reactFieldRefs(spec, values, path);
3532
- out.push(...checkFieldRefs(own, objectName, objectFields, where));
3533
- out.push(...checkFieldRefs(queried, objectName, objectFields, where, "queried"));
3746
+ out.push(
3747
+ ...checkFieldRefs(own, objectName, objectFields, where, "skipped", unprovisionedAnchors)
3748
+ );
3749
+ out.push(
3750
+ ...checkFieldRefs(queried, objectName, objectFields, where, "queried", unprovisionedAnchors)
3751
+ );
3534
3752
  }
3535
3753
  if (tag === "ObjectForm") {
3536
3754
  const raw = values.get("subforms");
3537
3755
  const subs = subformFieldRefs(raw === NOT_STATIC ? void 0 : raw, `${path}${PATH_SEP}subforms`);
3538
3756
  for (const sub of subs.child) {
3539
- out.push(...checkFieldRefs(sub.refs, sub.objectName, objectFields, where));
3757
+ out.push(
3758
+ ...checkFieldRefs(sub.refs, sub.objectName, objectFields, where, "skipped", unprovisionedAnchors)
3759
+ );
3540
3760
  }
3541
- out.push(...checkFieldRefs(subs.parent, objectName, objectFields, where));
3761
+ out.push(
3762
+ ...checkFieldRefs(subs.parent, objectName, objectFields, where, "skipped", unprovisionedAnchors)
3763
+ );
3542
3764
  }
3543
3765
  const schemaType = tag === "Block" ? strOf(values.get("type")) : SCHEMA_TYPE_BY_TAG.get(tag);
3544
3766
  if (schemaType && COMPONENT_FIELD_SPECS[schemaType]) {
@@ -3547,7 +3769,9 @@ function checkBlockFieldProps(tag, values, objectFields, where, path) {
3547
3769
  componentFieldRefs(schemaType, readableProps(values), path, PATH_SEP) ?? [],
3548
3770
  objectName,
3549
3771
  objectFields,
3550
- where
3772
+ where,
3773
+ "skipped",
3774
+ unprovisionedAnchors
3551
3775
  )
3552
3776
  );
3553
3777
  }
@@ -3579,6 +3803,7 @@ function localComponentNames(tsc, sf) {
3579
3803
  function validateReactPageProps(stack) {
3580
3804
  const findings = [];
3581
3805
  const objectFields = indexObjectFields(stack);
3806
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
3582
3807
  const searchTargets = indexObjectSearchTargets(stack);
3583
3808
  const pages = asArray12(stack.pages);
3584
3809
  for (let p = 0; p < pages.length; p++) {
@@ -3661,7 +3886,7 @@ function validateReactPageProps(stack) {
3661
3886
  }
3662
3887
  }
3663
3888
  if (tag === "ObjectChart" && !hasSpread) {
3664
- checkObjectChart({ values, where, path }, objectFields, findings);
3889
+ checkObjectChart({ values, where, path }, objectFields, findings, unprovisionedAnchors);
3665
3890
  }
3666
3891
  if (tag === "ListView" && !hasSpread) {
3667
3892
  findings.push(
@@ -3677,7 +3902,7 @@ function validateReactPageProps(stack) {
3677
3902
  }
3678
3903
  if (!hasSpread) {
3679
3904
  findings.push(
3680
- ...checkBlockFieldProps(tag, values, objectFields, where, path)
3905
+ ...checkBlockFieldProps(tag, values, objectFields, where, path, unprovisionedAnchors)
3681
3906
  );
3682
3907
  }
3683
3908
  }
@@ -3721,7 +3946,7 @@ function validatePageSourceStyling(stack) {
3721
3946
  }
3722
3947
 
3723
3948
  // src/validate-record-title.ts
3724
- var import_data5 = require("@objectstack/spec/data");
3949
+ var import_data6 = require("@objectstack/spec/data");
3725
3950
  var TITLE_FORMAT_RETIRED = "title-format-retired";
3726
3951
  var TITLE_UNRESOLVABLE = "title-unresolvable";
3727
3952
  function asArray14(v) {
@@ -3749,7 +3974,7 @@ function validateRecordTitle(stack) {
3749
3974
  hint: `titleFormat is a render-only template the server cannot return or query, and an explicit nameField now takes precedence. For a single-field title set nameField: '<field>'. For a composite title, add a formula field (returnType: 'text') and designate it via nameField.`
3750
3975
  });
3751
3976
  }
3752
- const completeness = (0, import_data5.objectTitleCompleteness)(obj);
3977
+ const completeness = (0, import_data6.objectTitleCompleteness)(obj);
3753
3978
  if (completeness.status === "none") {
3754
3979
  findings.push({
3755
3980
  severity: "warning",
@@ -3769,6 +3994,7 @@ var FIELD_GROUP_UNDECLARED = "field-group-undeclared";
3769
3994
  var FIELD_GROUP_EMPTY = "field-group-empty";
3770
3995
  var FIELD_GROUP_SHADOWED = "field-group-shadowed";
3771
3996
  var SEMANTIC_ROLE_FIELD_UNKNOWN = "semantic-role-field-unknown";
3997
+ var SEMANTIC_ROLE_FIELD_UNPROVISIONED = "semantic-role-field-unprovisioned";
3772
3998
  function asArray15(v) {
3773
3999
  if (Array.isArray(v)) return v;
3774
4000
  if (v && typeof v === "object") {
@@ -3787,6 +4013,15 @@ function validateSemanticRoles(stack) {
3787
4013
  const path = `objects[${i}]`;
3788
4014
  const fields = obj.fields && typeof obj.fields === "object" && !Array.isArray(obj.fields) ? obj.fields : {};
3789
4015
  const fieldNames = /* @__PURE__ */ new Set([...Object.keys(fields), ...injectedColumnsFor(obj)]);
4016
+ const unprovisioned = unprovisionedInjectedColumnsFor(obj);
4017
+ const unprovisionedPointer = (slot, entry) => ({
4018
+ severity: "warning",
4019
+ rule: SEMANTIC_ROLE_FIELD_UNPROVISIONED,
4020
+ where,
4021
+ path: `${path}.${slot}`,
4022
+ 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.`,
4023
+ 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.`
4024
+ });
3790
4025
  const declaredGroups = new Set(
3791
4026
  (Array.isArray(obj.fieldGroups) ? obj.fieldGroups : []).filter((g) => !!g && typeof g === "object").map((g) => g.key).filter((k) => typeof k === "string" && k.length > 0)
3792
4027
  );
@@ -3828,10 +4063,16 @@ function validateSemanticRoles(stack) {
3828
4063
  message: `${objName}: stageField "${stage}" is not a field on this object \u2014 consumers fall back to heuristic stage detection`,
3829
4064
  hint: `Point stageField at an existing select/status field, or set stageField: false to declare the object has no linear lifecycle.`
3830
4065
  });
4066
+ } else if (typeof stage === "string" && unprovisioned.has(stage)) {
4067
+ findings.push(unprovisionedPointer("stageField", stage));
3831
4068
  }
3832
4069
  const highlights = Array.isArray(obj.highlightFields) ? obj.highlightFields : Array.isArray(obj.compactLayout) ? obj.compactLayout : [];
3833
4070
  for (const entry of highlights) {
3834
- if (typeof entry !== "string" || entry.length === 0 || fieldNames.has(entry)) continue;
4071
+ if (typeof entry !== "string" || entry.length === 0) continue;
4072
+ if (fieldNames.has(entry)) {
4073
+ if (unprovisioned.has(entry)) findings.push(unprovisionedPointer("highlightFields", entry));
4074
+ continue;
4075
+ }
3835
4076
  findings.push({
3836
4077
  severity: "warning",
3837
4078
  rule: SEMANTIC_ROLE_FIELD_UNKNOWN,
@@ -3871,45 +4112,45 @@ function validateSemanticRoles(stack) {
3871
4112
  }
3872
4113
 
3873
4114
  // src/collection-entries.ts
3874
- function isRec8(v) {
4115
+ function isRec9(v) {
3875
4116
  return !!v && typeof v === "object" && !Array.isArray(v);
3876
4117
  }
3877
4118
  function collectionEntries(v, base) {
3878
4119
  if (Array.isArray(v)) {
3879
4120
  const out = [];
3880
4121
  for (let i = 0; i < v.length; i++) {
3881
- if (isRec8(v[i])) out.push({ rec: v[i], path: `${base}[${i}]` });
4122
+ if (isRec9(v[i])) out.push({ rec: v[i], path: `${base}[${i}]` });
3882
4123
  }
3883
4124
  return out;
3884
4125
  }
3885
- if (isRec8(v)) {
3886
- return Object.entries(v).filter(([, def]) => isRec8(def)).map(([name, def]) => ({ rec: { name, ...def }, path: `${base}.${name}` }));
4126
+ if (isRec9(v)) {
4127
+ return Object.entries(v).filter(([, def]) => isRec9(def)).map(([name, def]) => ({ rec: { name, ...def }, path: `${base}.${name}` }));
3887
4128
  }
3888
4129
  return [];
3889
4130
  }
3890
4131
 
3891
4132
  // src/view-walk.ts
3892
- function isRec9(v) {
4133
+ function isRec10(v) {
3893
4134
  return !!v && typeof v === "object" && !Array.isArray(v);
3894
4135
  }
3895
4136
  function strName5(v) {
3896
4137
  return typeof v === "string" && v.length > 0 ? v : void 0;
3897
4138
  }
3898
4139
  function viewObjectName(view) {
3899
- return strName5(view.objectName) ?? strName5(view.object) ?? (isRec9(view.data) ? strName5(view.data.object) : void 0);
4140
+ return strName5(view.objectName) ?? strName5(view.object) ?? (isRec10(view.data) ? strName5(view.data.object) : void 0);
3900
4141
  }
3901
4142
  function viewContainerSites(view, basePath) {
3902
- if (!isRec9(view)) return [];
4143
+ if (!isRec10(view)) return [];
3903
4144
  const sites = [{ view, path: basePath, surface: "", kind: "self" }];
3904
- if (isRec9(view.form)) {
4145
+ if (isRec10(view.form)) {
3905
4146
  sites.push({ view: view.form, path: `${basePath}.form`, surface: "form", kind: "form" });
3906
4147
  }
3907
4148
  for (const key of ["listViews", "formViews"]) {
3908
4149
  const container = view[key];
3909
- if (!isRec9(container)) continue;
4150
+ if (!isRec10(container)) continue;
3910
4151
  const kind = key === "listViews" ? "listView" : "formView";
3911
4152
  for (const [subKey, sub] of Object.entries(container)) {
3912
- if (!isRec9(sub)) continue;
4153
+ if (!isRec10(sub)) continue;
3913
4154
  sites.push({
3914
4155
  view: sub,
3915
4156
  path: `${basePath}.${key}.${subKey}`,
@@ -3934,7 +4175,7 @@ function asArray16(v) {
3934
4175
  }
3935
4176
  return [];
3936
4177
  }
3937
- function isRec10(v) {
4178
+ function isRec11(v) {
3938
4179
  return !!v && typeof v === "object" && !Array.isArray(v);
3939
4180
  }
3940
4181
  function strName6(v) {
@@ -3968,7 +4209,7 @@ function validateFormLayout(stack) {
3968
4209
  const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
3969
4210
  for (let s = 0; s < sections.length; s++) {
3970
4211
  const sec = sections[s];
3971
- const secFields = isRec10(sec) && Array.isArray(sec.fields) ? sec.fields : [];
4212
+ const secFields = isRec11(sec) && Array.isArray(sec.fields) ? sec.fields : [];
3972
4213
  for (let f = 0; f < secFields.length; f++) {
3973
4214
  const entry = secFields[f];
3974
4215
  const fname = fieldNameOf(entry);
@@ -3983,7 +4224,7 @@ function validateFormLayout(stack) {
3983
4224
  hint: `Fix the field name, or add "${fname}" to ${objName}. Section field references must match the object's field names exactly.`
3984
4225
  });
3985
4226
  }
3986
- const colSpan = isRec10(entry) ? entry.colSpan : void 0;
4227
+ const colSpan = isRec11(entry) ? entry.colSpan : void 0;
3987
4228
  if (colSpan != null) {
3988
4229
  findings.push({
3989
4230
  severity: "warning",
@@ -4004,6 +4245,64 @@ function validateFormLayout(stack) {
4004
4245
 
4005
4246
  // src/validate-visibility-predicates.ts
4006
4247
  var import_formula3 = require("@objectstack/formula");
4248
+
4249
+ // src/predicate-rhs-position.ts
4250
+ function isNode2(v) {
4251
+ return !!v && typeof v === "object" && typeof v.op === "string";
4252
+ }
4253
+ var EQUALITY_OPS = /* @__PURE__ */ new Set(["==", "!="]);
4254
+ var COMPREHENSION_MACROS = /* @__PURE__ */ new Set(["all", "exists", "exists_one", "map", "filter"]);
4255
+ function bareId(node) {
4256
+ if (!isNode2(node)) return null;
4257
+ return node.op === "id" && typeof node.args === "string" ? node.args : null;
4258
+ }
4259
+ function bareRhsOnlyIdentifiers(ast) {
4260
+ const rhs = /* @__PURE__ */ new Set();
4261
+ const elsewhere = /* @__PURE__ */ new Set();
4262
+ const walk = (node, suppressible) => {
4263
+ if (Array.isArray(node)) {
4264
+ for (const child of node) walk(child, suppressible);
4265
+ return;
4266
+ }
4267
+ if (!isNode2(node)) return;
4268
+ const args = node.args;
4269
+ if (node.op === "rcall" && Array.isArray(args) && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
4270
+ walk(args[1], suppressible);
4271
+ walk(args[2], false);
4272
+ return;
4273
+ }
4274
+ if (typeof node.op === "string" && EQUALITY_OPS.has(node.op) && Array.isArray(args) && args.length === 2) {
4275
+ const right = suppressible ? bareId(args[1]) : null;
4276
+ walk(args[0], suppressible);
4277
+ if (right !== null) {
4278
+ rhs.add(right);
4279
+ return;
4280
+ }
4281
+ walk(args[1], suppressible);
4282
+ return;
4283
+ }
4284
+ const name = bareId(node);
4285
+ if (name !== null) {
4286
+ elsewhere.add(name);
4287
+ return;
4288
+ }
4289
+ walk(args, suppressible);
4290
+ };
4291
+ walk(ast, true);
4292
+ for (const name of elsewhere) rhs.delete(name);
4293
+ return rhs;
4294
+ }
4295
+ function isRec12(v) {
4296
+ return !!v && typeof v === "object" && !Array.isArray(v);
4297
+ }
4298
+ function schemaIdOf(view) {
4299
+ const data = view.data;
4300
+ if (!isRec12(data)) return void 0;
4301
+ if (data.provider !== "schema") return void 0;
4302
+ return typeof data.schemaId === "string" ? data.schemaId : void 0;
4303
+ }
4304
+
4305
+ // src/validate-visibility-predicates.ts
4007
4306
  var VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
4008
4307
  var VISIBILITY_BARE_IDENTIFIER = "visibility-bare-identifier";
4009
4308
  var VISIBILITY_PREDICATE_SYNTAX = "visibility-predicate-syntax";
@@ -4051,7 +4350,7 @@ function boundName(overrun) {
4051
4350
  return overrun.limit && overrun.limitValue !== null ? `the \`${overrun.limit}\` budget (platform limit ${overrun.limitValue})` : "one of the platform's parse budgets";
4052
4351
  }
4053
4352
  var VIEW_PAGE_EXTRA_ROOTS = ["current_user", "page"];
4054
- function isNode2(v) {
4353
+ function isNode3(v) {
4055
4354
  return !!v && typeof v === "object" && typeof v.op === "string";
4056
4355
  }
4057
4356
  function namespaceRoots(node, out) {
@@ -4059,22 +4358,27 @@ function namespaceRoots(node, out) {
4059
4358
  for (const child of node) namespaceRoots(child, out);
4060
4359
  return;
4061
4360
  }
4062
- if (!isNode2(node)) return;
4361
+ if (!isNode3(node)) return;
4063
4362
  const args = node.args;
4064
4363
  if (Array.isArray(args)) {
4065
4364
  const receiver = node.op === "rcall" ? args[1] : args[0];
4066
- if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode2(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
4365
+ if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode3(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
4067
4366
  out.add(receiver.args);
4068
4367
  }
4069
4368
  }
4070
4369
  namespaceRoots(args, out);
4071
4370
  }
4072
- function firstBareIdentifier(source) {
4371
+ function firstBareIdentifier(source, literalRhs) {
4073
4372
  const ast = (0, import_formula3.parseCelToAst)(source);
4074
4373
  if (!ast) return null;
4075
4374
  const rooted = /* @__PURE__ */ new Set();
4076
4375
  namespaceRoots(ast, rooted);
4077
- return (0, import_formula3.firstUndeclaredReference)(source, [...VIEW_PAGE_EXTRA_ROOTS, ...rooted]);
4376
+ const literalSlot = literalRhs ? bareRhsOnlyIdentifiers(ast) : [];
4377
+ return (0, import_formula3.firstUndeclaredReference)(source, [
4378
+ ...VIEW_PAGE_EXTRA_ROOTS,
4379
+ ...rooted,
4380
+ ...literalSlot
4381
+ ]);
4078
4382
  }
4079
4383
  var CANONICAL_ROOT_BY_LAYER = {
4080
4384
  runtime: "record",
@@ -4083,16 +4387,16 @@ var CANONICAL_ROOT_BY_LAYER = {
4083
4387
  var MISLAYER_BY_LAYER = {
4084
4388
  runtime: {
4085
4389
  forbiddenRoot: "data",
4086
- 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).",
4390
+ 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).",
4087
4391
  hint: "Runtime record surfaces bind `record` + `current_user` (pages also expose `page.<var>`). Use e.g. `record.status == 'open'` instead of `data.status == 'open'`."
4088
4392
  },
4089
4393
  metadata: {
4090
4394
  forbiddenRoot: "record",
4091
- 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).",
4395
+ 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).",
4092
4396
  hint: "Metadata-editing forms bind `data` (the row under edit). Use e.g. `data.type == 'grid'` instead of `record.type == 'grid'`."
4093
4397
  }
4094
4398
  };
4095
- function checkElement(el, where, path, layer, findings) {
4399
+ function checkElement(el, where, path, layer, findings, literalRhs = false) {
4096
4400
  const raw = el[CANONICAL] ?? el.visibleOn ?? el.visibility;
4097
4401
  const source = predicateSource(raw);
4098
4402
  const rule = MISLAYER_BY_LAYER[layer];
@@ -4130,7 +4434,7 @@ function checkElement(el, where, path, layer, findings) {
4130
4434
  });
4131
4435
  }
4132
4436
  if (source && !refusal) {
4133
- const bare = firstBareIdentifier(source);
4437
+ const bare = firstBareIdentifier(source, literalRhs);
4134
4438
  if (bare) {
4135
4439
  const root = CANONICAL_ROOT_BY_LAYER[layer];
4136
4440
  findings.push({
@@ -4139,7 +4443,7 @@ function checkElement(el, where, path, layer, findings) {
4139
4443
  where,
4140
4444
  path,
4141
4445
  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).`,
4142
- 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`).")
4446
+ 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`).")
4143
4447
  });
4144
4448
  }
4145
4449
  }
@@ -4148,24 +4452,27 @@ function isFieldObject(entry) {
4148
4452
  return !!entry && typeof entry === "object" && !Array.isArray(entry);
4149
4453
  }
4150
4454
  function validateVisibilityPredicates(stack, opts = {}) {
4151
- const layer = opts.layer ?? "runtime";
4455
+ const declaredLayer = opts.layer ?? "runtime";
4152
4456
  const findings = [];
4153
4457
  for (const { rec: view, path: viewPath } of collectionEntries(stack.views, "views")) {
4154
4458
  const viewName = typeof view.name === "string" ? view.name : typeof view.object === "string" ? view.object : viewPath;
4155
4459
  for (const site of formViewSites(view, viewPath)) {
4156
4460
  const where = site.surface ? `view "${viewName}" \xB7 ${site.surface}` : `view "${viewName}"`;
4461
+ const schemaBound = schemaIdOf(site.view) !== void 0;
4462
+ const literalRhs = schemaBound;
4463
+ const layer = schemaBound ? "metadata" : declaredLayer;
4157
4464
  for (const bucket of ["sections", "groups"]) {
4158
4465
  const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
4159
4466
  for (let s = 0; s < sections.length; s++) {
4160
4467
  const sec = sections[s];
4161
4468
  if (!sec || typeof sec !== "object") continue;
4162
4469
  const secPath = `${site.path}.${bucket}[${s}]`;
4163
- checkElement(sec, where, secPath, layer, findings);
4470
+ checkElement(sec, where, secPath, layer, findings, literalRhs);
4164
4471
  const secFields = Array.isArray(sec.fields) ? sec.fields : [];
4165
4472
  for (let f = 0; f < secFields.length; f++) {
4166
4473
  const entry = secFields[f];
4167
4474
  if (isFieldObject(entry)) {
4168
- checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings);
4475
+ checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings, literalRhs);
4169
4476
  }
4170
4477
  }
4171
4478
  }
@@ -4176,7 +4483,7 @@ function validateVisibilityPredicates(stack, opts = {}) {
4176
4483
  const pageName = typeof page.name === "string" ? page.name : void 0;
4177
4484
  const where = `page "${pageName ?? pagePath}"`;
4178
4485
  for (const walked of walkPageComponents(page, pagePath)) {
4179
- checkElement(walked.component, where, walked.path, layer, findings);
4486
+ checkElement(walked.component, where, walked.path, declaredLayer, findings);
4180
4487
  }
4181
4488
  }
4182
4489
  return findings;
@@ -4188,9 +4495,9 @@ var import_kernel2 = require("@objectstack/spec/kernel");
4188
4495
  var import_spec = require("@objectstack/spec");
4189
4496
  var PREDICATE_PATH_UNRESOLVED = "predicate-path-unresolved";
4190
4497
  var PREDICATE_PATH_UNROOTED = "predicate-path-unrooted";
4498
+ var PREDICATE_RHS_PATH_SHAPED = "predicate-rhs-path-shaped";
4191
4499
  var PREDICATE_KEYS = ["visibleWhen", "visibleOn"];
4192
4500
  var ROOT = "data";
4193
- var COMPREHENSION_MACROS = /* @__PURE__ */ new Set(["all", "exists", "exists_one", "map", "filter"]);
4194
4501
  function defOf(schema) {
4195
4502
  if (!schema || typeof schema !== "object" && typeof schema !== "function") return void 0;
4196
4503
  const s = schema;
@@ -4283,11 +4590,11 @@ function stepInto(scope, segment) {
4283
4590
  if (!declared.includes(segment)) return { kind: "undeclared", declared };
4284
4591
  return { kind: "declared", next: propertyOf(u, segment) };
4285
4592
  }
4286
- function isNode3(v) {
4593
+ function isNode4(v) {
4287
4594
  return !!v && typeof v === "object" && typeof v.op === "string";
4288
4595
  }
4289
4596
  function memberChain(node) {
4290
- if (!isNode3(node)) return null;
4597
+ if (!isNode4(node)) return null;
4291
4598
  if (node.op === "id" && typeof node.args === "string") return [node.args];
4292
4599
  if (node.op === "." && Array.isArray(node.args) && typeof node.args[1] === "string") {
4293
4600
  const head = memberChain(node.args[0]);
@@ -4300,7 +4607,7 @@ function rootedPaths(node, out) {
4300
4607
  for (const child of node) rootedPaths(child, out);
4301
4608
  return;
4302
4609
  }
4303
- if (!isNode3(node)) return;
4610
+ if (!isNode4(node)) return;
4304
4611
  if (node.op === ".") {
4305
4612
  const chain = memberChain(node);
4306
4613
  if (chain && chain[0] === ROOT && chain.length > 1) {
@@ -4310,23 +4617,40 @@ function rootedPaths(node, out) {
4310
4617
  }
4311
4618
  rootedPaths(node.args, out);
4312
4619
  }
4620
+ var PATH_SHAPED_RHS = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/;
4621
+ function equalitySites(node, out) {
4622
+ if (Array.isArray(node)) {
4623
+ for (const child of node) equalitySites(child, out);
4624
+ return;
4625
+ }
4626
+ if (!isNode4(node)) return;
4627
+ const args = node.args;
4628
+ if (node.op === "rcall" && Array.isArray(args) && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
4629
+ equalitySites(args[1], out);
4630
+ return;
4631
+ }
4632
+ if (typeof node.op === "string" && EQUALITY_OPS.has(node.op) && Array.isArray(args) && args.length === 2) {
4633
+ out.push({ op: node.op, right: args[1] });
4634
+ }
4635
+ equalitySites(args, out);
4636
+ }
4313
4637
  function classifyIdentifiers(node, values, excluded) {
4314
4638
  if (Array.isArray(node)) {
4315
4639
  for (const child of node) classifyIdentifiers(child, values, excluded);
4316
4640
  return;
4317
4641
  }
4318
- if (!isNode3(node)) return;
4642
+ if (!isNode4(node)) return;
4319
4643
  const args = node.args;
4320
4644
  if (Array.isArray(args)) {
4321
4645
  const receiver = node.op === "rcall" ? args[1] : args[0];
4322
- if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode3(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
4646
+ if ((node.op === "." || node.op === ".?" || node.op === "[]" || node.op === "rcall") && isNode4(receiver) && receiver.op === "id" && typeof receiver.args === "string") {
4323
4647
  excluded.add(receiver.args);
4324
4648
  }
4325
4649
  if (node.op === "rcall" && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
4326
4650
  const macroArgs = args[2];
4327
4651
  if (Array.isArray(macroArgs) && macroArgs.length >= 2) {
4328
4652
  const bound = macroArgs[0];
4329
- if (isNode3(bound) && bound.op === "id" && typeof bound.args === "string") {
4653
+ if (isNode4(bound) && bound.op === "id" && typeof bound.args === "string") {
4330
4654
  excluded.add(bound.args);
4331
4655
  }
4332
4656
  }
@@ -4345,15 +4669,9 @@ function predicateSource2(v) {
4345
4669
  }
4346
4670
  return void 0;
4347
4671
  }
4348
- function isRec11(v) {
4672
+ function isRec13(v) {
4349
4673
  return !!v && typeof v === "object" && !Array.isArray(v);
4350
4674
  }
4351
- function schemaIdOf(view) {
4352
- const data = view.data;
4353
- if (!isRec11(data)) return void 0;
4354
- if (data.provider !== "schema") return void 0;
4355
- return typeof data.schemaId === "string" ? data.schemaId : void 0;
4356
- }
4357
4675
  function checkPredicate(source, scope, where, path, findings) {
4358
4676
  const ast = (0, import_formula4.parseCelToAst)(source);
4359
4677
  if (!ast) return;
@@ -4383,19 +4701,38 @@ function checkPredicate(source, scope, where, path, findings) {
4383
4701
  }
4384
4702
  }
4385
4703
  const declaredHere = keysOf(scope);
4386
- if (!declaredHere) return;
4387
- const values = /* @__PURE__ */ new Set();
4388
- const excluded = /* @__PURE__ */ new Set();
4389
- classifyIdentifiers(ast, values, excluded);
4390
- for (const id of values) {
4391
- if (excluded.has(id) || !declaredHere.includes(id)) continue;
4704
+ const rhsOnly = bareRhsOnlyIdentifiers(ast);
4705
+ if (declaredHere) {
4706
+ const values = /* @__PURE__ */ new Set();
4707
+ const excluded = /* @__PURE__ */ new Set();
4708
+ classifyIdentifiers(ast, values, excluded);
4709
+ for (const id of values) {
4710
+ if (excluded.has(id) || rhsOnly.has(id) || !declaredHere.includes(id)) continue;
4711
+ findings.push({
4712
+ severity: "error",
4713
+ rule: PREDICATE_PATH_UNROOTED,
4714
+ where,
4715
+ path,
4716
+ 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).`,
4717
+ 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).`
4718
+ });
4719
+ }
4720
+ }
4721
+ const sites = [];
4722
+ equalitySites(ast, sites);
4723
+ for (const { op, right } of sites) {
4724
+ const chain = memberChain(right);
4725
+ if (!chain) continue;
4726
+ const text = chain.join(".");
4727
+ if (!PATH_SHAPED_RHS.test(text)) continue;
4728
+ const dotted = chain.length > 1;
4392
4729
  findings.push({
4393
- severity: "error",
4394
- rule: PREDICATE_PATH_UNROOTED,
4730
+ severity: dotted ? "error" : "warning",
4731
+ rule: PREDICATE_RHS_PATH_SHAPED,
4395
4732
  where,
4396
4733
  path,
4397
- 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).`,
4398
- 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).`
4734
+ 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).`,
4735
+ 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.`
4399
4736
  });
4400
4737
  }
4401
4738
  }
@@ -4403,7 +4740,7 @@ function walkFields(entries, scope, where, base, findings, depth) {
4403
4740
  if (!Array.isArray(entries) || depth > 12) return;
4404
4741
  for (let i = 0; i < entries.length; i++) {
4405
4742
  const entry = entries[i];
4406
- if (!isRec11(entry)) continue;
4743
+ if (!isRec13(entry)) continue;
4407
4744
  const path = `${base}[${i}]`;
4408
4745
  for (const key of PREDICATE_KEYS) {
4409
4746
  const source = predicateSource2(entry[key]);
@@ -4430,15 +4767,14 @@ function validatePredicatePathRefs(stack, opts = {}) {
4430
4767
  try {
4431
4768
  root = resolveSchema(schemaId);
4432
4769
  } catch {
4433
- continue;
4770
+ root = void 0;
4434
4771
  }
4435
- if (!root) continue;
4436
4772
  const where = site.surface ? `view "${viewName}" \xB7 ${site.surface} (schema "${schemaId}")` : `view "${viewName}" (schema "${schemaId}")`;
4437
4773
  for (const bucket of ["sections", "groups"]) {
4438
4774
  const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
4439
4775
  for (let s = 0; s < sections.length; s++) {
4440
4776
  const section = sections[s];
4441
- if (!isRec11(section)) continue;
4777
+ if (!isRec13(section)) continue;
4442
4778
  const sectionPath = `${site.path}.${bucket}[${s}]`;
4443
4779
  for (const key of PREDICATE_KEYS) {
4444
4780
  const source = predicateSource2(section[key]);
@@ -4879,6 +5215,7 @@ var SECURITY_MASTER_DETAIL_UNGRANTED = "security-master-detail-ungranted";
4879
5215
  var SECURITY_FLS_UNQUALIFIED_KEY = "security-fls-unqualified-key";
4880
5216
  var SECURITY_GRANT_EXPIRED_AT_AUTHORING = "security-grant-expired-at-authoring";
4881
5217
  var SECURITY_DELEGATION_MISSING_REASON = "security-delegation-missing-reason";
5218
+ var SECURITY_CBP_NO_RELATION = "security-controlled-by-parent-no-relation";
4882
5219
  var CANONICAL_OWD = ["private", "public_read", "public_read_write", "controlled_by_parent"];
4883
5220
  var OWD_ALIAS_FIX = {
4884
5221
  read: "public_read",
@@ -4924,6 +5261,13 @@ function firstMasterDetailField(obj) {
4924
5261
  }
4925
5262
  return void 0;
4926
5263
  }
5264
+ function resolveCbpRelation(obj) {
5265
+ const entries = asArray19(obj.fields);
5266
+ const pick = (pred) => entries.find((f) => pred(f) && refOf(f));
5267
+ const found = pick((f) => f.type === "master_detail" && !!f.required) ?? pick((f) => f.type === "master_detail") ?? pick((f) => f.type === "lookup" && !!f.required);
5268
+ if (!found) return void 0;
5269
+ return { field: String(found.name ?? "?"), type: String(found.type), master: refOf(found) };
5270
+ }
4927
5271
  function grantsObjectAccess(p) {
4928
5272
  return p.allowRead === true || p.allowCreate === true || p.allowEdit === true || p.allowDelete === true || p.viewAllRecords === true || p.modifyAllRecords === true;
4929
5273
  }
@@ -4969,6 +5313,16 @@ function validateSecurityPosture(stack, opts) {
4969
5313
  });
4970
5314
  }
4971
5315
  }
5316
+ if (owd === "controlled_by_parent" && !resolveCbpRelation(obj)) {
5317
+ findings.push({
5318
+ severity: "error",
5319
+ rule: SECURITY_CBP_NO_RELATION,
5320
+ where: `object "${objName}"`,
5321
+ path: `${objPath}.sharingModel`,
5322
+ 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.`,
5323
+ 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'.`
5324
+ });
5325
+ }
4972
5326
  if (typeof external === "string") {
4973
5327
  if (OWD_ALIAS_FIX[external]) {
4974
5328
  findings.push({
@@ -5034,53 +5388,6 @@ function validateSecurityPosture(stack, opts) {
5034
5388
  }
5035
5389
  }
5036
5390
  }
5037
- const flagRole = (kind, name, label2, where, path) => {
5038
- if (identifierHasRoleToken(name)) {
5039
- findings.push({
5040
- severity: "error",
5041
- rule: SECURITY_ROLE_WORD,
5042
- where,
5043
- path,
5044
- 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).`,
5045
- hint: `Rename using 'position' for distribution groups or a domain word (e.g. 'function', 'duty').`
5046
- });
5047
- } else if (labelHasRoleWord(label2)) {
5048
- findings.push({
5049
- severity: "error",
5050
- rule: SECURITY_ROLE_WORD,
5051
- where,
5052
- path: `${path.replace(/\.name$/, "")}.label`,
5053
- message: `${kind} label "${String(label2)}" uses the reserved word "role" (ADR-0090 D3).`,
5054
- hint: `Relabel with 'Position' (distribution) or a domain word \u2014 admins must meet ONE vocabulary.`
5055
- });
5056
- }
5057
- };
5058
- for (let i = 0; i < objects.length; i++) {
5059
- const obj = objects[i];
5060
- if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
5061
- const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
5062
- flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
5063
- for (const f of asArray19(obj.fields)) {
5064
- flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
5065
- }
5066
- for (const [ai, action] of asArray19(obj.actions).entries()) {
5067
- flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
5068
- }
5069
- }
5070
- for (let i = 0; i < permissionSets.length; i++) {
5071
- const ps = permissionSets[i];
5072
- if (!ps || typeof ps !== "object") continue;
5073
- flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
5074
- }
5075
- for (const [i, pos] of asArray19(stack.positions).entries()) {
5076
- flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
5077
- }
5078
- for (const [i, app] of asArray19(stack.apps).entries()) {
5079
- flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
5080
- }
5081
- for (const [i, book] of asArray19(stack.books).entries()) {
5082
- flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
5083
- }
5084
5391
  const stackSetNames = new Set(
5085
5392
  permissionSets.map((ps) => typeof ps.name === "string" ? ps.name : void 0).filter((n) => !!n)
5086
5393
  );
@@ -5201,6 +5508,60 @@ function validateSecurityPosture(stack, opts) {
5201
5508
  }
5202
5509
  return findings;
5203
5510
  }
5511
+ function validateSecurityRoleWord(stack) {
5512
+ const findings = [];
5513
+ if (!stack || typeof stack !== "object") return findings;
5514
+ const objects = asArray19(stack.objects);
5515
+ const permissionSets = asArray19(stack.permissions);
5516
+ const flagRole = (kind, name, label2, where, path) => {
5517
+ if (identifierHasRoleToken(name)) {
5518
+ findings.push({
5519
+ severity: "error",
5520
+ rule: SECURITY_ROLE_WORD,
5521
+ where,
5522
+ path,
5523
+ 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).`,
5524
+ hint: `Rename using 'position' for distribution groups or a domain word (e.g. 'function', 'duty').`
5525
+ });
5526
+ } else if (labelHasRoleWord(label2)) {
5527
+ findings.push({
5528
+ severity: "error",
5529
+ rule: SECURITY_ROLE_WORD,
5530
+ where,
5531
+ path: `${path.replace(/\.name$/, "")}.label`,
5532
+ message: `${kind} label "${String(label2)}" uses the reserved word "role" (ADR-0090 D3).`,
5533
+ hint: `Relabel with 'Position' (distribution) or a domain word \u2014 admins must meet ONE vocabulary.`
5534
+ });
5535
+ }
5536
+ };
5537
+ for (let i = 0; i < objects.length; i++) {
5538
+ const obj = objects[i];
5539
+ if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
5540
+ const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
5541
+ flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
5542
+ for (const f of asArray19(obj.fields)) {
5543
+ flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
5544
+ }
5545
+ for (const [ai, action] of asArray19(obj.actions).entries()) {
5546
+ flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
5547
+ }
5548
+ }
5549
+ for (let i = 0; i < permissionSets.length; i++) {
5550
+ const ps = permissionSets[i];
5551
+ if (!ps || typeof ps !== "object") continue;
5552
+ flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
5553
+ }
5554
+ for (const [i, pos] of asArray19(stack.positions).entries()) {
5555
+ flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
5556
+ }
5557
+ for (const [i, app] of asArray19(stack.apps).entries()) {
5558
+ flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
5559
+ }
5560
+ for (const [i, book] of asArray19(stack.books).entries()) {
5561
+ flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
5562
+ }
5563
+ return findings;
5564
+ }
5204
5565
 
5205
5566
  // src/validate-org-axis-red-lines.ts
5206
5567
  var ORG_AXIS_PERMISSION_INHERITANCE = "org-axis-permission-inheritance";
@@ -5583,7 +5944,7 @@ function validateDashboardActionRefs(stack) {
5583
5944
  }
5584
5945
 
5585
5946
  // src/validate-filter-tokens.ts
5586
- var import_data6 = require("@objectstack/spec/data");
5947
+ var import_data7 = require("@objectstack/spec/data");
5587
5948
 
5588
5949
  // src/filter-walk.ts
5589
5950
  var FILTER_KEYS = /* @__PURE__ */ new Set(["filter", "filters", "runtimeFilter"]);
@@ -5643,7 +6004,7 @@ function walkAuthoredFilters(stack, surfaces, visit) {
5643
6004
 
5644
6005
  // src/validate-filter-tokens.ts
5645
6006
  var FILTER_TOKEN_UNKNOWN = "filter-token-unknown";
5646
- var KNOWN_LIST = import_data6.CONTEXT_TOKENS.join("}, {");
6007
+ var KNOWN_LIST = import_data7.CONTEXT_TOKENS.join("}, {");
5647
6008
  var TOKEN_FILTER_SURFACES = [
5648
6009
  { key: "dashboards", kind: "dashboard" },
5649
6010
  { key: "objects", kind: "object" },
@@ -5656,7 +6017,7 @@ var TOKEN_FILTER_SURFACES = [
5656
6017
  function walkFilterValues(node, path, where, out, seen) {
5657
6018
  if (node === null || node === void 0) return;
5658
6019
  if (typeof node === "string") {
5659
- const cls = (0, import_data6.classifyFilterToken)(node);
6020
+ const cls = (0, import_data7.classifyFilterToken)(node);
5660
6021
  if (cls?.kind === "unknown") {
5661
6022
  const suggestion = cls.suggestion;
5662
6023
  out.push({
@@ -5691,7 +6052,7 @@ function validateFilterTokens(stack) {
5691
6052
  }
5692
6053
 
5693
6054
  // src/validate-empty-combinators.ts
5694
- var import_data7 = require("@objectstack/spec/data");
6055
+ var import_data8 = require("@objectstack/spec/data");
5695
6056
  var FILTER_EMPTY_COMBINATOR = "filter-empty-combinator";
5696
6057
  var FILTER_EMPTY_NODE = "filter-empty-node";
5697
6058
  var EMPTY_COMBINATOR_SURFACES = [
@@ -5710,12 +6071,12 @@ function isFilterNode(value) {
5710
6071
  return proto === Object.prototype || proto === null;
5711
6072
  }
5712
6073
  var VERDICT_OF = {
5713
- $and: (0, import_data7.reduceFilterVerdict)({ $and: [] }),
5714
- $or: (0, import_data7.reduceFilterVerdict)({ $or: [] }),
5715
- $not: (0, import_data7.reduceFilterVerdict)({ $not: {} }),
5716
- node: (0, import_data7.reduceFilterVerdict)({}),
6074
+ $and: (0, import_data8.reduceFilterVerdict)({ $and: [] }),
6075
+ $or: (0, import_data8.reduceFilterVerdict)({ $or: [] }),
6076
+ $not: (0, import_data8.reduceFilterVerdict)({ $not: {} }),
6077
+ node: (0, import_data8.reduceFilterVerdict)({}),
5717
6078
  /** One TRUE disjunct absorbs its `$or`: the sibling branches stop mattering. */
5718
- orWithEmptyBranch: (0, import_data7.reduceFilterVerdict)({ $or: [{ status: "open" }, {}] })
6079
+ orWithEmptyBranch: (0, import_data8.reduceFilterVerdict)({ $or: [{ status: "open" }, {}] })
5719
6080
  };
5720
6081
  function rows(verdict) {
5721
6082
  if (verdict === "true") return "matches EVERY row";
@@ -6001,10 +6362,10 @@ function validateObjectReferences(stack) {
6001
6362
 
6002
6363
  // src/validate-nav-target-refs.ts
6003
6364
  var NAV_TARGET_UNRESOLVED = "nav-target-unresolved";
6004
- var isRec12 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
6365
+ var isRec14 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
6005
6366
  function asArray26(v) {
6006
- if (Array.isArray(v)) return v.filter(isRec12);
6007
- if (isRec12(v)) return Object.entries(v).map(([name, def]) => isRec12(def) ? { name, ...def } : { name });
6367
+ if (Array.isArray(v)) return v.filter(isRec14);
6368
+ if (isRec14(v)) return Object.entries(v).map(([name, def]) => isRec14(def) ? { name, ...def } : { name });
6008
6369
  return [];
6009
6370
  }
6010
6371
  function strName9(v) {
@@ -6026,7 +6387,7 @@ function namesOf(collection) {
6026
6387
  }
6027
6388
  function validateNavTargetRefs(stack) {
6028
6389
  const findings = [];
6029
- if (!isRec12(stack)) return findings;
6390
+ if (!isRec14(stack)) return findings;
6030
6391
  const apps = asArray26(stack.apps);
6031
6392
  if (apps.length === 0) return findings;
6032
6393
  const declared = /* @__PURE__ */ new Map();
@@ -6038,7 +6399,7 @@ function validateNavTargetRefs(stack) {
6038
6399
  const walk = (items, basePath) => {
6039
6400
  if (!Array.isArray(items)) return;
6040
6401
  for (const [ni, raw] of items.entries()) {
6041
- if (!isRec12(raw)) continue;
6402
+ if (!isRec14(raw)) continue;
6042
6403
  const nav = raw;
6043
6404
  const navPath = `${basePath}[${ni}]`;
6044
6405
  for (const [type, prop, collection, noun] of NAV_TARGETS) {
@@ -6069,16 +6430,84 @@ function validateNavTargetRefs(stack) {
6069
6430
  return findings;
6070
6431
  }
6071
6432
 
6433
+ // src/validate-nav-object-servability.ts
6434
+ var import_data9 = require("@objectstack/spec/data");
6435
+ var NAV_OBJECT_UNSERVABLE = "nav-object-unservable";
6436
+ var isRec15 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
6437
+ function asArray27(v) {
6438
+ if (Array.isArray(v)) return v.filter(isRec15);
6439
+ if (isRec15(v)) return Object.entries(v).map(([name, def]) => isRec15(def) ? { name, ...def } : { name });
6440
+ return [];
6441
+ }
6442
+ function strName10(v) {
6443
+ return typeof v === "string" && v.length > 0 ? v : void 0;
6444
+ }
6445
+ var isInterpolated3 = (s) => s.includes("${") || s.includes("{");
6446
+ function validateNavObjectServability(stack) {
6447
+ const findings = [];
6448
+ if (!isRec15(stack)) return findings;
6449
+ const apps = asArray27(stack.apps);
6450
+ if (apps.length === 0) return findings;
6451
+ const ownEnable = /* @__PURE__ */ new Map();
6452
+ const objects = asArray27(stack.objects);
6453
+ for (const [oi, obj] of objects.entries()) {
6454
+ const n = strName10(obj.name);
6455
+ if (!n) continue;
6456
+ ownEnable.set(n, { enable: obj.enable, path: `objects[${oi}].enable` });
6457
+ }
6458
+ if (ownEnable.size === 0) return findings;
6459
+ for (const [ai, app] of apps.entries()) {
6460
+ const appName = strName10(app.name) ?? `#${ai}`;
6461
+ const walk = (items, basePath) => {
6462
+ if (!Array.isArray(items)) return;
6463
+ for (const [ni, raw] of items.entries()) {
6464
+ if (!isRec15(raw)) continue;
6465
+ const nav = raw;
6466
+ const navPath = `${basePath}[${ni}]`;
6467
+ if (nav.type === "object") {
6468
+ const target = strName10(nav.objectName);
6469
+ const declared = target && !isInterpolated3(target) ? ownEnable.get(target) : void 0;
6470
+ if (target && declared && !(0, import_data9.canServeApiOperation)(declared.enable, "list")) {
6471
+ const enable = isRec15(declared.enable) ? declared.enable : {};
6472
+ const apiDisabled = enable.apiEnabled === false;
6473
+ 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(", ")})` : "");
6474
+ const answer = apiDisabled ? "404 `OBJECT_API_DISABLED`" : "405 `OBJECT_API_METHOD_NOT_ALLOWED`";
6475
+ const offendingKey = apiDisabled ? `${declared.path}.apiEnabled` : `${declared.path}.apiMethods`;
6476
+ findings.push({
6477
+ severity: "error",
6478
+ rule: NAV_OBJECT_UNSERVABLE,
6479
+ where: `app "${appName}" \xB7 nav "${strName10(nav.id) ?? strName10(nav.label) ?? `#${ni}`}"`,
6480
+ // The nav entry is where the dead row is authored; the `enable`
6481
+ // key that condemns it is named in the message, because the fix
6482
+ // may belong at either end.
6483
+ path: `${navPath}.objectName`,
6484
+ 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.`,
6485
+ 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.`
6486
+ });
6487
+ }
6488
+ }
6489
+ if (Array.isArray(nav.children)) walk(nav.children, `${navPath}.children`);
6490
+ }
6491
+ };
6492
+ walk(app.navigation, `apps[${ai}].navigation`);
6493
+ for (const [ari, area] of asArray27(app.areas).entries()) {
6494
+ walk(area.items, `apps[${ai}].areas[${ari}].items`);
6495
+ walk(area.navigation, `apps[${ai}].areas[${ari}].navigation`);
6496
+ }
6497
+ }
6498
+ return findings;
6499
+ }
6500
+
6072
6501
  // src/validate-action-name-refs.ts
6073
6502
  var ACTION_NAME_UNDEFINED = "action-name-undefined";
6074
- function asArray27(v) {
6503
+ function asArray28(v) {
6075
6504
  if (Array.isArray(v)) return v;
6076
6505
  if (v && typeof v === "object") {
6077
6506
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
6078
6507
  }
6079
6508
  return [];
6080
6509
  }
6081
- function strName10(v) {
6510
+ function strName11(v) {
6082
6511
  return typeof v === "string" && v.length > 0 ? v : void 0;
6083
6512
  }
6084
6513
  function strList(v) {
@@ -6115,14 +6544,14 @@ function suggest4(target, known) {
6115
6544
  }
6116
6545
  function collectActionNames(stack) {
6117
6546
  const names = /* @__PURE__ */ new Set();
6118
- for (const action of asArray27(stack.actions)) {
6119
- const n = strName10(action?.name);
6547
+ for (const action of asArray28(stack.actions)) {
6548
+ const n = strName11(action?.name);
6120
6549
  if (n) names.add(n);
6121
6550
  }
6122
- for (const obj of asArray27(stack.objects)) {
6551
+ for (const obj of asArray28(stack.objects)) {
6123
6552
  if (!obj || typeof obj !== "object") continue;
6124
- for (const action of asArray27(obj.actions)) {
6125
- const n = strName10(action?.name);
6553
+ for (const action of asArray28(obj.actions)) {
6554
+ const n = strName11(action?.name);
6126
6555
  if (n) names.add(n);
6127
6556
  }
6128
6557
  }
@@ -6165,7 +6594,7 @@ function validateActionNameRefs(stack) {
6165
6594
  if (!def || typeof def !== "object") continue;
6166
6595
  if (def.execution !== "aggregate") continue;
6167
6596
  if (def.actionDef !== void 0) continue;
6168
- const name = strName10(def.name);
6597
+ const name = strName11(def.name);
6169
6598
  if (!name) continue;
6170
6599
  check(
6171
6600
  name,
@@ -6176,11 +6605,11 @@ function validateActionNameRefs(stack) {
6176
6605
  );
6177
6606
  }
6178
6607
  };
6179
- const views = asArray27(stack.views);
6608
+ const views = asArray28(stack.views);
6180
6609
  for (let vi = 0; vi < views.length; vi++) {
6181
6610
  const view = views[vi];
6182
6611
  if (!view || typeof view !== "object") continue;
6183
- const viewName = strName10(view.name) ?? strName10(view.object) ?? `#${vi}`;
6612
+ const viewName = strName11(view.name) ?? strName11(view.object) ?? `#${vi}`;
6184
6613
  const owner = `view "${viewName}"`;
6185
6614
  checkListContainer(view.list, owner, "list", `views[${vi}].list`);
6186
6615
  const listViews = view.listViews;
@@ -6190,22 +6619,22 @@ function validateActionNameRefs(stack) {
6190
6619
  }
6191
6620
  }
6192
6621
  }
6193
- const objects = asArray27(stack.objects);
6622
+ const objects = asArray28(stack.objects);
6194
6623
  for (let oi = 0; oi < objects.length; oi++) {
6195
6624
  const obj = objects[oi];
6196
6625
  if (!obj || typeof obj !== "object") continue;
6197
6626
  const objListViews = obj.listViews;
6198
6627
  if (!objListViews || typeof objListViews !== "object" || Array.isArray(objListViews)) continue;
6199
- const owner = `object "${strName10(obj.name) ?? `#${oi}`}"`;
6628
+ const owner = `object "${strName11(obj.name) ?? `#${oi}`}"`;
6200
6629
  for (const [key, lv] of Object.entries(objListViews)) {
6201
6630
  checkListContainer(lv, owner, `listViews.${key}`, `objects[${oi}].listViews.${key}`);
6202
6631
  }
6203
6632
  }
6204
- const pages = asArray27(stack.pages);
6633
+ const pages = asArray28(stack.pages);
6205
6634
  for (let pi = 0; pi < pages.length; pi++) {
6206
6635
  const page = pages[pi];
6207
6636
  if (!page || typeof page !== "object") continue;
6208
- const pageName = strName10(page.name) ?? `#${pi}`;
6637
+ const pageName = strName11(page.name) ?? `#${pi}`;
6209
6638
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
6210
6639
  const props = component.properties;
6211
6640
  if (!props || typeof props !== "object") continue;
@@ -6213,39 +6642,39 @@ function validateActionNameRefs(stack) {
6213
6642
  for (let ai = 0; ai < names.length; ai++) {
6214
6643
  check(
6215
6644
  names[ai],
6216
- `page "${pageName}" \xB7 component "${strName10(component.type) ?? "?"}"`,
6645
+ `page "${pageName}" \xB7 component "${strName11(component.type) ?? "?"}"`,
6217
6646
  `${path}.properties.actionNames[${ai}]`,
6218
6647
  "Quick-actions bar"
6219
6648
  );
6220
6649
  }
6221
6650
  }
6222
6651
  }
6223
- const apps = asArray27(stack.apps);
6652
+ const apps = asArray28(stack.apps);
6224
6653
  for (let ai = 0; ai < apps.length; ai++) {
6225
6654
  const app = apps[ai];
6226
6655
  if (!app || typeof app !== "object") continue;
6227
- const appName = strName10(app.name) ?? `#${ai}`;
6656
+ const appName = strName11(app.name) ?? `#${ai}`;
6228
6657
  const walkNav = (items, basePath) => {
6229
- const navItems = asArray27(items);
6658
+ const navItems = asArray28(items);
6230
6659
  for (let ni = 0; ni < navItems.length; ni++) {
6231
6660
  const nav = navItems[ni];
6232
6661
  if (!nav || typeof nav !== "object") continue;
6233
6662
  const navPath = `${basePath}[${ni}]`;
6234
6663
  const actionDef = nav.actionDef;
6235
- const actionName = strName10(actionDef?.actionName);
6664
+ const actionName = strName11(actionDef?.actionName);
6236
6665
  if (nav.type === "action" && actionName) {
6237
6666
  check(
6238
6667
  actionName,
6239
- `app "${appName}" \xB7 nav "${strName10(nav.id) ?? `#${ni}`}"`,
6668
+ `app "${appName}" \xB7 nav "${strName11(nav.id) ?? `#${ni}`}"`,
6240
6669
  `${navPath}.actionDef.actionName`,
6241
6670
  "Navigation action item"
6242
6671
  );
6243
6672
  }
6244
- const runAction = strName10(nav.runAction);
6673
+ const runAction = strName11(nav.runAction);
6245
6674
  if (nav.type === "object" && runAction) {
6246
6675
  check(
6247
6676
  runAction,
6248
- `app "${appName}" \xB7 nav "${strName10(nav.id) ?? `#${ni}`}"`,
6677
+ `app "${appName}" \xB7 nav "${strName11(nav.id) ?? `#${ni}`}"`,
6249
6678
  `${navPath}.runAction`,
6250
6679
  "Navigation deep-link auto-run"
6251
6680
  );
@@ -6254,7 +6683,7 @@ function validateActionNameRefs(stack) {
6254
6683
  }
6255
6684
  };
6256
6685
  walkNav(app.navigation, `apps[${ai}].navigation`);
6257
- const areas = asArray27(app.areas);
6686
+ const areas = asArray28(app.areas);
6258
6687
  for (let ri = 0; ri < areas.length; ri++) {
6259
6688
  walkNav(areas[ri]?.navigation, `apps[${ai}].areas[${ri}].navigation`);
6260
6689
  }
@@ -6264,14 +6693,14 @@ function validateActionNameRefs(stack) {
6264
6693
 
6265
6694
  // src/validate-action-locations.ts
6266
6695
  var ACTION_NO_PLACEMENT = "action-no-placement";
6267
- function asArray28(v) {
6696
+ function asArray29(v) {
6268
6697
  if (Array.isArray(v)) return v;
6269
6698
  if (v && typeof v === "object") {
6270
6699
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
6271
6700
  }
6272
6701
  return [];
6273
6702
  }
6274
- function strName11(v) {
6703
+ function strName12(v) {
6275
6704
  return typeof v === "string" && v.length > 0 ? v : void 0;
6276
6705
  }
6277
6706
  function strList2(v) {
@@ -6285,8 +6714,8 @@ function collectNamePlacedActions(stack) {
6285
6714
  for (const key of ["rowActions", "bulkActions"]) {
6286
6715
  for (const n of strList2(list3[key])) placed.add(n);
6287
6716
  }
6288
- for (const def of asArray28(list3.bulkActionDefs)) {
6289
- const n = strName11(def?.name);
6717
+ for (const def of asArray29(list3.bulkActionDefs)) {
6718
+ const n = strName12(def?.name);
6290
6719
  if (n) placed.add(n);
6291
6720
  }
6292
6721
  };
@@ -6294,12 +6723,12 @@ function collectNamePlacedActions(stack) {
6294
6723
  if (!listViews || typeof listViews !== "object" || Array.isArray(listViews)) return;
6295
6724
  for (const lv of Object.values(listViews)) harvest(lv);
6296
6725
  };
6297
- for (const view of asArray28(stack.views)) {
6726
+ for (const view of asArray29(stack.views)) {
6298
6727
  if (!view || typeof view !== "object") continue;
6299
6728
  harvest(view.list);
6300
6729
  harvestListViews(view.listViews);
6301
6730
  }
6302
- for (const obj of asArray28(stack.objects)) {
6731
+ for (const obj of asArray29(stack.objects)) {
6303
6732
  if (!obj || typeof obj !== "object") continue;
6304
6733
  harvestListViews(obj.listViews);
6305
6734
  }
@@ -6312,7 +6741,7 @@ function validateActionLocations(stack) {
6312
6741
  const check = (action, path) => {
6313
6742
  if (!action || typeof action !== "object") return;
6314
6743
  if ("locations" in action) return;
6315
- const name = strName11(action.name);
6744
+ const name = strName12(action.name);
6316
6745
  if (!name) return;
6317
6746
  if (namePlaced.has(name)) return;
6318
6747
  findings.push({
@@ -6324,13 +6753,13 @@ function validateActionLocations(stack) {
6324
6753
  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."
6325
6754
  });
6326
6755
  };
6327
- const actions = asArray28(stack.actions);
6756
+ const actions = asArray29(stack.actions);
6328
6757
  for (let i = 0; i < actions.length; i++) check(actions[i], `actions[${i}]`);
6329
- const objects = asArray28(stack.objects);
6758
+ const objects = asArray29(stack.objects);
6330
6759
  for (let oi = 0; oi < objects.length; oi++) {
6331
6760
  const obj = objects[oi];
6332
6761
  if (!obj || typeof obj !== "object") continue;
6333
- const own = asArray28(obj.actions);
6762
+ const own = asArray29(obj.actions);
6334
6763
  for (let ai = 0; ai < own.length; ai++) check(own[ai], `objects[${oi}].actions[${ai}]`);
6335
6764
  }
6336
6765
  return findings;
@@ -6341,13 +6770,13 @@ var import_ui3 = require("@objectstack/spec/ui");
6341
6770
  var import_spec3 = require("@objectstack/spec");
6342
6771
  var COMPONENT_PROPS_UNKNOWN_KEY = "component-props-unknown-key";
6343
6772
  var COMPONENT_PROPS_INVALID = "component-props-invalid";
6344
- function isRec13(v) {
6773
+ function isRec16(v) {
6345
6774
  return !!v && typeof v === "object" && !Array.isArray(v);
6346
6775
  }
6347
- function strName12(v) {
6776
+ function strName13(v) {
6348
6777
  return typeof v === "string" && v.length > 0 ? v : void 0;
6349
6778
  }
6350
- function asArray29(v) {
6779
+ function asArray30(v) {
6351
6780
  if (Array.isArray(v)) return v;
6352
6781
  if (v && typeof v === "object") {
6353
6782
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -6358,23 +6787,36 @@ var PROPS_SCHEMAS = import_ui3.ComponentPropsMap;
6358
6787
  var DATASOURCE_SUPPLIED_PROP = "object";
6359
6788
  function suppliedByDataSource(issue, component) {
6360
6789
  if (issue.path.length !== 1 || issue.path[0] !== DATASOURCE_SUPPLIED_PROP) return false;
6361
- const dataSource = isRec13(component.dataSource) ? component.dataSource : void 0;
6362
- return strName12(dataSource?.object) !== void 0;
6790
+ const dataSource = isRec16(component.dataSource) ? component.dataSource : void 0;
6791
+ return strName13(dataSource?.object) !== void 0;
6792
+ }
6793
+ function unrecognizedKeysFromUnionArm(issue) {
6794
+ if (issue.code !== "invalid_union") return void 0;
6795
+ const arms = issue.errors;
6796
+ if (!arms || arms.length === 0) return void 0;
6797
+ let found;
6798
+ for (const arm of arms) {
6799
+ const keyIssues = arm.filter((inner) => inner.code === "unrecognized_keys");
6800
+ if (keyIssues.length === 0) continue;
6801
+ if (keyIssues.length !== arm.length || found) return void 0;
6802
+ found = keyIssues[0];
6803
+ }
6804
+ return found;
6363
6805
  }
6364
6806
  function validateComponentProps(stack) {
6365
6807
  const findings = [];
6366
- if (!isRec13(stack)) return findings;
6367
- const pages = asArray29(stack.pages);
6808
+ if (!isRec16(stack)) return findings;
6809
+ const pages = asArray30(stack.pages);
6368
6810
  for (let pi = 0; pi < pages.length; pi++) {
6369
6811
  const page = pages[pi];
6370
- if (!isRec13(page)) continue;
6371
- const pageName = strName12(page.name) ?? `#${pi}`;
6812
+ if (!isRec16(page)) continue;
6813
+ const pageName = strName13(page.name) ?? `#${pi}`;
6372
6814
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
6373
- const type = strName12(component.type);
6815
+ const type = strName13(component.type);
6374
6816
  if (!type) continue;
6375
6817
  const schema = PROPS_SCHEMAS[type];
6376
6818
  if (!schema) continue;
6377
- const props = isRec13(component.properties) ? component.properties : void 0;
6819
+ const props = isRec16(component.properties) ? component.properties : void 0;
6378
6820
  if (!props) continue;
6379
6821
  const where = `page "${pageName}" \xB7 ${type}`;
6380
6822
  const base = `${path}.properties`;
@@ -6393,6 +6835,20 @@ function validateComponentProps(stack) {
6393
6835
  for (const issue of parsed.error?.issues ?? []) {
6394
6836
  if (suppliedByDataSource(issue, component)) continue;
6395
6837
  const at = issue.path.length ? `${base}.${issue.path.join(".")}` : base;
6838
+ const armIssue = unrecognizedKeysFromUnionArm(issue);
6839
+ if (armIssue) {
6840
+ for (const key of armIssue.keys ?? []) {
6841
+ findings.push({
6842
+ severity: "warning",
6843
+ rule: COMPONENT_PROPS_UNKNOWN_KEY,
6844
+ where,
6845
+ path: `${at}.${key}`,
6846
+ message: `\`${key}\` is not a prop \`${type}\` declares (ComponentPropsMap, @objectstack/spec/ui): ${armIssue.message}`,
6847
+ hint: `Remove \`${key}\`, or declare it on \`${type}\`'s props schema if the component honours it.`
6848
+ });
6849
+ }
6850
+ continue;
6851
+ }
6396
6852
  if (issue.code === "unrecognized_keys") {
6397
6853
  for (const key of issue.keys ?? []) {
6398
6854
  findings.push({
@@ -6425,20 +6881,20 @@ var CHART_DIMENSION_UNKNOWN = "chart-dimension-unknown";
6425
6881
  var CHART_MEASURE_UNKNOWN = "chart-measure-unknown";
6426
6882
  var CHART_DATASET_UNKNOWN = "chart-dataset-unknown";
6427
6883
  var CHART_AXIS_NOT_SELECTED = "chart-axis-not-selected";
6428
- function asArray30(v) {
6884
+ function asArray31(v) {
6429
6885
  if (Array.isArray(v)) return v;
6430
6886
  if (v && typeof v === "object") {
6431
6887
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
6432
6888
  }
6433
6889
  return [];
6434
6890
  }
6435
- function strName13(v) {
6891
+ function strName14(v) {
6436
6892
  return typeof v === "string" && v.length > 0 ? v : void 0;
6437
6893
  }
6438
6894
  function strList3(v) {
6439
6895
  return Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.length > 0) : [];
6440
6896
  }
6441
- function isRec14(v) {
6897
+ function isRec17(v) {
6442
6898
  return !!v && typeof v === "object" && !Array.isArray(v);
6443
6899
  }
6444
6900
  function distance4(a, b) {
@@ -6476,17 +6932,17 @@ function list2(names) {
6476
6932
  }
6477
6933
  function indexDatasets(stack) {
6478
6934
  const out = /* @__PURE__ */ new Map();
6479
- for (const ds of asArray30(stack.datasets)) {
6480
- const name = strName13(ds.name);
6935
+ for (const ds of asArray31(stack.datasets)) {
6936
+ const name = strName14(ds.name);
6481
6937
  if (!name) continue;
6482
6938
  const dimensions = /* @__PURE__ */ new Set();
6483
- for (const d of asArray30(ds.dimensions)) {
6484
- const n = strName13(d.name);
6939
+ for (const d of asArray31(ds.dimensions)) {
6940
+ const n = strName14(d.name);
6485
6941
  if (n) dimensions.add(n);
6486
6942
  }
6487
6943
  const measures = /* @__PURE__ */ new Set();
6488
- for (const m of asArray30(ds.measures)) {
6489
- const n = strName13(m.name);
6944
+ for (const m of asArray31(ds.measures)) {
6945
+ const n = strName14(m.name);
6490
6946
  if (n) measures.add(n);
6491
6947
  }
6492
6948
  out.set(name, { dimensions, measures });
@@ -6564,29 +7020,29 @@ function validateChartBindings(stack) {
6564
7020
  if (binding.yAxis) measureRef(binding.yAxis.name, binding.yAxis.path, selected);
6565
7021
  for (const s of binding.series ?? []) measureRef(s.name, s.path, selected);
6566
7022
  };
6567
- const reports = asArray30(stack.reports);
7023
+ const reports = asArray31(stack.reports);
6568
7024
  for (let ri = 0; ri < reports.length; ri++) {
6569
7025
  const report = reports[ri];
6570
- if (!isRec14(report)) continue;
6571
- const reportName = strName13(report.name) ?? `#${ri}`;
7026
+ if (!isRec17(report)) continue;
7027
+ const reportName = strName14(report.name) ?? `#${ri}`;
6572
7028
  const checkReportChart = (chart, dataset, values, where, path) => {
6573
- if (!isRec14(chart)) return;
7029
+ if (!isRec17(chart)) return;
6574
7030
  check({
6575
7031
  dataset,
6576
7032
  // `values` is the report's measure SELECTION, not a chart ref; feeding
6577
7033
  // it in lets the yAxis "declared but not selected" check work without
6578
7034
  // reporting the selection itself twice.
6579
7035
  values: { names: values, path: `${path}.values` },
6580
- xAxis: strName13(chart.xAxis) ? { name: strName13(chart.xAxis), path: `${path}.chart.xAxis` } : void 0,
6581
- yAxis: strName13(chart.yAxis) ? { name: strName13(chart.yAxis), path: `${path}.chart.yAxis` } : void 0,
6582
- series: asArray30(chart.series).map((s, si) => ({ name: strName13(s.name), path: `${path}.chart.series[${si}].name` })).filter((s) => !!s.name),
7036
+ xAxis: strName14(chart.xAxis) ? { name: strName14(chart.xAxis), path: `${path}.chart.xAxis` } : void 0,
7037
+ yAxis: strName14(chart.yAxis) ? { name: strName14(chart.yAxis), path: `${path}.chart.yAxis` } : void 0,
7038
+ series: asArray31(chart.series).map((s, si) => ({ name: strName14(s.name), path: `${path}.chart.series[${si}].name` })).filter((s) => !!s.name),
6583
7039
  where,
6584
7040
  path: `${path}.chart`
6585
7041
  });
6586
7042
  };
6587
7043
  checkReportChart(
6588
7044
  report.chart,
6589
- strName13(report.dataset),
7045
+ strName14(report.dataset),
6590
7046
  strList3(report.values),
6591
7047
  `report "${reportName}" \xB7 chart`,
6592
7048
  `reports[${ri}]`
@@ -6594,45 +7050,45 @@ function validateChartBindings(stack) {
6594
7050
  const blocks = Array.isArray(report.blocks) ? report.blocks : [];
6595
7051
  for (let bi = 0; bi < blocks.length; bi++) {
6596
7052
  const block = blocks[bi];
6597
- if (!isRec14(block)) continue;
7053
+ if (!isRec17(block)) continue;
6598
7054
  checkReportChart(
6599
7055
  block.chart,
6600
- strName13(block.dataset),
7056
+ strName14(block.dataset),
6601
7057
  strList3(block.values),
6602
- `report "${reportName}" \xB7 block "${strName13(block.name) ?? `#${bi}`}" chart`,
7058
+ `report "${reportName}" \xB7 block "${strName14(block.name) ?? `#${bi}`}" chart`,
6603
7059
  `reports[${ri}].blocks[${bi}]`
6604
7060
  );
6605
7061
  }
6606
7062
  }
6607
7063
  const checkListChart = (container, where, path) => {
6608
- if (!isRec14(container)) return;
7064
+ if (!isRec17(container)) return;
6609
7065
  const chart = container.chart;
6610
- if (!isRec14(chart)) return;
7066
+ if (!isRec17(chart)) return;
6611
7067
  check({
6612
- dataset: strName13(chart.dataset),
7068
+ dataset: strName14(chart.dataset),
6613
7069
  dimensions: { names: strList3(chart.dimensions), path: `${path}.chart.dimensions` },
6614
7070
  values: { names: strList3(chart.values), path: `${path}.chart.values` },
6615
7071
  where,
6616
7072
  path: `${path}.chart`
6617
7073
  });
6618
7074
  };
6619
- const views = asArray30(stack.views);
7075
+ const views = asArray31(stack.views);
6620
7076
  for (let vi = 0; vi < views.length; vi++) {
6621
7077
  const view = views[vi];
6622
- if (!isRec14(view)) continue;
6623
- const viewName = strName13(view.name) ?? strName13(view.objectName) ?? `#${vi}`;
7078
+ if (!isRec17(view)) continue;
7079
+ const viewName = strName14(view.name) ?? strName14(view.objectName) ?? `#${vi}`;
6624
7080
  checkListChart(view.list, `view "${viewName}" \xB7 list chart`, `views[${vi}].list`);
6625
- if (isRec14(view.listViews)) {
7081
+ if (isRec17(view.listViews)) {
6626
7082
  for (const [key, lv] of Object.entries(view.listViews)) {
6627
7083
  checkListChart(lv, `view "${viewName}" \xB7 listViews.${key} chart`, `views[${vi}].listViews.${key}`);
6628
7084
  }
6629
7085
  }
6630
7086
  }
6631
- const objects = asArray30(stack.objects);
7087
+ const objects = asArray31(stack.objects);
6632
7088
  for (let oi = 0; oi < objects.length; oi++) {
6633
7089
  const obj = objects[oi];
6634
- if (!isRec14(obj) || !isRec14(obj.listViews)) continue;
6635
- const objName = strName13(obj.name) ?? `#${oi}`;
7090
+ if (!isRec17(obj) || !isRec17(obj.listViews)) continue;
7091
+ const objName = strName14(obj.name) ?? `#${oi}`;
6636
7092
  for (const [key, lv] of Object.entries(obj.listViews)) {
6637
7093
  checkListChart(
6638
7094
  lv,
@@ -6641,22 +7097,22 @@ function validateChartBindings(stack) {
6641
7097
  );
6642
7098
  }
6643
7099
  }
6644
- const pages = asArray30(stack.pages);
7100
+ const pages = asArray31(stack.pages);
6645
7101
  for (let pi = 0; pi < pages.length; pi++) {
6646
7102
  const page = pages[pi];
6647
- if (!isRec14(page)) continue;
6648
- const pageName = strName13(page.name) ?? `#${pi}`;
7103
+ if (!isRec17(page)) continue;
7104
+ const pageName = strName14(page.name) ?? `#${pi}`;
6649
7105
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
6650
- const props = isRec14(component.properties) ? component.properties : void 0;
6651
- if (!props || !strName13(props.dataset)) continue;
6652
- const axisRefs = asArray30(props.yAxis).map((a, ai) => ({ name: strName13(a.field), path: `${path}.properties.yAxis[${ai}].field` })).filter((a) => !!a.name);
6653
- const seriesRefs = asArray30(props.series).map((s, si) => ({ name: strName13(s.name), path: `${path}.properties.series[${si}].name` })).filter((s) => !!s.name);
7106
+ const props = isRec17(component.properties) ? component.properties : void 0;
7107
+ if (!props || !strName14(props.dataset)) continue;
7108
+ const axisRefs = asArray31(props.yAxis).map((a, ai) => ({ name: strName14(a.field), path: `${path}.properties.yAxis[${ai}].field` })).filter((a) => !!a.name);
7109
+ const seriesRefs = asArray31(props.series).map((s, si) => ({ name: strName14(s.name), path: `${path}.properties.series[${si}].name` })).filter((s) => !!s.name);
6654
7110
  check({
6655
- dataset: strName13(props.dataset),
7111
+ dataset: strName14(props.dataset),
6656
7112
  dimensions: { names: strList3(props.dimensions), path: `${path}.properties.dimensions` },
6657
7113
  values: { names: strList3(props.values), path: `${path}.properties.values` },
6658
7114
  series: [...axisRefs, ...seriesRefs],
6659
- where: `page "${pageName}" \xB7 ${strName13(component.type) ?? "chart"}`,
7115
+ where: `page "${pageName}" \xB7 ${strName14(component.type) ?? "chart"}`,
6660
7116
  path: `${path}.properties`
6661
7117
  });
6662
7118
  }
@@ -6670,11 +7126,11 @@ var import_meta4 = {};
6670
7126
  var VALIDATION_RULE_REGEX_UNCOMPILABLE = "validation-rule-regex-uncompilable";
6671
7127
  var VALIDATION_RULE_SCHEMA_UNCOMPILABLE = "validation-rule-json-schema-uncompilable";
6672
7128
  var RUNTIME_AJV_OPTIONS = { allErrors: true, strict: false };
6673
- var isRec15 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
6674
- function asArray31(v) {
6675
- if (Array.isArray(v)) return v.filter(isRec15);
6676
- if (isRec15(v)) {
6677
- return Object.entries(v).filter(([, def]) => isRec15(def)).map(([name, def]) => ({ name, ...def }));
7129
+ var isRec18 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7130
+ function asArray32(v) {
7131
+ if (Array.isArray(v)) return v.filter(isRec18);
7132
+ if (isRec18(v)) {
7133
+ return Object.entries(v).filter(([, def]) => isRec18(def)).map(([name, def]) => ({ name, ...def }));
6678
7134
  }
6679
7135
  return [];
6680
7136
  }
@@ -6691,7 +7147,7 @@ function loadAjv() {
6691
7147
  `@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.`
6692
7148
  );
6693
7149
  }
6694
- const ctor = isRec15(mod) && "default" in mod ? mod.default : mod;
7150
+ const ctor = isRec18(mod) && "default" in mod ? mod.default : mod;
6695
7151
  cachedAjv = ctor;
6696
7152
  return ctor;
6697
7153
  }
@@ -6706,7 +7162,7 @@ function loadAddFormats() {
6706
7162
  `@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.`
6707
7163
  );
6708
7164
  }
6709
- const plugin = isRec15(mod) && "default" in mod ? mod.default : mod;
7165
+ const plugin = isRec18(mod) && "default" in mod ? mod.default : mod;
6710
7166
  cachedAddFormats = plugin;
6711
7167
  return plugin;
6712
7168
  }
@@ -6736,17 +7192,17 @@ function flattenRules(rule, labelTrail, pathTrail, depth = 0) {
6736
7192
  if (depth >= MAX_RULE_NESTING_DEPTH) return out;
6737
7193
  for (const branch of ["then", "otherwise"]) {
6738
7194
  const nested = rule[branch];
6739
- if (isRec15(nested)) out.push(...flattenRules(nested, label2, `${path}.${branch}`, depth + 1));
7195
+ if (isRec18(nested)) out.push(...flattenRules(nested, label2, `${path}.${branch}`, depth + 1));
6740
7196
  }
6741
7197
  return out;
6742
7198
  }
6743
7199
  function walkObjectValidationRules(stack) {
6744
7200
  const walked = [];
6745
- if (!isRec15(stack)) return walked;
6746
- for (const obj of asArray31(stack.objects)) {
7201
+ if (!isRec18(stack)) return walked;
7202
+ for (const obj of asArray32(stack.objects)) {
6747
7203
  const objectName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
6748
7204
  const validations = obj.validations;
6749
- for (const authored of asArray31(validations)) {
7205
+ for (const authored of asArray32(validations)) {
6750
7206
  for (const { rule, label: label2, path } of flattenRules(authored, "", "")) {
6751
7207
  walked.push({
6752
7208
  rule,
@@ -6777,7 +7233,7 @@ function validateRuleCompilability(stack) {
6777
7233
  });
6778
7234
  }
6779
7235
  }
6780
- if (rule.type === "json_schema" && isRec15(rule.schema)) {
7236
+ if (rule.type === "json_schema" && isRec18(rule.schema)) {
6781
7237
  try {
6782
7238
  createRuntimeAjv().compile(rule.schema);
6783
7239
  } catch (err) {
@@ -6797,7 +7253,7 @@ function validateRuleCompilability(stack) {
6797
7253
 
6798
7254
  // src/validate-rule-schema-formats.ts
6799
7255
  var VALIDATION_RULE_SCHEMA_UNKNOWN_FORMAT = "validation-rule-json-schema-unknown-format";
6800
- var isRec16 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7256
+ var isRec19 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
6801
7257
  var SUBSCHEMA_KEYS = [
6802
7258
  "additionalItems",
6803
7259
  "additionalProperties",
@@ -6821,7 +7277,7 @@ var SUBSCHEMA_MAP_KEYS = [
6821
7277
  var MAX_SCHEMA_WALK_DEPTH = 32;
6822
7278
  var escapePointerSegment = (segment) => segment.replace(/~/g, "~0").replace(/\//g, "~1");
6823
7279
  function collectFormatUses(schema, pointer, out, depth) {
6824
- if (!isRec16(schema)) return;
7280
+ if (!isRec19(schema)) return;
6825
7281
  if (typeof schema.format === "string") {
6826
7282
  out.push({ pointer: `${pointer}/format`, name: schema.format });
6827
7283
  }
@@ -6840,7 +7296,7 @@ function collectFormatUses(schema, pointer, out, depth) {
6840
7296
  }
6841
7297
  for (const key of SUBSCHEMA_MAP_KEYS) {
6842
7298
  const value = schema[key];
6843
- if (!isRec16(value)) continue;
7299
+ if (!isRec19(value)) continue;
6844
7300
  for (const [name, entry] of Object.entries(value)) {
6845
7301
  collectFormatUses(entry, `${pointer}/${escapePointerSegment(key)}/${escapePointerSegment(name)}`, out, depth + 1);
6846
7302
  }
@@ -6848,13 +7304,13 @@ function collectFormatUses(schema, pointer, out, depth) {
6848
7304
  const items = schema.items;
6849
7305
  if (Array.isArray(items)) {
6850
7306
  items.forEach((entry, index) => collectFormatUses(entry, `${pointer}/items/${index}`, out, depth + 1));
6851
- } else if (isRec16(items)) {
7307
+ } else if (isRec19(items)) {
6852
7308
  collectFormatUses(items, `${pointer}/items`, out, depth + 1);
6853
7309
  }
6854
7310
  const dependencies = schema.dependencies;
6855
- if (isRec16(dependencies)) {
7311
+ if (isRec19(dependencies)) {
6856
7312
  for (const [name, entry] of Object.entries(dependencies)) {
6857
- if (!isRec16(entry)) continue;
7313
+ if (!isRec19(entry)) continue;
6858
7314
  collectFormatUses(entry, `${pointer}/dependencies/${escapePointerSegment(name)}`, out, depth + 1);
6859
7315
  }
6860
7316
  }
@@ -6893,7 +7349,7 @@ function validateRuleSchemaFormats(stack) {
6893
7349
  const findings = [];
6894
7350
  const pending = [];
6895
7351
  for (const { rule, objectName, label: label2, where, basePath } of walkObjectValidationRules(stack)) {
6896
- if (rule.type !== "json_schema" || !isRec16(rule.schema)) continue;
7352
+ if (rule.type !== "json_schema" || !isRec19(rule.schema)) continue;
6897
7353
  const uses = [];
6898
7354
  collectFormatUses(rule.schema, "", uses, 0);
6899
7355
  for (const use of uses) pending.push({ use, where, label: label2, objectName, basePath });
@@ -6921,7 +7377,7 @@ function validateRuleSchemaFormats(stack) {
6921
7377
  var import_system3 = require("@objectstack/spec/system");
6922
7378
 
6923
7379
  // src/build-access-matrix.ts
6924
- function asArray32(v) {
7380
+ function asArray33(v) {
6925
7381
  if (Array.isArray(v)) return v;
6926
7382
  if (v && typeof v === "object") {
6927
7383
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -6932,13 +7388,13 @@ function buildAccessMatrix(stack) {
6932
7388
  const entries = [];
6933
7389
  if (!stack || typeof stack !== "object") return { version: 1, entries };
6934
7390
  const owdByObject = /* @__PURE__ */ new Map();
6935
- for (const obj of asArray32(stack.objects)) {
7391
+ for (const obj of asArray33(stack.objects)) {
6936
7392
  const name = typeof obj.name === "string" ? obj.name : "";
6937
7393
  if (!name) continue;
6938
7394
  const owd = obj.sharingModel ?? obj.security?.sharingModel;
6939
7395
  if (typeof owd === "string") owdByObject.set(name, owd);
6940
7396
  }
6941
- for (const ps of asArray32(stack.permissions)) {
7397
+ for (const ps of asArray33(stack.permissions)) {
6942
7398
  const psName = typeof ps.name === "string" ? ps.name : "";
6943
7399
  if (!psName) continue;
6944
7400
  const objects = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
@@ -7011,34 +7467,34 @@ function diffAccessMatrix(before, after) {
7011
7467
 
7012
7468
  // src/validate-nav-access.ts
7013
7469
  var NAV_OBJECT_UNGRANTED = "nav-object-ungranted";
7014
- function asArray33(v) {
7470
+ function asArray34(v) {
7015
7471
  if (Array.isArray(v)) return v;
7016
7472
  if (v && typeof v === "object") {
7017
7473
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
7018
7474
  }
7019
7475
  return [];
7020
7476
  }
7021
- function strName14(v) {
7477
+ function strName15(v) {
7022
7478
  return typeof v === "string" && v.length > 0 ? v : void 0;
7023
7479
  }
7024
7480
  function collectNavExposures(stack) {
7025
7481
  const out = [];
7026
- const apps = asArray33(stack.apps);
7482
+ const apps = asArray34(stack.apps);
7027
7483
  for (let ai = 0; ai < apps.length; ai++) {
7028
7484
  const app = apps[ai];
7029
7485
  if (!app || typeof app !== "object") continue;
7030
- const appName = strName14(app.name) ?? `#${ai}`;
7486
+ const appName = strName15(app.name) ?? `#${ai}`;
7031
7487
  const walk = (items, basePath) => {
7032
- const navItems = asArray33(items);
7488
+ const navItems = asArray34(items);
7033
7489
  for (let ni = 0; ni < navItems.length; ni++) {
7034
7490
  const nav = navItems[ni];
7035
7491
  if (!nav || typeof nav !== "object") continue;
7036
7492
  const navPath = `${basePath}[${ni}]`;
7037
- const objectName = strName14(nav.objectName);
7493
+ const objectName = strName15(nav.objectName);
7038
7494
  if (nav.type === "object" && objectName) {
7039
7495
  out.push({
7040
7496
  objectName,
7041
- where: `app "${appName}" \xB7 nav "${strName14(nav.id) ?? `#${ni}`}"`,
7497
+ where: `app "${appName}" \xB7 nav "${strName15(nav.id) ?? `#${ni}`}"`,
7042
7498
  path: `${navPath}.objectName`
7043
7499
  });
7044
7500
  }
@@ -7046,7 +7502,7 @@ function collectNavExposures(stack) {
7046
7502
  }
7047
7503
  };
7048
7504
  walk(app.navigation, `apps[${ai}].navigation`);
7049
- const areas = asArray33(app.areas);
7505
+ const areas = asArray34(app.areas);
7050
7506
  for (let ri = 0; ri < areas.length; ri++) {
7051
7507
  walk(areas[ri]?.navigation, `apps[${ai}].areas[${ri}].navigation`);
7052
7508
  }
@@ -7056,13 +7512,13 @@ function collectNavExposures(stack) {
7056
7512
  function validateNavAccess(stack) {
7057
7513
  const findings = [];
7058
7514
  if (!stack || typeof stack !== "object") return findings;
7059
- const permissionSets = asArray33(stack.permissions);
7515
+ const permissionSets = asArray34(stack.permissions);
7060
7516
  if (permissionSets.length === 0) return findings;
7061
7517
  const exposures = collectNavExposures(stack);
7062
7518
  if (exposures.length === 0) return findings;
7063
7519
  const ownObjects = /* @__PURE__ */ new Set();
7064
- for (const obj of asArray33(stack.objects)) {
7065
- const n = strName14(obj.name);
7520
+ for (const obj of asArray34(stack.objects)) {
7521
+ const n = strName15(obj.name);
7066
7522
  if (n) ownObjects.add(n);
7067
7523
  }
7068
7524
  const readable = /* @__PURE__ */ new Set();
@@ -7095,15 +7551,15 @@ var import_spec4 = require("@objectstack/spec");
7095
7551
  var import_system4 = require("@objectstack/spec/system");
7096
7552
  var TRANSLATION_TARGET_UNKNOWN = "translation-target-unknown";
7097
7553
  var TRANSLATION_OPTION_KEY_UNKNOWN = "translation-option-key-unknown";
7098
- function isRec17(v) {
7554
+ function isRec20(v) {
7099
7555
  return !!v && typeof v === "object" && !Array.isArray(v);
7100
7556
  }
7101
- function asArray34(v) {
7557
+ function asArray35(v) {
7102
7558
  if (Array.isArray(v)) return v;
7103
- if (isRec17(v)) return Object.entries(v).map(([name, def]) => ({ name, ...isRec17(def) ? def : {} }));
7559
+ if (isRec20(v)) return Object.entries(v).map(([name, def]) => ({ name, ...isRec20(def) ? def : {} }));
7104
7560
  return [];
7105
7561
  }
7106
- function strName15(v) {
7562
+ function strName16(v) {
7107
7563
  return typeof v === "string" && v.length > 0 ? v : void 0;
7108
7564
  }
7109
7565
  function distance5(a, b) {
@@ -7163,34 +7619,34 @@ function collectViewRecord(view, factsFor) {
7163
7619
  };
7164
7620
  const addSections = (container, binding) => {
7165
7621
  if (!binding) return;
7166
- for (const section of asArray34(container.sections)) {
7167
- const sectionName = strName15(section.name);
7622
+ for (const section of asArray35(container.sections)) {
7623
+ const sectionName = strName16(section.name);
7168
7624
  if (sectionName) factsFor(binding).sections.add(sectionName);
7169
7625
  }
7170
7626
  };
7171
- const listBinding = isRec17(view.list) ? bindingOf(view.list) : void 0;
7172
- if (isRec17(view.list)) addView(listBinding, defaultListViewKey(listBinding, view));
7173
- addView(recordObject ?? listBinding, strName15(view.name));
7627
+ const listBinding = isRec20(view.list) ? bindingOf(view.list) : void 0;
7628
+ if (isRec20(view.list)) addView(listBinding, defaultListViewKey(listBinding, view));
7629
+ addView(recordObject ?? listBinding, strName16(view.name));
7174
7630
  const named = namedViewKeys(view);
7175
7631
  for (const family of ["listViews", "formViews"]) {
7176
7632
  const container = view[family];
7177
- if (!isRec17(container)) continue;
7633
+ if (!isRec20(container)) continue;
7178
7634
  const registryKeys = family === "listViews" ? named.list : named.form;
7179
7635
  let at = 0;
7180
7636
  for (const sub of Object.values(container)) {
7181
7637
  if (!sub || typeof sub !== "object") continue;
7182
7638
  const registryKey = registryKeys[at++];
7183
- if (!isRec17(sub)) continue;
7639
+ if (!isRec20(sub)) continue;
7184
7640
  const binding = bindingOf(sub) ?? listBinding;
7185
7641
  addView(binding, registryKey);
7186
7642
  addSections(sub, binding);
7187
7643
  }
7188
7644
  }
7189
- if (isRec17(view.form)) addSections(view.form, bindingOf(view.form) ?? listBinding);
7645
+ if (isRec20(view.form)) addSections(view.form, bindingOf(view.form) ?? listBinding);
7190
7646
  addSections(view, recordObject ?? listBinding);
7191
7647
  }
7192
7648
  function defaultListViewKey(object, container) {
7193
- if (!object || !isRec17(container.list)) return void 0;
7649
+ if (!object || !isRec20(container.list)) return void 0;
7194
7650
  const item = (0, import_spec4.expandViewContainer)(object, container).find(
7195
7651
  (i) => i.viewKind === "list" && i.isDefault
7196
7652
  );
@@ -7202,7 +7658,7 @@ function namedViewKeys(container) {
7202
7658
  const object = "probe";
7203
7659
  const prefix = `${object}.`;
7204
7660
  const bare = (name) => name.startsWith(prefix) ? name.slice(prefix.length) : name;
7205
- const countEntries = (v) => isRec17(v) ? Object.values(v).filter((e) => !!e && typeof e === "object").length : 0;
7661
+ const countEntries = (v) => isRec20(v) ? Object.values(v).filter((e) => !!e && typeof e === "object").length : 0;
7206
7662
  const listCount = countEntries(container.listViews);
7207
7663
  const formCount = countEntries(container.formViews);
7208
7664
  if (!listCount && !formCount) return { list: [], form: [] };
@@ -7220,14 +7676,14 @@ function readOptions(field) {
7220
7676
  values.add(opt);
7221
7677
  continue;
7222
7678
  }
7223
- if (!isRec17(opt)) continue;
7224
- const value = strName15(opt.value);
7679
+ if (!isRec20(opt)) continue;
7680
+ const value = strName16(opt.value);
7225
7681
  if (!value) continue;
7226
7682
  values.add(value);
7227
- const label2 = strName15(opt.label);
7683
+ const label2 = strName16(opt.label);
7228
7684
  if (label2) byLabel.set(label2.toLowerCase(), value);
7229
7685
  }
7230
- } else if (isRec17(raw)) {
7686
+ } else if (isRec20(raw)) {
7231
7687
  for (const [value, label2] of Object.entries(raw)) {
7232
7688
  values.add(value);
7233
7689
  if (typeof label2 === "string" && label2.length > 0) byLabel.set(label2.toLowerCase(), value);
@@ -7247,48 +7703,48 @@ function buildUniverse(stack) {
7247
7703
  }
7248
7704
  return facts;
7249
7705
  };
7250
- for (const obj of asArray34(stack.objects)) {
7251
- const objectName = strName15(obj.name);
7706
+ for (const obj of asArray35(stack.objects)) {
7707
+ const objectName = strName16(obj.name);
7252
7708
  if (!objectName) continue;
7253
7709
  const facts = factsFor(objectName);
7254
- for (const field of asArray34(obj.fields)) {
7255
- const fieldName = strName15(field.name);
7710
+ for (const field of asArray35(obj.fields)) {
7711
+ const fieldName = strName16(field.name);
7256
7712
  if (fieldName) facts.fields.set(fieldName, field);
7257
7713
  }
7258
- for (const action of asArray34(obj.actions)) {
7259
- const actionName = strName15(action.name);
7714
+ for (const action of asArray35(obj.actions)) {
7715
+ const actionName = strName16(action.name);
7260
7716
  if (actionName) facts.actions.set(actionName, action);
7261
7717
  }
7262
- for (const view of asArray34(obj.views)) {
7263
- collectViewRecord({ ...view, object: strName15(view.object) ?? objectName }, factsFor);
7718
+ for (const view of asArray35(obj.views)) {
7719
+ collectViewRecord({ ...view, object: strName16(view.object) ?? objectName }, factsFor);
7264
7720
  }
7265
7721
  collectViewRecord({ object: objectName, listViews: obj.listViews }, factsFor);
7266
- for (const group of asArray34(obj.fieldGroups)) {
7267
- const key = strName15(group.key) ?? strName15(group.name);
7722
+ for (const group of asArray35(obj.fieldGroups)) {
7723
+ const key = strName16(group.key) ?? strName16(group.name);
7268
7724
  if (key) facts.sections.add(key);
7269
7725
  }
7270
7726
  }
7271
- for (const view of asArray34(stack.views)) {
7727
+ for (const view of asArray35(stack.views)) {
7272
7728
  collectViewRecord(view, factsFor);
7273
7729
  }
7274
- const pages = asArray34(stack.pages);
7730
+ const pages = asArray35(stack.pages);
7275
7731
  for (let pi = 0; pi < pages.length; pi++) {
7276
7732
  for (const walked of walkPageComponents(pages[pi], `pages[${pi}]`)) {
7277
7733
  if (!walked.objectName) continue;
7278
- const props = isRec17(walked.component.properties) ? walked.component.properties : void 0;
7734
+ const props = isRec20(walked.component.properties) ? walked.component.properties : void 0;
7279
7735
  if (!props) continue;
7280
- for (const section of asArray34(props.sections)) {
7281
- const sectionName = strName15(section.name);
7736
+ for (const section of asArray35(props.sections)) {
7737
+ const sectionName = strName16(section.name);
7282
7738
  if (sectionName) factsFor(walked.objectName).sections.add(sectionName);
7283
7739
  }
7284
7740
  }
7285
7741
  }
7286
7742
  const globalActions = /* @__PURE__ */ new Map();
7287
7743
  const actionOwners = /* @__PURE__ */ new Map();
7288
- for (const action of asArray34(stack.actions)) {
7289
- const actionName = strName15(action.name);
7744
+ for (const action of asArray35(stack.actions)) {
7745
+ const actionName = strName16(action.name);
7290
7746
  if (!actionName) continue;
7291
- const owner = strName15(action.objectName) ?? strName15(action.object);
7747
+ const owner = strName16(action.objectName) ?? strName16(action.object);
7292
7748
  if (owner) {
7293
7749
  factsFor(owner).actions.set(actionName, action);
7294
7750
  actionOwners.set(actionName, owner);
@@ -7302,41 +7758,41 @@ function buildUniverse(stack) {
7302
7758
  }
7303
7759
  }
7304
7760
  const apps = /* @__PURE__ */ new Map();
7305
- for (const app of asArray34(stack.apps)) {
7306
- const appName = strName15(app.name);
7761
+ for (const app of asArray35(stack.apps)) {
7762
+ const appName = strName16(app.name);
7307
7763
  if (!appName) continue;
7308
7764
  const navIds = apps.get(appName) ?? /* @__PURE__ */ new Set();
7309
7765
  const walkNav = (items) => {
7310
- for (const item of asArray34(items)) {
7311
- const id = strName15(item.id);
7766
+ for (const item of asArray35(items)) {
7767
+ const id = strName16(item.id);
7312
7768
  if (id) navIds.add(id);
7313
7769
  if (item.children) walkNav(item.children);
7314
7770
  }
7315
7771
  };
7316
7772
  walkNav(app.navigation);
7317
- for (const area of asArray34(app.areas)) {
7318
- const areaId = strName15(area.id);
7773
+ for (const area of asArray35(app.areas)) {
7774
+ const areaId = strName16(area.id);
7319
7775
  if (areaId) navIds.add(areaId);
7320
7776
  walkNav(area.navigation);
7321
7777
  }
7322
7778
  apps.set(appName, navIds);
7323
7779
  }
7324
7780
  const dashboards = /* @__PURE__ */ new Map();
7325
- for (const dash of asArray34(stack.dashboards)) {
7326
- const dashName = strName15(dash.name);
7781
+ for (const dash of asArray35(stack.dashboards)) {
7782
+ const dashName = strName16(dash.name);
7327
7783
  if (!dashName) continue;
7328
7784
  const widgets = /* @__PURE__ */ new Set();
7329
- for (const widget of asArray34(dash.widgets)) {
7330
- const id = strName15(widget.id) ?? strName15(widget.name);
7785
+ for (const widget of asArray35(dash.widgets)) {
7786
+ const id = strName16(widget.id) ?? strName16(widget.name);
7331
7787
  if (id) widgets.add(id);
7332
7788
  }
7333
7789
  const actions = /* @__PURE__ */ new Set();
7334
7790
  const headerActions = [
7335
- ...asArray34(isRec17(dash.header) ? dash.header.actions : void 0),
7336
- ...asArray34(dash.actions)
7791
+ ...asArray35(isRec20(dash.header) ? dash.header.actions : void 0),
7792
+ ...asArray35(dash.actions)
7337
7793
  ];
7338
7794
  for (const action of headerActions) {
7339
- const key = strName15(action.actionUrl) ?? strName15(action.url) ?? strName15(action.name);
7795
+ const key = strName16(action.actionUrl) ?? strName16(action.url) ?? strName16(action.name);
7340
7796
  if (key) actions.add(key);
7341
7797
  }
7342
7798
  dashboards.set(dashName, { widgets, actions });
@@ -7348,7 +7804,7 @@ function localePath(bundleIndex, locale) {
7348
7804
  }
7349
7805
  function validateTranslationReferences(stack) {
7350
7806
  const findings = [];
7351
- if (!isRec17(stack)) return findings;
7807
+ if (!isRec20(stack)) return findings;
7352
7808
  const bundles = Array.isArray(stack.translations) ? stack.translations : [];
7353
7809
  if (bundles.length === 0) return findings;
7354
7810
  const universe = buildUniverse(stack);
@@ -7357,13 +7813,13 @@ function validateTranslationReferences(stack) {
7357
7813
  };
7358
7814
  for (let bi = 0; bi < bundles.length; bi++) {
7359
7815
  const bundle = bundles[bi];
7360
- if (!isRec17(bundle)) continue;
7816
+ if (!isRec20(bundle)) continue;
7361
7817
  for (const [locale, rawData] of Object.entries(bundle)) {
7362
- if (!isRec17(rawData)) continue;
7818
+ if (!isRec20(rawData)) continue;
7363
7819
  const base = localePath(bi, locale);
7364
7820
  const inLocale = `locale "${locale}"`;
7365
7821
  for (const [objectName, rawNode] of Object.entries(asRecord(rawData.objects))) {
7366
- if (!isRec17(rawNode)) continue;
7822
+ if (!isRec20(rawNode)) continue;
7367
7823
  const objPath = `${base}.objects.${objectName}`;
7368
7824
  const facts = universe.objects.get(objectName);
7369
7825
  if (!facts) {
@@ -7389,7 +7845,7 @@ function validateTranslationReferences(stack) {
7389
7845
  );
7390
7846
  continue;
7391
7847
  }
7392
- if (!isRec17(rawField)) continue;
7848
+ if (!isRec20(rawField)) continue;
7393
7849
  checkOptionKeys(findings, {
7394
7850
  optionMap: rawField.options,
7395
7851
  field,
@@ -7471,7 +7927,7 @@ function validateTranslationReferences(stack) {
7471
7927
  );
7472
7928
  continue;
7473
7929
  }
7474
- if (!isRec17(rawApp)) continue;
7930
+ if (!isRec20(rawApp)) continue;
7475
7931
  for (const navId of Object.keys(asRecord(rawApp.navigation))) {
7476
7932
  if (navIds.has(navId)) continue;
7477
7933
  orphan(
@@ -7494,7 +7950,7 @@ function validateTranslationReferences(stack) {
7494
7950
  );
7495
7951
  continue;
7496
7952
  }
7497
- if (!isRec17(rawDash)) continue;
7953
+ if (!isRec20(rawDash)) continue;
7498
7954
  for (const widgetId of Object.keys(asRecord(rawDash.widgets))) {
7499
7955
  if (dash.widgets.has(widgetId)) continue;
7500
7956
  orphan(
@@ -7519,7 +7975,7 @@ function validateTranslationReferences(stack) {
7519
7975
  return findings;
7520
7976
  }
7521
7977
  function asRecord(v) {
7522
- return isRec17(v) ? v : {};
7978
+ return isRec20(v) ? v : {};
7523
7979
  }
7524
7980
  function checkOptionKeys(findings, ctx) {
7525
7981
  const optionKeys = Object.keys(asRecord(ctx.optionMap));
@@ -7531,7 +7987,7 @@ function checkOptionKeys(findings, ctx) {
7531
7987
  rule: TRANSLATION_OPTION_KEY_UNKNOWN,
7532
7988
  where: ctx.where,
7533
7989
  path: ctx.path,
7534
- message: `Option translations are keyed under field "${ctx.fieldName}" of object "${ctx.objectName}", which declares no \`options\` at all (field type "${strName15(ctx.field.type) ?? "unknown"}"). Nothing reads this map.`,
7990
+ message: `Option translations are keyed under field "${ctx.fieldName}" of object "${ctx.objectName}", which declares no \`options\` at all (field type "${strName16(ctx.field.type) ?? "unknown"}"). Nothing reads this map.`,
7535
7991
  hint: `Declare the options on the field, move the translations to the field that owns them, or drop them.`
7536
7992
  });
7537
7993
  return;
@@ -7550,11 +8006,11 @@ function checkOptionKeys(findings, ctx) {
7550
8006
  }
7551
8007
  }
7552
8008
  function checkActionParams(findings, ctx) {
7553
- const rawParams = Object.keys(asRecord(isRec17(ctx.rawAction) ? ctx.rawAction.params : void 0));
8009
+ const rawParams = Object.keys(asRecord(isRec20(ctx.rawAction) ? ctx.rawAction.params : void 0));
7554
8010
  if (rawParams.length === 0) return;
7555
8011
  const declared = /* @__PURE__ */ new Set();
7556
- for (const param of asArray34(ctx.action.params)) {
7557
- const name = strName15(param.name) ?? strName15(param.field);
8012
+ for (const param of asArray35(ctx.action.params)) {
8013
+ const name = strName16(param.name) ?? strName16(param.field);
7558
8014
  if (name) declared.add(name);
7559
8015
  }
7560
8016
  for (const paramName of rawParams) {
@@ -7572,14 +8028,14 @@ function checkActionParams(findings, ctx) {
7572
8028
 
7573
8029
  // src/validate-translatable-sections.ts
7574
8030
  var TRANSLATION_SECTION_NAME_MISSING = "translation-section-name-missing";
7575
- function isRec18(v) {
8031
+ function isRec21(v) {
7576
8032
  return !!v && typeof v === "object" && !Array.isArray(v);
7577
8033
  }
7578
- function strName16(v) {
8034
+ function strName17(v) {
7579
8035
  return typeof v === "string" && v.length > 0 ? v : void 0;
7580
8036
  }
7581
8037
  function viewLabel(view) {
7582
- const name = strName16(view.name);
8038
+ const name = strName17(view.name);
7583
8039
  return name ? `view "${name}"` : "";
7584
8040
  }
7585
8041
  function joinWhere(...parts) {
@@ -7587,7 +8043,7 @@ function joinWhere(...parts) {
7587
8043
  }
7588
8044
  function collectViewSites(view, basePath, label2, sites) {
7589
8045
  const recordObject = viewObjectName(view);
7590
- const listBinding = isRec18(view.list) ? viewObjectName(view.list) ?? recordObject : void 0;
8046
+ const listBinding = isRec21(view.list) ? viewObjectName(view.list) ?? recordObject : void 0;
7591
8047
  for (const site of viewContainerSites(view, basePath)) {
7592
8048
  sites.push({
7593
8049
  path: `${site.path}.sections`,
@@ -7601,11 +8057,11 @@ function translatedObjectNames(stack) {
7601
8057
  const out = /* @__PURE__ */ new Set();
7602
8058
  const bundles = Array.isArray(stack.translations) ? stack.translations : [];
7603
8059
  for (const bundle of bundles) {
7604
- if (!isRec18(bundle)) continue;
8060
+ if (!isRec21(bundle)) continue;
7605
8061
  for (const data of Object.values(bundle)) {
7606
- if (!isRec18(data) || !isRec18(data.objects)) continue;
8062
+ if (!isRec21(data) || !isRec21(data.objects)) continue;
7607
8063
  for (const [objectName, node] of Object.entries(data.objects)) {
7608
- if (isRec18(node)) out.add(objectName);
8064
+ if (isRec21(node)) out.add(objectName);
7609
8065
  }
7610
8066
  }
7611
8067
  }
@@ -7617,22 +8073,22 @@ function suggestedName(label2) {
7617
8073
  }
7618
8074
  function validateTranslatableSections(stack) {
7619
8075
  const findings = [];
7620
- if (!isRec18(stack)) return findings;
8076
+ if (!isRec21(stack)) return findings;
7621
8077
  const translated = translatedObjectNames(stack);
7622
8078
  if (translated.size === 0) return findings;
7623
8079
  const sites = [];
7624
8080
  for (const { rec: obj, path: objPath } of collectionEntries(stack.objects, "objects")) {
7625
- const objectName = strName16(obj.name);
8081
+ const objectName = strName17(obj.name);
7626
8082
  if (!objectName) continue;
7627
8083
  for (const { rec: view, path } of collectionEntries(obj.views, `${objPath}.views`)) {
7628
8084
  collectViewSites(
7629
- { ...view, object: strName16(view.object) ?? objectName },
8085
+ { ...view, object: strName17(view.object) ?? objectName },
7630
8086
  path,
7631
8087
  viewLabel(view),
7632
8088
  sites
7633
8089
  );
7634
8090
  }
7635
- if (isRec18(obj.listViews)) {
8091
+ if (isRec21(obj.listViews)) {
7636
8092
  collectViewSites({ object: objectName, listViews: obj.listViews }, objPath, "", sites);
7637
8093
  }
7638
8094
  }
@@ -7640,13 +8096,13 @@ function validateTranslatableSections(stack) {
7640
8096
  collectViewSites(view, path, viewLabel(view), sites);
7641
8097
  }
7642
8098
  for (const { rec: page, path: pagePath } of collectionEntries(stack.pages, "pages")) {
7643
- const pageName = strName16(page.name);
8099
+ const pageName = strName17(page.name);
7644
8100
  const pageLabel = pageName ? `page "${pageName}"` : "";
7645
8101
  for (const walked of walkPageComponents(page, pagePath)) {
7646
8102
  if (!walked.objectName) continue;
7647
- const props = isRec18(walked.component.properties) ? walked.component.properties : void 0;
8103
+ const props = isRec21(walked.component.properties) ? walked.component.properties : void 0;
7648
8104
  if (!props) continue;
7649
- const type = strName16(walked.component.type) ?? "component";
8105
+ const type = strName17(walked.component.type) ?? "component";
7650
8106
  sites.push({
7651
8107
  path: `${walked.path}.properties.sections`,
7652
8108
  surface: joinWhere(pageLabel, type),
@@ -7661,9 +8117,9 @@ function validateTranslatableSections(stack) {
7661
8117
  if (!Array.isArray(site.sections)) continue;
7662
8118
  for (let i = 0; i < site.sections.length; i++) {
7663
8119
  const section = site.sections[i];
7664
- if (!isRec18(section)) continue;
7665
- if (strName16(section.name)) continue;
7666
- const heading = strName16(section.label);
8120
+ if (!isRec21(section)) continue;
8121
+ if (strName17(section.name)) continue;
8122
+ const heading = strName17(section.label);
7667
8123
  if (!heading) continue;
7668
8124
  const slug = suggestedName(heading);
7669
8125
  findings.push({
@@ -7681,14 +8137,14 @@ function validateTranslatableSections(stack) {
7681
8137
 
7682
8138
  // src/validate-ai-surface-affinity.ts
7683
8139
  var AI_SKILL_SURFACE_MISMATCH = "ai-skill-surface-mismatch";
7684
- function asArray35(v) {
8140
+ function asArray36(v) {
7685
8141
  if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
7686
8142
  if (v && typeof v === "object") {
7687
8143
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
7688
8144
  }
7689
8145
  return [];
7690
8146
  }
7691
- function strName17(v) {
8147
+ function strName18(v) {
7692
8148
  return typeof v === "string" && v.length > 0 ? v : void 0;
7693
8149
  }
7694
8150
  function surfaceOf(v) {
@@ -7698,18 +8154,18 @@ function validateAiSurfaceAffinity(stack) {
7698
8154
  const findings = [];
7699
8155
  if (!stack || typeof stack !== "object") return findings;
7700
8156
  const skillsByName = /* @__PURE__ */ new Map();
7701
- for (const skill of asArray35(stack.skills)) {
7702
- const n = strName17(skill.name);
8157
+ for (const skill of asArray36(stack.skills)) {
8158
+ const n = strName18(skill.name);
7703
8159
  if (n) skillsByName.set(n, skill);
7704
8160
  }
7705
- const agents = asArray35(stack.agents);
8161
+ const agents = asArray36(stack.agents);
7706
8162
  for (let ai = 0; ai < agents.length; ai++) {
7707
8163
  const agent = agents[ai];
7708
- const agentName = strName17(agent.name) ?? `#${ai}`;
8164
+ const agentName = strName18(agent.name) ?? `#${ai}`;
7709
8165
  const agentSurface = surfaceOf(agent.surface);
7710
8166
  const skillRefs = Array.isArray(agent.skills) ? agent.skills : [];
7711
8167
  for (let si = 0; si < skillRefs.length; si++) {
7712
- const ref = strName17(skillRefs[si]);
8168
+ const ref = strName18(skillRefs[si]);
7713
8169
  if (!ref) continue;
7714
8170
  const skill = skillsByName.get(ref);
7715
8171
  if (!skill) continue;
@@ -7731,14 +8187,14 @@ function validateAiSurfaceAffinity(stack) {
7731
8187
  // src/validate-ai-tool-references.ts
7732
8188
  var import_system5 = require("@objectstack/spec/system");
7733
8189
  var AI_SKILL_TOOL_UNRESOLVED = "ai-skill-tool-unresolved";
7734
- function asArray36(v) {
8190
+ function asArray37(v) {
7735
8191
  if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
7736
8192
  if (v && typeof v === "object") {
7737
8193
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
7738
8194
  }
7739
8195
  return [];
7740
8196
  }
7741
- function strName18(v) {
8197
+ function strName19(v) {
7742
8198
  return typeof v === "string" && v.length > 0 ? v : void 0;
7743
8199
  }
7744
8200
  function distance6(a, b) {
@@ -7779,26 +8235,26 @@ function materialisesAsTool(action) {
7779
8235
  if (!ai || typeof ai !== "object") return false;
7780
8236
  const aiRec = ai;
7781
8237
  if (aiRec.exposed !== true) return false;
7782
- if (!strName18(aiRec.description)) return false;
7783
- const type = strName18(action.type);
8238
+ if (!strName19(aiRec.description)) return false;
8239
+ const type = strName19(action.type);
7784
8240
  if (!type || !HEADLESS_ACTION_TYPES.has(type)) return false;
7785
8241
  if (type === "script") return Boolean(action.target || action.body);
7786
8242
  return Boolean(action.target);
7787
8243
  }
7788
8244
  function collectToolUniverse(stack) {
7789
8245
  const universe = new Set(import_system5.PLATFORM_PROVIDED_TOOL_NAMES);
7790
- for (const tool of asArray36(stack.tools)) {
7791
- const n = strName18(tool.name);
8246
+ for (const tool of asArray37(stack.tools)) {
8247
+ const n = strName19(tool.name);
7792
8248
  if (n) universe.add(n);
7793
8249
  }
7794
8250
  const addActionFamily = (actions) => {
7795
- for (const action of asArray36(actions)) {
7796
- const n = strName18(action.name);
8251
+ for (const action of asArray37(actions)) {
8252
+ const n = strName19(action.name);
7797
8253
  if (n && materialisesAsTool(action)) universe.add(`action_${n}`);
7798
8254
  }
7799
8255
  };
7800
8256
  addActionFamily(stack.actions);
7801
- for (const obj of asArray36(stack.objects)) {
8257
+ for (const obj of asArray37(stack.objects)) {
7802
8258
  addActionFamily(obj.actions);
7803
8259
  }
7804
8260
  return universe;
@@ -7806,13 +8262,13 @@ function collectToolUniverse(stack) {
7806
8262
  function collectUnexposedActionNames(stack) {
7807
8263
  const names = /* @__PURE__ */ new Set();
7808
8264
  const scan = (actions) => {
7809
- for (const action of asArray36(actions)) {
7810
- const n = strName18(action.name);
8265
+ for (const action of asArray37(actions)) {
8266
+ const n = strName19(action.name);
7811
8267
  if (n && !materialisesAsTool(action)) names.add(n);
7812
8268
  }
7813
8269
  };
7814
8270
  scan(stack.actions);
7815
- for (const obj of asArray36(stack.objects)) scan(obj.actions);
8271
+ for (const obj of asArray37(stack.objects)) scan(obj.actions);
7816
8272
  return names;
7817
8273
  }
7818
8274
  function validateAiToolReferences(stack) {
@@ -7830,13 +8286,13 @@ function validateAiToolReferences(stack) {
7830
8286
  }
7831
8287
  return universe.has(ref);
7832
8288
  };
7833
- const skills = asArray36(stack.skills);
8289
+ const skills = asArray37(stack.skills);
7834
8290
  for (let si = 0; si < skills.length; si++) {
7835
8291
  const skill = skills[si];
7836
- const skillName = strName18(skill.name) ?? `#${si}`;
8292
+ const skillName = strName19(skill.name) ?? `#${si}`;
7837
8293
  const refs = Array.isArray(skill.tools) ? skill.tools : [];
7838
8294
  for (let ti = 0; ti < refs.length; ti++) {
7839
- const ref = strName18(refs[ti]);
8295
+ const ref = strName19(refs[ti]);
7840
8296
  if (!ref || resolves(ref)) continue;
7841
8297
  const isPattern = ref.endsWith("*");
7842
8298
  const unexposed = !isPattern && ref.startsWith("action_") && unexposedActions.has(ref.slice("action_".length)) ? ref.slice("action_".length) : void 0;
@@ -7856,24 +8312,24 @@ function validateAiToolReferences(stack) {
7856
8312
  // src/validate-ai-agent-authoring.ts
7857
8313
  var AGENT_AUTHORING_WITHDRAWN = "agent-authoring-withdrawn";
7858
8314
  var DEFAULT_AGENT_OUTSIDE_ROSTER = "default-agent-outside-roster";
7859
- function asArray37(v) {
8315
+ function asArray38(v) {
7860
8316
  if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
7861
8317
  if (v && typeof v === "object") {
7862
8318
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
7863
8319
  }
7864
8320
  return [];
7865
8321
  }
7866
- function strName19(v) {
8322
+ function strName20(v) {
7867
8323
  return typeof v === "string" && v.length > 0 ? v : void 0;
7868
8324
  }
7869
8325
  var PLATFORM_AGENT_NAMES = /* @__PURE__ */ new Set(["ask", "build", "data_chat", "metadata_assistant"]);
7870
8326
  function validateAiAgentAuthoring(stack) {
7871
8327
  const findings = [];
7872
8328
  if (!stack || typeof stack !== "object") return findings;
7873
- const agents = asArray37(stack.agents);
8329
+ const agents = asArray38(stack.agents);
7874
8330
  for (let ai = 0; ai < agents.length; ai++) {
7875
8331
  const agent = agents[ai];
7876
- const name = strName19(agent.name) ?? `#${ai}`;
8332
+ const name = strName20(agent.name) ?? `#${ai}`;
7877
8333
  const isPlatformName = PLATFORM_AGENT_NAMES.has(name);
7878
8334
  const skillCount = Array.isArray(agent.skills) ? agent.skills.length : 0;
7879
8335
  findings.push({
@@ -7886,12 +8342,12 @@ function validateAiAgentAuthoring(stack) {
7886
8342
  });
7887
8343
  }
7888
8344
  const roster = [...PLATFORM_AGENT_NAMES].join(", ");
7889
- const apps = asArray37(stack.apps);
8345
+ const apps = asArray38(stack.apps);
7890
8346
  for (let appIdx = 0; appIdx < apps.length; appIdx++) {
7891
8347
  const app = apps[appIdx];
7892
- const defaultAgent = strName19(app.defaultAgent);
8348
+ const defaultAgent = strName20(app.defaultAgent);
7893
8349
  if (!defaultAgent || PLATFORM_AGENT_NAMES.has(defaultAgent)) continue;
7894
- const appName = strName19(app.name) ?? `#${appIdx}`;
8350
+ const appName = strName20(app.name) ?? `#${appIdx}`;
7895
8351
  findings.push({
7896
8352
  severity: "warning",
7897
8353
  rule: DEFAULT_AGENT_OUTSIDE_ROSTER,
@@ -7994,24 +8450,24 @@ var IMPLICIT_FIELDS2 = /* @__PURE__ */ new Set([
7994
8450
  "owner",
7995
8451
  "record_type"
7996
8452
  ]);
7997
- var isRec19 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7998
- function asArray38(v) {
7999
- if (Array.isArray(v)) return v.filter((x) => isRec19(x));
8000
- if (isRec19(v)) {
8453
+ var isRec22 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8454
+ function asArray39(v) {
8455
+ if (Array.isArray(v)) return v.filter((x) => isRec22(x));
8456
+ if (isRec22(v)) {
8001
8457
  return Object.entries(v).map(([name, def]) => ({
8002
8458
  name,
8003
- ...isRec19(def) ? def : {}
8459
+ ...isRec22(def) ? def : {}
8004
8460
  }));
8005
8461
  }
8006
8462
  return [];
8007
8463
  }
8008
8464
  function indexObjectFields2(stack) {
8009
8465
  const out = /* @__PURE__ */ new Map();
8010
- for (const obj of asArray38(stack.objects)) {
8466
+ for (const obj of asArray39(stack.objects)) {
8011
8467
  const name = typeof obj.name === "string" ? obj.name : void 0;
8012
8468
  if (!name) continue;
8013
8469
  const names = /* @__PURE__ */ new Set();
8014
- for (const f of asArray38(obj.fields)) {
8470
+ for (const f of asArray39(obj.fields)) {
8015
8471
  if (typeof f.name === "string" && f.name) names.add(f.name);
8016
8472
  }
8017
8473
  out.set(name, names);
@@ -8141,12 +8597,12 @@ ${source}
8141
8597
  }
8142
8598
  function validateHookBodyWrites(stack) {
8143
8599
  const findings = [];
8144
- const hooks = asArray38(stack.hooks);
8600
+ const hooks = asArray39(stack.hooks);
8145
8601
  if (hooks.length === 0) return findings;
8146
8602
  let objectFields = null;
8147
8603
  hooks.forEach((hook, hookIndex) => {
8148
8604
  const body = hook.body;
8149
- if (!isRec19(body) || body.language !== "js") return;
8605
+ if (!isRec22(body) || body.language !== "js") return;
8150
8606
  const source = body.source;
8151
8607
  if (typeof source !== "string" || source.trim() === "") return;
8152
8608
  const writes = extractHookBodyWrites(source).filter((w) => HOOK_APPLICABLE_IDS.has(w.patternId));
@@ -8227,13 +8683,13 @@ var ACTION_BODY_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_B
8227
8683
  var ACTION_RECORD_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_RECORD_WRITE_PATTERN_IDS.includes(p.id));
8228
8684
  var APPLICABLE_IDS = new Set(ACTION_BODY_WRITE_PATTERN_IDS);
8229
8685
  var RECORD_WRITE_IDS = new Set(ACTION_RECORD_WRITE_PATTERN_IDS);
8230
- var isRec20 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8231
- function asArray39(v) {
8232
- if (Array.isArray(v)) return v.filter((x) => isRec20(x));
8233
- if (isRec20(v)) {
8686
+ var isRec23 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8687
+ function asArray40(v) {
8688
+ if (Array.isArray(v)) return v.filter((x) => isRec23(x));
8689
+ if (isRec23(v)) {
8234
8690
  return Object.entries(v).map(([name, def]) => ({
8235
8691
  name,
8236
- ...isRec20(def) ? def : {}
8692
+ ...isRec23(def) ? def : {}
8237
8693
  }));
8238
8694
  }
8239
8695
  return [];
@@ -8247,11 +8703,11 @@ function collectActionBodies(stack) {
8247
8703
  const sites = [];
8248
8704
  const seen = /* @__PURE__ */ new Set();
8249
8705
  const collect = (actions, pathPrefix, parentObject) => {
8250
- asArray39(actions).forEach((action, index) => {
8706
+ asArray40(actions).forEach((action, index) => {
8251
8707
  const type = typeof action.type === "string" ? action.type : "script";
8252
8708
  if (type !== "script") return;
8253
8709
  const body = action.body;
8254
- if (!isRec20(body) || body.language !== "js") return;
8710
+ if (!isRec23(body) || body.language !== "js") return;
8255
8711
  const source = body.source;
8256
8712
  if (typeof source !== "string" || source.trim() === "") return;
8257
8713
  const name = typeof action.name === "string" && action.name ? action.name : `#${index}`;
@@ -8262,7 +8718,7 @@ function collectActionBodies(stack) {
8262
8718
  });
8263
8719
  };
8264
8720
  collect(stack.actions, "actions");
8265
- asArray39(stack.objects).forEach((obj, objIndex) => {
8721
+ asArray40(stack.objects).forEach((obj, objIndex) => {
8266
8722
  const parentObject = typeof obj.name === "string" && obj.name ? obj.name : void 0;
8267
8723
  collect(obj.actions, `objects[${objIndex}].actions`, parentObject);
8268
8724
  });
@@ -8270,7 +8726,7 @@ function collectActionBodies(stack) {
8270
8726
  }
8271
8727
  function validateActionBodyWrites(stack) {
8272
8728
  const findings = [];
8273
- if (!isRec20(stack)) return findings;
8729
+ if (!isRec23(stack)) return findings;
8274
8730
  const sites = collectActionBodies(stack);
8275
8731
  if (sites.length === 0) return findings;
8276
8732
  let objectFields = null;
@@ -8329,13 +8785,13 @@ var import_shared3 = require("@objectstack/spec/shared");
8329
8785
  var FLOW_NODE_WRITE_UNKNOWN_FIELD = "flow-node-write-unknown-field";
8330
8786
  var FLOW_WRITE_NODE_TYPES = ["update_record", "create_record"];
8331
8787
  var FLOW_WRITE_NODE_TYPES_DEFERRED = [];
8332
- var isRec21 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8333
- function asArray40(v) {
8334
- if (Array.isArray(v)) return v.filter((x) => isRec21(x));
8335
- if (isRec21(v)) {
8788
+ var isRec24 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8789
+ function asArray41(v) {
8790
+ if (Array.isArray(v)) return v.filter((x) => isRec24(x));
8791
+ if (isRec24(v)) {
8336
8792
  return Object.entries(v).map(([name, def]) => ({
8337
8793
  name,
8338
- ...isRec21(def) ? def : {}
8794
+ ...isRec24(def) ? def : {}
8339
8795
  }));
8340
8796
  }
8341
8797
  return [];
@@ -8348,8 +8804,8 @@ function readLiteralObjectName2(config) {
8348
8804
  var COVERED_TYPES = new Set(FLOW_WRITE_NODE_TYPES);
8349
8805
  function validateFlowNodeWrites(stack) {
8350
8806
  const findings = [];
8351
- if (!isRec21(stack)) return findings;
8352
- const flows = asArray40(stack.flows);
8807
+ if (!isRec24(stack)) return findings;
8808
+ const flows = asArray41(stack.flows);
8353
8809
  if (flows.length === 0) return findings;
8354
8810
  let objectFields = null;
8355
8811
  flows.forEach((flow, flowIndex) => {
@@ -8357,10 +8813,10 @@ function validateFlowNodeWrites(stack) {
8357
8813
  const walked = walkFlowNodes(flow, `flows[${flowIndex}]`);
8358
8814
  walked.forEach(({ node, path: nodePath, regionTrail }, walkIndex) => {
8359
8815
  if (typeof node.type !== "string" || !COVERED_TYPES.has(node.type)) return;
8360
- const config = isRec21(node.config) ? node.config : void 0;
8816
+ const config = isRec24(node.config) ? node.config : void 0;
8361
8817
  if (!config) return;
8362
8818
  const fields = config.fields;
8363
- if (!isRec21(fields)) return;
8819
+ if (!isRec24(fields)) return;
8364
8820
  const written = Object.keys(fields);
8365
8821
  if (written.length === 0) return;
8366
8822
  const objectName = readLiteralObjectName2(config);
@@ -8406,6 +8862,15 @@ var REFERENCE_INTEGRITY_RULES = [
8406
8862
  // `action` is deliberately absent (validateActionNameRefs owns it) and so is
8407
8863
  // `component` (an unregistered ref renders a named diagnostic, not silence).
8408
8864
  { name: "validateNavTargetRefs", run: validateNavTargetRefs },
8865
+ // [#7912] The THIRD question about a nav entry, after "does the target
8866
+ // resolve?" (above) and "is it granted?" (`validateNavAccess`): can the
8867
+ // destination serve at all? An object's own `enable` block can make its list
8868
+ // answer 404/405 for every persona, and no gate authorable on the entry
8869
+ // expresses that — which is how #7544's dead row survived review for a year.
8870
+ // The server now prunes such an entry from the `/meta` payload; the
8871
+ // maintainer ruling of 2026-08-12 makes THIS the mandatory companion, so the
8872
+ // prune is never silent to the author who wrote the row.
8873
+ { name: "validateNavObjectServability", run: validateNavObjectServability },
8409
8874
  { name: "validateTranslationReferences", run: validateTranslationReferences },
8410
8875
  // The same family from the other end (#5417). Its sibling above asks "does
8411
8876
  // this bundle key resolve?"; this one asks "is there a key at all?" — a form
@@ -8501,8 +8966,8 @@ function validateReferenceIntegrity(stack) {
8501
8966
 
8502
8967
  // src/lint-flow-patterns.ts
8503
8968
  var import_automation5 = require("@objectstack/spec/automation");
8504
- var import_data8 = require("@objectstack/spec/data");
8505
- function asArray41(v) {
8969
+ var import_data10 = require("@objectstack/spec/data");
8970
+ function asArray42(v) {
8506
8971
  if (Array.isArray(v)) return v;
8507
8972
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
8508
8973
  return [];
@@ -8776,7 +9241,7 @@ function scanBranchRouting(at, nodes, edges, findings) {
8776
9241
  function filterCarriesNoCondition(filter) {
8777
9242
  if (filter === void 0 || filter === null) return true;
8778
9243
  if (typeof filter !== "object" || Array.isArray(filter)) return false;
8779
- return (0, import_data8.reduceFilterVerdict)(filter) === "true";
9244
+ return (0, import_data10.reduceFilterVerdict)(filter) === "true";
8780
9245
  }
8781
9246
  function describeUnboundedFilter(filter) {
8782
9247
  if (filter === void 0 || filter === null) return "no `filter` key";
@@ -8886,7 +9351,7 @@ function scanApprovalReviseLoops(at, nodes, edges, findings) {
8886
9351
  }
8887
9352
  function lintFlowPatterns(stack) {
8888
9353
  const findings = [];
8889
- for (const flow of asArray41(stack.flows)) {
9354
+ for (const flow of asArray42(stack.flows)) {
8890
9355
  const flowName = typeof flow.name === "string" ? flow.name : "(unnamed flow)";
8891
9356
  const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
8892
9357
  const edges = Array.isArray(flow.edges) ? flow.edges : [];
@@ -8984,7 +9449,7 @@ var import_node_fs = require("fs");
8984
9449
  var import_meta6 = {};
8985
9450
  var LIVENESS_DEAD_PROPERTY = "liveness-dead-property";
8986
9451
  var LIVENESS_EXPERIMENTAL_PROPERTY = "liveness-experimental-property";
8987
- function asArray42(v) {
9452
+ function asArray43(v) {
8988
9453
  if (Array.isArray(v)) return v;
8989
9454
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
8990
9455
  return [];
@@ -9121,11 +9586,11 @@ function lintLivenessProperties(stack) {
9121
9586
  const findings = [];
9122
9587
  const objectWarn = loadWarnMap(dir, "object");
9123
9588
  const fieldWarn = loadWarnMap(dir, "field");
9124
- for (const obj of asArray42(stack.objects)) {
9589
+ for (const obj of asArray43(stack.objects)) {
9125
9590
  const objName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
9126
9591
  if (objectWarn.size > 0) checkItem("object", obj, `object '${objName}'`, objectWarn, findings);
9127
9592
  if (fieldWarn.size > 0) {
9128
- for (const field of asArray42(obj.fields)) {
9593
+ for (const field of asArray43(obj.fields)) {
9129
9594
  const fieldName = typeof field.name === "string" ? field.name : "(unnamed field)";
9130
9595
  checkItem("field", field, `object '${objName}' \xB7 field '${fieldName}'`, fieldWarn, findings);
9131
9596
  }
@@ -9134,7 +9599,7 @@ function lintLivenessProperties(stack) {
9134
9599
  for (const { type, key } of TYPE_COLLECTIONS) {
9135
9600
  const warnMap = loadWarnMap(dir, type);
9136
9601
  if (warnMap.size === 0) continue;
9137
- for (const item of asArray42(stack[key])) {
9602
+ for (const item of asArray43(stack[key])) {
9138
9603
  const name = typeof item.name === "string" ? item.name : typeof item.object === "string" ? item.object : `(unnamed ${type})`;
9139
9604
  checkItem(type, item, `${type} '${name}'`, warnMap, findings);
9140
9605
  }
@@ -9143,12 +9608,12 @@ function lintLivenessProperties(stack) {
9143
9608
  }
9144
9609
 
9145
9610
  // src/lint-autonumber-formats.ts
9146
- var import_data9 = require("@objectstack/spec/data");
9611
+ var import_data11 = require("@objectstack/spec/data");
9147
9612
  var AUTONUMBER_UNKNOWN_FIELD = "autonumber-references-unknown-field";
9148
9613
  var AUTONUMBER_OPTIONAL_FIELD = "autonumber-references-optional-field";
9149
9614
  var AUTONUMBER_SELF_REFERENCE = "autonumber-references-self";
9150
9615
  var AUTONUMBER_LITERAL_TOKEN = "autonumber-unrecognized-token";
9151
- function asArray43(v) {
9616
+ function asArray44(v) {
9152
9617
  if (Array.isArray(v)) return v;
9153
9618
  if (v && typeof v === "object") {
9154
9619
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -9157,9 +9622,9 @@ function asArray43(v) {
9157
9622
  }
9158
9623
  function lintAutonumberFormats(stack) {
9159
9624
  const findings = [];
9160
- for (const obj of asArray43(stack.objects)) {
9625
+ for (const obj of asArray44(stack.objects)) {
9161
9626
  const objectName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
9162
- const fields = asArray43(obj.fields);
9627
+ const fields = asArray44(obj.fields);
9163
9628
  const fieldMeta = /* @__PURE__ */ new Map();
9164
9629
  for (const f of fields) {
9165
9630
  if (typeof f.name === "string") fieldMeta.set(f.name, { required: f.required === true });
@@ -9169,8 +9634,8 @@ function lintAutonumberFormats(stack) {
9169
9634
  const name = typeof f.name === "string" ? f.name : "(unnamed field)";
9170
9635
  const fmt = typeof f.autonumberFormat === "string" ? f.autonumberFormat : typeof f.format === "string" ? f.format : "";
9171
9636
  if (!fmt) continue;
9172
- const tokens = (0, import_data9.parseAutonumberFormat)(fmt);
9173
- const refs = (0, import_data9.referencedFields)(tokens);
9637
+ const tokens = (0, import_data11.parseAutonumberFormat)(fmt);
9638
+ const refs = (0, import_data11.referencedFields)(tokens);
9174
9639
  const where = `object '${objectName}' \xB7 field '${name}' (autonumber "${fmt}")`;
9175
9640
  for (const t of tokens) {
9176
9641
  if (t.kind !== "literal") continue;
@@ -9225,7 +9690,7 @@ function lintAutonumberFormats(stack) {
9225
9690
 
9226
9691
  // src/lint-view-refs.ts
9227
9692
  var import_spec5 = require("@objectstack/spec");
9228
- function asArray44(v) {
9693
+ function asArray45(v) {
9229
9694
  if (Array.isArray(v)) return v;
9230
9695
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
9231
9696
  return [];
@@ -9253,7 +9718,7 @@ function lintViewRefs(stack) {
9253
9718
  s.add(kind);
9254
9719
  };
9255
9720
  const containers = [];
9256
- for (const v of asArray44(stack.views)) {
9721
+ for (const v of asArray45(stack.views)) {
9257
9722
  if (v.viewKind) {
9258
9723
  if (typeof v.name === "string") indexKind(v.name, v.viewKind === "form" ? "form" : "list");
9259
9724
  continue;
@@ -9262,7 +9727,7 @@ function lintViewRefs(stack) {
9262
9727
  const object = viewContainerObjectName(v);
9263
9728
  if (object) containers.push({ object, container: v });
9264
9729
  }
9265
- for (const obj of asArray44(stack.objects)) {
9730
+ for (const obj of asArray45(stack.objects)) {
9266
9731
  const object = typeof obj.name === "string" ? obj.name : void 0;
9267
9732
  if (!object) continue;
9268
9733
  if (obj.list || obj.form || obj.listViews || obj.formViews) {
@@ -9316,11 +9781,11 @@ function lintViewRefs(stack) {
9316
9781
  });
9317
9782
  }
9318
9783
  };
9319
- for (const obj of asArray44(stack.objects)) {
9784
+ for (const obj of asArray45(stack.objects)) {
9320
9785
  const object = typeof obj.name === "string" ? obj.name : void 0;
9321
- for (const action of asArray44(obj.actions)) checkAction(action, object);
9786
+ for (const action of asArray45(obj.actions)) checkAction(action, object);
9322
9787
  }
9323
- for (const action of asArray44(stack.actions)) checkAction(action);
9788
+ for (const action of asArray45(stack.actions)) checkAction(action);
9324
9789
  return findings;
9325
9790
  }
9326
9791
 
@@ -9624,7 +10089,6 @@ var CLI_ONLY = ["cli"];
9624
10089
  var CLI_AND_RUNTIME = ["cli", "runtime-publish"];
9625
10090
  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.";
9626
10091
  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.";
9627
- 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.";
9628
10092
  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.";
9629
10093
  var EXPRESSION_INVALID = "expression-invalid";
9630
10094
  var AUTHORING_RULES = [
@@ -9690,6 +10154,31 @@ var AUTHORING_RULES = [
9690
10154
  surfaceReason: RUNTIME_OBJECT_WRITES_P2,
9691
10155
  run: (stack) => validateFunctionalCompleteness(stack)
9692
10156
  },
10157
+ // [#7521, via cloud#1225] A managed object advertising a generic write verb
10158
+ // in `enable.apiMethods` that its own resolved affordances refuse. Every key
10159
+ // is one we know and each is individually valid, so #4001's unknown-key
10160
+ // rejection and the Zod parse both pass it; the contradiction is only visible
10161
+ // when the two keys are read TOGETHER, which nothing did at authoring time.
10162
+ //
10163
+ // `gating` because the declaration is already false when it ships: objectql's
10164
+ // registry strips the verb at registration, so the metadata advertises an API
10165
+ // the product does not serve. That strip has been correct and silent — a
10166
+ // `console.warn` on every control-plane boot that went unread for the life of
10167
+ // a real divergence (`sys_environment`/`sys_package`). This entry is the
10168
+ // ruling's "close it where the author is"; boot stays warn-and-strip.
10169
+ //
10170
+ // Pre-parse: the predicate reads only authored keys, and the finding must
10171
+ // survive an unrelated schema error elsewhere in the stack.
10172
+ {
10173
+ name: "validateManagedApiMethods",
10174
+ tier: "gating",
10175
+ input: "normalized",
10176
+ commands: ALL,
10177
+ source: "packages/lint/src/validate-managed-api-methods.ts",
10178
+ surfaces: CLI_ONLY,
10179
+ surfaceReason: RUNTIME_OBJECT_WRITES_P2,
10180
+ run: (stack) => validateManagedApiMethods(stack)
10181
+ },
9693
10182
  // A view container in `views: []` that registers zero views: nothing appears
9694
10183
  // in the Console, and the schema step cannot tell it from an intentionally
9695
10184
  // empty one. The FLAT-list-view arm no longer needs this tier — `ViewSchema`
@@ -10069,14 +10558,47 @@ var AUTHORING_RULES = [
10069
10558
  // what decides whether any given diagnostic gates, exactly as `lintFlowPatterns`
10070
10559
  // has worked since #3760. The promotion follows the #5762 precedent: a family
10071
10560
  // that gains an `error` finding moves its registry tier in the same edit.
10561
+ //
10562
+ // ─── The `views[]` visibility-predicate FAMILY at the runtime door (#7220) ───
10563
+ //
10564
+ // This entry and `validatePredicatePathRefs` below moved to `runtime-publish`
10565
+ // in ONE edit, on the maintainer's 2026-08-10 ruling, sequenced after #4717's
10566
+ // `advisories` channel landed (PR #7435). Before that move a `view` written
10567
+ // through Studio / REST `/meta` / MCP — the only door most tenants have, and
10568
+ // the door AI authors use — was judged by NONE of the family's rule ids (six
10569
+ // at the time of the move; seven since #7659 added
10570
+ // `predicate-rhs-path-shaped` inside the second entry).
10571
+ //
10572
+ // They move together on purpose, and the two entries carry one comment because
10573
+ // they are one wall: #7214's implementer wired its own rule here alone and then
10574
+ // REVERTED it, because a `view` refused for an unresolvable predicate PATH
10575
+ // while a predicate that does not parse at all walks through the same door is
10576
+ // less predictable than refusing neither. A half-wired wall is worse than an
10577
+ // unwired one, so `authoring-rule-wiring.test.ts` now pins the family property
10578
+ // directly: every id on this surface is gated at the runtime door, or none is.
10579
+ //
10580
+ // The previous `surfaceReason` on THIS entry was `RUNTIME_NEEDS_FULL_SNAPSHOT`,
10581
+ // and re-measuring it at move time found it false: both rule functions read
10582
+ // `stack.views` and `stack.pages` and NO other collection — never `objects` —
10583
+ // so the per-write snapshot the gate builds is not partial for them, it is
10584
+ // complete. (`pages` is simply absent on a `view` write, so the page half
10585
+ // contributes zero findings to both differential passes rather than inventing
10586
+ // any.) The reason was not describing this rule; it was the default a rule got
10587
+ // when nobody measured, which is the #4409/#4463 defect one layer in.
10588
+ //
10589
+ // Runtime input tier: the gate hands the rules the body as persisted, without
10590
+ // `normalizeStackInput`, so the ADR-0087 D2 alias fold does NOT run at this
10591
+ // door. That costs the family nothing — `validateVisibilityPredicates` reads
10592
+ // `visibleWhen ?? visibleOn ?? visibility` itself, canonical-first, precisely
10593
+ // so a caller handing it a raw authored object still gets a verdict.
10072
10594
  {
10073
10595
  name: "validateVisibilityPredicates",
10074
10596
  tier: "gating",
10075
10597
  input: "normalized",
10076
10598
  commands: ALL,
10077
10599
  source: "packages/lint/src/validate-visibility-predicates.ts",
10078
- surfaces: CLI_ONLY,
10079
- surfaceReason: RUNTIME_NEEDS_FULL_SNAPSHOT,
10600
+ surfaces: CLI_AND_RUNTIME,
10601
+ runtimeTypes: ["view"],
10080
10602
  run: (stack) => validateVisibilityPredicates(stack)
10081
10603
  },
10082
10604
  // #7010 — the same predicate surface, one question further in. The three
@@ -10093,14 +10615,28 @@ var AUTHORING_RULES = [
10093
10615
  // object's addressable path set is NOT closed (lookup traversal, system
10094
10616
  // columns, formula outputs), and an `error` gate over an open set generates
10095
10617
  // false build errors. See the rule's module note.
10618
+ //
10619
+ // #7659 adds a THIRD id here, `predicate-rhs-path-shaped`, which is not a
10620
+ // resolution question at all: the metadata-admin renderer resolves paths only
10621
+ // on the LEFT of `==` / `!=` and hands the right side to its literal parser,
10622
+ // so `data.a == data.b` resolves both sides cleanly, passes the two rules
10623
+ // above, and still compares against the string "data.b" — a constant verdict.
10624
+ // It carries `error` on a dotted chain (no reading under which it worked) and
10625
+ // `warning` on a bare word (`status == active` compares as the text today, so
10626
+ // refusing it would fail a build over metadata that renders correctly). The
10627
+ // per-finding severity is what gates, exactly as `lintFlowPatterns` has worked
10628
+ // since #3760; the entry's `gating` tier is unchanged because it already was.
10096
10629
  {
10097
10630
  name: "validatePredicatePathRefs",
10098
10631
  tier: "gating",
10099
10632
  input: "normalized",
10100
10633
  commands: ALL,
10101
10634
  source: "packages/lint/src/validate-predicate-path-refs.ts",
10102
- surfaces: CLI_ONLY,
10103
- surfaceReason: RUNTIME_VISIBILITY_FAMILY_IS_CLI_ONLY,
10635
+ // The second half of the #7220 family move — see the block above the
10636
+ // `validateVisibilityPredicates` entry. This is the rule whose solo wiring
10637
+ // was reverted; it is wired now because its siblings are.
10638
+ surfaces: CLI_AND_RUNTIME,
10639
+ runtimeTypes: ["view"],
10104
10640
  run: (stack) => validatePredicatePathRefs(stack)
10105
10641
  },
10106
10642
  // #1874 — flow authoring anti-patterns. Advisory by default; a finding marked
@@ -10260,16 +10796,110 @@ var AUTHORING_RULES = [
10260
10796
  // a runtime enforcement point (fail-closed OWD default, canonical enum, anchor
10261
10797
  // binding gate, vocabulary freeze), moving the failure from a runtime deny to
10262
10798
  // an author-time fix-it. Per ADR-0049 this is not advisory security.
10799
+ //
10800
+ // [#7576] The `surfaceReason` below is MEASURED. Its predecessor was not, and
10801
+ // was false in both halves — it read: "Already gated at this surface by a
10802
+ // DIFFERENT mechanism: plugin-security registers an ADR-0094 authoring gate on
10803
+ // `object` (`registerAuthoringGate`) that enforces the same OWD posture rules
10804
+ // on every runtime write. Running the linter here as well would double-report
10805
+ // one refusal in two vocabularies."
10806
+ //
10807
+ // - COVERAGE. `object-posture-gate.ts` reads exactly `sharingModel` and
10808
+ // `externalSharingModel` through a local `OWD_WIDTH`, and never touches
10809
+ // `fields`, `permissions`, `books` or `data`. Of the THIRTEEN rule ids this
10810
+ // block carries it covers ONE — `security-external-wider-than-internal`
10811
+ // (its R2). The gate's other half, R1 (env-tighten-only, ADR-0086 D1),
10812
+ // corresponds to no lint rule at all, so it is not coverage in the other
10813
+ // direction either. Twelve rules were enforced at no runtime door while
10814
+ // this field said they were.
10815
+ // - DOUBLE-REPORTING. It cannot happen, and not by luck: `saveMetaItem` runs
10816
+ // `assertRuntimeAuthoringRules` (this table, 422 `invalid_metadata`) BEFORE
10817
+ // `runAuthoringGate` (the ADR-0094 gate, 403 `owd_external_wider`), and
10818
+ // both refuse by THROWING. The first to fire ends the write, so an author
10819
+ // sees one refusal, never two. The stated cost of moving was imaginary; the
10820
+ // reason it has not moved is the measured one below.
10821
+ //
10822
+ // The move IS taken now — the #7891 programme's three slices, in order:
10823
+ //
10824
+ // - #8307: the ADR-0091 seed pair crossed (`runtimeTypes: ['seed']`), with
10825
+ // the isolation proof that the differential cancels every finding this
10826
+ // function derives from the sibling collections.
10827
+ // - #8309: the snapshot repair. The gate used to carry `objects` and
10828
+ // nothing else, so the three cross-collection rules judged a universe
10829
+ // missing the collection they compare against (measured: 38 phantom
10830
+ // `security-master-detail-ungranted` per-write vs 4 whole-stack,
10831
+ // PR #7886). `RuntimeStackContext` now carries `permissions`/`books` in
10832
+ // BOTH differential passes and `TYPE_TO_STACK_KEY` maps both types.
10833
+ // - #8310 slice 1: `runtimeTypes` gains `permission` + `book` (PR #8546).
10834
+ // `object` measured DIRTY on that tree and was escalated, not forced.
10835
+ // - #8310 slice 2 (this state): `object` crosses under the maintainer
10836
+ // ruling recorded on #8310 (2026-08-13, 「接受你的全部建议」): an
10837
+ // authored OWD is REQUIRED at the runtime object door — an object
10838
+ // publish with no authored `sharingModel` is refused with the 422 lint
10839
+ // envelope (`security-owd-unset`); absence is not a decision. The ~16
10840
+ // objectql/rest suite files that relied on OWD-less publishes were
10841
+ // repaired honestly (fixtures author their posture), and
10842
+ // `meta-object-owd-gate.test.ts` re-pins the door ORDER: this table
10843
+ // answers first (`saveMetaItem` runs it before `runAuthoringGate`), the
10844
+ // ADR-0094-seam 403 doors answer for what passes lint. The same ruling
10845
+ // retired the plugin gate's R2 `owd_external_wider` arm as a duplicate
10846
+ // of this door (R1 env-tighten-only STAYS — no lint rule covers it);
10847
+ // see `object-posture-gate.ts` and the ADR-0094 amendment.
10848
+ //
10849
+ // `security-role-word` is NOT in this entry any more — that is what the
10850
+ // `validateSecurityRoleWord` entry below records. It judges six collections
10851
+ // (objects, fields, actions, permission sets, positions, apps — plus books),
10852
+ // and `positions`/`apps` are neither carried by the per-write snapshot nor
10853
+ // mapped in `TYPE_TO_STACK_KEY`, so declaring `permission`/`book` on a
10854
+ // function that still contained it would have enforced ONE rule id for a
10855
+ // strict subset of its collections: a door where a permission set named
10856
+ // `role_manager` is refused and a position named `sales_role` walks through
10857
+ // — the #7220 failure this table refuses to build, in either direction. The
10858
+ // rule therefore stays behind WHOLE (#8310's explicit call), as its own
10859
+ // entry.
10860
+ //
10861
+ // This entry remains the rest of the D7 block (12 rule ids) as ONE
10862
+ // registration, not a per-rule split: the baseline/candidate differential is
10863
+ // what keeps a write of one declared type from leaking the other rules'
10864
+ // whole-stack findings — every finding derived from a sibling collection is
10865
+ // produced byte-identically in both passes and cancels in the diff. Only
10866
+ // findings the written item itself adds are attributed to the write.
10263
10867
  {
10264
10868
  name: "validateSecurityPosture",
10265
10869
  tier: "gating",
10266
10870
  input: "parsed",
10267
10871
  commands: ALL,
10268
10872
  source: "packages/lint/src/validate-security-posture.ts",
10269
- surfaces: CLI_ONLY,
10270
- 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.",
10873
+ surfaces: CLI_AND_RUNTIME,
10874
+ runtimeTypes: ["seed", "permission", "book", "object"],
10271
10875
  run: (stack) => validateSecurityPosture(stack)
10272
10876
  },
10877
+ // [ADR-0090 D3 / #8310] The vocabulary freeze, split out of
10878
+ // `validateSecurityPosture` the day the rest of that block crossed the
10879
+ // runtime wall — so that it could stay behind WHOLE rather than cross for
10880
+ // three of the six collections it judges (#7220: one rule id must sit on ONE
10881
+ // side of the wall). The split is a surface boundary, not taste: the rule's
10882
+ // verdict and findings are byte-identical to before on every CLI command
10883
+ // (both entries run on all three), and the runtime door does not run it for
10884
+ // ANY type.
10885
+ //
10886
+ // The road to crossing is concrete and short, recorded here so the next
10887
+ // seat prices it correctly: carry `positions`/`apps` in
10888
+ // `RuntimeStackContext` + `CONTEXT_STACK_KEYS`, map both types in
10889
+ // `TYPE_TO_STACK_KEY` (both are `allowRuntimeCreate: true`, so the writes
10890
+ // are real), then declare `runtimeTypes: ['object', 'permission', 'book',
10891
+ // 'position', 'app']` on THIS entry — all six collections in one edit, the
10892
+ // #7220 discipline satisfied.
10893
+ {
10894
+ name: "validateSecurityRoleWord",
10895
+ tier: "gating",
10896
+ input: "parsed",
10897
+ commands: ALL,
10898
+ source: "packages/lint/src/validate-security-posture.ts",
10899
+ surfaces: CLI_ONLY,
10900
+ 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.",
10901
+ run: (stack) => validateSecurityRoleWord(stack)
10902
+ },
10273
10903
  // ADR-0105 D6 — the org tree is a REPORTING dimension. An RLS policy or
10274
10904
  // sharing rule that walks it builds a second permission hierarchy (the
10275
10905
  // dual-hierarchy mistake ADR-0057 D5 retired) and cannot widen Layer 0 anyway,
@@ -10320,7 +10950,7 @@ var AUTHORING_RULES = [
10320
10950
  commands: ALL,
10321
10951
  source: "packages/lint/src/validate-rls-predicate-enforceability.ts",
10322
10952
  surfaces: CLI_ONLY,
10323
- 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.",
10953
+ 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.",
10324
10954
  run: (stack) => validateRlsPredicateEnforceability(stack)
10325
10955
  },
10326
10956
  // #4762 — the same "declared but enforces nothing" question, for the two
@@ -10396,8 +11026,34 @@ var TYPE_TO_STACK_KEY = {
10396
11026
  dashboard: "dashboards",
10397
11027
  agent: "agents",
10398
11028
  hook: "hooks",
10399
- seed: "seeds"
11029
+ // [#7576] `data`, NOT `seeds`. The metadata TYPE is `seed`; the stack KEY that
11030
+ // holds seeds is `data` (`ObjectStackDefinitionSchema.data: z.array(SeedSchema)`)
11031
+ // — a stack has no `seeds` key at all, and `PLURAL_TO_SINGULAR` declares no
11032
+ // mapping onto one either.
11033
+ //
11034
+ // The wrong spelling was INERT rather than harmless, and it is the #4449 shape
11035
+ // one surface over: the wiring guard asks only that a declared type HAS a
11036
+ // mapping, never that the mapping names a key some rule reads. So it would
11037
+ // have stayed green while the gate built `{ objects, seeds: [item] }` for
11038
+ // every seed write and every rule reading `stack.data` saw nothing — wired,
11039
+ // and running on nothing, with `rulesRun` reporting the rules as having run.
11040
+ // Nothing declared `seed` in `runtimeTypes` at the time, so correcting it
11041
+ // changed no behaviour then; it was corrected here, with the measurement
11042
+ // that found it (#7576), rather than left for the rollout card to trip
11043
+ // over. The ADR-0091 seed pair now DOES declare `seed` (#8307), so this
11044
+ // mapping is load-bearing today, not merely inert-and-correct.
11045
+ seed: "data",
11046
+ // [#8309] `permission`/`book` map ahead of their registration (#8310), the
11047
+ // same order `seed` arrived in: the mapping plus the enriched snapshot below
11048
+ // are this card's halves, and the `runtimeTypes` flip is deliberately NOT —
11049
+ // a mapping without a declaring rule is inert by construction (the gate
11050
+ // filters by `runtimeTypes` before it ever consults this table), while a
11051
+ // declaration without the mapping is the wired-onto-nothing state the wiring
11052
+ // guard refuses. Landing the mapping first keeps #8310 a registry data edit.
11053
+ permission: "permissions",
11054
+ book: "books"
10400
11055
  };
11056
+ var CONTEXT_STACK_KEYS = ["objects", "permissions", "books"];
10401
11057
  function runtimeAuthoringRulesFor(type) {
10402
11058
  return AUTHORING_RULES.filter(
10403
11059
  (r) => r.surfaces.includes("runtime-publish") && (r.runtimeTypes ?? []).includes(type)
@@ -10415,6 +11071,23 @@ function stackKeyForType(type) {
10415
11071
  return TYPE_TO_STACK_KEY[type] ?? null;
10416
11072
  }
10417
11073
  var fingerprint = (f) => `${f.rule}\0${f.where}\0${f.path}\0${f.message}`;
11074
+ function buildRuntimeWriteSnapshots(args) {
11075
+ const stackKey = stackKeyForType(args.type);
11076
+ if (!stackKey) return null;
11077
+ if (!args.item || typeof args.item !== "object") return null;
11078
+ const item = args.item;
11079
+ const itemName = typeof item.name === "string" ? item.name : void 0;
11080
+ const baseline = {};
11081
+ for (const key of CONTEXT_STACK_KEYS) {
11082
+ const collection = args.context?.[key] ?? [];
11083
+ baseline[key] = key === stackKey ? collection.filter((o) => !itemName || o?.name !== itemName) : collection;
11084
+ }
11085
+ const candidate = {
11086
+ ...baseline,
11087
+ [stackKey]: [...baseline[stackKey] ?? [], item]
11088
+ };
11089
+ return { baseline, candidate };
11090
+ }
10418
11091
  function runRules(rules, stack, ctx) {
10419
11092
  const findings = [];
10420
11093
  for (const rule of rules) {
@@ -10437,19 +11110,15 @@ function runRuntimeAuthoringRules(args) {
10437
11110
  const rules = runtimeAuthoringRulesFor(args.type);
10438
11111
  const empty = { errors: [], advisories: [], rulesRun: [] };
10439
11112
  if (rules.length === 0) return empty;
10440
- const stackKey = stackKeyForType(args.type);
10441
- if (!stackKey) return empty;
10442
- if (!args.item || typeof args.item !== "object") return empty;
10443
- const item = args.item;
10444
- const itemName = typeof item.name === "string" ? item.name : void 0;
10445
- const contextObjects = args.context?.objects ?? [];
11113
+ const snapshots = buildRuntimeWriteSnapshots({
11114
+ type: args.type,
11115
+ item: args.item,
11116
+ ...args.context !== void 0 ? { context: args.context } : {}
11117
+ });
11118
+ if (!snapshots) return empty;
10446
11119
  const ctx = { sduiManifest: args.sduiManifest };
10447
- const writesIntoContext = stackKey === "objects";
10448
- const baselineObjects = writesIntoContext ? contextObjects.filter((o) => !itemName || o?.name !== itemName) : contextObjects;
10449
- const baseline = { objects: baselineObjects };
10450
- const candidate = writesIntoContext ? { objects: [...baselineObjects, item] } : { objects: baselineObjects, [stackKey]: [item] };
10451
- const before = new Set(runRules(rules, baseline, ctx).map(fingerprint));
10452
- const added = runRules(rules, candidate, ctx).filter((f) => !before.has(fingerprint(f)));
11120
+ const before = new Set(runRules(rules, snapshots.baseline, ctx).map(fingerprint));
11121
+ const added = runRules(rules, snapshots.candidate, ctx).filter((f) => !before.has(fingerprint(f)));
10453
11122
  return {
10454
11123
  errors: added.filter((f) => f.severity === "error"),
10455
11124
  advisories: added.filter((f) => f.severity !== "error"),
@@ -10499,6 +11168,7 @@ function runRuntimeAuthoringRules(args) {
10499
11168
  DASHBOARD_ACTION_ROUTE_UNRESOLVED,
10500
11169
  DASHBOARD_ACTION_TARGET_UNDEFINED,
10501
11170
  DASHBOARD_FILTER_FIELD_UNKNOWN,
11171
+ DASHBOARD_FILTER_FIELD_UNPROVISIONED,
10502
11172
  DEFAULT_AGENT_OUTSIDE_ROSTER,
10503
11173
  EXPRESSION_INVALID,
10504
11174
  FIELD_GROUP_EMPTY,
@@ -10525,6 +11195,7 @@ function runRuntimeAuthoringRules(args) {
10525
11195
  FLOW_NODE_WRITE_UNKNOWN_FIELD,
10526
11196
  FLOW_PHANTOM_AGGREGATION,
10527
11197
  FLOW_RUNAS_UNSCOPED,
11198
+ FLOW_TEMPLATE_FIELD_UNPROVISIONED,
10528
11199
  FLOW_TEMPLATE_LOOKUP_TRAVERSAL,
10529
11200
  FLOW_TEMPLATE_UNKNOWN_FIELD,
10530
11201
  FLOW_TIME_RELATIVE_ANTIPATTERN,
@@ -10546,9 +11217,11 @@ function runRuntimeAuthoringRules(args) {
10546
11217
  LIST_VIEW_FILTERS_IN_VIEWS_MODE,
10547
11218
  LIVENESS_DEAD_PROPERTY,
10548
11219
  LIVENESS_EXPERIMENTAL_PROPERTY,
11220
+ MANAGED_API_METHOD_UNAFFORDABLE,
10549
11221
  MAX_SCHEMA_WALK_DEPTH,
10550
11222
  MEASURE_AGGREGATE_INCOHERENT,
10551
11223
  NAV_OBJECT_UNGRANTED,
11224
+ NAV_OBJECT_UNSERVABLE,
10552
11225
  NAV_TARGET_UNRESOLVED,
10553
11226
  NULL_GUARD_HINT,
10554
11227
  OBJECT_REFERENCE_UNKNOWN,
@@ -10557,15 +11230,18 @@ function runRuntimeAuthoringRules(args) {
10557
11230
  ORG_AXIS_CROSS_ORG_BU_GRANT,
10558
11231
  ORG_AXIS_PERMISSION_INHERITANCE,
10559
11232
  PAGE_FIELD_UNKNOWN,
11233
+ PAGE_FIELD_UNPROVISIONED,
10560
11234
  PAGE_SOURCE_CLASSNAME,
10561
11235
  PREDICATE_PATH_UNRESOLVED,
10562
11236
  PREDICATE_PATH_UNROOTED,
11237
+ PREDICATE_RHS_PATH_SHAPED,
10563
11238
  PRE_SEAL_PHASES,
10564
11239
  REACT_BLOCK_NEEDS_RECORD_CONTEXT,
10565
11240
  REACT_CHART_AGGREGATE_INVALID,
10566
11241
  REACT_CHART_AXIS_UNKNOWN,
10567
11242
  REACT_CHART_DRILLDOWN_INVALID,
10568
11243
  REACT_CHART_FIELD_UNKNOWN,
11244
+ REACT_CHART_FIELD_UNPROVISIONED,
10569
11245
  REFERENCE_INTEGRITY_RULES,
10570
11246
  RLS_PREDICATE_OVER_BUDGET,
10571
11247
  RLS_PREDICATE_UNENFORCEABLE,
@@ -10576,6 +11252,7 @@ function runRuntimeAuthoringRules(args) {
10576
11252
  SEARCHABLE_FIELD_UNSEARCHABLE,
10577
11253
  SECURITY_ANCHOR_HIGH_PRIVILEGE,
10578
11254
  SECURITY_BOOK_AUDIENCE_UNKNOWN_SET,
11255
+ SECURITY_CBP_NO_RELATION,
10579
11256
  SECURITY_DELEGATION_MISSING_REASON,
10580
11257
  SECURITY_EXTERNAL_WIDER,
10581
11258
  SECURITY_FLS_UNQUALIFIED_KEY,
@@ -10589,6 +11266,7 @@ function runRuntimeAuthoringRules(args) {
10589
11266
  SEED_INSERT_MODE_DUPLICATES_ON_REPLAY,
10590
11267
  SEED_VALUE_OUTSIDE_STATE_MACHINE,
10591
11268
  SEMANTIC_ROLE_FIELD_UNKNOWN,
11269
+ SEMANTIC_ROLE_FIELD_UNPROVISIONED,
10592
11270
  SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
10593
11271
  SHARING_RULE_UNLOWERABLE_CONDITION,
10594
11272
  STARTUP_OPEN_VOCABULARY_VERDICT,
@@ -10626,6 +11304,7 @@ function runRuntimeAuthoringRules(args) {
10626
11304
  WIDGET_MEASURE_UNKNOWN,
10627
11305
  authoringRulesFor,
10628
11306
  buildAccessMatrix,
11307
+ buildRuntimeWriteSnapshots,
10629
11308
  diffAccessMatrix,
10630
11309
  extractHookBodyWriteSet,
10631
11310
  extractHookBodyWrites,
@@ -10669,7 +11348,9 @@ function runRuntimeAuthoringRules(args) {
10669
11348
  validateHookBodyWrites,
10670
11349
  validateJsxPages,
10671
11350
  validateListViewMode,
11351
+ validateManagedApiMethods,
10672
11352
  validateNavAccess,
11353
+ validateNavObjectServability,
10673
11354
  validateNavTargetRefs,
10674
11355
  validateObjectReferences,
10675
11356
  validateOrgAxisRedLines,
@@ -10687,6 +11368,7 @@ function runRuntimeAuthoringRules(args) {
10687
11368
  validateRuleSchemaFormats,
10688
11369
  validateSearchableFields,
10689
11370
  validateSecurityPosture,
11371
+ validateSecurityRoleWord,
10690
11372
  validateSeedReplaySafety,
10691
11373
  validateSeedStateMachine,
10692
11374
  validateSemanticRoles,