@objectstack/lint 17.0.0-rc.4 → 17.0.0-rc.6

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,10 +61,13 @@ __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
+ DEFAULT_AGENT_OUTSIDE_ROSTER: () => DEFAULT_AGENT_OUTSIDE_ROSTER,
64
65
  EXPRESSION_INVALID: () => EXPRESSION_INVALID,
65
66
  FIELD_GROUP_EMPTY: () => FIELD_GROUP_EMPTY,
66
67
  FIELD_GROUP_SHADOWED: () => FIELD_GROUP_SHADOWED,
67
68
  FIELD_GROUP_UNDECLARED: () => FIELD_GROUP_UNDECLARED,
69
+ FILTER_EMPTY_COMBINATOR: () => FILTER_EMPTY_COMBINATOR,
70
+ FILTER_EMPTY_NODE: () => FILTER_EMPTY_NODE,
68
71
  FILTER_TOKEN_UNKNOWN: () => FILTER_TOKEN_UNKNOWN,
69
72
  FLOW_APPROVAL_REVISE_DEAD_END: () => FLOW_APPROVAL_REVISE_DEAD_END,
70
73
  FLOW_APPROVAL_REVISE_DISABLED: () => FLOW_APPROVAL_REVISE_DISABLED,
@@ -91,6 +94,7 @@ __export(index_exports, {
91
94
  FLOW_TIME_RELATIVE_DESCRIPTOR_UNROUTABLE: () => FLOW_TIME_RELATIVE_DESCRIPTOR_UNROUTABLE,
92
95
  FLOW_TRIGGER_UNKNOWN_EVENT: () => FLOW_TRIGGER_UNKNOWN_EVENT,
93
96
  FLOW_TRIGGER_UNKNOWN_OBJECT: () => FLOW_TRIGGER_UNKNOWN_OBJECT,
97
+ FLOW_TRIGGER_UNROUTABLE: () => FLOW_TRIGGER_UNROUTABLE,
94
98
  FLOW_UPDATE_READONLY_FIELD: () => FLOW_UPDATE_READONLY_FIELD,
95
99
  FLOW_UPDATE_READONLY_WHEN_FIELD: () => FLOW_UPDATE_READONLY_WHEN_FIELD,
96
100
  FLOW_WRITE_NODE_TYPES: () => FLOW_WRITE_NODE_TYPES,
@@ -111,19 +115,25 @@ __export(index_exports, {
111
115
  NULL_GUARD_HINT: () => NULL_GUARD_HINT,
112
116
  OBJECT_REFERENCE_UNKNOWN: () => OBJECT_REFERENCE_UNKNOWN,
113
117
  OBJECT_REFERENCE_UNREGISTERED_PLATFORM: () => OBJECT_REFERENCE_UNREGISTERED_PLATFORM,
118
+ OPEN_VOCABULARY_PROBES: () => OPEN_VOCABULARY_PROBES,
114
119
  ORG_AXIS_CROSS_ORG_BU_GRANT: () => ORG_AXIS_CROSS_ORG_BU_GRANT,
115
120
  ORG_AXIS_PERMISSION_INHERITANCE: () => ORG_AXIS_PERMISSION_INHERITANCE,
116
121
  PAGE_FIELD_UNKNOWN: () => PAGE_FIELD_UNKNOWN,
117
122
  PAGE_SOURCE_CLASSNAME: () => PAGE_SOURCE_CLASSNAME,
123
+ PREDICATE_PATH_UNRESOLVED: () => PREDICATE_PATH_UNRESOLVED,
124
+ PREDICATE_PATH_UNROOTED: () => PREDICATE_PATH_UNROOTED,
125
+ PRE_SEAL_PHASES: () => PRE_SEAL_PHASES,
118
126
  REACT_BLOCK_NEEDS_RECORD_CONTEXT: () => REACT_BLOCK_NEEDS_RECORD_CONTEXT,
119
127
  REACT_CHART_AGGREGATE_INVALID: () => REACT_CHART_AGGREGATE_INVALID,
120
128
  REACT_CHART_AXIS_UNKNOWN: () => REACT_CHART_AXIS_UNKNOWN,
121
129
  REACT_CHART_DRILLDOWN_INVALID: () => REACT_CHART_DRILLDOWN_INVALID,
122
130
  REACT_CHART_FIELD_UNKNOWN: () => REACT_CHART_FIELD_UNKNOWN,
123
131
  REFERENCE_INTEGRITY_RULES: () => REFERENCE_INTEGRITY_RULES,
132
+ RLS_PREDICATE_OVER_BUDGET: () => RLS_PREDICATE_OVER_BUDGET,
124
133
  RLS_PREDICATE_UNENFORCEABLE: () => RLS_PREDICATE_UNENFORCEABLE,
125
134
  RLS_PREDICATE_UNPARSEABLE: () => RLS_PREDICATE_UNPARSEABLE,
126
135
  RUNTIME_AJV_OPTIONS: () => RUNTIME_AJV_OPTIONS,
136
+ SEAL_MARKERS: () => SEAL_MARKERS,
127
137
  SEARCHABLE_FIELD_UNKNOWN: () => SEARCHABLE_FIELD_UNKNOWN,
128
138
  SEARCHABLE_FIELD_UNSEARCHABLE: () => SEARCHABLE_FIELD_UNSEARCHABLE,
129
139
  SECURITY_ANCHOR_HIGH_PRIVILEGE: () => SECURITY_ANCHOR_HIGH_PRIVILEGE,
@@ -143,6 +153,9 @@ __export(index_exports, {
143
153
  SEMANTIC_ROLE_FIELD_UNKNOWN: () => SEMANTIC_ROLE_FIELD_UNKNOWN,
144
154
  SHARING_RULE_RUNTIME_VARIABLE_CONDITION: () => SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
145
155
  SHARING_RULE_UNLOWERABLE_CONDITION: () => SHARING_RULE_UNLOWERABLE_CONDITION,
156
+ STARTUP_OPEN_VOCABULARY_VERDICT: () => STARTUP_OPEN_VOCABULARY_VERDICT,
157
+ STARTUP_VERDICT_ASSERTIVE_WORDING: () => STARTUP_VERDICT_ASSERTIVE_WORDING,
158
+ STARTUP_VERDICT_HINT: () => STARTUP_VERDICT_HINT,
146
159
  STYLE_CLASSNAME_TAILWIND: () => STYLE_CLASSNAME_TAILWIND,
147
160
  STYLE_NODE_MISSING_ID: () => STYLE_NODE_MISSING_ID,
148
161
  STYLE_RESPONSIVE_NO_BASE: () => STYLE_RESPONSIVE_NO_BASE,
@@ -164,7 +177,9 @@ __export(index_exports, {
164
177
  VIEW_KEY_COLLISION: () => VIEW_KEY_COLLISION,
165
178
  VIEW_REF_FORM_TARGET_KIND: () => VIEW_REF_FORM_TARGET_KIND,
166
179
  VIEW_REF_FORM_TARGET_MISSING: () => VIEW_REF_FORM_TARGET_MISSING,
167
- VISIBILITY_ALIAS_DEPRECATED: () => VISIBILITY_ALIAS_DEPRECATED,
180
+ VISIBILITY_BARE_IDENTIFIER: () => VISIBILITY_BARE_IDENTIFIER,
181
+ VISIBILITY_PREDICATE_OVER_BUDGET: () => VISIBILITY_PREDICATE_OVER_BUDGET,
182
+ VISIBILITY_PREDICATE_SYNTAX: () => VISIBILITY_PREDICATE_SYNTAX,
168
183
  VISIBILITY_ROOT_MISLAYERED: () => VISIBILITY_ROOT_MISLAYERED,
169
184
  WIDGET_DATASET_UNKNOWN: () => WIDGET_DATASET_UNKNOWN,
170
185
  WIDGET_DIMENSION_UNKNOWN: () => WIDGET_DIMENSION_UNKNOWN,
@@ -176,6 +191,7 @@ __export(index_exports, {
176
191
  diffAccessMatrix: () => diffAccessMatrix,
177
192
  extractHookBodyWriteSet: () => extractHookBodyWriteSet,
178
193
  extractHookBodyWrites: () => extractHookBodyWrites,
194
+ findStartupRegistryVerdicts: () => findStartupRegistryVerdicts,
179
195
  findUnguardedNullableOperands: () => findUnguardedNullableOperands,
180
196
  isSourceAuthoredPage: () => isSourceAuthoredPage,
181
197
  lintAutonumberFormats: () => lintAutonumberFormats,
@@ -205,6 +221,7 @@ __export(index_exports, {
205
221
  validateChartBindings: () => validateChartBindings,
206
222
  validateComponentProps: () => validateComponentProps,
207
223
  validateDashboardActionRefs: () => validateDashboardActionRefs,
224
+ validateEmptyCombinators: () => validateEmptyCombinators,
208
225
  validateFilterTokens: () => validateFilterTokens,
209
226
  validateFlowNodeWrites: () => validateFlowNodeWrites,
210
227
  validateFlowTemplatePaths: () => validateFlowTemplatePaths,
@@ -220,6 +237,7 @@ __export(index_exports, {
220
237
  validateOrgAxisRedLines: () => validateOrgAxisRedLines,
221
238
  validatePageFieldBindings: () => validatePageFieldBindings,
222
239
  validatePageSourceStyling: () => validatePageSourceStyling,
240
+ validatePredicatePathRefs: () => validatePredicatePathRefs,
223
241
  validateReactPageProps: () => validateReactPageProps,
224
242
  validateReactPages: () => validateReactPages,
225
243
  validateReadonlyFlowWrites: () => validateReadonlyFlowWrites,
@@ -941,6 +959,38 @@ function validateStackExpressions(stack) {
941
959
  for (const e of res.errors) issues.push({ where, message: e.message, source: e.source, severity: "error" });
942
960
  for (const w of res.warnings) issues.push({ where, message: w.message, source: w.source, severity: "warning" });
943
961
  };
962
+ const FIELD_RULE_BOUND_ROOTS = ["record", "previous", "parent"];
963
+ const FIELD_RULE_USER_ROOTS = ["current_user", "user", "ctx", "os"];
964
+ const FIELD_RULE_SLOT_CONSEQUENCE_GENERIC = "the predicate faults, and a faulting rule never produces the verdict you declared \u2014 each slot resolves the fault to its own fallback, and none of those fallbacks is yours";
965
+ const FIELD_RULE_SLOT_CONSEQUENCE = {
966
+ visibleWhen: "the predicate faults and the renderer falls back to VISIBLE (`resolveFieldRuleState` evaluates visibility with `fallback: true`, and no server-side gate evaluates a field-level `visibleWhen` at all), leaving the field the test was meant to hide showing for everyone (#6146)",
967
+ readonlyWhen: "the predicate faults \u2014 and the two ends fault in OPPOSITE directions. The server treats the field as LOCKED (`isReadonlyWhenLocked` will not waive a declared lock it could not evaluate, #4889) and drops your value from the payload, while the form still renders the field editable (`fallback: false`). Per ADR-0057 D10 the server is the one that decides: the field looks writable, the save reports success, and the value silently never lands",
968
+ requiredWhen: "the predicate faults and the requirement is never enforced anywhere \u2014 the server logs it and SKIPS the check (fail-open, #4977 deliberately did not take #4889's carve-out) and the form does not mark the field required either, so a record saves with the field empty",
969
+ // Listed rather than left to the `??` below, so the map covers every slot
970
+ // the field walk passes and the default stays unreachable. `FieldSchema`
971
+ // declares this key only as a `retiredKey`, which rejects it by name, so
972
+ // there is no fourth runtime to measure — the honest clause is the generic
973
+ // one, not a fabricated fourth cell (#6716).
974
+ conditionalRequired: FIELD_RULE_SLOT_CONSEQUENCE_GENERIC
975
+ };
976
+ const checkFieldRuleRoot = (where, slot, raw) => {
977
+ const source = celSourceOf(raw);
978
+ if (!source) return;
979
+ const roots = (0, import_formula2.collectCelRootIdentifiers)(source);
980
+ if (!roots.ok) return;
981
+ const kept = import_formula2.SCOPE_ROOTS.filter(
982
+ (r) => !FIELD_RULE_BOUND_ROOTS.includes(r) && roots.roots.includes(r)
983
+ );
984
+ if (kept.length === 0) return;
985
+ const root = FIELD_RULE_USER_ROOTS.find((r) => kept.includes(r)) ?? kept[0];
986
+ const prescription = FIELD_RULE_USER_ROOTS.includes(root) ? `To gate the CHOICES of a select by user, move the predicate to the option's own \`visibleWhen\` (\`options: [{ \u2026, visibleWhen: \u2026 }]\`) \u2014 per-option is the one \`*When\` surface that binds \`current_user\` and its ADR-0068 aliases. To hide the FIELD by role, declare field-level security on a permission set (\`fields: { '<object>.<field>': { readable: false } }\`), which the server enforces. To gate on record state, rewrite the predicate against \`record\`.` : root === "data" ? `\`data\` is the root of a METADATA form (a \`*.form\` module \u2014 the metadata row being edited); this is an OBJECT field, whose runtime form binds the row as \`record\` \u2014 one key name, two form kinds, two roots. Rewrite \`data.<key>\` as \`record.<field>\`.` : `\`${root}\` is declared platform-wide and bound at OTHER evaluation sites (flow, automation, screen and action predicates), never at the field level. Rewrite the predicate against \`record\` (plus \`previous\`, and \`parent\` on a master-detail line item), or move the decision to a surface that binds \`${root}\`.`;
987
+ issues.push({
988
+ where,
989
+ message: `\`${slot}\` reads \`${root}\`, but a field-level conditional rule binds only \`record\` (plus \`previous\`, and \`parent\` on a master-detail line item) \u2014 \`${root}\` is unbound here, so ${FIELD_RULE_SLOT_CONSEQUENCE[slot] ?? FIELD_RULE_SLOT_CONSEQUENCE_GENERIC}. ` + prescription,
990
+ source,
991
+ severity: "error"
992
+ });
993
+ };
944
994
  const checkDeclaredPredicate = (where, raw) => {
945
995
  if (raw == null) return;
946
996
  const res = (0, import_formula2.validateExpression)("predicate", raw);
@@ -973,7 +1023,14 @@ function validateStackExpressions(stack) {
973
1023
  if (retired.length > 0) {
974
1024
  issues.push({
975
1025
  where: `${at} \xB7 node '${node.id}' (script) callable`,
976
- message: `script node carries \`${retired.map((k) => `config.${k}`).join("`, `")}\` \u2014 retired in @objectstack/spec 17 (#4343). The built-in 'email'/'slack' actions were logger-backed stubs that delivered nothing, and inline \`config.script\` was never executed. ` + (action && action !== "invoke_function" && !["email", "slack"].includes(action) ? `\`actionType: '${action}'\` named a registered function \u2014 move it to \`function: '${action}'\`. ` : `Use a \`notify\` node for mail, a \`connector_action\` (Slack connector) or \`http\` node for Slack, and a registered function for logic. `) + `Run \`os migrate meta --from 16\` to rewrite it automatically.`,
1026
+ message: `script node carries \`${retired.map((k) => `config.${k}`).join("`, `")}\` \u2014 retired in @objectstack/spec 17 (#4343). The built-in 'email'/'slack' actions were logger-backed stubs that delivered nothing, and inline \`config.script\` was never executed. ` + (action && action !== "invoke_function" && !["email", "slack"].includes(action) ? `\`actionType: '${action}'\` named a registered function \u2014 move it to \`function: '${action}'\`. ` : `Use a \`notify\` node for mail, a \`connector_action\` (Slack connector) or \`http\` node for Slack, and a registered function for logic. `) + // #6856 route D (maintainer-ruled): the house sentence names the TOOL's
1027
+ // behaviour, never the retired key's fate — "rewrite it" reads two ways
1028
+ // over a branch that DELETES the key (template/recipients/variables/script),
1029
+ // "rewrite existing sources" only one. Plain-quoted (not a template literal)
1030
+ // so this site is a member of `retired-key-migrate-sentence.test.ts`'s
1031
+ // widened scan (#7030) on the same textual shape as the spec corpus — no
1032
+ // interpolation lives in this clause, so nothing is lost switching quote style.
1033
+ "Run `os migrate meta --from 16` to rewrite existing sources automatically.",
977
1034
  source: JSON.stringify({ id: node.id, type: node.type, config: cfg })
978
1035
  });
979
1036
  } else if (!fn) {
@@ -1007,13 +1064,27 @@ function validateStackExpressions(stack) {
1007
1064
  for (const [fname, f] of fieldList) {
1008
1065
  for (const key of ["requiredWhen", "readonlyWhen", "conditionalRequired", "visibleWhen"]) {
1009
1066
  check(`object '${objectName}' \xB7 field '${fname}' ${key}`, f[key], objectName, "record");
1067
+ checkFieldRuleRoot(`object '${objectName}' \xB7 field '${fname}' ${key}`, key, f[key]);
1068
+ }
1069
+ for (const [oi, opt] of asArray2(f.options).entries()) {
1070
+ const label2 = typeof opt.value === "string" ? `'${opt.value}'` : `#${oi}`;
1071
+ check(
1072
+ `object '${objectName}' \xB7 field '${fname}' option ${label2} visibleWhen`,
1073
+ opt.visibleWhen,
1074
+ objectName,
1075
+ "record"
1076
+ );
1010
1077
  }
1011
- const roWhenSource = celSourceOf(f.readonlyWhen);
1012
- if (masters !== 1 && roWhenSource && readsParentRoot(roWhenSource)) {
1078
+ for (const [slot, raw, consequence2] of [
1079
+ ["readonlyWhen", f.readonlyWhen, `the field would be locked on every write`],
1080
+ ["requiredWhen", f.requiredWhen, `the requirement would never be enforced \u2014 the predicate faults, the server logs and skips it, and the field stays optional in the database`]
1081
+ ]) {
1082
+ const source = celSourceOf(raw);
1083
+ if (masters === 1 || !source || !readsParentRoot(source)) continue;
1013
1084
  issues.push({
1014
- where: `object '${objectName}' \xB7 field '${fname}' readonlyWhen`,
1015
- message: `\`readonlyWhen\` reads \`parent\`, but object '${objectName}' declares ${masters === 0 ? "no" : `${masters}`} \`master_detail\` relationship${masters === 1 ? "" : "s"} \u2014 so the server has no header record to bind as \`parent\` and the field would be locked on every write. ` + (masters === 0 ? `Declare the owning relationship as \`Field.masterDetail('<master>')\`, or rewrite the predicate against \`record\`.` : `\`parent\` needs exactly one master; name the header explicitly through \`record.<fk>\` state instead, or model the extra relationship as a \`lookup\`.`),
1016
- source: roWhenSource,
1085
+ where: `object '${objectName}' \xB7 field '${fname}' ${slot}`,
1086
+ message: `\`${slot}\` reads \`parent\`, but object '${objectName}' declares ${masters === 0 ? "no" : `${masters}`} \`master_detail\` relationship${masters === 1 ? "" : "s"} \u2014 so the server has no header record to bind as \`parent\` and ${consequence2}. ` + (masters === 0 ? `Declare the owning relationship as \`Field.masterDetail('<master>')\`, or rewrite the predicate against \`record\`.` : `\`parent\` needs exactly one master; name the header explicitly through \`record.<fk>\` state instead, or model the extra relationship as a \`lookup\`.`),
1087
+ source,
1017
1088
  severity: "error"
1018
1089
  });
1019
1090
  }
@@ -1106,6 +1177,348 @@ function validateStackExpressions(stack) {
1106
1177
  return issues;
1107
1178
  }
1108
1179
 
1180
+ // src/lint-startup-registry-verdict.ts
1181
+ var import_node_module = require("module");
1182
+ var import_meta = {};
1183
+ var cachedTs = null;
1184
+ function loadTypeScript() {
1185
+ if (cachedTs) return cachedTs;
1186
+ const anchor = typeof import_meta !== "undefined" && import_meta.url ? import_meta.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
1187
+ try {
1188
+ cachedTs = (0, import_node_module.createRequire)(anchor)("typescript");
1189
+ } catch (err) {
1190
+ throw new Error(
1191
+ `@objectstack/lint: checking plugin source for startup registry verdicts requires the "typescript" 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 "typescript" in the image; it is only loaded when plugin source is actually checked.`
1192
+ );
1193
+ }
1194
+ return cachedTs;
1195
+ }
1196
+ var STARTUP_OPEN_VOCABULARY_VERDICT = "startup-open-vocabulary-verdict";
1197
+ var STARTUP_VERDICT_ASSERTIVE_WORDING = "startup-verdict-assertive-wording";
1198
+ var OPEN_VOCABULARY_PROBES = /* @__PURE__ */ new Map([
1199
+ [
1200
+ "getRegisteredNodeTypes",
1201
+ "ADR-0018 keeps the flow node-type vocabulary open and runtime-extensible \u2014 a plugin registers its executor from its own init()/start(), which can be after this line (#4771)"
1202
+ ],
1203
+ [
1204
+ "knownNodeTypes",
1205
+ "ADR-0018 keeps the flow node-type vocabulary open and runtime-extensible \u2014 a plugin registers its executor from its own init()/start(), which can be after this line (#4771)"
1206
+ ],
1207
+ [
1208
+ "getUnknownNodeTypeAudit",
1209
+ 'the unknown-node-type audit is a snapshot of an OPEN vocabulary a plugin can still extend during boot; it is read-only by design and answers "unknown as of now", never "unknown for this deployment" (#4771)'
1210
+ ],
1211
+ [
1212
+ "getActionDescriptors",
1213
+ 'ADR-0018 action descriptors are published by plugins during boot, so a type missing from them here means "not published YET" (#4771)'
1214
+ ],
1215
+ [
1216
+ "getRegisteredExecutors",
1217
+ "executors are contributed by plugins during boot \u2014 an executor missing here may simply be a plugin that has not started"
1218
+ ],
1219
+ [
1220
+ "listExecutors",
1221
+ "executors are contributed by plugins during boot \u2014 an executor missing here may simply be a plugin that has not started"
1222
+ ],
1223
+ [
1224
+ "getRegisteredTools",
1225
+ "AI tools are contributed by plugins during boot, so the registered set is not final until every plugin has started"
1226
+ ],
1227
+ [
1228
+ "listConnectors",
1229
+ "connectors are contributed by plugins during boot (StackSchema.connectors documents provider-bound instances registered by plugins), so the list is not final until every plugin has started"
1230
+ ],
1231
+ [
1232
+ "getRegisteredConnectors",
1233
+ "connectors are contributed by plugins during boot, so the registered set is not final until every plugin has started"
1234
+ ],
1235
+ [
1236
+ "listCapabilities",
1237
+ "ADR-0066 capabilities are declared by every installed package, so the set is not final until every plugin has contributed its declarations"
1238
+ ],
1239
+ [
1240
+ "getRegisteredCapabilities",
1241
+ "ADR-0066 capabilities are declared by every installed package, so the set is not final until every plugin has contributed its declarations"
1242
+ ],
1243
+ [
1244
+ "listProviders",
1245
+ "providers are contributed by plugins during boot \u2014 a provider missing here may simply be a plugin that has not started"
1246
+ ],
1247
+ [
1248
+ "getRegisteredProviders",
1249
+ "providers are contributed by plugins during boot \u2014 a provider missing here may simply be a plugin that has not started"
1250
+ ]
1251
+ ]);
1252
+ var PRE_SEAL_PHASES = /* @__PURE__ */ new Map([
1253
+ ["constructor", "the constructor runs at composition time \u2014 before ANY plugin has been initialized"],
1254
+ ["init", "another plugin's init() may not have run yet (ADR-0116), so the vocabulary is still filling"],
1255
+ [
1256
+ "start",
1257
+ "sibling plugins' start() have not all run, and ADR-0018 lets a plugin register its contributions from start() \u2014 the vocabulary is still filling"
1258
+ ]
1259
+ ]);
1260
+ var SEAL_MARKERS = ["seal"];
1261
+ var VERDICT_LEVELS = /* @__PURE__ */ new Set(["warn", "error", "fatal"]);
1262
+ var ALL_LEVELS = /* @__PURE__ */ new Set(["warn", "error", "fatal", "info", "debug", "trace", "log"]);
1263
+ var PERSISTENCE_CALLEES = /* @__PURE__ */ new Set(["insert", "insertOne", "update", "updateOne", "upsert", "save", "saveMetaItem"]);
1264
+ var ASSERTIVE_PHRASES = [
1265
+ "will fail",
1266
+ "fails at execution",
1267
+ "nothing will register",
1268
+ "no plugin provides",
1269
+ "is not installed",
1270
+ "is not configured",
1271
+ "you need",
1272
+ "you must install",
1273
+ "must be provisioned"
1274
+ ];
1275
+ var HEDGE_PHRASES = [
1276
+ "not yet",
1277
+ "yet been registered",
1278
+ "so far",
1279
+ "as of",
1280
+ "may still",
1281
+ "might still",
1282
+ "still be registered",
1283
+ "has started",
1284
+ "have started"
1285
+ ];
1286
+ var STARTUP_VERDICT_HINT = 'Take one of the three shapes the fixes took: (1) resolve where the value is USED, not where you start \u2014 a lazy accessor or a `kernel:ready`/`kernel:bootstrapped` hook sees a provider that registered later (`createLazyCacheRateLimitStorage()` in plugin-auth, #4772); (2) seal the vocabulary, then judge \u2014 have the host declare the moment it can no longer grow and draw the conclusion there (`AutomationEngine.sealNodeTypeVocabulary()`, called at `kernel:bootstrapped`, #4771); (3) order the verdict AFTER the mutation it describes, so it cannot attest to a state this same boot goes on to contradict (the ADR-0104 attestation, #4769). If the conclusion must stay here, keep the two worlds apart in the wording: "no executor registered YET (as of plugin start)" is true; "will fail at execution time" is a claim about a world that has not finished forming.';
1287
+ function isFunctionLike(t, node) {
1288
+ return t.isFunctionDeclaration(node) || t.isFunctionExpression(node) || t.isArrowFunction(node) || t.isMethodDeclaration(node) || t.isConstructorDeclaration(node) || t.isGetAccessorDeclaration(node) || t.isSetAccessorDeclaration(node);
1289
+ }
1290
+ function walkSameTick(t, node, visit) {
1291
+ node.forEachChild((child) => {
1292
+ if (isFunctionLike(t, child) || t.isClassDeclaration(child) || t.isClassExpression(child)) return;
1293
+ visit(child);
1294
+ walkSameTick(t, child, visit);
1295
+ });
1296
+ }
1297
+ function walkAll(t, node, visit) {
1298
+ node.forEachChild((child) => {
1299
+ visit(child);
1300
+ walkAll(t, child, visit);
1301
+ });
1302
+ }
1303
+ function calleeName(t, node) {
1304
+ if (!t.isCallExpression(node)) return void 0;
1305
+ const expr = node.expression;
1306
+ if (t.isIdentifier(expr)) return expr.text;
1307
+ if (t.isPropertyAccessExpression(expr) && t.isIdentifier(expr.name)) return expr.name.text;
1308
+ return void 0;
1309
+ }
1310
+ function loggerLevel(t, node) {
1311
+ if (!t.isCallExpression(node)) return void 0;
1312
+ const expr = node.expression;
1313
+ if (!t.isPropertyAccessExpression(expr) || !t.isIdentifier(expr.name)) return void 0;
1314
+ const level = expr.name.text;
1315
+ if (!ALL_LEVELS.has(level)) return void 0;
1316
+ const receiver = expr.expression;
1317
+ let receiverName;
1318
+ if (t.isIdentifier(receiver)) receiverName = receiver.text;
1319
+ else if (t.isPropertyAccessExpression(receiver) && t.isIdentifier(receiver.name)) receiverName = receiver.name.text;
1320
+ if (!receiverName) return void 0;
1321
+ return /^(logger|log|console)$/i.test(receiverName) ? level : void 0;
1322
+ }
1323
+ function literalText(t, node) {
1324
+ const parts = [];
1325
+ const take = (n) => {
1326
+ if (t.isStringLiteralLike(n)) parts.push(n.text);
1327
+ else if (t.isTemplateHead(n) || t.isTemplateMiddle(n) || t.isTemplateTail(n)) parts.push(n.text);
1328
+ };
1329
+ take(node);
1330
+ walkAll(t, node, take);
1331
+ return parts.join(" ").toLowerCase();
1332
+ }
1333
+ function mentionsSeal(t, node) {
1334
+ let found = false;
1335
+ const visit = (n) => {
1336
+ if (found) return;
1337
+ if (t.isIdentifier(n)) {
1338
+ const lower = n.text.toLowerCase();
1339
+ if (SEAL_MARKERS.some((m) => lower.includes(m))) found = true;
1340
+ }
1341
+ };
1342
+ visit(node);
1343
+ walkAll(t, node, visit);
1344
+ return found;
1345
+ }
1346
+ function moduleLevelMutableBindings(t, sf) {
1347
+ const names = /* @__PURE__ */ new Set();
1348
+ for (const st of sf.statements) {
1349
+ if (!t.isVariableStatement(st)) continue;
1350
+ if (st.declarationList.flags & t.NodeFlags.Const) continue;
1351
+ for (const d of st.declarationList.declarations) {
1352
+ if (t.isIdentifier(d.name)) names.add(d.name.text);
1353
+ }
1354
+ }
1355
+ return names;
1356
+ }
1357
+ function indexFunctionBodies(t, sf) {
1358
+ const byName = /* @__PURE__ */ new Map();
1359
+ walkAll(t, sf, (node) => {
1360
+ if (t.isFunctionDeclaration(node) && node.name && node.body) byName.set(node.name.text, node.body);
1361
+ else if (t.isMethodDeclaration(node) && t.isIdentifier(node.name) && node.body) byName.set(node.name.text, node.body);
1362
+ else if (t.isVariableDeclaration(node) && t.isIdentifier(node.name) && node.initializer && (t.isArrowFunction(node.initializer) || t.isFunctionExpression(node.initializer)) && node.initializer.body) {
1363
+ byName.set(node.name.text, node.initializer.body);
1364
+ }
1365
+ });
1366
+ return byName;
1367
+ }
1368
+ function collectLifecycleUnits(t, sf) {
1369
+ const units = [];
1370
+ const readClass = (node) => {
1371
+ const phases = [];
1372
+ let hasLifecycle = false;
1373
+ for (const member of node.members) {
1374
+ if (t.isMethodDeclaration(member) && t.isIdentifier(member.name)) {
1375
+ if (member.name.text === "init" || member.name.text === "start") hasLifecycle = true;
1376
+ } else if (t.isPropertyDeclaration(member) && t.isIdentifier(member.name) && (member.name.text === "init" || member.name.text === "start") && member.initializer && (t.isArrowFunction(member.initializer) || t.isFunctionExpression(member.initializer))) {
1377
+ hasLifecycle = true;
1378
+ }
1379
+ }
1380
+ if (!hasLifecycle) return;
1381
+ for (const member of node.members) {
1382
+ if (t.isConstructorDeclaration(member) && member.body) {
1383
+ phases.push({ phase: "constructor", body: member.body });
1384
+ } else if (t.isMethodDeclaration(member) && t.isIdentifier(member.name) && member.body) {
1385
+ if (PRE_SEAL_PHASES.has(member.name.text)) phases.push({ phase: member.name.text, body: member.body });
1386
+ } else if (t.isPropertyDeclaration(member) && t.isIdentifier(member.name) && PRE_SEAL_PHASES.has(member.name.text) && member.initializer && (t.isArrowFunction(member.initializer) || t.isFunctionExpression(member.initializer)) && member.initializer.body) {
1387
+ phases.push({ phase: member.name.text, body: member.initializer.body });
1388
+ }
1389
+ }
1390
+ if (phases.length) units.push({ label: node.name?.text ?? "<anonymous class>", phases });
1391
+ };
1392
+ const readObjectLiteral = (node) => {
1393
+ const phases = [];
1394
+ let name;
1395
+ let hasLifecycle = false;
1396
+ for (const prop of node.properties) {
1397
+ if (!t.isPropertyAssignment(prop) || !t.isIdentifier(prop.name)) continue;
1398
+ const key = prop.name.text;
1399
+ if (key === "name" && t.isStringLiteralLike(prop.initializer)) name = prop.initializer.text;
1400
+ if ((key === "init" || key === "start") && isFunctionLike(t, prop.initializer)) hasLifecycle = true;
1401
+ if (PRE_SEAL_PHASES.has(key) && (t.isArrowFunction(prop.initializer) || t.isFunctionExpression(prop.initializer)) && prop.initializer.body) {
1402
+ phases.push({ phase: key, body: prop.initializer.body });
1403
+ }
1404
+ }
1405
+ if (!name || !hasLifecycle || !phases.length) return;
1406
+ units.push({ label: `${name} (object plugin)`, phases });
1407
+ };
1408
+ walkAll(t, sf, (node) => {
1409
+ if (t.isClassDeclaration(node) || t.isClassExpression(node)) readClass(node);
1410
+ else if (t.isObjectLiteralExpression(node)) readObjectLiteral(node);
1411
+ });
1412
+ return units;
1413
+ }
1414
+ function findStartupRegistryVerdicts(source, options = {}) {
1415
+ if (!source || !source.trim()) return [];
1416
+ let mentionsAny = false;
1417
+ for (const probe of OPEN_VOCABULARY_PROBES.keys()) {
1418
+ if (source.includes(probe)) {
1419
+ mentionsAny = true;
1420
+ break;
1421
+ }
1422
+ }
1423
+ if (!mentionsAny) return [];
1424
+ const t = loadTypeScript();
1425
+ const fileLabel = options.file ?? "source";
1426
+ let sf;
1427
+ try {
1428
+ sf = t.createSourceFile(fileLabel, source, t.ScriptTarget.Latest, true, t.ScriptKind.TS);
1429
+ } catch {
1430
+ return [];
1431
+ }
1432
+ const findings = [];
1433
+ const moduleBindings = moduleLevelMutableBindings(t, sf);
1434
+ const functionBodies = indexFunctionBodies(t, sf);
1435
+ const lineOf = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
1436
+ for (const unit of collectLifecycleUnits(t, sf)) {
1437
+ for (const { phase, body } of unit.phases) {
1438
+ const scopes = [];
1439
+ const seenNames = /* @__PURE__ */ new Set();
1440
+ const collect = (b, depth) => {
1441
+ scopes.push(b);
1442
+ if (depth >= 2) return;
1443
+ walkSameTick(t, b, (child) => {
1444
+ const name = calleeName(t, child);
1445
+ if (!name || seenNames.has(name)) return;
1446
+ const helper = functionBodies.get(name);
1447
+ if (!helper) return;
1448
+ seenNames.add(name);
1449
+ collect(helper, depth + 1);
1450
+ });
1451
+ };
1452
+ collect(body, 0);
1453
+ const reads = [];
1454
+ for (const scope of scopes) {
1455
+ walkSameTick(t, scope, (node) => {
1456
+ const name = calleeName(t, node);
1457
+ if (name && OPEN_VOCABULARY_PROBES.has(name)) reads.push({ probe: name, node });
1458
+ });
1459
+ }
1460
+ if (!reads.length) continue;
1461
+ if (scopes.some((scope) => mentionsSeal(t, scope))) continue;
1462
+ const records = [];
1463
+ for (const scope of scopes) {
1464
+ walkSameTick(t, scope, (node) => {
1465
+ const level = loggerLevel(t, node);
1466
+ if (level && VERDICT_LEVELS.has(level)) {
1467
+ records.push({ kind: "announced", detail: `${level} log`, line: lineOf(node), node });
1468
+ return;
1469
+ }
1470
+ const cn = calleeName(t, node);
1471
+ if (cn && PERSISTENCE_CALLEES.has(cn)) {
1472
+ records.push({ kind: "persisted", detail: `${cn}()`, line: lineOf(node), node });
1473
+ return;
1474
+ }
1475
+ if (t.isBinaryExpression(node) && node.operatorToken.kind === t.SyntaxKind.EqualsToken) {
1476
+ const lhs = node.left;
1477
+ let target;
1478
+ if (t.isPropertyAccessExpression(lhs) && t.isIdentifier(lhs.name)) {
1479
+ target = lhs.expression.kind === t.SyntaxKind.ThisKeyword ? `this.${lhs.name.text}` : `<obj>.${lhs.name.text}`;
1480
+ } else if (t.isIdentifier(lhs) && moduleBindings.has(lhs.text)) {
1481
+ target = `module-level \`${lhs.text}\``;
1482
+ }
1483
+ if (target) records.push({ kind: "cached", detail: target, line: lineOf(node), node });
1484
+ }
1485
+ });
1486
+ }
1487
+ if (!records.length) continue;
1488
+ const read = reads[0];
1489
+ const record = records[0];
1490
+ const where = `${unit.label}.${phase}${phase === "constructor" ? "" : "()"}`;
1491
+ const note = OPEN_VOCABULARY_PROBES.get(read.probe);
1492
+ const phaseNote = PRE_SEAL_PHASES.get(phase);
1493
+ findings.push({
1494
+ severity: "warning",
1495
+ rule: STARTUP_OPEN_VOCABULARY_VERDICT,
1496
+ where,
1497
+ path: `${fileLabel}:${lineOf(read.node)}`,
1498
+ message: `\`${read.probe}()\` reads a vocabulary that is still filling, and the conclusion is recorded (${record.kind}: ${record.detail}, line ${record.line}). ${note}; ${phaseNote}. "absent" here has two meanings the recorded verdict cannot tell apart \u2014 no provider in this deployment, or a provider that registers later in this same boot \u2014 and nothing retracts the record when the second one turns out to be the case (#4771 / #4772).`,
1499
+ hint: STARTUP_VERDICT_HINT
1500
+ });
1501
+ for (const rec of records) {
1502
+ if (rec.kind !== "announced") continue;
1503
+ const text = literalText(t, rec.node);
1504
+ if (!text) continue;
1505
+ if (HEDGE_PHRASES.some((h) => text.includes(h))) continue;
1506
+ const hit = ASSERTIVE_PHRASES.find((p) => text.includes(p));
1507
+ if (!hit) continue;
1508
+ findings.push({
1509
+ severity: "warning",
1510
+ rule: STARTUP_VERDICT_ASSERTIVE_WORDING,
1511
+ where,
1512
+ path: `${fileLabel}:${rec.line}`,
1513
+ message: `the diagnostic says "${hit}" about a vocabulary that can still grow during this boot. #4771 printed "will fail at execution time" for eight approval flows 0.8s before the executor that runs them was registered, and a deployment that genuinely lacked the plugin printed the identical eight \u2014 so the line could not tell an operator which of the two they had. #4772's remedy ("you need Redis") sent operators to fix a problem they did not have, and connecting Redis did not change the message.`,
1514
+ hint: STARTUP_VERDICT_HINT
1515
+ });
1516
+ }
1517
+ }
1518
+ }
1519
+ return findings;
1520
+ }
1521
+
1109
1522
  // src/validate-list-view-mode.ts
1110
1523
  var LIST_VIEW_FILTERS_IN_VIEWS_MODE = "list-view-filters-in-views-mode";
1111
1524
  function asArray3(v) {
@@ -1245,6 +1658,7 @@ var FLOW_DRAFT_STATUS_AMBIGUOUS = "flow-draft-status-ambiguous";
1245
1658
  var FLOW_TRIGGER_UNKNOWN_EVENT = "flow-trigger-unknown-event";
1246
1659
  var FLOW_TIME_RELATIVE_DESCRIPTOR_INVALID = "flow-time-relative-descriptor-invalid";
1247
1660
  var FLOW_TIME_RELATIVE_DESCRIPTOR_UNROUTABLE = "flow-time-relative-descriptor-unroutable";
1661
+ var FLOW_TRIGGER_UNROUTABLE = "flow-trigger-unroutable";
1248
1662
  var VALID_RECORD_TRIGGER = /^record-(?:before|after)-(?:create|insert|update|delete|write)$/;
1249
1663
  function asArray4(v) {
1250
1664
  if (Array.isArray(v)) return v;
@@ -1262,6 +1676,11 @@ function renderNonObject(v) {
1262
1676
  if (t === "bigint") return `${String(v)}n (a bigint)`;
1263
1677
  return `a ${t}`;
1264
1678
  }
1679
+ function renderTriggerToken(v) {
1680
+ if (typeof v === "string") return `'${v}'`;
1681
+ const json = JSON.stringify(v);
1682
+ return json === void 0 ? `a ${typeof v}` : json;
1683
+ }
1265
1684
  function startNodeOf(flow) {
1266
1685
  const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
1267
1686
  const index = nodes.findIndex((n) => n?.type === "start");
@@ -1381,6 +1800,25 @@ function validateFlowTriggerReadiness(stack) {
1381
1800
  hint: `config.timeRelative describes WHICH records to sweep \u2014 an object: { object, dateField, and exactly one of withinDays | offsetDays } (plus optional filter / maxRecords). A cadence like 'daily' is not a descriptor: HOW OFTEN the sweep runs is the sibling key config.schedule on the same start node (it defaults to daily, so it is usually omitted). See TimeRelativeTriggerSchema and content/docs/references/automation/time-relative-trigger.mdx.`
1382
1801
  });
1383
1802
  }
1803
+ const routesToSomeTrigger = isRecordTriggered2 || isArrayRecordTriggered || isTimeRelative || config.schedule != null || flow.type === "schedule" || flow.type === "api" || triggerType === "api";
1804
+ if (start && flow.type === "record_change" && !routesToSomeTrigger) {
1805
+ const hasTriggerType = config.triggerType != null;
1806
+ findings.push({
1807
+ // `error` (#5762's criterion, applied to a fourth id). The verdict is
1808
+ // the engine's own routing chain — literal `startsWith`/`typeof` tests
1809
+ // with no registry lookup in them — so no installed package can make
1810
+ // this token resolve. `registerTrigger` is keyed by the RESOLVED type,
1811
+ // which is the near-miss worth stating: a plugin can supply the
1812
+ // record-change trigger itself, and it still would not help, because
1813
+ // the flow never reaches the point of asking for one.
1814
+ severity: "error",
1815
+ rule: FLOW_TRIGGER_UNROUTABLE,
1816
+ where: `flow "${flowName}" \u203A start node`,
1817
+ path: `flows[${flowIndex}].nodes[${start.index}].config.triggerType`,
1818
+ message: `declares type: 'record_change' but ` + (hasTriggerType ? `its start node's triggerType is ${renderTriggerToken(config.triggerType)}, which the engine routes to NO trigger` : `its start node has no triggerType at all, so there is nothing for the engine to route`) + ` \u2014 it binds a record-change flow only for a token starting with 'record-', so this flow is demoted to a manual one and never fires. Nothing NAMES it: the unbound-flow audit resolves the same binding and skips the flow as "manual \u2014 nothing to bind", so neither the boot warning nor the startup summary lists it; the only trace is the banner's flow count being one higher than its bound count.`,
1819
+ hint: `Use record-{before,after}-{create,update,delete,write} ('write' is create OR update in one flow, #3427; create/insert are synonyms). If the flow really is launched by hand or from a screen, declare type: 'autolaunched' or 'screen' instead of 'record_change' \u2014 those types have no trigger to be missing.`
1820
+ });
1821
+ }
1384
1822
  if (isAutoTriggered && (flow.status == null || flow.status === "draft")) {
1385
1823
  findings.push({
1386
1824
  severity: "warning",
@@ -2150,14 +2588,14 @@ function validateJsxPages(stack, opts = {}) {
2150
2588
  }
2151
2589
 
2152
2590
  // src/validate-react-pages.ts
2153
- var import_node_module = require("module");
2154
- var import_meta = {};
2591
+ var import_node_module2 = require("module");
2592
+ var import_meta2 = {};
2155
2593
  var cachedTransform = null;
2156
2594
  function loadSucraseTransform() {
2157
2595
  if (cachedTransform) return cachedTransform;
2158
- const anchor = typeof import_meta !== "undefined" && import_meta.url ? import_meta.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
2596
+ const anchor = typeof import_meta2 !== "undefined" && import_meta2.url ? import_meta2.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
2159
2597
  try {
2160
- cachedTransform = (0, import_node_module.createRequire)(anchor)("sucrase").transform;
2598
+ cachedTransform = (0, import_node_module2.createRequire)(anchor)("sucrase").transform;
2161
2599
  } catch (err) {
2162
2600
  throw new Error(
2163
2601
  `@objectstack/lint: validating a kind:'react' page requires the "sucrase" 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 "sucrase" in the image; it is only loaded when a react-source page is validated.`
@@ -2204,7 +2642,7 @@ function validateReactPages(stack) {
2204
2642
  }
2205
2643
 
2206
2644
  // src/validate-react-page-props.ts
2207
- var import_node_module2 = require("module");
2645
+ var import_node_module3 = require("module");
2208
2646
  var import_ui2 = require("@objectstack/spec/ui");
2209
2647
  var import_data4 = require("@objectstack/spec/data");
2210
2648
 
@@ -2323,7 +2761,19 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
2323
2761
  where,
2324
2762
  path: `${path}[${i}]`,
2325
2763
  message: `${subject} entry "${name}" is not a field on object "${objectName}". The declaration is stale: searching it can never match, and the engine silently drops it \u2014 leaving a narrower search than declared, or the auto-default set once every entry is dropped.` + (dotted ? "" : suggest2(name, known)),
2326
- hint: (dotted ? `'search' scans this object's own columns, so a related record's column cannot be a search target \u2014 expand the relation and search the related object, or copy the value onto a formula field here. ` : `Fix the name, or add "${name}" to ${objectName}.fields. `) + `Clients echo this declaration verbatim as the '$searchFields' override, so a stale entry becomes a 400 INVALID_FIELD on list search (#4254), not just a quietly narrowed one.` + (known.size > 0 ? ` Object fields: ${[...known].sort().join(", ")}.` : "")
2764
+ hint: (dotted ? `'search' scans this object's own columns, so a related record's column cannot be a search target \u2014 expand the relation and search the related object, or copy the value onto a stored text field here. ` : `Fix the name, or add "${name}" to ${objectName}.fields. `) + `Clients echo this declaration verbatim as the '$searchFields' override, so a stale entry becomes a 400 INVALID_FIELD on list search (#4254), not just a quietly narrowed one.` + (known.size > 0 ? ` Object fields: ${[...known].sort().join(", ")}.` : "")
2765
+ });
2766
+ continue;
2767
+ }
2768
+ if ((0, import_data3.isVirtualSearchField)(target.fields[name])) {
2769
+ const vtype = target.fields[name]?.type;
2770
+ findings.push({
2771
+ severity: "error",
2772
+ rule: SEARCHABLE_FIELD_UNSEARCHABLE,
2773
+ where,
2774
+ path: `${path}[${i}]`,
2775
+ message: `${subject} entry "${name}" on object "${objectName}" is a virtual '${vtype}' field: its value is computed on read and never stored, so no driver materializes a column for 'search' to scan and the entry can never match. It reads as search coverage and delivers none \u2014 the runtime used to admit it verbatim because the declaration named it (#6674).`,
2776
+ hint: `Mirror the computed value onto a stored text field on "${objectName}" and declare that instead, or drop "${name}". At runtime the ingress gate now refuses this entry with 400 INVALID_FIELD, the same answer a stale entry gets (#4254).`
2327
2777
  });
2328
2778
  continue;
2329
2779
  }
@@ -2358,7 +2808,7 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
2358
2808
  where,
2359
2809
  path: `${path}[${i}]`,
2360
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).`,
2361
- 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 text/formula 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.`
2811
+ 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.`
2362
2812
  });
2363
2813
  }
2364
2814
  return findings;
@@ -2558,8 +3008,21 @@ var COMPONENT_FIELD_SPECS = {
2558
3008
  "element:number": { props: ["field"] },
2559
3009
  "element:filter": { props: ["fields"] },
2560
3010
  "element:form": { props: ["fields"] },
2561
- // The schema says `displayField`; real pages author `labelField`. Accept both.
2562
- "element:record_picker": { props: ["displayField", "labelField", "searchFields"] }
3011
+ // `labelField` is the one field-bearing prop this element declares. Its former
3012
+ // companions `displayField` (renamed to `labelField`, ADR-0087 D2) and
3013
+ // `searchFields` (deleted, ADR-0049) were retired in #5775 and are
3014
+ // `retiredKey()` tombstones on `ElementRecordPickerPropsSchema` — so no
3015
+ // spec-conformant page carries either, and this rule's job (resolve a field
3016
+ // NAME against the object) is not the question a retired key raises (#6629).
3017
+ //
3018
+ // A non-conformant page that writes one anyway is not left unattended: the
3019
+ // #5068 props gate reports the key with its rename/delete prescription. That
3020
+ // gate is advisory and CLI-only and lives in a different registry
3021
+ // (`authoring-rules`) from this suite, so it neither precedes nor suppresses
3022
+ // this rule — what these two entries actually added was a SECOND finding,
3023
+ // saying a field named by a key that no longer exists does not exist either.
3024
+ // The prescription is the useful half; this half was noise on top of it.
3025
+ "element:record_picker": { props: ["labelField"] }
2563
3026
  };
2564
3027
  var RELATED_LIST_TYPE = "record:related_list";
2565
3028
  function componentFieldRefs(type, props, basePath, sep = ".") {
@@ -2719,19 +3182,19 @@ function describeIssue(issue, root, depth = 0) {
2719
3182
  }
2720
3183
 
2721
3184
  // src/validate-react-page-props.ts
2722
- var import_meta2 = {};
2723
- var cachedTs = null;
2724
- function loadTypeScript() {
2725
- if (cachedTs) return cachedTs;
2726
- const anchor = typeof import_meta2 !== "undefined" && import_meta2.url ? import_meta2.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
3185
+ var import_meta3 = {};
3186
+ var cachedTs2 = null;
3187
+ function loadTypeScript2() {
3188
+ if (cachedTs2) return cachedTs2;
3189
+ const anchor = typeof import_meta3 !== "undefined" && import_meta3.url ? import_meta3.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
2727
3190
  try {
2728
- cachedTs = (0, import_node_module2.createRequire)(anchor)("typescript");
3191
+ cachedTs2 = (0, import_node_module3.createRequire)(anchor)("typescript");
2729
3192
  } catch (err) {
2730
3193
  throw new Error(
2731
3194
  `@objectstack/lint: validating a kind:'react' page requires the "typescript" 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 "typescript" in the image; it is only loaded when a react-source page is validated.`
2732
3195
  );
2733
3196
  }
2734
- return cachedTs;
3197
+ return cachedTs2;
2735
3198
  }
2736
3199
  var asArray12 = (v) => Array.isArray(v) ? v : [];
2737
3200
  var BLOCKS = new Map(
@@ -2864,7 +3327,7 @@ function checkChartAggregate(raw, push2) {
2864
3327
  "warning",
2865
3328
  REACT_CHART_AGGREGATE_INVALID,
2866
3329
  "aggregate.groupBy is not set, so the aggregate returns ONE ungrouped row and the chart plots a single point.",
2867
- "Add aggregate.groupBy (a field name, or { field, dateGranularity } to bucket dates) to give the chart a category axis. Deliberate single-value charts are tolerated at warning level for now: ChartAggregateSchema declares groupBy required while ObjectChart honours its absence by falling back to xAxisKey \u2014 objectstack#5583 decides which of the two moves."
3330
+ "Add aggregate.groupBy (a field name, or { field, dateGranularity } to bucket dates) to give the chart a category axis. objectstack#5583 ruled that an ungrouped single-value chart is NOT a supported <ObjectChart> shape \u2014 groupBy stays required, and a single number belongs in an object-metric block instead. This stays a warning rather than an error only because promoting it is its own step."
2868
3331
  );
2869
3332
  }
2870
3333
  const parsed = import_ui2.ChartAggregateSchema.safeParse(raw);
@@ -3124,7 +3587,7 @@ function validateReactPageProps(stack) {
3124
3587
  const source = page.source;
3125
3588
  if (typeof source !== "string" || source.trim() === "") continue;
3126
3589
  const name = String(page.name ?? `#${p}`);
3127
- const tsc = loadTypeScript();
3590
+ const tsc = loadTypeScript2();
3128
3591
  let sf;
3129
3592
  try {
3130
3593
  sf = tsc.createSourceFile("page.tsx", source, tsc.ScriptTarget.Latest, true, tsc.ScriptKind.TSX);
@@ -3382,7 +3845,7 @@ function validateSemanticRoles(stack) {
3382
3845
  (h) => typeof h === "string" && h.length > 0
3383
3846
  );
3384
3847
  if (declaredStrings.length > 0 && declaredGroups.size > 0) {
3385
- const declaredTitle = [obj.nameField, obj.primaryField, obj.displayNameField].find((v) => typeof v === "string" && v.length > 0 && fieldNames.has(v));
3848
+ const declaredTitle = [obj.nameField, obj.displayNameField].find((v) => typeof v === "string" && v.length > 0 && fieldNames.has(v));
3386
3849
  const titleField = declaredTitle ?? ["name", "full_name", "title", "subject", "display_name"].find((c) => fieldNames.has(c));
3387
3850
  const stripSet = new Set(
3388
3851
  declaredStrings.filter((h) => h !== titleField).slice(0, 4)
@@ -3407,6 +3870,60 @@ function validateSemanticRoles(stack) {
3407
3870
  return findings;
3408
3871
  }
3409
3872
 
3873
+ // src/collection-entries.ts
3874
+ function isRec8(v) {
3875
+ return !!v && typeof v === "object" && !Array.isArray(v);
3876
+ }
3877
+ function collectionEntries(v, base) {
3878
+ if (Array.isArray(v)) {
3879
+ const out = [];
3880
+ for (let i = 0; i < v.length; i++) {
3881
+ if (isRec8(v[i])) out.push({ rec: v[i], path: `${base}[${i}]` });
3882
+ }
3883
+ return out;
3884
+ }
3885
+ if (isRec8(v)) {
3886
+ return Object.entries(v).filter(([, def]) => isRec8(def)).map(([name, def]) => ({ rec: { name, ...def }, path: `${base}.${name}` }));
3887
+ }
3888
+ return [];
3889
+ }
3890
+
3891
+ // src/view-walk.ts
3892
+ function isRec9(v) {
3893
+ return !!v && typeof v === "object" && !Array.isArray(v);
3894
+ }
3895
+ function strName5(v) {
3896
+ return typeof v === "string" && v.length > 0 ? v : void 0;
3897
+ }
3898
+ function viewObjectName(view) {
3899
+ return strName5(view.objectName) ?? strName5(view.object) ?? (isRec9(view.data) ? strName5(view.data.object) : void 0);
3900
+ }
3901
+ function viewContainerSites(view, basePath) {
3902
+ if (!isRec9(view)) return [];
3903
+ const sites = [{ view, path: basePath, surface: "", kind: "self" }];
3904
+ if (isRec9(view.form)) {
3905
+ sites.push({ view: view.form, path: `${basePath}.form`, surface: "form", kind: "form" });
3906
+ }
3907
+ for (const key of ["listViews", "formViews"]) {
3908
+ const container = view[key];
3909
+ if (!isRec9(container)) continue;
3910
+ const kind = key === "listViews" ? "listView" : "formView";
3911
+ for (const [subKey, sub] of Object.entries(container)) {
3912
+ if (!isRec9(sub)) continue;
3913
+ sites.push({
3914
+ view: sub,
3915
+ path: `${basePath}.${key}.${subKey}`,
3916
+ surface: `${key}.${subKey}`,
3917
+ kind
3918
+ });
3919
+ }
3920
+ }
3921
+ return sites;
3922
+ }
3923
+ function formViewSites(view, basePath) {
3924
+ return viewContainerSites(view, basePath).filter((site) => site.kind !== "listView");
3925
+ }
3926
+
3410
3927
  // src/validate-form-layout.ts
3411
3928
  var FORM_FIELD_UNKNOWN = "form-field-unknown";
3412
3929
  var FORM_COLSPAN_ABSOLUTE = "absolute-colspan-discouraged";
@@ -3417,6 +3934,12 @@ function asArray16(v) {
3417
3934
  }
3418
3935
  return [];
3419
3936
  }
3937
+ function isRec10(v) {
3938
+ return !!v && typeof v === "object" && !Array.isArray(v);
3939
+ }
3940
+ function strName6(v) {
3941
+ return typeof v === "string" && v.length > 0 ? v : void 0;
3942
+ }
3420
3943
  function fieldNameOf(entry) {
3421
3944
  if (typeof entry === "string") return entry.length > 0 ? entry : null;
3422
3945
  if (entry && typeof entry === "object" && !Array.isArray(entry)) {
@@ -3425,13 +3948,6 @@ function fieldNameOf(entry) {
3425
3948
  }
3426
3949
  return null;
3427
3950
  }
3428
- function boundObject(view) {
3429
- const data = view.data;
3430
- if (data && typeof data === "object" && typeof data.object === "string") {
3431
- return data.object;
3432
- }
3433
- return typeof view.objectName === "string" ? view.objectName : void 0;
3434
- }
3435
3951
  function validateFormLayout(stack) {
3436
3952
  const findings = [];
3437
3953
  const objectFields = /* @__PURE__ */ new Map();
@@ -3441,44 +3957,44 @@ function validateFormLayout(stack) {
3441
3957
  const fields = obj.fields && typeof obj.fields === "object" && !Array.isArray(obj.fields) ? Object.keys(obj.fields) : [];
3442
3958
  objectFields.set(name, new Set(fields));
3443
3959
  }
3444
- const views = asArray16(stack.views);
3445
- for (let i = 0; i < views.length; i++) {
3446
- const view = views[i];
3447
- if (!view || typeof view !== "object") continue;
3448
- const sections = Array.isArray(view.sections) ? view.sections : null;
3449
- if (!sections) continue;
3450
- const viewName = typeof view.name === "string" ? view.name : `(view ${i})`;
3451
- const objName = boundObject(view);
3452
- const known = objName ? objectFields.get(objName) : void 0;
3453
- const where = `view "${viewName}"`;
3454
- const base = `views[${i}]`;
3455
- for (let s = 0; s < sections.length; s++) {
3456
- const sec = sections[s];
3457
- const secFields = sec && typeof sec === "object" && Array.isArray(sec.fields) ? sec.fields : [];
3458
- for (let f = 0; f < secFields.length; f++) {
3459
- const entry = secFields[f];
3460
- const fname = fieldNameOf(entry);
3461
- const fpath = `${base}.sections[${s}].fields[${f}]`;
3462
- if (fname && known && !known.has(fname)) {
3463
- findings.push({
3464
- severity: "warning",
3465
- rule: FORM_FIELD_UNKNOWN,
3466
- where,
3467
- path: fpath,
3468
- message: `${viewName}: field "${fname}" is not a field on object "${objName}" \u2014 it is silently skipped and never renders on the form`,
3469
- hint: `Fix the field name, or add "${fname}" to ${objName}. Section field references must match the object's field names exactly.`
3470
- });
3471
- }
3472
- const colSpan = entry && typeof entry === "object" && !Array.isArray(entry) ? entry.colSpan : void 0;
3473
- if (colSpan != null) {
3474
- findings.push({
3475
- severity: "warning",
3476
- rule: FORM_COLSPAN_ABSOLUTE,
3477
- where,
3478
- path: `${fpath}.colSpan`,
3479
- message: `${viewName}: field "${fname ?? "?"}" sets absolute colSpan ${String(colSpan)} \u2014 the form's column count is derived per surface (mobile 1 / modal 2 / page 3-4), so a fixed span only aligns at one width`,
3480
- hint: `Prefer span: 'full' (whole row at any column count), or omit for auto width. The renderer clamps colSpan to the current column count.`
3481
- });
3960
+ for (const { rec: view, path: viewPath } of collectionEntries(stack.views, "views")) {
3961
+ const viewName = strName6(view.name) ?? strName6(view.object) ?? viewPath;
3962
+ const containerObject = viewObjectName(view);
3963
+ for (const site of formViewSites(view, viewPath)) {
3964
+ const objName = viewObjectName(site.view) ?? containerObject;
3965
+ const known = objName ? objectFields.get(objName) : void 0;
3966
+ const where = site.surface ? `view "${viewName}" \xB7 ${site.surface}` : `view "${viewName}"`;
3967
+ for (const bucket of ["sections", "groups"]) {
3968
+ const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
3969
+ for (let s = 0; s < sections.length; s++) {
3970
+ const sec = sections[s];
3971
+ const secFields = isRec10(sec) && Array.isArray(sec.fields) ? sec.fields : [];
3972
+ for (let f = 0; f < secFields.length; f++) {
3973
+ const entry = secFields[f];
3974
+ const fname = fieldNameOf(entry);
3975
+ const fpath = `${site.path}.${bucket}[${s}].fields[${f}]`;
3976
+ if (fname && known && !known.has(fname)) {
3977
+ findings.push({
3978
+ severity: "warning",
3979
+ rule: FORM_FIELD_UNKNOWN,
3980
+ where,
3981
+ path: fpath,
3982
+ message: `${viewName}: field "${fname}" is not a field on object "${objName}" \u2014 it is silently skipped and never renders on the form`,
3983
+ hint: `Fix the field name, or add "${fname}" to ${objName}. Section field references must match the object's field names exactly.`
3984
+ });
3985
+ }
3986
+ const colSpan = isRec10(entry) ? entry.colSpan : void 0;
3987
+ if (colSpan != null) {
3988
+ findings.push({
3989
+ severity: "warning",
3990
+ rule: FORM_COLSPAN_ABSOLUTE,
3991
+ where,
3992
+ path: `${fpath}.colSpan`,
3993
+ message: `${viewName}: field "${fname ?? "?"}" sets absolute colSpan ${String(colSpan)} \u2014 the form's column count is derived per surface (mobile 1 / modal 2 / page 3-4), so a fixed span only aligns at one width`,
3994
+ hint: `Prefer span: 'full' (whole row at any column count), or omit for auto width. The renderer clamps colSpan to the current column count.`
3995
+ });
3996
+ }
3997
+ }
3482
3998
  }
3483
3999
  }
3484
4000
  }
@@ -3487,17 +4003,12 @@ function validateFormLayout(stack) {
3487
4003
  }
3488
4004
 
3489
4005
  // src/validate-visibility-predicates.ts
3490
- var VISIBILITY_ALIAS_DEPRECATED = "visibility-alias-deprecated";
4006
+ var import_formula3 = require("@objectstack/formula");
3491
4007
  var VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
4008
+ var VISIBILITY_BARE_IDENTIFIER = "visibility-bare-identifier";
4009
+ var VISIBILITY_PREDICATE_SYNTAX = "visibility-predicate-syntax";
4010
+ var VISIBILITY_PREDICATE_OVER_BUDGET = "visibility-predicate-over-budget";
3492
4011
  var CANONICAL = "visibleWhen";
3493
- var ALIASES = ["visibleOn", "visibility"];
3494
- function asArray17(v) {
3495
- if (Array.isArray(v)) return v;
3496
- if (v && typeof v === "object") {
3497
- return Object.entries(v).map(([name, def]) => ({ name, ...def }));
3498
- }
3499
- return [];
3500
- }
3501
4012
  function predicateSource(v) {
3502
4013
  if (typeof v === "string") return v;
3503
4014
  if (v && typeof v === "object" && typeof v.source === "string") {
@@ -3508,6 +4019,67 @@ function predicateSource(v) {
3508
4019
  function usesRoot(source, root) {
3509
4020
  return new RegExp(`(^|[^.\\w$])${root}\\.\\w`).test(source);
3510
4021
  }
4022
+ function withoutStringLiterals(source) {
4023
+ return source.replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, (lit) => " ".repeat(lit.length));
4024
+ }
4025
+ var NON_CEL_SPELLINGS = [
4026
+ { wrote: "===", cel: "==", example: "record.country == 'USA'", re: /===/ },
4027
+ { wrote: "!==", cel: "!=", example: "record.country != 'USA'", re: /!==/ },
4028
+ { wrote: "<>", cel: "!=", example: "record.country != 'USA'", re: /<>/ },
4029
+ { wrote: "and", cel: "&&", example: "record.a == 1 && record.b == 2", re: /(?<![.\w$])and(?![\w$])/i },
4030
+ { wrote: "or", cel: "||", example: "record.a == 1 || record.b == 2", re: /(?<![.\w$])or(?![\w$])/i },
4031
+ { wrote: "not", cel: "!", example: "!record.archived", re: /(?<![.\w$])not(?![\w$])/i },
4032
+ // Assignment where a comparison was meant. Last, and fenced off from every
4033
+ // operator that legitimately contains `=` (`==`, `!=`, `<=`, `>=`).
4034
+ { wrote: "=", cel: "==", example: "record.status == 'open'", re: /(?<![=!<>])=(?!=)/ }
4035
+ ];
4036
+ function quoteSource(source) {
4037
+ const flat = source.replace(/\s+/g, " ").trim();
4038
+ return flat.length > 120 ? `${flat.slice(0, 117)}...` : flat;
4039
+ }
4040
+ function celRefusal(source) {
4041
+ if (!source.trim()) return null;
4042
+ const parsed = (0, import_formula3.parseCelToAstWithReason)(source);
4043
+ if (parsed.ok || parsed.kind === "empty") return null;
4044
+ if (parsed.kind === "bounds") return { kind: "bounds", overrun: parsed.overrun };
4045
+ const identifiers = (0, import_formula3.collectCelRootIdentifiers)(source);
4046
+ const detail = identifiers.ok ? "the expression could not be parsed" : identifiers.error.split("\n")[0].trim();
4047
+ const scannable = withoutStringLiterals(source);
4048
+ return { kind: "syntax", detail, token: NON_CEL_SPELLINGS.find((s) => s.re.test(scannable)) ?? null };
4049
+ }
4050
+ function boundName(overrun) {
4051
+ return overrun.limit && overrun.limitValue !== null ? `the \`${overrun.limit}\` budget (platform limit ${overrun.limitValue})` : "one of the platform's parse budgets";
4052
+ }
4053
+ var VIEW_PAGE_EXTRA_ROOTS = ["current_user", "page"];
4054
+ function isNode2(v) {
4055
+ return !!v && typeof v === "object" && typeof v.op === "string";
4056
+ }
4057
+ function namespaceRoots(node, out) {
4058
+ if (Array.isArray(node)) {
4059
+ for (const child of node) namespaceRoots(child, out);
4060
+ return;
4061
+ }
4062
+ if (!isNode2(node)) return;
4063
+ const args = node.args;
4064
+ if (Array.isArray(args)) {
4065
+ 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") {
4067
+ out.add(receiver.args);
4068
+ }
4069
+ }
4070
+ namespaceRoots(args, out);
4071
+ }
4072
+ function firstBareIdentifier(source) {
4073
+ const ast = (0, import_formula3.parseCelToAst)(source);
4074
+ if (!ast) return null;
4075
+ const rooted = /* @__PURE__ */ new Set();
4076
+ namespaceRoots(ast, rooted);
4077
+ return (0, import_formula3.firstUndeclaredReference)(source, [...VIEW_PAGE_EXTRA_ROOTS, ...rooted]);
4078
+ }
4079
+ var CANONICAL_ROOT_BY_LAYER = {
4080
+ runtime: "record",
4081
+ metadata: "data"
4082
+ };
3511
4083
  var MISLAYER_BY_LAYER = {
3512
4084
  runtime: {
3513
4085
  forbiddenRoot: "data",
@@ -3521,18 +4093,6 @@ var MISLAYER_BY_LAYER = {
3521
4093
  }
3522
4094
  };
3523
4095
  function checkElement(el, where, path, layer, findings) {
3524
- for (const alias of ALIASES) {
3525
- if (el[alias] !== void 0) {
3526
- findings.push({
3527
- severity: "warning",
3528
- rule: VISIBILITY_ALIAS_DEPRECATED,
3529
- where,
3530
- path: `${path}.${alias}`,
3531
- message: `\`${alias}\` is the deprecated spelling of the conditional-visibility predicate (ADR-0089). It still works \u2014 it is normalized to \`visibleWhen\` at parse \u2014 but the canonical key is \`visibleWhen\`.`,
3532
- hint: `Rename the key \`${alias}\` \u2192 \`visibleWhen\` (same CEL value).`
3533
- });
3534
- }
3535
- }
3536
4096
  const raw = el[CANONICAL] ?? el.visibleOn ?? el.visibility;
3537
4097
  const source = predicateSource(raw);
3538
4098
  const rule = MISLAYER_BY_LAYER[layer];
@@ -3546,6 +4106,43 @@ function checkElement(el, where, path, layer, findings) {
3546
4106
  hint: rule.hint
3547
4107
  });
3548
4108
  }
4109
+ const refusal = source ? celRefusal(source) : null;
4110
+ if (source && refusal?.kind === "bounds") {
4111
+ const bound = boundName(refusal.overrun);
4112
+ const root = CANONICAL_ROOT_BY_LAYER[layer];
4113
+ findings.push({
4114
+ severity: "error",
4115
+ rule: VISIBILITY_PREDICATE_OVER_BUDGET,
4116
+ where,
4117
+ path,
4118
+ message: `visibility predicate is syntactically valid CEL but overruns ${bound} (${refusal.overrun.summary}) (predicate: \`${quoteSource(source)}\`). The canonical front end refuses it, so it can never evaluate, and the console falls OPEN: the element renders unconditionally and looks exactly like one with no predicate at all (#5149).`,
4119
+ hint: `There is no syntax or dialect error to correct here \u2014 this is a SIZE fault, not a dialect mistake, so re-spelling the predicate will not fix it. Make it smaller, or move the work off the predicate: (1) collapse a long \`${root}.f == 'a' || ${root}.f == 'b' || \u2026\` chain into a single \`${root}.f in ['a', 'b', \u2026]\`, which is far fewer AST nodes (\`maxListElements\` is 64, so a very large set needs option 2); (2) precompute the heavy part into a formula/rollup field on the object and test that one field instead. Logic genuinely this large is not element visibility \u2014 compute it once on the record rather than re-deriving it in every predicate that needs it.`
4120
+ });
4121
+ }
4122
+ if (source && refusal?.kind === "syntax") {
4123
+ findings.push({
4124
+ severity: "error",
4125
+ rule: VISIBILITY_PREDICATE_SYNTAX,
4126
+ where,
4127
+ path,
4128
+ message: `visibility predicate is not valid CEL \u2014 ${refusal.detail} (predicate: \`${quoteSource(source)}\`). A predicate that does not parse can never evaluate, and the console falls OPEN: the element renders unconditionally and looks exactly like one with no predicate at all (#5149).`,
4129
+ hint: refusal.token ? `\`${refusal.token.wrote}\` is not a CEL operator \u2014 CEL spells it \`${refusal.token.cel}\`. Replace \`${refusal.token.wrote}\` with \`${refusal.token.cel}\`, e.g. \`${refusal.token.example}\`.` : `Visibility predicates are bare CEL, e.g. \`record.status == 'open'\`. Spellings from other languages do not parse: write \`==\` (not \`===\`), \`!=\` (not \`!==\` or \`<>\`), \`&&\` (not \`and\`), \`||\` (not \`or\`), \`!\` (not \`not\`).`
4130
+ });
4131
+ }
4132
+ if (source && !refusal) {
4133
+ const bare = firstBareIdentifier(source);
4134
+ if (bare) {
4135
+ const root = CANONICAL_ROOT_BY_LAYER[layer];
4136
+ findings.push({
4137
+ severity: "error",
4138
+ rule: VISIBILITY_BARE_IDENTIFIER,
4139
+ where,
4140
+ path,
4141
+ 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`).")
4143
+ });
4144
+ }
4145
+ }
3549
4146
  }
3550
4147
  function isFieldObject(entry) {
3551
4148
  return !!entry && typeof entry === "object" && !Array.isArray(entry);
@@ -3553,43 +4150,304 @@ function isFieldObject(entry) {
3553
4150
  function validateVisibilityPredicates(stack, opts = {}) {
3554
4151
  const layer = opts.layer ?? "runtime";
3555
4152
  const findings = [];
3556
- const views = asArray17(stack.views);
3557
- for (let i = 0; i < views.length; i++) {
3558
- const view = views[i];
3559
- if (!view || typeof view !== "object") continue;
3560
- const viewName = typeof view.name === "string" ? view.name : `(view ${i})`;
3561
- const where = `view "${viewName}"`;
3562
- for (const bucket of ["sections", "groups"]) {
3563
- const sections = Array.isArray(view[bucket]) ? view[bucket] : [];
3564
- for (let s = 0; s < sections.length; s++) {
3565
- const sec = sections[s];
3566
- if (!sec || typeof sec !== "object") continue;
3567
- const secPath = `views[${i}].${bucket}[${s}]`;
3568
- checkElement(sec, where, secPath, layer, findings);
3569
- const secFields = Array.isArray(sec.fields) ? sec.fields : [];
3570
- for (let f = 0; f < secFields.length; f++) {
3571
- const entry = secFields[f];
3572
- if (isFieldObject(entry)) {
3573
- checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings);
4153
+ for (const { rec: view, path: viewPath } of collectionEntries(stack.views, "views")) {
4154
+ const viewName = typeof view.name === "string" ? view.name : typeof view.object === "string" ? view.object : viewPath;
4155
+ for (const site of formViewSites(view, viewPath)) {
4156
+ const where = site.surface ? `view "${viewName}" \xB7 ${site.surface}` : `view "${viewName}"`;
4157
+ for (const bucket of ["sections", "groups"]) {
4158
+ const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
4159
+ for (let s = 0; s < sections.length; s++) {
4160
+ const sec = sections[s];
4161
+ if (!sec || typeof sec !== "object") continue;
4162
+ const secPath = `${site.path}.${bucket}[${s}]`;
4163
+ checkElement(sec, where, secPath, layer, findings);
4164
+ const secFields = Array.isArray(sec.fields) ? sec.fields : [];
4165
+ for (let f = 0; f < secFields.length; f++) {
4166
+ const entry = secFields[f];
4167
+ if (isFieldObject(entry)) {
4168
+ checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings);
4169
+ }
3574
4170
  }
3575
4171
  }
3576
4172
  }
3577
4173
  }
3578
4174
  }
3579
- const pages = asArray17(stack.pages);
3580
- for (let i = 0; i < pages.length; i++) {
3581
- const page = pages[i];
3582
- if (!page || typeof page !== "object") continue;
3583
- const pageName = typeof page.name === "string" ? page.name : `(page ${i})`;
3584
- const where = `page "${pageName}"`;
3585
- const regions = Array.isArray(page.regions) ? page.regions : [];
3586
- for (let r = 0; r < regions.length; r++) {
3587
- const region = regions[r];
3588
- const components = region && typeof region === "object" && Array.isArray(region.components) ? region.components : [];
3589
- for (let c = 0; c < components.length; c++) {
3590
- const comp = components[c];
3591
- if (comp && typeof comp === "object") {
3592
- checkElement(comp, where, `pages[${i}].regions[${r}].components[${c}]`, layer, findings);
4175
+ for (const { rec: page, path: pagePath } of collectionEntries(stack.pages, "pages")) {
4176
+ const pageName = typeof page.name === "string" ? page.name : void 0;
4177
+ const where = `page "${pageName ?? pagePath}"`;
4178
+ for (const walked of walkPageComponents(page, pagePath)) {
4179
+ checkElement(walked.component, where, walked.path, layer, findings);
4180
+ }
4181
+ }
4182
+ return findings;
4183
+ }
4184
+
4185
+ // src/validate-predicate-path-refs.ts
4186
+ var import_formula4 = require("@objectstack/formula");
4187
+ var import_kernel2 = require("@objectstack/spec/kernel");
4188
+ var import_spec = require("@objectstack/spec");
4189
+ var PREDICATE_PATH_UNRESOLVED = "predicate-path-unresolved";
4190
+ var PREDICATE_PATH_UNROOTED = "predicate-path-unrooted";
4191
+ var PREDICATE_KEYS = ["visibleWhen", "visibleOn"];
4192
+ var ROOT = "data";
4193
+ var COMPREHENSION_MACROS = /* @__PURE__ */ new Set(["all", "exists", "exists_one", "map", "filter"]);
4194
+ function defOf(schema) {
4195
+ if (!schema || typeof schema !== "object" && typeof schema !== "function") return void 0;
4196
+ const s = schema;
4197
+ return s.def ?? s._def;
4198
+ }
4199
+ function peel(schema, depth = 0) {
4200
+ if (!schema || depth > 25) return schema;
4201
+ const d = defOf(schema);
4202
+ if (!d) return schema;
4203
+ switch (d.type) {
4204
+ case "optional":
4205
+ case "nullable":
4206
+ case "default":
4207
+ case "prefault":
4208
+ case "readonly":
4209
+ case "catch":
4210
+ case "nonoptional":
4211
+ return peel(d.innerType, depth + 1);
4212
+ case "lazy":
4213
+ return peel(d.getter(), depth + 1);
4214
+ case "pipe": {
4215
+ const inner = peel(d.in, depth + 1);
4216
+ return defOf(inner)?.type === "transform" ? peel(d.out, depth + 1) : inner;
4217
+ }
4218
+ default:
4219
+ return schema;
4220
+ }
4221
+ }
4222
+ function optionsOf(d) {
4223
+ return Array.isArray(d?.options) ? d.options : [];
4224
+ }
4225
+ function keysOf(schema, depth = 0) {
4226
+ if (depth > 25) return null;
4227
+ const u = peel(schema);
4228
+ const d = defOf(u);
4229
+ if (d?.type === "object") return Object.keys(d.shape ?? u.shape ?? {});
4230
+ if (d?.type === "union" || d?.type === "discriminated_union") {
4231
+ const all = /* @__PURE__ */ new Set();
4232
+ let keyBearing = false;
4233
+ for (const option of optionsOf(d)) {
4234
+ const k = keysOf(option, depth + 1);
4235
+ if (!k) continue;
4236
+ keyBearing = true;
4237
+ for (const key of k) all.add(key);
4238
+ }
4239
+ return keyBearing ? [...all] : null;
4240
+ }
4241
+ if (d?.type === "intersection") {
4242
+ const left = keysOf(d.left, depth + 1);
4243
+ const right = keysOf(d.right, depth + 1);
4244
+ if (!left && !right) return null;
4245
+ return [.../* @__PURE__ */ new Set([...left ?? [], ...right ?? []])];
4246
+ }
4247
+ return null;
4248
+ }
4249
+ function propertyOf(schema, key, depth = 0) {
4250
+ if (depth > 25) return void 0;
4251
+ const u = peel(schema);
4252
+ const d = defOf(u);
4253
+ if (d?.type === "object") return (d.shape ?? u.shape ?? {})[key];
4254
+ if (d?.type === "union" || d?.type === "discriminated_union") {
4255
+ for (const option of optionsOf(d)) {
4256
+ const found = propertyOf(option, key, depth + 1);
4257
+ if (found !== void 0) return found;
4258
+ }
4259
+ }
4260
+ if (d?.type === "intersection") {
4261
+ return propertyOf(d.left, key, depth + 1) ?? propertyOf(d.right, key, depth + 1);
4262
+ }
4263
+ return void 0;
4264
+ }
4265
+ function rowScopeOf(scope, key) {
4266
+ const prop = propertyOf(scope, key);
4267
+ if (prop === void 0) return void 0;
4268
+ let node = peel(prop);
4269
+ for (let i = 0; i < 25; i++) {
4270
+ const d = defOf(node);
4271
+ if (d?.type === "array") node = peel(d.element);
4272
+ else if (d?.type === "record") node = peel(d.valueType);
4273
+ else return node;
4274
+ }
4275
+ return node;
4276
+ }
4277
+ function stepInto(scope, segment) {
4278
+ const u = peel(scope);
4279
+ const d = defOf(u);
4280
+ if (d?.type === "record") return { kind: "declared", next: d.valueType };
4281
+ const declared = keysOf(u);
4282
+ if (declared === null) return { kind: "opaque" };
4283
+ if (!declared.includes(segment)) return { kind: "undeclared", declared };
4284
+ return { kind: "declared", next: propertyOf(u, segment) };
4285
+ }
4286
+ function isNode3(v) {
4287
+ return !!v && typeof v === "object" && typeof v.op === "string";
4288
+ }
4289
+ function memberChain(node) {
4290
+ if (!isNode3(node)) return null;
4291
+ if (node.op === "id" && typeof node.args === "string") return [node.args];
4292
+ if (node.op === "." && Array.isArray(node.args) && typeof node.args[1] === "string") {
4293
+ const head = memberChain(node.args[0]);
4294
+ return head ? [...head, node.args[1]] : null;
4295
+ }
4296
+ return null;
4297
+ }
4298
+ function rootedPaths(node, out) {
4299
+ if (Array.isArray(node)) {
4300
+ for (const child of node) rootedPaths(child, out);
4301
+ return;
4302
+ }
4303
+ if (!isNode3(node)) return;
4304
+ if (node.op === ".") {
4305
+ const chain = memberChain(node);
4306
+ if (chain && chain[0] === ROOT && chain.length > 1) {
4307
+ out.push(chain.slice(1));
4308
+ return;
4309
+ }
4310
+ }
4311
+ rootedPaths(node.args, out);
4312
+ }
4313
+ function classifyIdentifiers(node, values, excluded) {
4314
+ if (Array.isArray(node)) {
4315
+ for (const child of node) classifyIdentifiers(child, values, excluded);
4316
+ return;
4317
+ }
4318
+ if (!isNode3(node)) return;
4319
+ const args = node.args;
4320
+ if (Array.isArray(args)) {
4321
+ 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") {
4323
+ excluded.add(receiver.args);
4324
+ }
4325
+ if (node.op === "rcall" && typeof args[0] === "string" && COMPREHENSION_MACROS.has(args[0])) {
4326
+ const macroArgs = args[2];
4327
+ if (Array.isArray(macroArgs) && macroArgs.length >= 2) {
4328
+ const bound = macroArgs[0];
4329
+ if (isNode3(bound) && bound.op === "id" && typeof bound.args === "string") {
4330
+ excluded.add(bound.args);
4331
+ }
4332
+ }
4333
+ }
4334
+ }
4335
+ if (node.op === "id" && typeof node.args === "string") {
4336
+ values.add(node.args);
4337
+ return;
4338
+ }
4339
+ classifyIdentifiers(args, values, excluded);
4340
+ }
4341
+ function predicateSource2(v) {
4342
+ if (typeof v === "string") return v;
4343
+ if (v && typeof v === "object" && typeof v.source === "string") {
4344
+ return v.source;
4345
+ }
4346
+ return void 0;
4347
+ }
4348
+ function isRec11(v) {
4349
+ return !!v && typeof v === "object" && !Array.isArray(v);
4350
+ }
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
+ function checkPredicate(source, scope, where, path, findings) {
4358
+ const ast = (0, import_formula4.parseCelToAst)(source);
4359
+ if (!ast) return;
4360
+ const paths = [];
4361
+ rootedPaths(ast, paths);
4362
+ for (const segments of paths) {
4363
+ let cursor = scope;
4364
+ const walked = [];
4365
+ for (const segment of segments) {
4366
+ const step = stepInto(cursor, segment);
4367
+ if (step.kind === "opaque") break;
4368
+ if (step.kind === "undeclared") {
4369
+ const full = [ROOT, ...walked, segment].join(".");
4370
+ const container = walked.length ? `${ROOT}.${walked.join(".")}` : ROOT;
4371
+ findings.push({
4372
+ severity: "error",
4373
+ rule: PREDICATE_PATH_UNRESOLVED,
4374
+ where,
4375
+ path,
4376
+ message: `predicate references \`${full}\`, which the target schema does not declare \u2014 \`${segment}\` is not a key of \`${container}\`. The reference resolves to nothing, so 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).`,
4377
+ hint: `${(0, import_spec.formatSuggestion)((0, import_spec.findClosestMatches)(segment, step.declared)) || `\`${container}\` declares: ${step.declared.slice(0, 12).sort().join(", ")}`} Every reference must resolve against the schema the form edits.`
4378
+ });
4379
+ break;
4380
+ }
4381
+ walked.push(segment);
4382
+ cursor = step.next;
4383
+ }
4384
+ }
4385
+ 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;
4392
+ findings.push({
4393
+ severity: "error",
4394
+ rule: PREDICATE_PATH_UNROOTED,
4395
+ where,
4396
+ 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).`
4399
+ });
4400
+ }
4401
+ }
4402
+ function walkFields(entries, scope, where, base, findings, depth) {
4403
+ if (!Array.isArray(entries) || depth > 12) return;
4404
+ for (let i = 0; i < entries.length; i++) {
4405
+ const entry = entries[i];
4406
+ if (!isRec11(entry)) continue;
4407
+ const path = `${base}[${i}]`;
4408
+ for (const key of PREDICATE_KEYS) {
4409
+ const source = predicateSource2(entry[key]);
4410
+ if (source !== void 0 && source.trim()) {
4411
+ checkPredicate(source, scope, where, `${path}.${key}`, findings);
4412
+ break;
4413
+ }
4414
+ }
4415
+ if (Array.isArray(entry.fields) && entry.fields.length > 0 && typeof entry.field === "string") {
4416
+ const row = scope === void 0 ? void 0 : rowScopeOf(scope, entry.field);
4417
+ walkFields(entry.fields, row, where, `${path}.fields`, findings, depth + 1);
4418
+ }
4419
+ }
4420
+ }
4421
+ function validatePredicatePathRefs(stack, opts = {}) {
4422
+ const resolveSchema = opts.resolveSchema ?? ((schemaId) => (0, import_kernel2.getMetadataTypeSchema)(schemaId));
4423
+ const findings = [];
4424
+ for (const { rec: view, path: viewPath } of collectionEntries(stack.views, "views")) {
4425
+ const viewName = typeof view.name === "string" ? view.name : typeof view.object === "string" ? view.object : viewPath;
4426
+ for (const site of formViewSites(view, viewPath)) {
4427
+ const schemaId = schemaIdOf(site.view);
4428
+ if (!schemaId) continue;
4429
+ let root;
4430
+ try {
4431
+ root = resolveSchema(schemaId);
4432
+ } catch {
4433
+ continue;
4434
+ }
4435
+ if (!root) continue;
4436
+ const where = site.surface ? `view "${viewName}" \xB7 ${site.surface} (schema "${schemaId}")` : `view "${viewName}" (schema "${schemaId}")`;
4437
+ for (const bucket of ["sections", "groups"]) {
4438
+ const sections = Array.isArray(site.view[bucket]) ? site.view[bucket] : [];
4439
+ for (let s = 0; s < sections.length; s++) {
4440
+ const section = sections[s];
4441
+ if (!isRec11(section)) continue;
4442
+ const sectionPath = `${site.path}.${bucket}[${s}]`;
4443
+ for (const key of PREDICATE_KEYS) {
4444
+ const source = predicateSource2(section[key]);
4445
+ if (source !== void 0 && source.trim()) {
4446
+ checkPredicate(source, root, where, `${sectionPath}.${key}`, findings);
4447
+ break;
4448
+ }
4449
+ }
4450
+ walkFields(section.fields, root, where, `${sectionPath}.fields`, findings, 0);
3593
4451
  }
3594
4452
  }
3595
4453
  }
@@ -3600,7 +4458,7 @@ function validateVisibilityPredicates(stack, opts = {}) {
3600
4458
  // src/validate-capability-references.ts
3601
4459
  var import_security = require("@objectstack/spec/security");
3602
4460
  var CAPABILITY_REFERENCE_UNKNOWN = "capability-reference-unknown";
3603
- function asArray18(v) {
4461
+ function asArray17(v) {
3604
4462
  if (Array.isArray(v)) return v;
3605
4463
  if (v && typeof v === "object") {
3606
4464
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -3625,13 +4483,13 @@ function validateCapabilityReferences(stack) {
3625
4483
  const findings = [];
3626
4484
  if (!stack || typeof stack !== "object") return findings;
3627
4485
  const known = new Set(import_security.PLATFORM_CAPABILITY_NAMES);
3628
- for (const cap of asArray18(stack.capabilities)) {
4486
+ for (const cap of asArray17(stack.capabilities)) {
3629
4487
  if (typeof cap.name === "string" && cap.name.length > 0) known.add(cap.name);
3630
4488
  }
3631
- for (const ps of asArray18(stack.permissions)) {
4489
+ for (const ps of asArray17(stack.permissions)) {
3632
4490
  for (const cap of asCapArray(ps.systemPermissions)) known.add(cap);
3633
4491
  }
3634
- for (const seed of asArray18(stack.data)) {
4492
+ for (const seed of asArray17(stack.data)) {
3635
4493
  if (seed.object !== "sys_capability") continue;
3636
4494
  for (const rec of Array.isArray(seed.records) ? seed.records : []) {
3637
4495
  const name = rec?.name;
@@ -3650,7 +4508,7 @@ function validateCapabilityReferences(stack) {
3650
4508
  hint
3651
4509
  });
3652
4510
  };
3653
- const objects = asArray18(stack.objects);
4511
+ const objects = asArray17(stack.objects);
3654
4512
  for (let i = 0; i < objects.length; i++) {
3655
4513
  const obj = objects[i];
3656
4514
  if (!obj || typeof obj !== "object") continue;
@@ -3659,27 +4517,27 @@ function validateCapabilityReferences(stack) {
3659
4517
  for (const { cap, key } of flattenObjectRequired(obj.requiredPermissions)) {
3660
4518
  flag(cap, `object "${objName}"`, `${objPath}.requiredPermissions${key ? `.${key}` : ""}`);
3661
4519
  }
3662
- const fields = asArray18(obj.fields);
4520
+ const fields = asArray17(obj.fields);
3663
4521
  for (const f of fields) {
3664
4522
  const fname = typeof f.name === "string" ? f.name : "(field)";
3665
4523
  for (const cap of asCapArray(f.requiredPermissions)) {
3666
4524
  flag(cap, `field "${objName}.${fname}"`, `${objPath}.fields.${fname}.requiredPermissions`);
3667
4525
  }
3668
4526
  }
3669
- for (const [ai, action] of asArray18(obj.actions).entries()) {
4527
+ for (const [ai, action] of asArray17(obj.actions).entries()) {
3670
4528
  const aName = typeof action.name === "string" ? action.name : `(action ${ai})`;
3671
4529
  for (const cap of asCapArray(action.requiredPermissions)) {
3672
4530
  flag(cap, `action "${objName}.${aName}"`, `${objPath}.actions[${ai}].requiredPermissions`);
3673
4531
  }
3674
4532
  }
3675
4533
  }
3676
- for (const [i, action] of asArray18(stack.actions).entries()) {
4534
+ for (const [i, action] of asArray17(stack.actions).entries()) {
3677
4535
  const aName = typeof action.name === "string" ? action.name : `(action ${i})`;
3678
4536
  for (const cap of asCapArray(action.requiredPermissions)) {
3679
4537
  flag(cap, `action "${aName}"`, `actions[${i}].requiredPermissions`);
3680
4538
  }
3681
4539
  }
3682
- const apps = asArray18(stack.apps);
4540
+ const apps = asArray17(stack.apps);
3683
4541
  for (let i = 0; i < apps.length; i++) {
3684
4542
  const app = apps[i];
3685
4543
  if (!app || typeof app !== "object") continue;
@@ -3707,8 +4565,8 @@ function validateCapabilityReferences(stack) {
3707
4565
 
3708
4566
  // src/validate-approval-approvers.ts
3709
4567
  var import_automation4 = require("@objectstack/spec/automation");
3710
- var import_spec = require("@objectstack/spec");
3711
- var import_formula3 = require("@objectstack/formula");
4568
+ var import_spec2 = require("@objectstack/spec");
4569
+ var import_formula5 = require("@objectstack/formula");
3712
4570
  var APPROVAL_APPROVER_NOT_MEMBERSHIP_TIER = "approval-approver-not-membership-tier";
3713
4571
  var APPROVAL_APPROVER_TYPE_DEPRECATED = "approval-approver-type-deprecated";
3714
4572
  var APPROVAL_APPROVER_TYPE_UNKNOWN = "approval-approver-type-unknown";
@@ -3722,13 +4580,13 @@ var APPROVAL_APPROVER_CROSS_ORG_UNSUPPORTED = "approval-approver-cross-org-unsup
3722
4580
  var EXPRESSION_ROOTS = /* @__PURE__ */ new Set(["current", "trigger", "vars"]);
3723
4581
  var RESERVED_OUTPUT_KEYS = /* @__PURE__ */ new Set(["decision", "requestId"]);
3724
4582
  var GROUP_ROUTED_TYPES = /* @__PURE__ */ new Set(["position", "team", "department"]);
3725
- var MEMBERSHIP_TIERS = new Set(import_spec.BUILTIN_MEMBERSHIP_ROLES);
3726
- var MEMBERSHIP_TIER_LIST = import_spec.BUILTIN_MEMBERSHIP_ROLES.join("/");
4583
+ var MEMBERSHIP_TIERS = new Set(import_spec2.BUILTIN_MEMBERSHIP_ROLES);
4584
+ var MEMBERSHIP_TIER_LIST = import_spec2.BUILTIN_MEMBERSHIP_ROLES.join("/");
3727
4585
  var TYPE_FIX = {
3728
4586
  business_unit: "department",
3729
4587
  bu: "department"
3730
4588
  };
3731
- function asArray19(v) {
4589
+ function asArray18(v) {
3732
4590
  if (Array.isArray(v)) return v;
3733
4591
  if (v && typeof v === "object") {
3734
4592
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -3738,7 +4596,7 @@ function asArray19(v) {
3738
4596
  function validateApprovalApprovers(stack) {
3739
4597
  const findings = [];
3740
4598
  if (!stack || typeof stack !== "object") return findings;
3741
- const flows = asArray19(stack.flows);
4599
+ const flows = asArray18(stack.flows);
3742
4600
  const validTypes = new Set(import_automation4.ApproverType.options);
3743
4601
  for (let fi = 0; fi < flows.length; fi++) {
3744
4602
  const flow = flows[fi];
@@ -3783,7 +4641,7 @@ function validateApprovalApprovers(stack) {
3783
4641
  hint: `Write a CEL expression over current.* (the record's live state at node entry), trigger.* (the submit-time snapshot) or vars.* (flow variables), e.g. current.approvers_dynamic or vars.approval_lead.picked_departments.`
3784
4642
  });
3785
4643
  } else {
3786
- const parsed = (0, import_formula3.collectCelRootIdentifiers)(source);
4644
+ const parsed = (0, import_formula5.collectCelRootIdentifiers)(source);
3787
4645
  if (!parsed.ok) {
3788
4646
  findings.push({
3789
4647
  severity: "error",
@@ -4033,7 +4891,7 @@ var OWD_WIDTH = {
4033
4891
  public_read: 1,
4034
4892
  public_read_write: 2
4035
4893
  };
4036
- function asArray20(v) {
4894
+ function asArray19(v) {
4037
4895
  if (Array.isArray(v)) return v;
4038
4896
  if (v && typeof v === "object") {
4039
4897
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -4059,7 +4917,7 @@ function refOf(def) {
4059
4917
  return typeof r === "string" && r ? r : void 0;
4060
4918
  }
4061
4919
  function firstMasterDetailField(obj) {
4062
- for (const f of asArray20(obj.fields)) {
4920
+ for (const f of asArray19(obj.fields)) {
4063
4921
  if (f.type === "master_detail") {
4064
4922
  return { name: String(f.name ?? "?"), parent: refOf(f) };
4065
4923
  }
@@ -4072,8 +4930,8 @@ function grantsObjectAccess(p) {
4072
4930
  function validateSecurityPosture(stack, opts) {
4073
4931
  const findings = [];
4074
4932
  if (!stack || typeof stack !== "object") return findings;
4075
- const objects = asArray20(stack.objects);
4076
- const permissionSets = asArray20(stack.permissions);
4933
+ const objects = asArray19(stack.objects);
4934
+ const permissionSets = asArray19(stack.permissions);
4077
4935
  for (let i = 0; i < objects.length; i++) {
4078
4936
  const obj = objects[i];
4079
4937
  if (!obj || typeof obj !== "object") continue;
@@ -4202,10 +5060,10 @@ function validateSecurityPosture(stack, opts) {
4202
5060
  if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
4203
5061
  const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
4204
5062
  flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
4205
- for (const f of asArray20(obj.fields)) {
5063
+ for (const f of asArray19(obj.fields)) {
4206
5064
  flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
4207
5065
  }
4208
- for (const [ai, action] of asArray20(obj.actions).entries()) {
5066
+ for (const [ai, action] of asArray19(obj.actions).entries()) {
4209
5067
  flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
4210
5068
  }
4211
5069
  }
@@ -4214,19 +5072,19 @@ function validateSecurityPosture(stack, opts) {
4214
5072
  if (!ps || typeof ps !== "object") continue;
4215
5073
  flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
4216
5074
  }
4217
- for (const [i, pos] of asArray20(stack.positions).entries()) {
5075
+ for (const [i, pos] of asArray19(stack.positions).entries()) {
4218
5076
  flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
4219
5077
  }
4220
- for (const [i, app] of asArray20(stack.apps).entries()) {
5078
+ for (const [i, app] of asArray19(stack.apps).entries()) {
4221
5079
  flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
4222
5080
  }
4223
- for (const [i, book] of asArray20(stack.books).entries()) {
5081
+ for (const [i, book] of asArray19(stack.books).entries()) {
4224
5082
  flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
4225
5083
  }
4226
5084
  const stackSetNames = new Set(
4227
5085
  permissionSets.map((ps) => typeof ps.name === "string" ? ps.name : void 0).filter((n) => !!n)
4228
5086
  );
4229
- for (const [i, book] of asArray20(stack.books).entries()) {
5087
+ for (const [i, book] of asArray19(stack.books).entries()) {
4230
5088
  const audience = book.audience;
4231
5089
  if (!audience || typeof audience !== "object") continue;
4232
5090
  const setName = audience.permissionSet;
@@ -4304,7 +5162,7 @@ function validateSecurityPosture(stack, opts) {
4304
5162
  }
4305
5163
  const GRANT_SEED_OBJECTS = /* @__PURE__ */ new Set(["sys_user_position", "sys_user_permission_set"]);
4306
5164
  const nowMs = opts?.nowMs ?? Date.now();
4307
- for (const [i, seed] of asArray20(stack.data).entries()) {
5165
+ for (const [i, seed] of asArray19(stack.data).entries()) {
4308
5166
  const seedObject = typeof seed.object === "string" ? seed.object : "";
4309
5167
  if (!GRANT_SEED_OBJECTS.has(seedObject)) continue;
4310
5168
  const records = Array.isArray(seed.records) ? seed.records : [];
@@ -4349,7 +5207,7 @@ var ORG_AXIS_PERMISSION_INHERITANCE = "org-axis-permission-inheritance";
4349
5207
  var ORG_AXIS_CROSS_ORG_BU_GRANT = "org-axis-cross-org-bu-grant";
4350
5208
  var ORG_PARENT_FIELD = "parent_organization_id";
4351
5209
  var BU_TREE_RECIPIENT_TYPES = /* @__PURE__ */ new Set(["business_unit", "unit_and_subordinates"]);
4352
- function asArray21(v) {
5210
+ function asArray20(v) {
4353
5211
  if (Array.isArray(v)) return v;
4354
5212
  if (v && typeof v === "object") {
4355
5213
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -4379,9 +5237,9 @@ var INHERITANCE_HINT = `Remove the ${ORG_PARENT_FIELD} reference. Cross-organiza
4379
5237
  function validateOrgAxisRedLines(stack) {
4380
5238
  const findings = [];
4381
5239
  const cfg = stack ?? {};
4382
- const permissionSets = asArray21(cfg.permissions);
5240
+ const permissionSets = asArray20(cfg.permissions);
4383
5241
  permissionSets.forEach((ps, psIndex) => {
4384
- asArray21(ps.rowLevelSecurity).forEach((policy, pIndex) => {
5242
+ asArray20(ps.rowLevelSecurity).forEach((policy, pIndex) => {
4385
5243
  for (const clause of ["using", "check"]) {
4386
5244
  if (!str(policy[clause]).includes(ORG_PARENT_FIELD)) continue;
4387
5245
  findings.push({
@@ -4395,7 +5253,7 @@ function validateOrgAxisRedLines(stack) {
4395
5253
  }
4396
5254
  });
4397
5255
  });
4398
- asArray21(cfg.sharingRules).forEach((rule, rIndex) => {
5256
+ asArray20(cfg.sharingRules).forEach((rule, rIndex) => {
4399
5257
  const slots = [
4400
5258
  { key: "condition", text: expressionText(rule.condition) },
4401
5259
  { key: "sharedWith", text: JSON.stringify(rule.sharedWith ?? "") ?? "" }
@@ -4413,9 +5271,9 @@ function validateOrgAxisRedLines(stack) {
4413
5271
  }
4414
5272
  });
4415
5273
  const tenancyDisabledObjects = new Set(
4416
- asArray21(cfg.objects).filter((o) => isTenancyDisabled(o)).map((o) => str(o.name)).filter(Boolean)
5274
+ asArray20(cfg.objects).filter((o) => isTenancyDisabled(o)).map((o) => str(o.name)).filter(Boolean)
4417
5275
  );
4418
- asArray21(cfg.sharingRules).forEach((rule, rIndex) => {
5276
+ asArray20(cfg.sharingRules).forEach((rule, rIndex) => {
4419
5277
  const target = str(rule.object);
4420
5278
  if (!target || !tenancyDisabledObjects.has(target)) return;
4421
5279
  const sharedWith = rule.sharedWith;
@@ -4435,10 +5293,10 @@ function validateOrgAxisRedLines(stack) {
4435
5293
  }
4436
5294
 
4437
5295
  // src/validate-sharing-rule-enforceability.ts
4438
- var import_formula4 = require("@objectstack/formula");
5296
+ var import_formula6 = require("@objectstack/formula");
4439
5297
  var SHARING_RULE_UNLOWERABLE_CONDITION = "sharing-rule-unlowerable-condition";
4440
5298
  var SHARING_RULE_RUNTIME_VARIABLE_CONDITION = "sharing-rule-runtime-variable-condition";
4441
- function asArray22(v) {
5299
+ function asArray21(v) {
4442
5300
  if (Array.isArray(v)) return v;
4443
5301
  if (v && typeof v === "object") {
4444
5302
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -4465,10 +5323,10 @@ var PUSHDOWN_SUBSET = "The lowerable subset is: `==` `!=` `>` `<` `>=` `<=`, `in
4465
5323
  function validateSharingRuleEnforceability(stack) {
4466
5324
  const findings = [];
4467
5325
  const cfg = stack ?? {};
4468
- asArray22(cfg.sharingRules).forEach((rule, index) => {
5326
+ asArray21(cfg.sharingRules).forEach((rule, index) => {
4469
5327
  const input = toCompilerInput(rule.condition);
4470
5328
  if (input === null) return;
4471
- const result = (0, import_formula4.compileCelToFilter)(input, { variables: {} });
5329
+ const result = (0, import_formula6.compileCelToFilter)(input, { variables: {} });
4472
5330
  if (result.ok) return;
4473
5331
  if (result.reason === "parse-error") return;
4474
5332
  const name = str2(rule.name) || String(index);
@@ -4501,10 +5359,11 @@ function validateSharingRuleEnforceability(stack) {
4501
5359
  }
4502
5360
 
4503
5361
  // src/validate-rls-predicate-enforceability.ts
4504
- var import_formula5 = require("@objectstack/formula");
5362
+ var import_formula7 = require("@objectstack/formula");
4505
5363
  var RLS_PREDICATE_UNENFORCEABLE = "rls-predicate-unenforceable";
4506
5364
  var RLS_PREDICATE_UNPARSEABLE = "rls-predicate-unparseable";
4507
- function asArray23(v) {
5365
+ var RLS_PREDICATE_OVER_BUDGET = "rls-predicate-over-budget";
5366
+ function asArray22(v) {
4508
5367
  if (Array.isArray(v)) return v;
4509
5368
  if (v && typeof v === "object") {
4510
5369
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -4515,6 +5374,13 @@ function str3(v) {
4515
5374
  return typeof v === "string" ? v : "";
4516
5375
  }
4517
5376
  var PUSHDOWN_SUBSET2 = "The lowerable subset is: `==` `!=` `>` `<` `>=` `<=`, `in`, `&&` `||` `!`, `== null` / `!= null`, and the string methods `startsWith` / `endsWith` / `contains` \u2014 over SINGLE-column field paths (ADR-0058 D2), compared against a literal or a `current_user.*` value.";
5377
+ function boundsOverrunOf(bridged) {
5378
+ const parsed = (0, import_formula7.parseCelToAstWithReason)(bridged);
5379
+ return !parsed.ok && parsed.kind === "bounds" ? parsed.overrun : null;
5380
+ }
5381
+ function quote(source) {
5382
+ return source.length > 200 ? `${source.slice(0, 197)}...` : source;
5383
+ }
4518
5384
  function consequence(clause) {
4519
5385
  const dropped = 'so `RLSCompiler` DROPS the policy at request time (one WARN line \u2014 "has an uncompilable predicate \u2026 and was DROPPED (no enforcement)" \u2014 is the only signal, and nothing reports it at authoring time). ';
4520
5386
  return clause === "using" ? dropped + "When it is the only applicable policy for that object and operation, `compileFilter` returns the `RLS_DENY_FILTER` sentinel instead, which is AND-ed onto the where clause: every select / update / delete on the object matches ZERO rows. When other policies also apply, this one just vanishes from the OR and grants none of the access it appears to." : dropped + "On the ADR-0058 D4 write path that leaves the post-image `check` as the `RLS_DENY_FILTER` sentinel, which no record can satisfy: every insert / update the policy governs fails with `PermissionDeniedError`. The policy reads as a write rule and behaves as a blanket refusal.";
@@ -4522,20 +5388,36 @@ function consequence(clause) {
4522
5388
  function validateRlsPredicateEnforceability(stack) {
4523
5389
  const findings = [];
4524
5390
  const cfg = stack ?? {};
4525
- asArray23(cfg.permissions).forEach((ps, psIndex) => {
4526
- asArray23(ps.rowLevelSecurity).forEach((policy, pIndex) => {
5391
+ asArray22(cfg.permissions).forEach((ps, psIndex) => {
5392
+ asArray22(ps.rowLevelSecurity).forEach((policy, pIndex) => {
4527
5393
  for (const clause of ["using", "check"]) {
4528
5394
  const source = str3(policy[clause]);
4529
5395
  if (!source.trim()) continue;
4530
- if ((0, import_formula5.isSupportedRlsExpression)(source)) continue;
4531
- const why = (0, import_formula5.isPushdownableCel)((0, import_formula5.sqlPredicateToCel)(source));
5396
+ if ((0, import_formula7.isSupportedRlsExpression)(source)) continue;
5397
+ const bridged = (0, import_formula7.sqlPredicateToCel)(source);
5398
+ const why = (0, import_formula7.isPushdownableCel)(bridged);
4532
5399
  const detail = why.ok ? "" : why.detail;
4533
5400
  const parseError = !why.ok && why.reason === "parse-error";
5401
+ const overrun = parseError ? boundsOverrunOf(bridged) : null;
4534
5402
  const psName = str3(ps.name) || String(psIndex);
4535
5403
  const policyName = str3(policy.name) || String(pIndex);
4536
5404
  const object = str3(policy.object);
4537
5405
  const where = `permission set "${psName}" policy "${policyName}"` + (object ? ` on object "${object}"` : "");
4538
5406
  const path = `permissions[${psIndex}].rowLevelSecurity[${pIndex}].${clause}`;
5407
+ if (overrun) {
5408
+ const bound = overrun.limit ?? "an unnamed platform CEL bound";
5409
+ const budget = overrun.limitValue !== null ? ` (platform limit ${overrun.limitValue})` : "";
5410
+ const measured = overrun.measured !== null ? `, this predicate measures ${overrun.measured}` : "";
5411
+ findings.push({
5412
+ severity: "error",
5413
+ rule: RLS_PREDICATE_OVER_BUDGET,
5414
+ where,
5415
+ path,
5416
+ message: `RLS ${clause} \`${quote(source)}\` is syntactically valid, lowerable CEL but overruns the platform parse bound ${bound}${budget}${measured} (${overrun.summary}), ` + consequence(clause),
5417
+ hint: `There is no syntax or dialect error to correct here \u2014 the predicate is well-formed CEL and is simply too large for ${bound}${budget}, so the fix is to make it smaller or to move the work off the predicate. (1) Collapse a long \`field == a || field == b || \u2026\` chain into a single \`field in [a, b, \u2026]\`, which is far fewer AST nodes (\`maxListElements\` is 64, so a very large set needs option 2). (2) Pre-resolve the set into a membership key the runtime exposes and test \`field in current_user.<key>\` (ADR-0105 D11) \u2014 one comparison whatever the set size. (3) Denormalise a repeated sub-expression onto this object as a formula/rollup field and test that single column. (4) Split a TOP-LEVEL \`||\` across several \`rowLevelSecurity\` policies: applicable policies are OR-ed, so that is equivalent \u2014 but never split a top-level \`&&\` this way, which would WIDEN access rather than preserve it. Logic genuinely this large is not a row filter: move it to a hook or action body (\`ScriptBody { language: 'js' }\`, the L2 sandboxed surface).`
5418
+ });
5419
+ continue;
5420
+ }
4539
5421
  if (parseError) {
4540
5422
  findings.push({
4541
5423
  severity: "error",
@@ -4564,14 +5446,14 @@ function validateRlsPredicateEnforceability(stack) {
4564
5446
  // src/validate-dashboard-action-refs.ts
4565
5447
  var DASHBOARD_ACTION_TARGET_UNDEFINED = "dashboard-action-target-undefined";
4566
5448
  var DASHBOARD_ACTION_ROUTE_UNRESOLVED = "dashboard-action-route-unresolved";
4567
- function asArray24(v) {
5449
+ function asArray23(v) {
4568
5450
  if (Array.isArray(v)) return v;
4569
5451
  if (v && typeof v === "object") {
4570
5452
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
4571
5453
  }
4572
5454
  return [];
4573
5455
  }
4574
- function strName5(v) {
5456
+ function strName7(v) {
4575
5457
  return typeof v === "string" && v.length > 0 ? v : void 0;
4576
5458
  }
4577
5459
  var MODAL_VERB_RE = /^(?:create|new|add|edit|update)_(.+)$/;
@@ -4588,7 +5470,7 @@ var URL_COLLECTION_TO_STACK_KEY = {
4588
5470
  views: "views"
4589
5471
  };
4590
5472
  function viewContainerName(item) {
4591
- return strName5(item.name) ?? strName5(item.id) ?? strName5(item.object) ?? strName5(item.list?.data && item.list.data.object) ?? strName5(item.form?.data && item.form.data.object);
5473
+ return strName7(item.name) ?? strName7(item.id) ?? strName7(item.object) ?? strName7(item.list?.data && item.list.data.object) ?? strName7(item.form?.data && item.form.data.object);
4592
5474
  }
4593
5475
  function collectKnownTargets(stack) {
4594
5476
  const actions = /* @__PURE__ */ new Set();
@@ -4598,22 +5480,22 @@ function collectKnownTargets(stack) {
4598
5480
  const pages = /* @__PURE__ */ new Set();
4599
5481
  const views = /* @__PURE__ */ new Set();
4600
5482
  const collectNames = (v, into, name) => {
4601
- for (const item of asArray24(v)) {
5483
+ for (const item of asArray23(v)) {
4602
5484
  if (!item || typeof item !== "object") continue;
4603
5485
  const n = name(item);
4604
5486
  if (n) into.add(n);
4605
5487
  }
4606
5488
  };
4607
- collectNames(stack.actions, actions, (a) => strName5(a.name));
4608
- for (const obj of asArray24(stack.objects)) {
5489
+ collectNames(stack.actions, actions, (a) => strName7(a.name));
5490
+ for (const obj of asArray23(stack.objects)) {
4609
5491
  if (!obj || typeof obj !== "object") continue;
4610
- const n = strName5(obj.name);
5492
+ const n = strName7(obj.name);
4611
5493
  if (n) objects.add(n);
4612
- collectNames(obj.actions, actions, (a) => strName5(a.name));
5494
+ collectNames(obj.actions, actions, (a) => strName7(a.name));
4613
5495
  }
4614
- collectNames(stack.reports, reports, (r) => strName5(r.name));
4615
- collectNames(stack.dashboards, dashboards, (d) => strName5(d.name));
4616
- collectNames(stack.pages, pages, (p) => strName5(p.name));
5496
+ collectNames(stack.reports, reports, (r) => strName7(r.name));
5497
+ collectNames(stack.dashboards, dashboards, (d) => strName7(d.name));
5498
+ collectNames(stack.pages, pages, (p) => strName7(p.name));
4617
5499
  collectNames(stack.views, views, viewContainerName);
4618
5500
  for (const o of objects) views.add(o);
4619
5501
  return { actions, objects, reports, dashboards, pages, views };
@@ -4645,14 +5527,14 @@ function resolveUrlRoute(target, known) {
4645
5527
  function validateDashboardActionRefs(stack) {
4646
5528
  const findings = [];
4647
5529
  if (!stack || typeof stack !== "object") return findings;
4648
- const dashboards = asArray24(stack.dashboards);
5530
+ const dashboards = asArray23(stack.dashboards);
4649
5531
  if (dashboards.length === 0) return findings;
4650
5532
  const known = collectKnownTargets(stack);
4651
5533
  const checkOne = (action, where, path) => {
4652
- const target = strName5(action.actionUrl);
5534
+ const target = strName7(action.actionUrl);
4653
5535
  if (!target) return;
4654
5536
  if (target.includes("${")) return;
4655
- const actionType = strName5(action.actionType) ?? "url";
5537
+ const actionType = strName7(action.actionType) ?? "url";
4656
5538
  if (actionType === "script" || actionType === "modal") {
4657
5539
  if (resolveActionTarget(actionType, target, known)) return;
4658
5540
  const kindWord = actionType === "script" ? "script" : "modal";
@@ -4683,13 +5565,13 @@ function validateDashboardActionRefs(stack) {
4683
5565
  for (let di = 0; di < dashboards.length; di++) {
4684
5566
  const dash = dashboards[di];
4685
5567
  if (!dash || typeof dash !== "object") continue;
4686
- const dashName = strName5(dash.name) ?? `(dashboard ${di})`;
5568
+ const dashName = strName7(dash.name) ?? `(dashboard ${di})`;
4687
5569
  const dashPath = `dashboards[${di}]`;
4688
- const headerActions = asArray24(dash.header?.actions);
5570
+ const headerActions = asArray23(dash.header?.actions);
4689
5571
  for (let ai = 0; ai < headerActions.length; ai++) {
4690
5572
  const action = headerActions[ai];
4691
5573
  if (!action || typeof action !== "object") continue;
4692
- const label2 = strName5(action.label) ?? strName5(action.actionUrl) ?? `#${ai}`;
5574
+ const label2 = strName7(action.label) ?? strName7(action.actionUrl) ?? `#${ai}`;
4693
5575
  checkOne(
4694
5576
  action,
4695
5577
  `dashboard "${dashName}" \xB7 header action "${label2}"`,
@@ -4702,9 +5584,10 @@ function validateDashboardActionRefs(stack) {
4702
5584
 
4703
5585
  // src/validate-filter-tokens.ts
4704
5586
  var import_data6 = require("@objectstack/spec/data");
4705
- var FILTER_TOKEN_UNKNOWN = "filter-token-unknown";
5587
+
5588
+ // src/filter-walk.ts
4706
5589
  var FILTER_KEYS = /* @__PURE__ */ new Set(["filter", "filters", "runtimeFilter"]);
4707
- function asArray25(v) {
5590
+ function asArray24(v) {
4708
5591
  if (Array.isArray(v)) return v;
4709
5592
  if (v && typeof v === "object") {
4710
5593
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -4714,7 +5597,62 @@ function asArray25(v) {
4714
5597
  function label(v, fallback) {
4715
5598
  return typeof v === "string" && v.length > 0 ? v : fallback;
4716
5599
  }
5600
+ function scanForFilters(node, path, where, visit, seen = /* @__PURE__ */ new Set()) {
5601
+ if (!node || typeof node !== "object") return;
5602
+ if (seen.has(node)) return;
5603
+ seen.add(node);
5604
+ if (Array.isArray(node)) {
5605
+ node.forEach((v, i) => scanForFilters(v, `${path}[${i}]`, where, visit, seen));
5606
+ return;
5607
+ }
5608
+ for (const [k, v] of Object.entries(node)) {
5609
+ const childPath = `${path}.${k}`;
5610
+ if (FILTER_KEYS.has(k)) {
5611
+ visit({ value: v, path: childPath, where });
5612
+ continue;
5613
+ }
5614
+ scanForFilters(v, childPath, where, visit, seen);
5615
+ }
5616
+ }
5617
+ function walkAuthoredFilters(stack, surfaces, visit) {
5618
+ if (!stack || typeof stack !== "object") return;
5619
+ for (const { key, kind } of surfaces) {
5620
+ const items = asArray24(stack[key]);
5621
+ items.forEach((item, i) => {
5622
+ const name = label(item.name ?? item.id, `#${i}`);
5623
+ if (kind === "dashboard") {
5624
+ const widgets = Array.isArray(item.widgets) ? item.widgets : [];
5625
+ widgets.forEach((w, wi) => {
5626
+ const wName = label(w.id ?? w.title, `#${wi}`);
5627
+ scanForFilters(
5628
+ w,
5629
+ `${key}[${i}].widgets[${wi}]`,
5630
+ `dashboard "${name}" \xB7 widget "${wName}"`,
5631
+ visit,
5632
+ /* @__PURE__ */ new Set()
5633
+ );
5634
+ });
5635
+ const { widgets: _skip, ...rest } = item;
5636
+ scanForFilters(rest, `${key}[${i}]`, `dashboard "${name}"`, visit, /* @__PURE__ */ new Set());
5637
+ return;
5638
+ }
5639
+ scanForFilters(item, `${key}[${i}]`, `${kind} "${name}"`, visit, /* @__PURE__ */ new Set());
5640
+ });
5641
+ }
5642
+ }
5643
+
5644
+ // src/validate-filter-tokens.ts
5645
+ var FILTER_TOKEN_UNKNOWN = "filter-token-unknown";
4717
5646
  var KNOWN_LIST = import_data6.CONTEXT_TOKENS.join("}, {");
5647
+ var TOKEN_FILTER_SURFACES = [
5648
+ { key: "dashboards", kind: "dashboard" },
5649
+ { key: "objects", kind: "object" },
5650
+ { key: "views", kind: "view" },
5651
+ { key: "reports", kind: "report" },
5652
+ { key: "datasets", kind: "dataset" },
5653
+ { key: "pages", kind: "page" },
5654
+ { key: "apps", kind: "app" }
5655
+ ];
4718
5656
  function walkFilterValues(node, path, where, out, seen) {
4719
5657
  if (node === null || node === void 0) return;
4720
5658
  if (typeof node === "string") {
@@ -4743,58 +5681,132 @@ function walkFilterValues(node, path, where, out, seen) {
4743
5681
  walkFilterValues(v, `${path}.${k}`, where, out, seen);
4744
5682
  }
4745
5683
  }
4746
- function scanForFilters(node, path, where, out, seen) {
4747
- if (!node || typeof node !== "object") return;
4748
- if (seen.has(node)) return;
4749
- seen.add(node);
4750
- if (Array.isArray(node)) {
4751
- node.forEach((v, i) => scanForFilters(v, `${path}[${i}]`, where, out, seen));
5684
+ function validateFilterTokens(stack) {
5685
+ if (!stack || typeof stack !== "object") return [];
5686
+ const out = [];
5687
+ walkAuthoredFilters(stack, TOKEN_FILTER_SURFACES, ({ value, path, where }) => {
5688
+ walkFilterValues(value, path, where, out, /* @__PURE__ */ new Set());
5689
+ });
5690
+ return out;
5691
+ }
5692
+
5693
+ // src/validate-empty-combinators.ts
5694
+ var import_data7 = require("@objectstack/spec/data");
5695
+ var FILTER_EMPTY_COMBINATOR = "filter-empty-combinator";
5696
+ var FILTER_EMPTY_NODE = "filter-empty-node";
5697
+ var EMPTY_COMBINATOR_SURFACES = [
5698
+ { key: "dashboards", kind: "dashboard" },
5699
+ { key: "objects", kind: "object" },
5700
+ { key: "views", kind: "view" },
5701
+ { key: "reports", kind: "report" },
5702
+ { key: "datasets", kind: "dataset" },
5703
+ { key: "pages", kind: "page" },
5704
+ { key: "apps", kind: "app" },
5705
+ { key: "flows", kind: "flow" }
5706
+ ];
5707
+ function isFilterNode(value) {
5708
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
5709
+ const proto = Object.getPrototypeOf(value);
5710
+ return proto === Object.prototype || proto === null;
5711
+ }
5712
+ 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)({}),
5717
+ /** One TRUE disjunct absorbs its `$or`: the sibling branches stop mattering. */
5718
+ orWithEmptyBranch: (0, import_data7.reduceFilterVerdict)({ $or: [{ status: "open" }, {}] })
5719
+ };
5720
+ function rows(verdict) {
5721
+ if (verdict === "true") return "matches EVERY row";
5722
+ if (verdict === "false") return "matches NO row";
5723
+ return "carries a real predicate";
5724
+ }
5725
+ var MATCH_NONE_SPELLING = "If you really do want a predicate that selects nothing, `{ <field>: { $in: [] } }` is the declared spelling for it (an empty `$in` list matches nothing, on every backend) \u2014 it says so where an empty combinator only implies it.";
5726
+ var OMIT_THE_KEY = 'To express "no filter", DELETE the key \u2014 an absent `filter` and a filter that reduces to TRUE run identically, and only the absent key says so to the next reader (and to the next AI author that copies this metadata).';
5727
+ function emitEmptyCombinator(key, path, ctx) {
5728
+ const spelling = key === "$not" ? "`$not: {}`" : `\`${key}: []\``;
5729
+ const message = key === "$and" ? `\`$and: []\` is a conjunction of ZERO conditions. Under the #5322 identity ruling it ${rows(VERDICT_OF.$and)} \u2014 the key is authored, and it constrains nothing, so this surface reads as filtered and is not.` : key === "$or" ? `\`$or: []\` is a disjunction of ZERO branches. Under the #5322 identity ruling it ${rows(VERDICT_OF.$or)}: this surface renders permanently empty, and on a read scope it hides every row (fail-closed by design \u2014 #5134).` : `\`$not: {}\` negates an EMPTY node. An empty node is TRUE and NOT TRUE is FALSE, so it ${rows(VERDICT_OF.$not)} \u2014 the opposite of the "no filter" an empty operand looks like.`;
5730
+ const hint = key === "$and" ? `${OMIT_THE_KEY} To express a constraint, put the conditions in the array. ${MATCH_NONE_SPELLING}` : key === "$or" ? `If you meant "no filter", this is its OPPOSITE: emptying the array does not relax the filter, it closes it. ${OMIT_THE_KEY} If you meant to offer alternatives, put the branches in the array. ${MATCH_NONE_SPELLING}` : `Put the condition you are negating inside \`$not\` (\`{ $not: { status: 'closed' } }\`). ${OMIT_THE_KEY} ${MATCH_NONE_SPELLING}`;
5731
+ ctx.out.push({
5732
+ severity: "error",
5733
+ rule: FILTER_EMPTY_COMBINATOR,
5734
+ where: ctx.where,
5735
+ path,
5736
+ message: `${message} A literal ${spelling} is not an authoring surface (#5330).`,
5737
+ hint: `${hint} A PROGRAMMATIC producer that loops to zero operands keeps the runtime identity unchanged \u2014 this rule judges only what is written in the metadata.`
5738
+ });
5739
+ }
5740
+ function emitEmptyNode(position, path, ctx) {
5741
+ if (position === "root") {
5742
+ ctx.out.push({
5743
+ severity: "error",
5744
+ rule: FILTER_EMPTY_NODE,
5745
+ where: ctx.where,
5746
+ path,
5747
+ message: `An EMPTY filter node (\`{}\`) is authored here. Under the #5322 identity ruling an empty node is TRUE \u2014 it ${rows(VERDICT_OF.node)}, exactly as if the key were absent \u2014 so a filter is declared and enforces nothing.`,
5748
+ hint: `${OMIT_THE_KEY} If you meant to constrain something, write the condition into the node. ${MATCH_NONE_SPELLING}`
5749
+ });
4752
5750
  return;
4753
5751
  }
4754
- for (const [k, v] of Object.entries(node)) {
4755
- const childPath = `${path}.${k}`;
4756
- if (FILTER_KEYS.has(k)) {
4757
- walkFilterValues(v, childPath, where, out, /* @__PURE__ */ new Set());
5752
+ if (position === "or-branch") {
5753
+ ctx.out.push({
5754
+ severity: "error",
5755
+ rule: FILTER_EMPTY_NODE,
5756
+ where: ctx.where,
5757
+ path,
5758
+ message: `An EMPTY branch (\`{}\`) of a \`$or\`. An empty node is TRUE, and one TRUE disjunct ABSORBS the whole disjunction (\`{ $or: [{ status: 'open' }, {}] }\` ${rows(VERDICT_OF.orWithEmptyBranch)}), so every branch you wrote beside it is dead.`,
5759
+ hint: "Delete the empty branch \u2014 the `$or` then means what it looks like. If it was meant to carry a condition, write it. (A compiler that DROPPED the empty branch instead would silently NARROW the scope to the surviving branches, which is why the runtime absorbs rather than filters \u2014 #5297.)"
5760
+ });
5761
+ return;
5762
+ }
5763
+ ctx.out.push({
5764
+ severity: "error",
5765
+ rule: FILTER_EMPTY_NODE,
5766
+ where: ctx.where,
5767
+ path,
5768
+ message: "An EMPTY branch (`{}`) of a `$and`. An empty node is TRUE \u2014 the AND identity \u2014 so the branch contributes no condition and the conjunction means whatever its other branches mean.",
5769
+ hint: "Delete the empty branch, or write the condition it was meant to carry. A branch that constrains nothing is indistinguishable from one whose condition was lost in an edit."
5770
+ });
5771
+ }
5772
+ function scanNodeKeys(node, path, ctx) {
5773
+ for (const [key, value] of Object.entries(node)) {
5774
+ if (key === "$and" || key === "$or") {
5775
+ if (!Array.isArray(value)) continue;
5776
+ if (value.length === 0) {
5777
+ emitEmptyCombinator(key, `${path}.${key}`, ctx);
5778
+ continue;
5779
+ }
5780
+ value.forEach((element, index) => {
5781
+ scanBranch(element, `${path}.${key}[${index}]`, key === "$and" ? "and-branch" : "or-branch", ctx);
5782
+ });
5783
+ continue;
5784
+ }
5785
+ if (key === "$not") {
5786
+ if (!isFilterNode(value)) continue;
5787
+ if (Object.keys(value).length === 0) {
5788
+ emitEmptyCombinator("$not", `${path}.$not`, ctx);
5789
+ continue;
5790
+ }
5791
+ scanNodeKeys(value, `${path}.$not`, ctx);
4758
5792
  continue;
4759
5793
  }
4760
- scanForFilters(v, childPath, where, out, seen);
4761
5794
  }
4762
5795
  }
4763
- function validateFilterTokens(stack) {
5796
+ function scanBranch(value, path, position, ctx) {
5797
+ if (!isFilterNode(value)) return;
5798
+ if (Object.keys(value).length === 0) {
5799
+ emitEmptyNode(position, path, ctx);
5800
+ return;
5801
+ }
5802
+ scanNodeKeys(value, path, ctx);
5803
+ }
5804
+ function validateEmptyCombinators(stack) {
4764
5805
  if (!stack || typeof stack !== "object") return [];
4765
5806
  const out = [];
4766
- const surfaces = [
4767
- ["dashboards", "dashboard"],
4768
- ["objects", "object"],
4769
- ["views", "view"],
4770
- ["reports", "report"],
4771
- ["datasets", "dataset"],
4772
- ["pages", "page"],
4773
- ["apps", "app"]
4774
- ];
4775
- for (const [key, kind] of surfaces) {
4776
- const items = asArray25(stack[key]);
4777
- items.forEach((item, i) => {
4778
- const name = label(item.name ?? item.id, `#${i}`);
4779
- if (kind === "dashboard") {
4780
- const widgets = Array.isArray(item.widgets) ? item.widgets : [];
4781
- widgets.forEach((w, wi) => {
4782
- const wName = label(w.id ?? w.title, `#${wi}`);
4783
- scanForFilters(
4784
- w,
4785
- `${key}[${i}].widgets[${wi}]`,
4786
- `dashboard "${name}" \xB7 widget "${wName}"`,
4787
- out,
4788
- /* @__PURE__ */ new Set()
4789
- );
4790
- });
4791
- const { widgets: _skip, ...rest } = item;
4792
- scanForFilters(rest, `${key}[${i}]`, `dashboard "${name}"`, out, /* @__PURE__ */ new Set());
4793
- return;
4794
- }
4795
- scanForFilters(item, `${key}[${i}]`, `${kind} "${name}"`, out, /* @__PURE__ */ new Set());
4796
- });
4797
- }
5807
+ walkAuthoredFilters(stack, EMPTY_COMBINATOR_SURFACES, ({ value, path, where }) => {
5808
+ scanBranch(value, path, "root", { where, out });
5809
+ });
4798
5810
  return out;
4799
5811
  }
4800
5812
 
@@ -4803,14 +5815,14 @@ var import_system2 = require("@objectstack/spec/system");
4803
5815
  var PLATFORM_NAMES = [...import_system2.PLATFORM_PROVIDED_OBJECT_NAMES];
4804
5816
  var OBJECT_REFERENCE_UNKNOWN = "object-reference-unknown";
4805
5817
  var OBJECT_REFERENCE_UNREGISTERED_PLATFORM = "object-reference-unregistered-platform";
4806
- function asArray26(v) {
5818
+ function asArray25(v) {
4807
5819
  if (Array.isArray(v)) return v;
4808
5820
  if (v && typeof v === "object") {
4809
5821
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
4810
5822
  }
4811
5823
  return [];
4812
5824
  }
4813
- function strName6(v) {
5825
+ function strName8(v) {
4814
5826
  return typeof v === "string" && v.length > 0 ? v : void 0;
4815
5827
  }
4816
5828
  function isInterpolated(target) {
@@ -4850,14 +5862,14 @@ function distance2(a, b) {
4850
5862
  function validateObjectReferences(stack) {
4851
5863
  const findings = [];
4852
5864
  if (!stack || typeof stack !== "object") return findings;
4853
- const objects = asArray26(stack.objects);
5865
+ const objects = asArray25(stack.objects);
4854
5866
  const ownObjects = /* @__PURE__ */ new Set();
4855
5867
  for (const obj of objects) {
4856
- const n = strName6(obj.name);
5868
+ const n = strName8(obj.name);
4857
5869
  if (n) ownObjects.add(n);
4858
5870
  }
4859
5871
  const check = (target, where, path, subject, fix) => {
4860
- const name = strName6(target);
5872
+ const name = strName8(target);
4861
5873
  if (!name) return;
4862
5874
  if (isInterpolated(name)) return;
4863
5875
  if (ownObjects.has(name)) return;
@@ -4883,21 +5895,21 @@ function validateObjectReferences(stack) {
4883
5895
  });
4884
5896
  };
4885
5897
  const checkActionParams2 = (action, actionPath, actionLabel) => {
4886
- const params = asArray26(action.params);
5898
+ const params = asArray25(action.params);
4887
5899
  for (let pi = 0; pi < params.length; pi++) {
4888
5900
  const param = params[pi];
4889
5901
  if (!param || typeof param !== "object") continue;
4890
- const paramLabel = strName6(param.name) ?? strName6(param.field) ?? `#${pi}`;
5902
+ const paramLabel = strName8(param.name) ?? strName8(param.field) ?? `#${pi}`;
4891
5903
  const where = `${actionLabel} \xB7 param "${paramLabel}"`;
4892
5904
  check(
4893
- strName6(param.reference),
5905
+ strName8(param.reference),
4894
5906
  where,
4895
5907
  `${actionPath}.params[${pi}].reference`,
4896
5908
  "record-picker target",
4897
5909
  "Without a resolvable target the picker degrades to a raw record-id text input."
4898
5910
  );
4899
5911
  check(
4900
- strName6(param.objectOverride),
5912
+ strName8(param.objectOverride),
4901
5913
  where,
4902
5914
  `${actionPath}.params[${pi}].objectOverride`,
4903
5915
  "field-backed param object",
@@ -4905,70 +5917,70 @@ function validateObjectReferences(stack) {
4905
5917
  );
4906
5918
  }
4907
5919
  };
4908
- const globalActions = asArray26(stack.actions);
5920
+ const globalActions = asArray25(stack.actions);
4909
5921
  for (let ai = 0; ai < globalActions.length; ai++) {
4910
5922
  const action = globalActions[ai];
4911
5923
  if (!action || typeof action !== "object") continue;
4912
- checkActionParams2(action, `actions[${ai}]`, `action "${strName6(action.name) ?? `#${ai}`}"`);
5924
+ checkActionParams2(action, `actions[${ai}]`, `action "${strName8(action.name) ?? `#${ai}`}"`);
4913
5925
  }
4914
5926
  for (let oi = 0; oi < objects.length; oi++) {
4915
5927
  const obj = objects[oi];
4916
5928
  if (!obj || typeof obj !== "object") continue;
4917
- const objName = strName6(obj.name) ?? `#${oi}`;
4918
- const objActions = asArray26(obj.actions);
5929
+ const objName = strName8(obj.name) ?? `#${oi}`;
5930
+ const objActions = asArray25(obj.actions);
4919
5931
  for (let ai = 0; ai < objActions.length; ai++) {
4920
5932
  const action = objActions[ai];
4921
5933
  if (!action || typeof action !== "object") continue;
4922
5934
  checkActionParams2(
4923
5935
  action,
4924
5936
  `objects[${oi}].actions[${ai}]`,
4925
- `object "${objName}" \xB7 action "${strName6(action.name) ?? `#${ai}`}"`
5937
+ `object "${objName}" \xB7 action "${strName8(action.name) ?? `#${ai}`}"`
4926
5938
  );
4927
5939
  }
4928
5940
  }
4929
- const dashboards = asArray26(stack.dashboards);
5941
+ const dashboards = asArray25(stack.dashboards);
4930
5942
  for (let di = 0; di < dashboards.length; di++) {
4931
5943
  const dash = dashboards[di];
4932
5944
  if (!dash || typeof dash !== "object") continue;
4933
- const dashName = strName6(dash.name) ?? `#${di}`;
4934
- const filters = asArray26(dash.globalFilters);
5945
+ const dashName = strName8(dash.name) ?? `#${di}`;
5946
+ const filters = asArray25(dash.globalFilters);
4935
5947
  for (let fi = 0; fi < filters.length; fi++) {
4936
5948
  const filter = filters[fi];
4937
5949
  if (!filter || typeof filter !== "object") continue;
4938
5950
  const optionsFrom = filter.optionsFrom;
4939
5951
  if (!optionsFrom || typeof optionsFrom !== "object") continue;
4940
5952
  check(
4941
- strName6(optionsFrom.object),
4942
- `dashboard "${dashName}" \xB7 filter "${strName6(filter.name) ?? `#${fi}`}"`,
5953
+ strName8(optionsFrom.object),
5954
+ `dashboard "${dashName}" \xB7 filter "${strName8(filter.name) ?? `#${fi}`}"`,
4943
5955
  `dashboards[${di}].globalFilters[${fi}].optionsFrom.object`,
4944
5956
  "filter options source",
4945
5957
  "The dropdown fetches its options from this object; an unknown one renders an always-empty filter."
4946
5958
  );
4947
5959
  }
4948
5960
  }
4949
- const apps = asArray26(stack.apps);
5961
+ const apps = asArray25(stack.apps);
4950
5962
  for (let ai = 0; ai < apps.length; ai++) {
4951
5963
  const app = apps[ai];
4952
5964
  if (!app || typeof app !== "object") continue;
4953
- const appName = strName6(app.name) ?? `#${ai}`;
5965
+ const appName = strName8(app.name) ?? `#${ai}`;
4954
5966
  const walkNav = (items, basePath) => {
4955
- const navItems = asArray26(items);
5967
+ const navItems = asArray25(items);
4956
5968
  for (let ni = 0; ni < navItems.length; ni++) {
4957
5969
  const nav = navItems[ni];
4958
5970
  if (!nav || typeof nav !== "object") continue;
4959
- const navId = strName6(nav.id) ?? `#${ni}`;
5971
+ const navId = strName8(nav.id) ?? `#${ni}`;
4960
5972
  const where = `app "${appName}" \xB7 nav "${navId}"`;
4961
5973
  const navPath = `${basePath}[${ni}]`;
4962
5974
  check(
4963
- strName6(nav.requiresObject),
5975
+ strName8(nav.requiresObject),
4964
5976
  where,
4965
5977
  `${navPath}.requiresObject`,
4966
5978
  "capability gate object",
4967
5979
  "The entry is hidden unless this object is registered, so a typo hides it permanently \u2014 and it suppresses the nav cross-reference check that would have caught the target."
4968
5980
  );
4969
- if (nav.requiresObject && strName6(nav.objectName)) {
5981
+ if (nav.requiresObject && strName8(nav.objectName)) {
4970
5982
  check(
4971
- strName6(nav.objectName),
5983
+ strName8(nav.objectName),
4972
5984
  where,
4973
5985
  `${navPath}.objectName`,
4974
5986
  "navigation target",
@@ -4979,7 +5991,7 @@ function validateObjectReferences(stack) {
4979
5991
  }
4980
5992
  };
4981
5993
  walkNav(app.navigation, `apps[${ai}].navigation`);
4982
- const areas = asArray26(app.areas);
5994
+ const areas = asArray25(app.areas);
4983
5995
  for (let ri = 0; ri < areas.length; ri++) {
4984
5996
  walkNav(areas[ri]?.navigation, `apps[${ai}].areas[${ri}].navigation`);
4985
5997
  }
@@ -4989,13 +6001,13 @@ function validateObjectReferences(stack) {
4989
6001
 
4990
6002
  // src/validate-nav-target-refs.ts
4991
6003
  var NAV_TARGET_UNRESOLVED = "nav-target-unresolved";
4992
- var isRec8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
4993
- function asArray27(v) {
4994
- if (Array.isArray(v)) return v.filter(isRec8);
4995
- if (isRec8(v)) return Object.entries(v).map(([name, def]) => isRec8(def) ? { name, ...def } : { name });
6004
+ var isRec12 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
6005
+ 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 });
4996
6008
  return [];
4997
6009
  }
4998
- function strName7(v) {
6010
+ function strName9(v) {
4999
6011
  return typeof v === "string" && v.length > 0 ? v : void 0;
5000
6012
  }
5001
6013
  var isInterpolated2 = (s) => s.includes("${") || s.includes("{");
@@ -5006,32 +6018,32 @@ var NAV_TARGETS = [
5006
6018
  ];
5007
6019
  function namesOf(collection) {
5008
6020
  const out = /* @__PURE__ */ new Set();
5009
- for (const entry of asArray27(collection)) {
5010
- const n = strName7(entry.name);
6021
+ for (const entry of asArray26(collection)) {
6022
+ const n = strName9(entry.name);
5011
6023
  if (n) out.add(n);
5012
6024
  }
5013
6025
  return out;
5014
6026
  }
5015
6027
  function validateNavTargetRefs(stack) {
5016
6028
  const findings = [];
5017
- if (!isRec8(stack)) return findings;
5018
- const apps = asArray27(stack.apps);
6029
+ if (!isRec12(stack)) return findings;
6030
+ const apps = asArray26(stack.apps);
5019
6031
  if (apps.length === 0) return findings;
5020
6032
  const declared = /* @__PURE__ */ new Map();
5021
6033
  for (const [, , collection] of NAV_TARGETS) {
5022
6034
  declared.set(collection, namesOf(stack[collection]));
5023
6035
  }
5024
6036
  for (const [ai, app] of apps.entries()) {
5025
- const appName = strName7(app.name) ?? `#${ai}`;
6037
+ const appName = strName9(app.name) ?? `#${ai}`;
5026
6038
  const walk = (items, basePath) => {
5027
6039
  if (!Array.isArray(items)) return;
5028
6040
  for (const [ni, raw] of items.entries()) {
5029
- if (!isRec8(raw)) continue;
6041
+ if (!isRec12(raw)) continue;
5030
6042
  const nav = raw;
5031
6043
  const navPath = `${basePath}[${ni}]`;
5032
6044
  for (const [type, prop, collection, noun] of NAV_TARGETS) {
5033
6045
  if (nav.type !== type) continue;
5034
- const target = strName7(nav[prop]);
6046
+ const target = strName9(nav[prop]);
5035
6047
  if (!target || isInterpolated2(target)) continue;
5036
6048
  const known = declared.get(collection);
5037
6049
  if (known.has(target)) continue;
@@ -5039,7 +6051,7 @@ function validateNavTargetRefs(stack) {
5039
6051
  findings.push({
5040
6052
  severity: "warning",
5041
6053
  rule: NAV_TARGET_UNRESOLVED,
5042
- where: `app "${appName}" \xB7 nav "${strName7(nav.id) ?? strName7(nav.label) ?? `#${ni}`}"`,
6054
+ where: `app "${appName}" \xB7 nav "${strName9(nav.id) ?? strName9(nav.label) ?? `#${ni}`}"`,
5043
6055
  path: `${navPath}.${prop}`,
5044
6056
  message: `Navigation targets ${noun} '${target}', which this stack does not declare in \`${collection}\`. ` + (emptyCollection ? `The stack declares NO ${collection} at all, so \`defineStack\`'s own cross-reference check skipped this entry entirely (it is gated on \`${collection === "pages" ? "pageNames" : collection === "reports" ? "reportNames" : "dashboardNames"}.size > 0\`) \u2014 nothing else will report it. ` : "") + `The entry renders in the sidebar and resolves to nothing when clicked. If another package provides this ${noun}, this is expected and advisory only.`,
5045
6057
  hint: `Declare the ${noun} in \`${collection}\`, correct the name, or remove the nav entry if the ${noun} is gone.`
@@ -5049,7 +6061,7 @@ function validateNavTargetRefs(stack) {
5049
6061
  }
5050
6062
  };
5051
6063
  walk(app.navigation, `apps[${ai}].navigation`);
5052
- for (const [ari, area] of asArray27(app.areas).entries()) {
6064
+ for (const [ari, area] of asArray26(app.areas).entries()) {
5053
6065
  walk(area.items, `apps[${ai}].areas[${ari}].items`);
5054
6066
  walk(area.navigation, `apps[${ai}].areas[${ari}].navigation`);
5055
6067
  }
@@ -5059,14 +6071,14 @@ function validateNavTargetRefs(stack) {
5059
6071
 
5060
6072
  // src/validate-action-name-refs.ts
5061
6073
  var ACTION_NAME_UNDEFINED = "action-name-undefined";
5062
- function asArray28(v) {
6074
+ function asArray27(v) {
5063
6075
  if (Array.isArray(v)) return v;
5064
6076
  if (v && typeof v === "object") {
5065
6077
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
5066
6078
  }
5067
6079
  return [];
5068
6080
  }
5069
- function strName8(v) {
6081
+ function strName10(v) {
5070
6082
  return typeof v === "string" && v.length > 0 ? v : void 0;
5071
6083
  }
5072
6084
  function strList(v) {
@@ -5103,14 +6115,14 @@ function suggest4(target, known) {
5103
6115
  }
5104
6116
  function collectActionNames(stack) {
5105
6117
  const names = /* @__PURE__ */ new Set();
5106
- for (const action of asArray28(stack.actions)) {
5107
- const n = strName8(action?.name);
6118
+ for (const action of asArray27(stack.actions)) {
6119
+ const n = strName10(action?.name);
5108
6120
  if (n) names.add(n);
5109
6121
  }
5110
- for (const obj of asArray28(stack.objects)) {
6122
+ for (const obj of asArray27(stack.objects)) {
5111
6123
  if (!obj || typeof obj !== "object") continue;
5112
- for (const action of asArray28(obj.actions)) {
5113
- const n = strName8(action?.name);
6124
+ for (const action of asArray27(obj.actions)) {
6125
+ const n = strName10(action?.name);
5114
6126
  if (n) names.add(n);
5115
6127
  }
5116
6128
  }
@@ -5153,7 +6165,7 @@ function validateActionNameRefs(stack) {
5153
6165
  if (!def || typeof def !== "object") continue;
5154
6166
  if (def.execution !== "aggregate") continue;
5155
6167
  if (def.actionDef !== void 0) continue;
5156
- const name = strName8(def.name);
6168
+ const name = strName10(def.name);
5157
6169
  if (!name) continue;
5158
6170
  check(
5159
6171
  name,
@@ -5164,11 +6176,11 @@ function validateActionNameRefs(stack) {
5164
6176
  );
5165
6177
  }
5166
6178
  };
5167
- const views = asArray28(stack.views);
6179
+ const views = asArray27(stack.views);
5168
6180
  for (let vi = 0; vi < views.length; vi++) {
5169
6181
  const view = views[vi];
5170
6182
  if (!view || typeof view !== "object") continue;
5171
- const viewName = strName8(view.name) ?? strName8(view.object) ?? `#${vi}`;
6183
+ const viewName = strName10(view.name) ?? strName10(view.object) ?? `#${vi}`;
5172
6184
  const owner = `view "${viewName}"`;
5173
6185
  checkListContainer(view.list, owner, "list", `views[${vi}].list`);
5174
6186
  const listViews = view.listViews;
@@ -5178,22 +6190,22 @@ function validateActionNameRefs(stack) {
5178
6190
  }
5179
6191
  }
5180
6192
  }
5181
- const objects = asArray28(stack.objects);
6193
+ const objects = asArray27(stack.objects);
5182
6194
  for (let oi = 0; oi < objects.length; oi++) {
5183
6195
  const obj = objects[oi];
5184
6196
  if (!obj || typeof obj !== "object") continue;
5185
6197
  const objListViews = obj.listViews;
5186
6198
  if (!objListViews || typeof objListViews !== "object" || Array.isArray(objListViews)) continue;
5187
- const owner = `object "${strName8(obj.name) ?? `#${oi}`}"`;
6199
+ const owner = `object "${strName10(obj.name) ?? `#${oi}`}"`;
5188
6200
  for (const [key, lv] of Object.entries(objListViews)) {
5189
6201
  checkListContainer(lv, owner, `listViews.${key}`, `objects[${oi}].listViews.${key}`);
5190
6202
  }
5191
6203
  }
5192
- const pages = asArray28(stack.pages);
6204
+ const pages = asArray27(stack.pages);
5193
6205
  for (let pi = 0; pi < pages.length; pi++) {
5194
6206
  const page = pages[pi];
5195
6207
  if (!page || typeof page !== "object") continue;
5196
- const pageName = strName8(page.name) ?? `#${pi}`;
6208
+ const pageName = strName10(page.name) ?? `#${pi}`;
5197
6209
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
5198
6210
  const props = component.properties;
5199
6211
  if (!props || typeof props !== "object") continue;
@@ -5201,39 +6213,48 @@ function validateActionNameRefs(stack) {
5201
6213
  for (let ai = 0; ai < names.length; ai++) {
5202
6214
  check(
5203
6215
  names[ai],
5204
- `page "${pageName}" \xB7 component "${strName8(component.type) ?? "?"}"`,
6216
+ `page "${pageName}" \xB7 component "${strName10(component.type) ?? "?"}"`,
5205
6217
  `${path}.properties.actionNames[${ai}]`,
5206
6218
  "Quick-actions bar"
5207
6219
  );
5208
6220
  }
5209
6221
  }
5210
6222
  }
5211
- const apps = asArray28(stack.apps);
6223
+ const apps = asArray27(stack.apps);
5212
6224
  for (let ai = 0; ai < apps.length; ai++) {
5213
6225
  const app = apps[ai];
5214
6226
  if (!app || typeof app !== "object") continue;
5215
- const appName = strName8(app.name) ?? `#${ai}`;
6227
+ const appName = strName10(app.name) ?? `#${ai}`;
5216
6228
  const walkNav = (items, basePath) => {
5217
- const navItems = asArray28(items);
6229
+ const navItems = asArray27(items);
5218
6230
  for (let ni = 0; ni < navItems.length; ni++) {
5219
6231
  const nav = navItems[ni];
5220
6232
  if (!nav || typeof nav !== "object") continue;
5221
6233
  const navPath = `${basePath}[${ni}]`;
5222
6234
  const actionDef = nav.actionDef;
5223
- const actionName = strName8(actionDef?.actionName);
6235
+ const actionName = strName10(actionDef?.actionName);
5224
6236
  if (nav.type === "action" && actionName) {
5225
6237
  check(
5226
6238
  actionName,
5227
- `app "${appName}" \xB7 nav "${strName8(nav.id) ?? `#${ni}`}"`,
6239
+ `app "${appName}" \xB7 nav "${strName10(nav.id) ?? `#${ni}`}"`,
5228
6240
  `${navPath}.actionDef.actionName`,
5229
6241
  "Navigation action item"
5230
6242
  );
5231
6243
  }
6244
+ const runAction = strName10(nav.runAction);
6245
+ if (nav.type === "object" && runAction) {
6246
+ check(
6247
+ runAction,
6248
+ `app "${appName}" \xB7 nav "${strName10(nav.id) ?? `#${ni}`}"`,
6249
+ `${navPath}.runAction`,
6250
+ "Navigation deep-link auto-run"
6251
+ );
6252
+ }
5232
6253
  if (Array.isArray(nav.children)) walkNav(nav.children, `${navPath}.children`);
5233
6254
  }
5234
6255
  };
5235
6256
  walkNav(app.navigation, `apps[${ai}].navigation`);
5236
- const areas = asArray28(app.areas);
6257
+ const areas = asArray27(app.areas);
5237
6258
  for (let ri = 0; ri < areas.length; ri++) {
5238
6259
  walkNav(areas[ri]?.navigation, `apps[${ai}].areas[${ri}].navigation`);
5239
6260
  }
@@ -5243,14 +6264,14 @@ function validateActionNameRefs(stack) {
5243
6264
 
5244
6265
  // src/validate-action-locations.ts
5245
6266
  var ACTION_NO_PLACEMENT = "action-no-placement";
5246
- function asArray29(v) {
6267
+ function asArray28(v) {
5247
6268
  if (Array.isArray(v)) return v;
5248
6269
  if (v && typeof v === "object") {
5249
6270
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
5250
6271
  }
5251
6272
  return [];
5252
6273
  }
5253
- function strName9(v) {
6274
+ function strName11(v) {
5254
6275
  return typeof v === "string" && v.length > 0 ? v : void 0;
5255
6276
  }
5256
6277
  function strList2(v) {
@@ -5264,8 +6285,8 @@ function collectNamePlacedActions(stack) {
5264
6285
  for (const key of ["rowActions", "bulkActions"]) {
5265
6286
  for (const n of strList2(list3[key])) placed.add(n);
5266
6287
  }
5267
- for (const def of asArray29(list3.bulkActionDefs)) {
5268
- const n = strName9(def?.name);
6288
+ for (const def of asArray28(list3.bulkActionDefs)) {
6289
+ const n = strName11(def?.name);
5269
6290
  if (n) placed.add(n);
5270
6291
  }
5271
6292
  };
@@ -5273,12 +6294,12 @@ function collectNamePlacedActions(stack) {
5273
6294
  if (!listViews || typeof listViews !== "object" || Array.isArray(listViews)) return;
5274
6295
  for (const lv of Object.values(listViews)) harvest(lv);
5275
6296
  };
5276
- for (const view of asArray29(stack.views)) {
6297
+ for (const view of asArray28(stack.views)) {
5277
6298
  if (!view || typeof view !== "object") continue;
5278
6299
  harvest(view.list);
5279
6300
  harvestListViews(view.listViews);
5280
6301
  }
5281
- for (const obj of asArray29(stack.objects)) {
6302
+ for (const obj of asArray28(stack.objects)) {
5282
6303
  if (!obj || typeof obj !== "object") continue;
5283
6304
  harvestListViews(obj.listViews);
5284
6305
  }
@@ -5291,7 +6312,7 @@ function validateActionLocations(stack) {
5291
6312
  const check = (action, path) => {
5292
6313
  if (!action || typeof action !== "object") return;
5293
6314
  if ("locations" in action) return;
5294
- const name = strName9(action.name);
6315
+ const name = strName11(action.name);
5295
6316
  if (!name) return;
5296
6317
  if (namePlaced.has(name)) return;
5297
6318
  findings.push({
@@ -5300,16 +6321,16 @@ function validateActionLocations(stack) {
5300
6321
  where: `action "${name}"`,
5301
6322
  path,
5302
6323
  message: `Action "${name}" declares no \`locations\` and no view places it by name, so it renders on no surface \u2014 the button exists in metadata and nowhere in the UI.`,
5303
- hint: "Add the surface it belongs on, e.g. `locations: ['record_header']` (or `list_item`, `list_toolbar`, `record_more`, `record_section`, `record_related`, `global_nav`); 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."
6324
+ 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."
5304
6325
  });
5305
6326
  };
5306
- const actions = asArray29(stack.actions);
6327
+ const actions = asArray28(stack.actions);
5307
6328
  for (let i = 0; i < actions.length; i++) check(actions[i], `actions[${i}]`);
5308
- const objects = asArray29(stack.objects);
6329
+ const objects = asArray28(stack.objects);
5309
6330
  for (let oi = 0; oi < objects.length; oi++) {
5310
6331
  const obj = objects[oi];
5311
6332
  if (!obj || typeof obj !== "object") continue;
5312
- const own = asArray29(obj.actions);
6333
+ const own = asArray28(obj.actions);
5313
6334
  for (let ai = 0; ai < own.length; ai++) check(own[ai], `objects[${oi}].actions[${ai}]`);
5314
6335
  }
5315
6336
  return findings;
@@ -5317,16 +6338,16 @@ function validateActionLocations(stack) {
5317
6338
 
5318
6339
  // src/validate-component-props.ts
5319
6340
  var import_ui3 = require("@objectstack/spec/ui");
5320
- var import_spec2 = require("@objectstack/spec");
6341
+ var import_spec3 = require("@objectstack/spec");
5321
6342
  var COMPONENT_PROPS_UNKNOWN_KEY = "component-props-unknown-key";
5322
6343
  var COMPONENT_PROPS_INVALID = "component-props-invalid";
5323
- function isRec9(v) {
6344
+ function isRec13(v) {
5324
6345
  return !!v && typeof v === "object" && !Array.isArray(v);
5325
6346
  }
5326
- function strName10(v) {
6347
+ function strName12(v) {
5327
6348
  return typeof v === "string" && v.length > 0 ? v : void 0;
5328
6349
  }
5329
- function asArray30(v) {
6350
+ function asArray29(v) {
5330
6351
  if (Array.isArray(v)) return v;
5331
6352
  if (v && typeof v === "object") {
5332
6353
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -5337,27 +6358,27 @@ var PROPS_SCHEMAS = import_ui3.ComponentPropsMap;
5337
6358
  var DATASOURCE_SUPPLIED_PROP = "object";
5338
6359
  function suppliedByDataSource(issue, component) {
5339
6360
  if (issue.path.length !== 1 || issue.path[0] !== DATASOURCE_SUPPLIED_PROP) return false;
5340
- const dataSource = isRec9(component.dataSource) ? component.dataSource : void 0;
5341
- return strName10(dataSource?.object) !== void 0;
6361
+ const dataSource = isRec13(component.dataSource) ? component.dataSource : void 0;
6362
+ return strName12(dataSource?.object) !== void 0;
5342
6363
  }
5343
6364
  function validateComponentProps(stack) {
5344
6365
  const findings = [];
5345
- if (!isRec9(stack)) return findings;
5346
- const pages = asArray30(stack.pages);
6366
+ if (!isRec13(stack)) return findings;
6367
+ const pages = asArray29(stack.pages);
5347
6368
  for (let pi = 0; pi < pages.length; pi++) {
5348
6369
  const page = pages[pi];
5349
- if (!isRec9(page)) continue;
5350
- const pageName = strName10(page.name) ?? `#${pi}`;
6370
+ if (!isRec13(page)) continue;
6371
+ const pageName = strName12(page.name) ?? `#${pi}`;
5351
6372
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
5352
- const type = strName10(component.type);
6373
+ const type = strName12(component.type);
5353
6374
  if (!type) continue;
5354
6375
  const schema = PROPS_SCHEMAS[type];
5355
6376
  if (!schema) continue;
5356
- const props = isRec9(component.properties) ? component.properties : void 0;
6377
+ const props = isRec13(component.properties) ? component.properties : void 0;
5357
6378
  if (!props) continue;
5358
6379
  const where = `page "${pageName}" \xB7 ${type}`;
5359
6380
  const base = `${path}.properties`;
5360
- for (const f of (0, import_spec2.lintUnknownKeysAgainstSchema)(schema, props, type, base)) {
6381
+ for (const f of (0, import_spec3.lintUnknownKeysAgainstSchema)(schema, props, type, base)) {
5361
6382
  findings.push({
5362
6383
  severity: "warning",
5363
6384
  rule: COMPONENT_PROPS_UNKNOWN_KEY,
@@ -5404,20 +6425,20 @@ var CHART_DIMENSION_UNKNOWN = "chart-dimension-unknown";
5404
6425
  var CHART_MEASURE_UNKNOWN = "chart-measure-unknown";
5405
6426
  var CHART_DATASET_UNKNOWN = "chart-dataset-unknown";
5406
6427
  var CHART_AXIS_NOT_SELECTED = "chart-axis-not-selected";
5407
- function asArray31(v) {
6428
+ function asArray30(v) {
5408
6429
  if (Array.isArray(v)) return v;
5409
6430
  if (v && typeof v === "object") {
5410
6431
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
5411
6432
  }
5412
6433
  return [];
5413
6434
  }
5414
- function strName11(v) {
6435
+ function strName13(v) {
5415
6436
  return typeof v === "string" && v.length > 0 ? v : void 0;
5416
6437
  }
5417
6438
  function strList3(v) {
5418
6439
  return Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.length > 0) : [];
5419
6440
  }
5420
- function isRec10(v) {
6441
+ function isRec14(v) {
5421
6442
  return !!v && typeof v === "object" && !Array.isArray(v);
5422
6443
  }
5423
6444
  function distance4(a, b) {
@@ -5455,17 +6476,17 @@ function list2(names) {
5455
6476
  }
5456
6477
  function indexDatasets(stack) {
5457
6478
  const out = /* @__PURE__ */ new Map();
5458
- for (const ds of asArray31(stack.datasets)) {
5459
- const name = strName11(ds.name);
6479
+ for (const ds of asArray30(stack.datasets)) {
6480
+ const name = strName13(ds.name);
5460
6481
  if (!name) continue;
5461
6482
  const dimensions = /* @__PURE__ */ new Set();
5462
- for (const d of asArray31(ds.dimensions)) {
5463
- const n = strName11(d.name);
6483
+ for (const d of asArray30(ds.dimensions)) {
6484
+ const n = strName13(d.name);
5464
6485
  if (n) dimensions.add(n);
5465
6486
  }
5466
6487
  const measures = /* @__PURE__ */ new Set();
5467
- for (const m of asArray31(ds.measures)) {
5468
- const n = strName11(m.name);
6488
+ for (const m of asArray30(ds.measures)) {
6489
+ const n = strName13(m.name);
5469
6490
  if (n) measures.add(n);
5470
6491
  }
5471
6492
  out.set(name, { dimensions, measures });
@@ -5543,29 +6564,29 @@ function validateChartBindings(stack) {
5543
6564
  if (binding.yAxis) measureRef(binding.yAxis.name, binding.yAxis.path, selected);
5544
6565
  for (const s of binding.series ?? []) measureRef(s.name, s.path, selected);
5545
6566
  };
5546
- const reports = asArray31(stack.reports);
6567
+ const reports = asArray30(stack.reports);
5547
6568
  for (let ri = 0; ri < reports.length; ri++) {
5548
6569
  const report = reports[ri];
5549
- if (!isRec10(report)) continue;
5550
- const reportName = strName11(report.name) ?? `#${ri}`;
6570
+ if (!isRec14(report)) continue;
6571
+ const reportName = strName13(report.name) ?? `#${ri}`;
5551
6572
  const checkReportChart = (chart, dataset, values, where, path) => {
5552
- if (!isRec10(chart)) return;
6573
+ if (!isRec14(chart)) return;
5553
6574
  check({
5554
6575
  dataset,
5555
6576
  // `values` is the report's measure SELECTION, not a chart ref; feeding
5556
6577
  // it in lets the yAxis "declared but not selected" check work without
5557
6578
  // reporting the selection itself twice.
5558
6579
  values: { names: values, path: `${path}.values` },
5559
- xAxis: strName11(chart.xAxis) ? { name: strName11(chart.xAxis), path: `${path}.chart.xAxis` } : void 0,
5560
- yAxis: strName11(chart.yAxis) ? { name: strName11(chart.yAxis), path: `${path}.chart.yAxis` } : void 0,
5561
- series: asArray31(chart.series).map((s, si) => ({ name: strName11(s.name), path: `${path}.chart.series[${si}].name` })).filter((s) => !!s.name),
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),
5562
6583
  where,
5563
6584
  path: `${path}.chart`
5564
6585
  });
5565
6586
  };
5566
6587
  checkReportChart(
5567
6588
  report.chart,
5568
- strName11(report.dataset),
6589
+ strName13(report.dataset),
5569
6590
  strList3(report.values),
5570
6591
  `report "${reportName}" \xB7 chart`,
5571
6592
  `reports[${ri}]`
@@ -5573,45 +6594,45 @@ function validateChartBindings(stack) {
5573
6594
  const blocks = Array.isArray(report.blocks) ? report.blocks : [];
5574
6595
  for (let bi = 0; bi < blocks.length; bi++) {
5575
6596
  const block = blocks[bi];
5576
- if (!isRec10(block)) continue;
6597
+ if (!isRec14(block)) continue;
5577
6598
  checkReportChart(
5578
6599
  block.chart,
5579
- strName11(block.dataset),
6600
+ strName13(block.dataset),
5580
6601
  strList3(block.values),
5581
- `report "${reportName}" \xB7 block "${strName11(block.name) ?? `#${bi}`}" chart`,
6602
+ `report "${reportName}" \xB7 block "${strName13(block.name) ?? `#${bi}`}" chart`,
5582
6603
  `reports[${ri}].blocks[${bi}]`
5583
6604
  );
5584
6605
  }
5585
6606
  }
5586
6607
  const checkListChart = (container, where, path) => {
5587
- if (!isRec10(container)) return;
6608
+ if (!isRec14(container)) return;
5588
6609
  const chart = container.chart;
5589
- if (!isRec10(chart)) return;
6610
+ if (!isRec14(chart)) return;
5590
6611
  check({
5591
- dataset: strName11(chart.dataset),
6612
+ dataset: strName13(chart.dataset),
5592
6613
  dimensions: { names: strList3(chart.dimensions), path: `${path}.chart.dimensions` },
5593
6614
  values: { names: strList3(chart.values), path: `${path}.chart.values` },
5594
6615
  where,
5595
6616
  path: `${path}.chart`
5596
6617
  });
5597
6618
  };
5598
- const views = asArray31(stack.views);
6619
+ const views = asArray30(stack.views);
5599
6620
  for (let vi = 0; vi < views.length; vi++) {
5600
6621
  const view = views[vi];
5601
- if (!isRec10(view)) continue;
5602
- const viewName = strName11(view.name) ?? strName11(view.objectName) ?? `#${vi}`;
6622
+ if (!isRec14(view)) continue;
6623
+ const viewName = strName13(view.name) ?? strName13(view.objectName) ?? `#${vi}`;
5603
6624
  checkListChart(view.list, `view "${viewName}" \xB7 list chart`, `views[${vi}].list`);
5604
- if (isRec10(view.listViews)) {
6625
+ if (isRec14(view.listViews)) {
5605
6626
  for (const [key, lv] of Object.entries(view.listViews)) {
5606
6627
  checkListChart(lv, `view "${viewName}" \xB7 listViews.${key} chart`, `views[${vi}].listViews.${key}`);
5607
6628
  }
5608
6629
  }
5609
6630
  }
5610
- const objects = asArray31(stack.objects);
6631
+ const objects = asArray30(stack.objects);
5611
6632
  for (let oi = 0; oi < objects.length; oi++) {
5612
6633
  const obj = objects[oi];
5613
- if (!isRec10(obj) || !isRec10(obj.listViews)) continue;
5614
- const objName = strName11(obj.name) ?? `#${oi}`;
6634
+ if (!isRec14(obj) || !isRec14(obj.listViews)) continue;
6635
+ const objName = strName13(obj.name) ?? `#${oi}`;
5615
6636
  for (const [key, lv] of Object.entries(obj.listViews)) {
5616
6637
  checkListChart(
5617
6638
  lv,
@@ -5620,22 +6641,22 @@ function validateChartBindings(stack) {
5620
6641
  );
5621
6642
  }
5622
6643
  }
5623
- const pages = asArray31(stack.pages);
6644
+ const pages = asArray30(stack.pages);
5624
6645
  for (let pi = 0; pi < pages.length; pi++) {
5625
6646
  const page = pages[pi];
5626
- if (!isRec10(page)) continue;
5627
- const pageName = strName11(page.name) ?? `#${pi}`;
6647
+ if (!isRec14(page)) continue;
6648
+ const pageName = strName13(page.name) ?? `#${pi}`;
5628
6649
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
5629
- const props = isRec10(component.properties) ? component.properties : void 0;
5630
- if (!props || !strName11(props.dataset)) continue;
5631
- const axisRefs = asArray31(props.yAxis).map((a, ai) => ({ name: strName11(a.field), path: `${path}.properties.yAxis[${ai}].field` })).filter((a) => !!a.name);
5632
- const seriesRefs = asArray31(props.series).map((s, si) => ({ name: strName11(s.name), path: `${path}.properties.series[${si}].name` })).filter((s) => !!s.name);
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);
5633
6654
  check({
5634
- dataset: strName11(props.dataset),
6655
+ dataset: strName13(props.dataset),
5635
6656
  dimensions: { names: strList3(props.dimensions), path: `${path}.properties.dimensions` },
5636
6657
  values: { names: strList3(props.values), path: `${path}.properties.values` },
5637
6658
  series: [...axisRefs, ...seriesRefs],
5638
- where: `page "${pageName}" \xB7 ${strName11(component.type) ?? "chart"}`,
6659
+ where: `page "${pageName}" \xB7 ${strName13(component.type) ?? "chart"}`,
5639
6660
  path: `${path}.properties`
5640
6661
  });
5641
6662
  }
@@ -5644,16 +6665,16 @@ function validateChartBindings(stack) {
5644
6665
  }
5645
6666
 
5646
6667
  // src/validate-rule-compilability.ts
5647
- var import_node_module3 = require("module");
5648
- var import_meta3 = {};
6668
+ var import_node_module4 = require("module");
6669
+ var import_meta4 = {};
5649
6670
  var VALIDATION_RULE_REGEX_UNCOMPILABLE = "validation-rule-regex-uncompilable";
5650
6671
  var VALIDATION_RULE_SCHEMA_UNCOMPILABLE = "validation-rule-json-schema-uncompilable";
5651
6672
  var RUNTIME_AJV_OPTIONS = { allErrors: true, strict: false };
5652
- var isRec11 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5653
- function asArray32(v) {
5654
- if (Array.isArray(v)) return v.filter(isRec11);
5655
- if (isRec11(v)) {
5656
- return Object.entries(v).filter(([, def]) => isRec11(def)).map(([name, def]) => ({ name, ...def }));
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 }));
5657
6678
  }
5658
6679
  return [];
5659
6680
  }
@@ -5661,31 +6682,31 @@ var cachedAjv = null;
5661
6682
  var cachedAddFormats = null;
5662
6683
  function loadAjv() {
5663
6684
  if (cachedAjv) return cachedAjv;
5664
- const anchor = typeof import_meta3 !== "undefined" && import_meta3.url ? import_meta3.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
6685
+ const anchor = typeof import_meta4 !== "undefined" && import_meta4.url ? import_meta4.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
5665
6686
  let mod;
5666
6687
  try {
5667
- mod = (0, import_node_module3.createRequire)(anchor)("ajv");
6688
+ mod = (0, import_node_module4.createRequire)(anchor)("ajv");
5668
6689
  } catch (err) {
5669
6690
  throw new Error(
5670
6691
  `@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.`
5671
6692
  );
5672
6693
  }
5673
- const ctor = isRec11(mod) && "default" in mod ? mod.default : mod;
6694
+ const ctor = isRec15(mod) && "default" in mod ? mod.default : mod;
5674
6695
  cachedAjv = ctor;
5675
6696
  return ctor;
5676
6697
  }
5677
6698
  function loadAddFormats() {
5678
6699
  if (cachedAddFormats) return cachedAddFormats;
5679
- const anchor = typeof import_meta3 !== "undefined" && import_meta3.url ? import_meta3.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
6700
+ const anchor = typeof import_meta4 !== "undefined" && import_meta4.url ? import_meta4.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
5680
6701
  let mod;
5681
6702
  try {
5682
- mod = (0, import_node_module3.createRequire)(anchor)("ajv-formats");
6703
+ mod = (0, import_node_module4.createRequire)(anchor)("ajv-formats");
5683
6704
  } catch (err) {
5684
6705
  throw new Error(
5685
6706
  `@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.`
5686
6707
  );
5687
6708
  }
5688
- const plugin = isRec11(mod) && "default" in mod ? mod.default : mod;
6709
+ const plugin = isRec15(mod) && "default" in mod ? mod.default : mod;
5689
6710
  cachedAddFormats = plugin;
5690
6711
  return plugin;
5691
6712
  }
@@ -5715,17 +6736,17 @@ function flattenRules(rule, labelTrail, pathTrail, depth = 0) {
5715
6736
  if (depth >= MAX_RULE_NESTING_DEPTH) return out;
5716
6737
  for (const branch of ["then", "otherwise"]) {
5717
6738
  const nested = rule[branch];
5718
- if (isRec11(nested)) out.push(...flattenRules(nested, label2, `${path}.${branch}`, depth + 1));
6739
+ if (isRec15(nested)) out.push(...flattenRules(nested, label2, `${path}.${branch}`, depth + 1));
5719
6740
  }
5720
6741
  return out;
5721
6742
  }
5722
6743
  function walkObjectValidationRules(stack) {
5723
6744
  const walked = [];
5724
- if (!isRec11(stack)) return walked;
5725
- for (const obj of asArray32(stack.objects)) {
6745
+ if (!isRec15(stack)) return walked;
6746
+ for (const obj of asArray31(stack.objects)) {
5726
6747
  const objectName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
5727
6748
  const validations = obj.validations;
5728
- for (const authored of asArray32(validations)) {
6749
+ for (const authored of asArray31(validations)) {
5729
6750
  for (const { rule, label: label2, path } of flattenRules(authored, "", "")) {
5730
6751
  walked.push({
5731
6752
  rule,
@@ -5756,7 +6777,7 @@ function validateRuleCompilability(stack) {
5756
6777
  });
5757
6778
  }
5758
6779
  }
5759
- if (rule.type === "json_schema" && isRec11(rule.schema)) {
6780
+ if (rule.type === "json_schema" && isRec15(rule.schema)) {
5760
6781
  try {
5761
6782
  createRuntimeAjv().compile(rule.schema);
5762
6783
  } catch (err) {
@@ -5776,7 +6797,7 @@ function validateRuleCompilability(stack) {
5776
6797
 
5777
6798
  // src/validate-rule-schema-formats.ts
5778
6799
  var VALIDATION_RULE_SCHEMA_UNKNOWN_FORMAT = "validation-rule-json-schema-unknown-format";
5779
- var isRec12 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
6800
+ var isRec16 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5780
6801
  var SUBSCHEMA_KEYS = [
5781
6802
  "additionalItems",
5782
6803
  "additionalProperties",
@@ -5800,7 +6821,7 @@ var SUBSCHEMA_MAP_KEYS = [
5800
6821
  var MAX_SCHEMA_WALK_DEPTH = 32;
5801
6822
  var escapePointerSegment = (segment) => segment.replace(/~/g, "~0").replace(/\//g, "~1");
5802
6823
  function collectFormatUses(schema, pointer, out, depth) {
5803
- if (!isRec12(schema)) return;
6824
+ if (!isRec16(schema)) return;
5804
6825
  if (typeof schema.format === "string") {
5805
6826
  out.push({ pointer: `${pointer}/format`, name: schema.format });
5806
6827
  }
@@ -5819,7 +6840,7 @@ function collectFormatUses(schema, pointer, out, depth) {
5819
6840
  }
5820
6841
  for (const key of SUBSCHEMA_MAP_KEYS) {
5821
6842
  const value = schema[key];
5822
- if (!isRec12(value)) continue;
6843
+ if (!isRec16(value)) continue;
5823
6844
  for (const [name, entry] of Object.entries(value)) {
5824
6845
  collectFormatUses(entry, `${pointer}/${escapePointerSegment(key)}/${escapePointerSegment(name)}`, out, depth + 1);
5825
6846
  }
@@ -5827,13 +6848,13 @@ function collectFormatUses(schema, pointer, out, depth) {
5827
6848
  const items = schema.items;
5828
6849
  if (Array.isArray(items)) {
5829
6850
  items.forEach((entry, index) => collectFormatUses(entry, `${pointer}/items/${index}`, out, depth + 1));
5830
- } else if (isRec12(items)) {
6851
+ } else if (isRec16(items)) {
5831
6852
  collectFormatUses(items, `${pointer}/items`, out, depth + 1);
5832
6853
  }
5833
6854
  const dependencies = schema.dependencies;
5834
- if (isRec12(dependencies)) {
6855
+ if (isRec16(dependencies)) {
5835
6856
  for (const [name, entry] of Object.entries(dependencies)) {
5836
- if (!isRec12(entry)) continue;
6857
+ if (!isRec16(entry)) continue;
5837
6858
  collectFormatUses(entry, `${pointer}/dependencies/${escapePointerSegment(name)}`, out, depth + 1);
5838
6859
  }
5839
6860
  }
@@ -5872,7 +6893,7 @@ function validateRuleSchemaFormats(stack) {
5872
6893
  const findings = [];
5873
6894
  const pending = [];
5874
6895
  for (const { rule, objectName, label: label2, where, basePath } of walkObjectValidationRules(stack)) {
5875
- if (rule.type !== "json_schema" || !isRec12(rule.schema)) continue;
6896
+ if (rule.type !== "json_schema" || !isRec16(rule.schema)) continue;
5876
6897
  const uses = [];
5877
6898
  collectFormatUses(rule.schema, "", uses, 0);
5878
6899
  for (const use of uses) pending.push({ use, where, label: label2, objectName, basePath });
@@ -5900,7 +6921,7 @@ function validateRuleSchemaFormats(stack) {
5900
6921
  var import_system3 = require("@objectstack/spec/system");
5901
6922
 
5902
6923
  // src/build-access-matrix.ts
5903
- function asArray33(v) {
6924
+ function asArray32(v) {
5904
6925
  if (Array.isArray(v)) return v;
5905
6926
  if (v && typeof v === "object") {
5906
6927
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -5911,13 +6932,13 @@ function buildAccessMatrix(stack) {
5911
6932
  const entries = [];
5912
6933
  if (!stack || typeof stack !== "object") return { version: 1, entries };
5913
6934
  const owdByObject = /* @__PURE__ */ new Map();
5914
- for (const obj of asArray33(stack.objects)) {
6935
+ for (const obj of asArray32(stack.objects)) {
5915
6936
  const name = typeof obj.name === "string" ? obj.name : "";
5916
6937
  if (!name) continue;
5917
6938
  const owd = obj.sharingModel ?? obj.security?.sharingModel;
5918
6939
  if (typeof owd === "string") owdByObject.set(name, owd);
5919
6940
  }
5920
- for (const ps of asArray33(stack.permissions)) {
6941
+ for (const ps of asArray32(stack.permissions)) {
5921
6942
  const psName = typeof ps.name === "string" ? ps.name : "";
5922
6943
  if (!psName) continue;
5923
6944
  const objects = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
@@ -5990,34 +7011,34 @@ function diffAccessMatrix(before, after) {
5990
7011
 
5991
7012
  // src/validate-nav-access.ts
5992
7013
  var NAV_OBJECT_UNGRANTED = "nav-object-ungranted";
5993
- function asArray34(v) {
7014
+ function asArray33(v) {
5994
7015
  if (Array.isArray(v)) return v;
5995
7016
  if (v && typeof v === "object") {
5996
7017
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
5997
7018
  }
5998
7019
  return [];
5999
7020
  }
6000
- function strName12(v) {
7021
+ function strName14(v) {
6001
7022
  return typeof v === "string" && v.length > 0 ? v : void 0;
6002
7023
  }
6003
7024
  function collectNavExposures(stack) {
6004
7025
  const out = [];
6005
- const apps = asArray34(stack.apps);
7026
+ const apps = asArray33(stack.apps);
6006
7027
  for (let ai = 0; ai < apps.length; ai++) {
6007
7028
  const app = apps[ai];
6008
7029
  if (!app || typeof app !== "object") continue;
6009
- const appName = strName12(app.name) ?? `#${ai}`;
7030
+ const appName = strName14(app.name) ?? `#${ai}`;
6010
7031
  const walk = (items, basePath) => {
6011
- const navItems = asArray34(items);
7032
+ const navItems = asArray33(items);
6012
7033
  for (let ni = 0; ni < navItems.length; ni++) {
6013
7034
  const nav = navItems[ni];
6014
7035
  if (!nav || typeof nav !== "object") continue;
6015
7036
  const navPath = `${basePath}[${ni}]`;
6016
- const objectName = strName12(nav.objectName);
7037
+ const objectName = strName14(nav.objectName);
6017
7038
  if (nav.type === "object" && objectName) {
6018
7039
  out.push({
6019
7040
  objectName,
6020
- where: `app "${appName}" \xB7 nav "${strName12(nav.id) ?? `#${ni}`}"`,
7041
+ where: `app "${appName}" \xB7 nav "${strName14(nav.id) ?? `#${ni}`}"`,
6021
7042
  path: `${navPath}.objectName`
6022
7043
  });
6023
7044
  }
@@ -6025,7 +7046,7 @@ function collectNavExposures(stack) {
6025
7046
  }
6026
7047
  };
6027
7048
  walk(app.navigation, `apps[${ai}].navigation`);
6028
- const areas = asArray34(app.areas);
7049
+ const areas = asArray33(app.areas);
6029
7050
  for (let ri = 0; ri < areas.length; ri++) {
6030
7051
  walk(areas[ri]?.navigation, `apps[${ai}].areas[${ri}].navigation`);
6031
7052
  }
@@ -6035,13 +7056,13 @@ function collectNavExposures(stack) {
6035
7056
  function validateNavAccess(stack) {
6036
7057
  const findings = [];
6037
7058
  if (!stack || typeof stack !== "object") return findings;
6038
- const permissionSets = asArray34(stack.permissions);
7059
+ const permissionSets = asArray33(stack.permissions);
6039
7060
  if (permissionSets.length === 0) return findings;
6040
7061
  const exposures = collectNavExposures(stack);
6041
7062
  if (exposures.length === 0) return findings;
6042
7063
  const ownObjects = /* @__PURE__ */ new Set();
6043
- for (const obj of asArray34(stack.objects)) {
6044
- const n = strName12(obj.name);
7064
+ for (const obj of asArray33(stack.objects)) {
7065
+ const n = strName14(obj.name);
6045
7066
  if (n) ownObjects.add(n);
6046
7067
  }
6047
7068
  const readable = /* @__PURE__ */ new Set();
@@ -6070,18 +7091,19 @@ function validateNavAccess(stack) {
6070
7091
  }
6071
7092
 
6072
7093
  // src/validate-translation-references.ts
7094
+ var import_spec4 = require("@objectstack/spec");
6073
7095
  var import_system4 = require("@objectstack/spec/system");
6074
7096
  var TRANSLATION_TARGET_UNKNOWN = "translation-target-unknown";
6075
7097
  var TRANSLATION_OPTION_KEY_UNKNOWN = "translation-option-key-unknown";
6076
- function isRec13(v) {
7098
+ function isRec17(v) {
6077
7099
  return !!v && typeof v === "object" && !Array.isArray(v);
6078
7100
  }
6079
- function asArray35(v) {
7101
+ function asArray34(v) {
6080
7102
  if (Array.isArray(v)) return v;
6081
- if (isRec13(v)) return Object.entries(v).map(([name, def]) => ({ name, ...isRec13(def) ? def : {} }));
7103
+ if (isRec17(v)) return Object.entries(v).map(([name, def]) => ({ name, ...isRec17(def) ? def : {} }));
6082
7104
  return [];
6083
7105
  }
6084
- function strName13(v) {
7106
+ function strName15(v) {
6085
7107
  return typeof v === "string" && v.length > 0 ? v : void 0;
6086
7108
  }
6087
7109
  function distance5(a, b) {
@@ -6141,30 +7163,52 @@ function collectViewRecord(view, factsFor) {
6141
7163
  };
6142
7164
  const addSections = (container, binding) => {
6143
7165
  if (!binding) return;
6144
- for (const section of asArray35(container.sections)) {
6145
- const sectionName = strName13(section.name);
7166
+ for (const section of asArray34(container.sections)) {
7167
+ const sectionName = strName15(section.name);
6146
7168
  if (sectionName) factsFor(binding).sections.add(sectionName);
6147
7169
  }
6148
7170
  };
6149
- const listBinding = isRec13(view.list) ? bindingOf(view.list) : void 0;
6150
- if (isRec13(view.list)) addView(listBinding, strName13(view.list.name));
6151
- addView(recordObject ?? listBinding, strName13(view.name));
6152
- for (const key of ["listViews", "formViews"]) {
6153
- const container = view[key];
6154
- if (!isRec13(container)) continue;
6155
- for (const [subKey, sub] of Object.entries(container)) {
6156
- if (!isRec13(sub)) continue;
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));
7174
+ const named = namedViewKeys(view);
7175
+ for (const family of ["listViews", "formViews"]) {
7176
+ const container = view[family];
7177
+ if (!isRec17(container)) continue;
7178
+ const registryKeys = family === "listViews" ? named.list : named.form;
7179
+ let at = 0;
7180
+ for (const sub of Object.values(container)) {
7181
+ if (!sub || typeof sub !== "object") continue;
7182
+ const registryKey = registryKeys[at++];
7183
+ if (!isRec17(sub)) continue;
6157
7184
  const binding = bindingOf(sub) ?? listBinding;
6158
- addView(binding, subKey);
6159
- addView(binding, strName13(sub.name));
7185
+ addView(binding, registryKey);
6160
7186
  addSections(sub, binding);
6161
7187
  }
6162
7188
  }
6163
- if (isRec13(view.form)) addSections(view.form, bindingOf(view.form) ?? listBinding);
7189
+ if (isRec17(view.form)) addSections(view.form, bindingOf(view.form) ?? listBinding);
6164
7190
  addSections(view, recordObject ?? listBinding);
6165
7191
  }
6166
- function viewObjectName(view) {
6167
- return strName13(view.objectName) ?? strName13(view.object) ?? (isRec13(view.data) ? strName13(view.data.object) : void 0);
7192
+ function defaultListViewKey(object, container) {
7193
+ if (!object || !isRec17(container.list)) return void 0;
7194
+ const item = (0, import_spec4.expandViewContainer)(object, container).find(
7195
+ (i) => i.viewKind === "list" && i.isDefault
7196
+ );
7197
+ if (!item) return void 0;
7198
+ const prefix = `${object}.`;
7199
+ return item.name.startsWith(prefix) ? item.name.slice(prefix.length) : item.name;
7200
+ }
7201
+ function namedViewKeys(container) {
7202
+ const object = "probe";
7203
+ const prefix = `${object}.`;
7204
+ 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;
7206
+ const listCount = countEntries(container.listViews);
7207
+ const formCount = countEntries(container.formViews);
7208
+ if (!listCount && !formCount) return { list: [], form: [] };
7209
+ const items = (0, import_spec4.expandViewContainer)(object, container);
7210
+ const keysOf2 = (kind, count) => items.filter((i) => i.viewKind === kind).slice(0, count).map((i) => bare(i.name));
7211
+ return { list: keysOf2("list", listCount), form: keysOf2("form", formCount) };
6168
7212
  }
6169
7213
  function readOptions(field) {
6170
7214
  const raw = field.options;
@@ -6176,14 +7220,14 @@ function readOptions(field) {
6176
7220
  values.add(opt);
6177
7221
  continue;
6178
7222
  }
6179
- if (!isRec13(opt)) continue;
6180
- const value = strName13(opt.value);
7223
+ if (!isRec17(opt)) continue;
7224
+ const value = strName15(opt.value);
6181
7225
  if (!value) continue;
6182
7226
  values.add(value);
6183
- const label2 = strName13(opt.label);
7227
+ const label2 = strName15(opt.label);
6184
7228
  if (label2) byLabel.set(label2.toLowerCase(), value);
6185
7229
  }
6186
- } else if (isRec13(raw)) {
7230
+ } else if (isRec17(raw)) {
6187
7231
  for (const [value, label2] of Object.entries(raw)) {
6188
7232
  values.add(value);
6189
7233
  if (typeof label2 === "string" && label2.length > 0) byLabel.set(label2.toLowerCase(), value);
@@ -6203,48 +7247,48 @@ function buildUniverse(stack) {
6203
7247
  }
6204
7248
  return facts;
6205
7249
  };
6206
- for (const obj of asArray35(stack.objects)) {
6207
- const objectName = strName13(obj.name);
7250
+ for (const obj of asArray34(stack.objects)) {
7251
+ const objectName = strName15(obj.name);
6208
7252
  if (!objectName) continue;
6209
7253
  const facts = factsFor(objectName);
6210
- for (const field of asArray35(obj.fields)) {
6211
- const fieldName = strName13(field.name);
7254
+ for (const field of asArray34(obj.fields)) {
7255
+ const fieldName = strName15(field.name);
6212
7256
  if (fieldName) facts.fields.set(fieldName, field);
6213
7257
  }
6214
- for (const action of asArray35(obj.actions)) {
6215
- const actionName = strName13(action.name);
7258
+ for (const action of asArray34(obj.actions)) {
7259
+ const actionName = strName15(action.name);
6216
7260
  if (actionName) facts.actions.set(actionName, action);
6217
7261
  }
6218
- for (const view of asArray35(obj.views)) {
6219
- collectViewRecord({ ...view, object: strName13(view.object) ?? objectName }, factsFor);
7262
+ for (const view of asArray34(obj.views)) {
7263
+ collectViewRecord({ ...view, object: strName15(view.object) ?? objectName }, factsFor);
6220
7264
  }
6221
7265
  collectViewRecord({ object: objectName, listViews: obj.listViews }, factsFor);
6222
- for (const group of asArray35(obj.fieldGroups)) {
6223
- const key = strName13(group.key) ?? strName13(group.name);
7266
+ for (const group of asArray34(obj.fieldGroups)) {
7267
+ const key = strName15(group.key) ?? strName15(group.name);
6224
7268
  if (key) facts.sections.add(key);
6225
7269
  }
6226
7270
  }
6227
- for (const view of asArray35(stack.views)) {
7271
+ for (const view of asArray34(stack.views)) {
6228
7272
  collectViewRecord(view, factsFor);
6229
7273
  }
6230
- const pages = asArray35(stack.pages);
7274
+ const pages = asArray34(stack.pages);
6231
7275
  for (let pi = 0; pi < pages.length; pi++) {
6232
7276
  for (const walked of walkPageComponents(pages[pi], `pages[${pi}]`)) {
6233
7277
  if (!walked.objectName) continue;
6234
- const props = isRec13(walked.component.properties) ? walked.component.properties : void 0;
7278
+ const props = isRec17(walked.component.properties) ? walked.component.properties : void 0;
6235
7279
  if (!props) continue;
6236
- for (const section of asArray35(props.sections)) {
6237
- const sectionName = strName13(section.name);
7280
+ for (const section of asArray34(props.sections)) {
7281
+ const sectionName = strName15(section.name);
6238
7282
  if (sectionName) factsFor(walked.objectName).sections.add(sectionName);
6239
7283
  }
6240
7284
  }
6241
7285
  }
6242
7286
  const globalActions = /* @__PURE__ */ new Map();
6243
7287
  const actionOwners = /* @__PURE__ */ new Map();
6244
- for (const action of asArray35(stack.actions)) {
6245
- const actionName = strName13(action.name);
7288
+ for (const action of asArray34(stack.actions)) {
7289
+ const actionName = strName15(action.name);
6246
7290
  if (!actionName) continue;
6247
- const owner = strName13(action.objectName) ?? strName13(action.object);
7291
+ const owner = strName15(action.objectName) ?? strName15(action.object);
6248
7292
  if (owner) {
6249
7293
  factsFor(owner).actions.set(actionName, action);
6250
7294
  actionOwners.set(actionName, owner);
@@ -6258,41 +7302,41 @@ function buildUniverse(stack) {
6258
7302
  }
6259
7303
  }
6260
7304
  const apps = /* @__PURE__ */ new Map();
6261
- for (const app of asArray35(stack.apps)) {
6262
- const appName = strName13(app.name);
7305
+ for (const app of asArray34(stack.apps)) {
7306
+ const appName = strName15(app.name);
6263
7307
  if (!appName) continue;
6264
7308
  const navIds = apps.get(appName) ?? /* @__PURE__ */ new Set();
6265
7309
  const walkNav = (items) => {
6266
- for (const item of asArray35(items)) {
6267
- const id = strName13(item.id);
7310
+ for (const item of asArray34(items)) {
7311
+ const id = strName15(item.id);
6268
7312
  if (id) navIds.add(id);
6269
7313
  if (item.children) walkNav(item.children);
6270
7314
  }
6271
7315
  };
6272
7316
  walkNav(app.navigation);
6273
- for (const area of asArray35(app.areas)) {
6274
- const areaId = strName13(area.id);
7317
+ for (const area of asArray34(app.areas)) {
7318
+ const areaId = strName15(area.id);
6275
7319
  if (areaId) navIds.add(areaId);
6276
7320
  walkNav(area.navigation);
6277
7321
  }
6278
7322
  apps.set(appName, navIds);
6279
7323
  }
6280
7324
  const dashboards = /* @__PURE__ */ new Map();
6281
- for (const dash of asArray35(stack.dashboards)) {
6282
- const dashName = strName13(dash.name);
7325
+ for (const dash of asArray34(stack.dashboards)) {
7326
+ const dashName = strName15(dash.name);
6283
7327
  if (!dashName) continue;
6284
7328
  const widgets = /* @__PURE__ */ new Set();
6285
- for (const widget of asArray35(dash.widgets)) {
6286
- const id = strName13(widget.id) ?? strName13(widget.name);
7329
+ for (const widget of asArray34(dash.widgets)) {
7330
+ const id = strName15(widget.id) ?? strName15(widget.name);
6287
7331
  if (id) widgets.add(id);
6288
7332
  }
6289
7333
  const actions = /* @__PURE__ */ new Set();
6290
7334
  const headerActions = [
6291
- ...asArray35(isRec13(dash.header) ? dash.header.actions : void 0),
6292
- ...asArray35(dash.actions)
7335
+ ...asArray34(isRec17(dash.header) ? dash.header.actions : void 0),
7336
+ ...asArray34(dash.actions)
6293
7337
  ];
6294
7338
  for (const action of headerActions) {
6295
- const key = strName13(action.actionUrl) ?? strName13(action.url) ?? strName13(action.name);
7339
+ const key = strName15(action.actionUrl) ?? strName15(action.url) ?? strName15(action.name);
6296
7340
  if (key) actions.add(key);
6297
7341
  }
6298
7342
  dashboards.set(dashName, { widgets, actions });
@@ -6304,7 +7348,7 @@ function localePath(bundleIndex, locale) {
6304
7348
  }
6305
7349
  function validateTranslationReferences(stack) {
6306
7350
  const findings = [];
6307
- if (!isRec13(stack)) return findings;
7351
+ if (!isRec17(stack)) return findings;
6308
7352
  const bundles = Array.isArray(stack.translations) ? stack.translations : [];
6309
7353
  if (bundles.length === 0) return findings;
6310
7354
  const universe = buildUniverse(stack);
@@ -6313,13 +7357,13 @@ function validateTranslationReferences(stack) {
6313
7357
  };
6314
7358
  for (let bi = 0; bi < bundles.length; bi++) {
6315
7359
  const bundle = bundles[bi];
6316
- if (!isRec13(bundle)) continue;
7360
+ if (!isRec17(bundle)) continue;
6317
7361
  for (const [locale, rawData] of Object.entries(bundle)) {
6318
- if (!isRec13(rawData)) continue;
7362
+ if (!isRec17(rawData)) continue;
6319
7363
  const base = localePath(bi, locale);
6320
7364
  const inLocale = `locale "${locale}"`;
6321
7365
  for (const [objectName, rawNode] of Object.entries(asRecord(rawData.objects))) {
6322
- if (!isRec13(rawNode)) continue;
7366
+ if (!isRec17(rawNode)) continue;
6323
7367
  const objPath = `${base}.objects.${objectName}`;
6324
7368
  const facts = universe.objects.get(objectName);
6325
7369
  if (!facts) {
@@ -6345,7 +7389,7 @@ function validateTranslationReferences(stack) {
6345
7389
  );
6346
7390
  continue;
6347
7391
  }
6348
- if (!isRec13(rawField)) continue;
7392
+ if (!isRec17(rawField)) continue;
6349
7393
  checkOptionKeys(findings, {
6350
7394
  optionMap: rawField.options,
6351
7395
  field,
@@ -6427,7 +7471,7 @@ function validateTranslationReferences(stack) {
6427
7471
  );
6428
7472
  continue;
6429
7473
  }
6430
- if (!isRec13(rawApp)) continue;
7474
+ if (!isRec17(rawApp)) continue;
6431
7475
  for (const navId of Object.keys(asRecord(rawApp.navigation))) {
6432
7476
  if (navIds.has(navId)) continue;
6433
7477
  orphan(
@@ -6450,7 +7494,7 @@ function validateTranslationReferences(stack) {
6450
7494
  );
6451
7495
  continue;
6452
7496
  }
6453
- if (!isRec13(rawDash)) continue;
7497
+ if (!isRec17(rawDash)) continue;
6454
7498
  for (const widgetId of Object.keys(asRecord(rawDash.widgets))) {
6455
7499
  if (dash.widgets.has(widgetId)) continue;
6456
7500
  orphan(
@@ -6475,7 +7519,7 @@ function validateTranslationReferences(stack) {
6475
7519
  return findings;
6476
7520
  }
6477
7521
  function asRecord(v) {
6478
- return isRec13(v) ? v : {};
7522
+ return isRec17(v) ? v : {};
6479
7523
  }
6480
7524
  function checkOptionKeys(findings, ctx) {
6481
7525
  const optionKeys = Object.keys(asRecord(ctx.optionMap));
@@ -6487,7 +7531,7 @@ function checkOptionKeys(findings, ctx) {
6487
7531
  rule: TRANSLATION_OPTION_KEY_UNKNOWN,
6488
7532
  where: ctx.where,
6489
7533
  path: ctx.path,
6490
- message: `Option translations are keyed under field "${ctx.fieldName}" of object "${ctx.objectName}", which declares no \`options\` at all (field type "${strName13(ctx.field.type) ?? "unknown"}"). Nothing reads this map.`,
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.`,
6491
7535
  hint: `Declare the options on the field, move the translations to the field that owns them, or drop them.`
6492
7536
  });
6493
7537
  return;
@@ -6506,11 +7550,11 @@ function checkOptionKeys(findings, ctx) {
6506
7550
  }
6507
7551
  }
6508
7552
  function checkActionParams(findings, ctx) {
6509
- const rawParams = Object.keys(asRecord(isRec13(ctx.rawAction) ? ctx.rawAction.params : void 0));
7553
+ const rawParams = Object.keys(asRecord(isRec17(ctx.rawAction) ? ctx.rawAction.params : void 0));
6510
7554
  if (rawParams.length === 0) return;
6511
7555
  const declared = /* @__PURE__ */ new Set();
6512
- for (const param of asArray35(ctx.action.params)) {
6513
- const name = strName13(param.name) ?? strName13(param.field);
7556
+ for (const param of asArray34(ctx.action.params)) {
7557
+ const name = strName15(param.name) ?? strName15(param.field);
6514
7558
  if (name) declared.add(name);
6515
7559
  }
6516
7560
  for (const paramName of rawParams) {
@@ -6528,76 +7572,40 @@ function checkActionParams(findings, ctx) {
6528
7572
 
6529
7573
  // src/validate-translatable-sections.ts
6530
7574
  var TRANSLATION_SECTION_NAME_MISSING = "translation-section-name-missing";
6531
- function isRec14(v) {
7575
+ function isRec18(v) {
6532
7576
  return !!v && typeof v === "object" && !Array.isArray(v);
6533
7577
  }
6534
- function strName14(v) {
7578
+ function strName16(v) {
6535
7579
  return typeof v === "string" && v.length > 0 ? v : void 0;
6536
7580
  }
6537
- function viewObjectName2(view) {
6538
- return strName14(view.objectName) ?? strName14(view.object) ?? (isRec14(view.data) ? strName14(view.data.object) : void 0);
6539
- }
6540
- function collectionEntries(v, base) {
6541
- if (Array.isArray(v)) {
6542
- const out = [];
6543
- for (let i = 0; i < v.length; i++) {
6544
- if (isRec14(v[i])) out.push({ rec: v[i], path: `${base}[${i}]` });
6545
- }
6546
- return out;
6547
- }
6548
- if (isRec14(v)) {
6549
- return Object.entries(v).filter(([, def]) => isRec14(def)).map(([name, def]) => ({ rec: { name, ...def }, path: `${base}.${name}` }));
6550
- }
6551
- return [];
6552
- }
6553
7581
  function viewLabel(view) {
6554
- const name = strName14(view.name);
7582
+ const name = strName16(view.name);
6555
7583
  return name ? `view "${name}"` : "";
6556
7584
  }
6557
7585
  function joinWhere(...parts) {
6558
7586
  return parts.filter((p) => p.length > 0).join(" \xB7 ");
6559
7587
  }
6560
7588
  function collectViewSites(view, basePath, label2, sites) {
6561
- const recordObject = viewObjectName2(view);
6562
- const listBinding = isRec14(view.list) ? viewObjectName2(view.list) ?? recordObject : void 0;
6563
- const bindingOf = (container) => viewObjectName2(container) ?? recordObject;
6564
- sites.push({
6565
- path: `${basePath}.sections`,
6566
- surface: label2,
6567
- objectName: recordObject ?? listBinding,
6568
- sections: view.sections
6569
- });
6570
- if (isRec14(view.form)) {
7589
+ const recordObject = viewObjectName(view);
7590
+ const listBinding = isRec18(view.list) ? viewObjectName(view.list) ?? recordObject : void 0;
7591
+ for (const site of viewContainerSites(view, basePath)) {
6571
7592
  sites.push({
6572
- path: `${basePath}.form.sections`,
6573
- surface: joinWhere(label2, "form"),
6574
- objectName: bindingOf(view.form) ?? listBinding,
6575
- sections: view.form.sections
7593
+ path: `${site.path}.sections`,
7594
+ surface: joinWhere(label2, site.surface),
7595
+ objectName: viewObjectName(site.view) ?? recordObject ?? listBinding,
7596
+ sections: site.view.sections
6576
7597
  });
6577
7598
  }
6578
- for (const key of ["listViews", "formViews"]) {
6579
- const container = view[key];
6580
- if (!isRec14(container)) continue;
6581
- for (const [subKey, sub] of Object.entries(container)) {
6582
- if (!isRec14(sub)) continue;
6583
- sites.push({
6584
- path: `${basePath}.${key}.${subKey}.sections`,
6585
- surface: joinWhere(label2, `${key}.${subKey}`),
6586
- objectName: bindingOf(sub) ?? listBinding,
6587
- sections: sub.sections
6588
- });
6589
- }
6590
- }
6591
7599
  }
6592
7600
  function translatedObjectNames(stack) {
6593
7601
  const out = /* @__PURE__ */ new Set();
6594
7602
  const bundles = Array.isArray(stack.translations) ? stack.translations : [];
6595
7603
  for (const bundle of bundles) {
6596
- if (!isRec14(bundle)) continue;
7604
+ if (!isRec18(bundle)) continue;
6597
7605
  for (const data of Object.values(bundle)) {
6598
- if (!isRec14(data) || !isRec14(data.objects)) continue;
7606
+ if (!isRec18(data) || !isRec18(data.objects)) continue;
6599
7607
  for (const [objectName, node] of Object.entries(data.objects)) {
6600
- if (isRec14(node)) out.add(objectName);
7608
+ if (isRec18(node)) out.add(objectName);
6601
7609
  }
6602
7610
  }
6603
7611
  }
@@ -6609,22 +7617,22 @@ function suggestedName(label2) {
6609
7617
  }
6610
7618
  function validateTranslatableSections(stack) {
6611
7619
  const findings = [];
6612
- if (!isRec14(stack)) return findings;
7620
+ if (!isRec18(stack)) return findings;
6613
7621
  const translated = translatedObjectNames(stack);
6614
7622
  if (translated.size === 0) return findings;
6615
7623
  const sites = [];
6616
7624
  for (const { rec: obj, path: objPath } of collectionEntries(stack.objects, "objects")) {
6617
- const objectName = strName14(obj.name);
7625
+ const objectName = strName16(obj.name);
6618
7626
  if (!objectName) continue;
6619
7627
  for (const { rec: view, path } of collectionEntries(obj.views, `${objPath}.views`)) {
6620
7628
  collectViewSites(
6621
- { ...view, object: strName14(view.object) ?? objectName },
7629
+ { ...view, object: strName16(view.object) ?? objectName },
6622
7630
  path,
6623
7631
  viewLabel(view),
6624
7632
  sites
6625
7633
  );
6626
7634
  }
6627
- if (isRec14(obj.listViews)) {
7635
+ if (isRec18(obj.listViews)) {
6628
7636
  collectViewSites({ object: objectName, listViews: obj.listViews }, objPath, "", sites);
6629
7637
  }
6630
7638
  }
@@ -6632,13 +7640,13 @@ function validateTranslatableSections(stack) {
6632
7640
  collectViewSites(view, path, viewLabel(view), sites);
6633
7641
  }
6634
7642
  for (const { rec: page, path: pagePath } of collectionEntries(stack.pages, "pages")) {
6635
- const pageName = strName14(page.name);
7643
+ const pageName = strName16(page.name);
6636
7644
  const pageLabel = pageName ? `page "${pageName}"` : "";
6637
7645
  for (const walked of walkPageComponents(page, pagePath)) {
6638
7646
  if (!walked.objectName) continue;
6639
- const props = isRec14(walked.component.properties) ? walked.component.properties : void 0;
7647
+ const props = isRec18(walked.component.properties) ? walked.component.properties : void 0;
6640
7648
  if (!props) continue;
6641
- const type = strName14(walked.component.type) ?? "component";
7649
+ const type = strName16(walked.component.type) ?? "component";
6642
7650
  sites.push({
6643
7651
  path: `${walked.path}.properties.sections`,
6644
7652
  surface: joinWhere(pageLabel, type),
@@ -6653,9 +7661,9 @@ function validateTranslatableSections(stack) {
6653
7661
  if (!Array.isArray(site.sections)) continue;
6654
7662
  for (let i = 0; i < site.sections.length; i++) {
6655
7663
  const section = site.sections[i];
6656
- if (!isRec14(section)) continue;
6657
- if (strName14(section.name)) continue;
6658
- const heading = strName14(section.label) ?? strName14(section.title);
7664
+ if (!isRec18(section)) continue;
7665
+ if (strName16(section.name)) continue;
7666
+ const heading = strName16(section.label);
6659
7667
  if (!heading) continue;
6660
7668
  const slug = suggestedName(heading);
6661
7669
  findings.push({
@@ -6673,14 +7681,14 @@ function validateTranslatableSections(stack) {
6673
7681
 
6674
7682
  // src/validate-ai-surface-affinity.ts
6675
7683
  var AI_SKILL_SURFACE_MISMATCH = "ai-skill-surface-mismatch";
6676
- function asArray36(v) {
7684
+ function asArray35(v) {
6677
7685
  if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
6678
7686
  if (v && typeof v === "object") {
6679
7687
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
6680
7688
  }
6681
7689
  return [];
6682
7690
  }
6683
- function strName15(v) {
7691
+ function strName17(v) {
6684
7692
  return typeof v === "string" && v.length > 0 ? v : void 0;
6685
7693
  }
6686
7694
  function surfaceOf(v) {
@@ -6690,18 +7698,18 @@ function validateAiSurfaceAffinity(stack) {
6690
7698
  const findings = [];
6691
7699
  if (!stack || typeof stack !== "object") return findings;
6692
7700
  const skillsByName = /* @__PURE__ */ new Map();
6693
- for (const skill of asArray36(stack.skills)) {
6694
- const n = strName15(skill.name);
7701
+ for (const skill of asArray35(stack.skills)) {
7702
+ const n = strName17(skill.name);
6695
7703
  if (n) skillsByName.set(n, skill);
6696
7704
  }
6697
- const agents = asArray36(stack.agents);
7705
+ const agents = asArray35(stack.agents);
6698
7706
  for (let ai = 0; ai < agents.length; ai++) {
6699
7707
  const agent = agents[ai];
6700
- const agentName = strName15(agent.name) ?? `#${ai}`;
7708
+ const agentName = strName17(agent.name) ?? `#${ai}`;
6701
7709
  const agentSurface = surfaceOf(agent.surface);
6702
7710
  const skillRefs = Array.isArray(agent.skills) ? agent.skills : [];
6703
7711
  for (let si = 0; si < skillRefs.length; si++) {
6704
- const ref = strName15(skillRefs[si]);
7712
+ const ref = strName17(skillRefs[si]);
6705
7713
  if (!ref) continue;
6706
7714
  const skill = skillsByName.get(ref);
6707
7715
  if (!skill) continue;
@@ -6723,14 +7731,14 @@ function validateAiSurfaceAffinity(stack) {
6723
7731
  // src/validate-ai-tool-references.ts
6724
7732
  var import_system5 = require("@objectstack/spec/system");
6725
7733
  var AI_SKILL_TOOL_UNRESOLVED = "ai-skill-tool-unresolved";
6726
- function asArray37(v) {
7734
+ function asArray36(v) {
6727
7735
  if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
6728
7736
  if (v && typeof v === "object") {
6729
7737
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
6730
7738
  }
6731
7739
  return [];
6732
7740
  }
6733
- function strName16(v) {
7741
+ function strName18(v) {
6734
7742
  return typeof v === "string" && v.length > 0 ? v : void 0;
6735
7743
  }
6736
7744
  function distance6(a, b) {
@@ -6771,26 +7779,26 @@ function materialisesAsTool(action) {
6771
7779
  if (!ai || typeof ai !== "object") return false;
6772
7780
  const aiRec = ai;
6773
7781
  if (aiRec.exposed !== true) return false;
6774
- if (!strName16(aiRec.description)) return false;
6775
- const type = strName16(action.type);
7782
+ if (!strName18(aiRec.description)) return false;
7783
+ const type = strName18(action.type);
6776
7784
  if (!type || !HEADLESS_ACTION_TYPES.has(type)) return false;
6777
7785
  if (type === "script") return Boolean(action.target || action.body);
6778
7786
  return Boolean(action.target);
6779
7787
  }
6780
7788
  function collectToolUniverse(stack) {
6781
7789
  const universe = new Set(import_system5.PLATFORM_PROVIDED_TOOL_NAMES);
6782
- for (const tool of asArray37(stack.tools)) {
6783
- const n = strName16(tool.name);
7790
+ for (const tool of asArray36(stack.tools)) {
7791
+ const n = strName18(tool.name);
6784
7792
  if (n) universe.add(n);
6785
7793
  }
6786
7794
  const addActionFamily = (actions) => {
6787
- for (const action of asArray37(actions)) {
6788
- const n = strName16(action.name);
7795
+ for (const action of asArray36(actions)) {
7796
+ const n = strName18(action.name);
6789
7797
  if (n && materialisesAsTool(action)) universe.add(`action_${n}`);
6790
7798
  }
6791
7799
  };
6792
7800
  addActionFamily(stack.actions);
6793
- for (const obj of asArray37(stack.objects)) {
7801
+ for (const obj of asArray36(stack.objects)) {
6794
7802
  addActionFamily(obj.actions);
6795
7803
  }
6796
7804
  return universe;
@@ -6798,13 +7806,13 @@ function collectToolUniverse(stack) {
6798
7806
  function collectUnexposedActionNames(stack) {
6799
7807
  const names = /* @__PURE__ */ new Set();
6800
7808
  const scan = (actions) => {
6801
- for (const action of asArray37(actions)) {
6802
- const n = strName16(action.name);
7809
+ for (const action of asArray36(actions)) {
7810
+ const n = strName18(action.name);
6803
7811
  if (n && !materialisesAsTool(action)) names.add(n);
6804
7812
  }
6805
7813
  };
6806
7814
  scan(stack.actions);
6807
- for (const obj of asArray37(stack.objects)) scan(obj.actions);
7815
+ for (const obj of asArray36(stack.objects)) scan(obj.actions);
6808
7816
  return names;
6809
7817
  }
6810
7818
  function validateAiToolReferences(stack) {
@@ -6822,13 +7830,13 @@ function validateAiToolReferences(stack) {
6822
7830
  }
6823
7831
  return universe.has(ref);
6824
7832
  };
6825
- const skills = asArray37(stack.skills);
7833
+ const skills = asArray36(stack.skills);
6826
7834
  for (let si = 0; si < skills.length; si++) {
6827
7835
  const skill = skills[si];
6828
- const skillName = strName16(skill.name) ?? `#${si}`;
7836
+ const skillName = strName18(skill.name) ?? `#${si}`;
6829
7837
  const refs = Array.isArray(skill.tools) ? skill.tools : [];
6830
7838
  for (let ti = 0; ti < refs.length; ti++) {
6831
- const ref = strName16(refs[ti]);
7839
+ const ref = strName18(refs[ti]);
6832
7840
  if (!ref || resolves(ref)) continue;
6833
7841
  const isPattern = ref.endsWith("*");
6834
7842
  const unexposed = !isPattern && ref.startsWith("action_") && unexposedActions.has(ref.slice("action_".length)) ? ref.slice("action_".length) : void 0;
@@ -6847,24 +7855,25 @@ function validateAiToolReferences(stack) {
6847
7855
 
6848
7856
  // src/validate-ai-agent-authoring.ts
6849
7857
  var AGENT_AUTHORING_WITHDRAWN = "agent-authoring-withdrawn";
6850
- function asArray38(v) {
7858
+ var DEFAULT_AGENT_OUTSIDE_ROSTER = "default-agent-outside-roster";
7859
+ function asArray37(v) {
6851
7860
  if (Array.isArray(v)) return v.filter((x) => !!x && typeof x === "object");
6852
7861
  if (v && typeof v === "object") {
6853
7862
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
6854
7863
  }
6855
7864
  return [];
6856
7865
  }
6857
- function strName17(v) {
7866
+ function strName19(v) {
6858
7867
  return typeof v === "string" && v.length > 0 ? v : void 0;
6859
7868
  }
6860
7869
  var PLATFORM_AGENT_NAMES = /* @__PURE__ */ new Set(["ask", "build", "data_chat", "metadata_assistant"]);
6861
7870
  function validateAiAgentAuthoring(stack) {
6862
7871
  const findings = [];
6863
7872
  if (!stack || typeof stack !== "object") return findings;
6864
- const agents = asArray38(stack.agents);
7873
+ const agents = asArray37(stack.agents);
6865
7874
  for (let ai = 0; ai < agents.length; ai++) {
6866
7875
  const agent = agents[ai];
6867
- const name = strName17(agent.name) ?? `#${ai}`;
7876
+ const name = strName19(agent.name) ?? `#${ai}`;
6868
7877
  const isPlatformName = PLATFORM_AGENT_NAMES.has(name);
6869
7878
  const skillCount = Array.isArray(agent.skills) ? agent.skills.length : 0;
6870
7879
  findings.push({
@@ -6876,25 +7885,41 @@ function validateAiAgentAuthoring(stack) {
6876
7885
  hint: isPlatformName ? `Remove the declaration; the platform owns "${name}". Extend it with skills instead.` : `Delete the agent and express its capability as skills. Everything an agent carried that a skill does not is persona text: move the useful parts of \`instructions\` into the skills' own instructions.` + (skillCount > 0 ? ` The ${skillCount} skill${skillCount === 1 ? "" : "s"} this agent references already carry the capability \u2014 they attach to the platform agent by \`surface\` affinity, so nothing is lost by dropping the persona.` : ``)
6877
7886
  });
6878
7887
  }
7888
+ const roster = [...PLATFORM_AGENT_NAMES].join(", ");
7889
+ const apps = asArray37(stack.apps);
7890
+ for (let appIdx = 0; appIdx < apps.length; appIdx++) {
7891
+ const app = apps[appIdx];
7892
+ const defaultAgent = strName19(app.defaultAgent);
7893
+ if (!defaultAgent || PLATFORM_AGENT_NAMES.has(defaultAgent)) continue;
7894
+ const appName = strName19(app.name) ?? `#${appIdx}`;
7895
+ findings.push({
7896
+ severity: "warning",
7897
+ rule: DEFAULT_AGENT_OUTSIDE_ROSTER,
7898
+ where: `app "${appName}".defaultAgent`,
7899
+ path: `apps[${appIdx}].defaultAgent`,
7900
+ message: `app "${appName}" pins \`defaultAgent\` to "${defaultAgent}", which is not in the platform agent roster (${roster}). The kernel ships exactly two agents (ADR-0063 \xA72) and resolves this key against them and their legacy aliases only \u2014 an unrecognized name is not rejected, it silently falls back to the platform default at runtime, so the pin has no effect and the value drifts from what actually serves the app.`,
7901
+ hint: `Set \`defaultAgent\` to one of the platform agent names: ${roster}. If the goal is a dedicated persona or capability, express it as skills instead \u2014 they attach to "ask" / "build" by surface affinity, not as a custom \`defaultAgent\` value.`
7902
+ });
7903
+ }
6879
7904
  return findings;
6880
7905
  }
6881
7906
 
6882
7907
  // src/validate-hook-body-writes.ts
6883
- var import_node_module4 = require("module");
7908
+ var import_node_module5 = require("module");
6884
7909
  var import_shared = require("@objectstack/spec/shared");
6885
- var import_meta4 = {};
6886
- var cachedTs2 = null;
6887
- function loadTypeScript2() {
6888
- if (cachedTs2) return cachedTs2;
6889
- const anchor = typeof import_meta4 !== "undefined" && import_meta4.url ? import_meta4.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
7910
+ var import_meta5 = {};
7911
+ var cachedTs3 = null;
7912
+ function loadTypeScript3() {
7913
+ if (cachedTs3) return cachedTs3;
7914
+ const anchor = typeof import_meta5 !== "undefined" && import_meta5.url ? import_meta5.url : typeof __filename !== "undefined" ? __filename : process.cwd() + "/";
6890
7915
  try {
6891
- cachedTs2 = (0, import_node_module4.createRequire)(anchor)("typescript");
7916
+ cachedTs3 = (0, import_node_module5.createRequire)(anchor)("typescript");
6892
7917
  } catch (err) {
6893
7918
  throw new Error(
6894
7919
  `@objectstack/lint: checking an L2 (language:'js') hook body requires the "typescript" 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 "typescript" in the image; it is only loaded when a hook with a JS body is validated.`
6895
7920
  );
6896
7921
  }
6897
- return cachedTs2;
7922
+ return cachedTs3;
6898
7923
  }
6899
7924
  var HOOK_BODY_WRITE_UNKNOWN_FIELD = "hook-body-write-unknown-field";
6900
7925
  var HOOK_BODY_WRITE_PATTERNS = [
@@ -6969,24 +7994,24 @@ var IMPLICIT_FIELDS2 = /* @__PURE__ */ new Set([
6969
7994
  "owner",
6970
7995
  "record_type"
6971
7996
  ]);
6972
- var isRec15 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
6973
- function asArray39(v) {
6974
- if (Array.isArray(v)) return v.filter((x) => isRec15(x));
6975
- if (isRec15(v)) {
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)) {
6976
8001
  return Object.entries(v).map(([name, def]) => ({
6977
8002
  name,
6978
- ...isRec15(def) ? def : {}
8003
+ ...isRec19(def) ? def : {}
6979
8004
  }));
6980
8005
  }
6981
8006
  return [];
6982
8007
  }
6983
8008
  function indexObjectFields2(stack) {
6984
8009
  const out = /* @__PURE__ */ new Map();
6985
- for (const obj of asArray39(stack.objects)) {
8010
+ for (const obj of asArray38(stack.objects)) {
6986
8011
  const name = typeof obj.name === "string" ? obj.name : void 0;
6987
8012
  if (!name) continue;
6988
8013
  const names = /* @__PURE__ */ new Set();
6989
- for (const f of asArray39(obj.fields)) {
8014
+ for (const f of asArray38(obj.fields)) {
6990
8015
  if (typeof f.name === "string" && f.name) names.add(f.name);
6991
8016
  }
6992
8017
  out.set(name, names);
@@ -7005,7 +8030,7 @@ function extractHookBodyWriteSet(source) {
7005
8030
  if (!/\bctx\b/.test(source) && !/\bObject\b/.test(source)) {
7006
8031
  return { writes: [], ctxRecordEscapes: false };
7007
8032
  }
7008
- const tsc = loadTypeScript2();
8033
+ const tsc = loadTypeScript3();
7009
8034
  const sf = tsc.createSourceFile(
7010
8035
  "hook-body.ts",
7011
8036
  `async function __body(ctx) {
@@ -7116,12 +8141,12 @@ ${source}
7116
8141
  }
7117
8142
  function validateHookBodyWrites(stack) {
7118
8143
  const findings = [];
7119
- const hooks = asArray39(stack.hooks);
8144
+ const hooks = asArray38(stack.hooks);
7120
8145
  if (hooks.length === 0) return findings;
7121
8146
  let objectFields = null;
7122
8147
  hooks.forEach((hook, hookIndex) => {
7123
8148
  const body = hook.body;
7124
- if (!isRec15(body) || body.language !== "js") return;
8149
+ if (!isRec19(body) || body.language !== "js") return;
7125
8150
  const source = body.source;
7126
8151
  if (typeof source !== "string" || source.trim() === "") return;
7127
8152
  const writes = extractHookBodyWrites(source).filter((w) => HOOK_APPLICABLE_IDS.has(w.patternId));
@@ -7202,13 +8227,13 @@ var ACTION_BODY_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_B
7202
8227
  var ACTION_RECORD_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_RECORD_WRITE_PATTERN_IDS.includes(p.id));
7203
8228
  var APPLICABLE_IDS = new Set(ACTION_BODY_WRITE_PATTERN_IDS);
7204
8229
  var RECORD_WRITE_IDS = new Set(ACTION_RECORD_WRITE_PATTERN_IDS);
7205
- var isRec16 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7206
- function asArray40(v) {
7207
- if (Array.isArray(v)) return v.filter((x) => isRec16(x));
7208
- if (isRec16(v)) {
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)) {
7209
8234
  return Object.entries(v).map(([name, def]) => ({
7210
8235
  name,
7211
- ...isRec16(def) ? def : {}
8236
+ ...isRec20(def) ? def : {}
7212
8237
  }));
7213
8238
  }
7214
8239
  return [];
@@ -7222,11 +8247,11 @@ function collectActionBodies(stack) {
7222
8247
  const sites = [];
7223
8248
  const seen = /* @__PURE__ */ new Set();
7224
8249
  const collect = (actions, pathPrefix, parentObject) => {
7225
- asArray40(actions).forEach((action, index) => {
8250
+ asArray39(actions).forEach((action, index) => {
7226
8251
  const type = typeof action.type === "string" ? action.type : "script";
7227
8252
  if (type !== "script") return;
7228
8253
  const body = action.body;
7229
- if (!isRec16(body) || body.language !== "js") return;
8254
+ if (!isRec20(body) || body.language !== "js") return;
7230
8255
  const source = body.source;
7231
8256
  if (typeof source !== "string" || source.trim() === "") return;
7232
8257
  const name = typeof action.name === "string" && action.name ? action.name : `#${index}`;
@@ -7237,7 +8262,7 @@ function collectActionBodies(stack) {
7237
8262
  });
7238
8263
  };
7239
8264
  collect(stack.actions, "actions");
7240
- asArray40(stack.objects).forEach((obj, objIndex) => {
8265
+ asArray39(stack.objects).forEach((obj, objIndex) => {
7241
8266
  const parentObject = typeof obj.name === "string" && obj.name ? obj.name : void 0;
7242
8267
  collect(obj.actions, `objects[${objIndex}].actions`, parentObject);
7243
8268
  });
@@ -7245,7 +8270,7 @@ function collectActionBodies(stack) {
7245
8270
  }
7246
8271
  function validateActionBodyWrites(stack) {
7247
8272
  const findings = [];
7248
- if (!isRec16(stack)) return findings;
8273
+ if (!isRec20(stack)) return findings;
7249
8274
  const sites = collectActionBodies(stack);
7250
8275
  if (sites.length === 0) return findings;
7251
8276
  let objectFields = null;
@@ -7304,13 +8329,13 @@ var import_shared3 = require("@objectstack/spec/shared");
7304
8329
  var FLOW_NODE_WRITE_UNKNOWN_FIELD = "flow-node-write-unknown-field";
7305
8330
  var FLOW_WRITE_NODE_TYPES = ["update_record", "create_record"];
7306
8331
  var FLOW_WRITE_NODE_TYPES_DEFERRED = [];
7307
- var isRec17 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7308
- function asArray41(v) {
7309
- if (Array.isArray(v)) return v.filter((x) => isRec17(x));
7310
- if (isRec17(v)) {
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)) {
7311
8336
  return Object.entries(v).map(([name, def]) => ({
7312
8337
  name,
7313
- ...isRec17(def) ? def : {}
8338
+ ...isRec21(def) ? def : {}
7314
8339
  }));
7315
8340
  }
7316
8341
  return [];
@@ -7323,8 +8348,8 @@ function readLiteralObjectName2(config) {
7323
8348
  var COVERED_TYPES = new Set(FLOW_WRITE_NODE_TYPES);
7324
8349
  function validateFlowNodeWrites(stack) {
7325
8350
  const findings = [];
7326
- if (!isRec17(stack)) return findings;
7327
- const flows = asArray41(stack.flows);
8351
+ if (!isRec21(stack)) return findings;
8352
+ const flows = asArray40(stack.flows);
7328
8353
  if (flows.length === 0) return findings;
7329
8354
  let objectFields = null;
7330
8355
  flows.forEach((flow, flowIndex) => {
@@ -7332,10 +8357,10 @@ function validateFlowNodeWrites(stack) {
7332
8357
  const walked = walkFlowNodes(flow, `flows[${flowIndex}]`);
7333
8358
  walked.forEach(({ node, path: nodePath, regionTrail }, walkIndex) => {
7334
8359
  if (typeof node.type !== "string" || !COVERED_TYPES.has(node.type)) return;
7335
- const config = isRec17(node.config) ? node.config : void 0;
8360
+ const config = isRec21(node.config) ? node.config : void 0;
7336
8361
  if (!config) return;
7337
8362
  const fields = config.fields;
7338
- if (!isRec17(fields)) return;
8363
+ if (!isRec21(fields)) return;
7339
8364
  const written = Object.keys(fields);
7340
8365
  if (written.length === 0) return;
7341
8366
  const objectName = readLiteralObjectName2(config);
@@ -7476,7 +8501,8 @@ function validateReferenceIntegrity(stack) {
7476
8501
 
7477
8502
  // src/lint-flow-patterns.ts
7478
8503
  var import_automation5 = require("@objectstack/spec/automation");
7479
- function asArray42(v) {
8504
+ var import_data8 = require("@objectstack/spec/data");
8505
+ function asArray41(v) {
7480
8506
  if (Array.isArray(v)) return v;
7481
8507
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
7482
8508
  return [];
@@ -7750,7 +8776,21 @@ function scanBranchRouting(at, nodes, edges, findings) {
7750
8776
  function filterCarriesNoCondition(filter) {
7751
8777
  if (filter === void 0 || filter === null) return true;
7752
8778
  if (typeof filter !== "object" || Array.isArray(filter)) return false;
7753
- return Object.keys(filter).length === 0;
8779
+ return (0, import_data8.reduceFilterVerdict)(filter) === "true";
8780
+ }
8781
+ function describeUnboundedFilter(filter) {
8782
+ if (filter === void 0 || filter === null) return "no `filter` key";
8783
+ if (Object.keys(filter).length === 0) return "an EMPTY `filter`";
8784
+ return `a \`filter\` that REDUCES TO TRUE (\`${previewFilter(filter)}\`)`;
8785
+ }
8786
+ function previewFilter(filter) {
8787
+ try {
8788
+ const json = JSON.stringify(filter);
8789
+ if (typeof json !== "string") return typeof filter;
8790
+ return json.length > 80 ? `${json.slice(0, 77)}...` : json;
8791
+ } catch {
8792
+ return typeof filter;
8793
+ }
7754
8794
  }
7755
8795
  function scanUnboundedBulkWrites(at, nodes, findings) {
7756
8796
  for (const node of nodes) {
@@ -7761,10 +8801,10 @@ function scanUnboundedBulkWrites(at, nodes, findings) {
7761
8801
  if (cfg.multi !== true) continue;
7762
8802
  if (!filterCarriesNoCondition(cfg.filter)) continue;
7763
8803
  const objectName = typeof cfg.objectName === "string" && cfg.objectName ? cfg.objectName : "(unnamed object)";
7764
- const filterState = cfg.filter === void 0 || cfg.filter === null ? "no `filter` key" : "an EMPTY `filter`";
8804
+ const filterState = describeUnboundedFilter(cfg.filter);
7765
8805
  findings.push({
7766
8806
  where: `${at} \xB7 node '${String(node.id)}' (${nodeType})`,
7767
- message: `declares \`multi: true\` with ${filterState} \u2014 this is a WHOLE-OBJECT write, by declaration: every row of '${objectName}' is ${consequence2.verb} on every run. The executor forwards \`where: {}\` plus the bulk intent, ${consequence2.dispatchNote}, and it lands on \`${consequence2.engineCall}\` with no predicate. Nothing refuses it at run time, so the only feedback is the step's \`acted\` row count \u2014 reported AFTER the rows are gone.`,
8807
+ message: `declares \`multi: true\` with ${filterState} \u2014 this is a WHOLE-OBJECT write, by declaration: every row of '${objectName}' is ${consequence2.verb} on every run. The executor forwards the filter as \`where\` (an absent key becomes \`{}\`) plus the bulk intent, ${consequence2.dispatchNote}, and it lands on \`${consequence2.engineCall}\` bounded by nothing \u2014 a filter that reduces to TRUE constrains no row. Nothing refuses it at run time, so the only feedback is the step's \`acted\` row count \u2014 reported AFTER the rows are gone.`,
7768
8808
  hint: `Write the constraint you mean into \`filter\` (e.g. \`{ status: 'closed' }\` \u2014 see examples/app-showcase \`showcase_inquiry_purge\`, bulk intent bounded by a predicate). If emptying '${objectName}' really is the intent, keep it: this is a warning, not a gate, and the run-time path stays open. Distinct from the #3810 erased-condition guard, which REFUSES this node at run time when a condition you WROTE interpolated to nothing \u2014 that guard is keyed on "a written condition is gone" and deliberately not on "the filter is empty", which is the fact this rule judges at authoring time. (#5482, #5393)`,
7769
8809
  // Warning, not `error`: see the severity policy at the top of this file.
7770
8810
  // The shape has a legitimate reading the engine grants on purpose, so it is
@@ -7846,7 +8886,7 @@ function scanApprovalReviseLoops(at, nodes, edges, findings) {
7846
8886
  }
7847
8887
  function lintFlowPatterns(stack) {
7848
8888
  const findings = [];
7849
- for (const flow of asArray42(stack.flows)) {
8889
+ for (const flow of asArray41(stack.flows)) {
7850
8890
  const flowName = typeof flow.name === "string" ? flow.name : "(unnamed flow)";
7851
8891
  const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
7852
8892
  const edges = Array.isArray(flow.edges) ? flow.edges : [];
@@ -7938,20 +8978,20 @@ function lintFlowPatterns(stack) {
7938
8978
  }
7939
8979
 
7940
8980
  // src/lint-liveness-properties.ts
7941
- var import_node_module5 = require("module");
8981
+ var import_node_module6 = require("module");
7942
8982
  var import_node_path = require("path");
7943
8983
  var import_node_fs = require("fs");
7944
- var import_meta5 = {};
8984
+ var import_meta6 = {};
7945
8985
  var LIVENESS_DEAD_PROPERTY = "liveness-dead-property";
7946
8986
  var LIVENESS_EXPERIMENTAL_PROPERTY = "liveness-experimental-property";
7947
- function asArray43(v) {
8987
+ function asArray42(v) {
7948
8988
  if (Array.isArray(v)) return v;
7949
8989
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
7950
8990
  return [];
7951
8991
  }
7952
8992
  function resolveLivenessDir() {
7953
8993
  try {
7954
- const require2 = (0, import_node_module5.createRequire)(import_meta5.url);
8994
+ const require2 = (0, import_node_module6.createRequire)(import_meta6.url);
7955
8995
  const pkgJson = require2.resolve("@objectstack/spec/package.json");
7956
8996
  const dir = (0, import_node_path.join)((0, import_node_path.dirname)(pkgJson), "liveness");
7957
8997
  return (0, import_node_fs.existsSync)(dir) ? dir : null;
@@ -8066,6 +9106,13 @@ var TYPE_COLLECTIONS = [
8066
9106
  // checks every widget on the dashboard. Registering it here is not optional
8067
9107
  // bookkeeping: without it the ledger would be newly correct and newly
8068
9108
  // silent, which is the shape this lint exists to prevent.
9109
+ //
9110
+ // As of #6774 the dashboard ledger warns on NOTHING — four of those five were
9111
+ // retired in 17.0.0 (#5010) and `colorVariant` went `live` when objectui#3799
9112
+ // gave it a renderer. The type STAYS listed, the resolved state `webhook` and
9113
+ // `email_template` already sit in: a zero-warn entry costs one empty map
9114
+ // lookup, and it means a future regression that re-deadens a widget key warns
9115
+ // on its own instead of waiting for someone to notice this list again.
8069
9116
  { type: "dashboard", key: "dashboards" }
8070
9117
  ];
8071
9118
  function lintLivenessProperties(stack) {
@@ -8074,11 +9121,11 @@ function lintLivenessProperties(stack) {
8074
9121
  const findings = [];
8075
9122
  const objectWarn = loadWarnMap(dir, "object");
8076
9123
  const fieldWarn = loadWarnMap(dir, "field");
8077
- for (const obj of asArray43(stack.objects)) {
9124
+ for (const obj of asArray42(stack.objects)) {
8078
9125
  const objName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
8079
9126
  if (objectWarn.size > 0) checkItem("object", obj, `object '${objName}'`, objectWarn, findings);
8080
9127
  if (fieldWarn.size > 0) {
8081
- for (const field of asArray43(obj.fields)) {
9128
+ for (const field of asArray42(obj.fields)) {
8082
9129
  const fieldName = typeof field.name === "string" ? field.name : "(unnamed field)";
8083
9130
  checkItem("field", field, `object '${objName}' \xB7 field '${fieldName}'`, fieldWarn, findings);
8084
9131
  }
@@ -8087,7 +9134,7 @@ function lintLivenessProperties(stack) {
8087
9134
  for (const { type, key } of TYPE_COLLECTIONS) {
8088
9135
  const warnMap = loadWarnMap(dir, type);
8089
9136
  if (warnMap.size === 0) continue;
8090
- for (const item of asArray43(stack[key])) {
9137
+ for (const item of asArray42(stack[key])) {
8091
9138
  const name = typeof item.name === "string" ? item.name : typeof item.object === "string" ? item.object : `(unnamed ${type})`;
8092
9139
  checkItem(type, item, `${type} '${name}'`, warnMap, findings);
8093
9140
  }
@@ -8096,12 +9143,12 @@ function lintLivenessProperties(stack) {
8096
9143
  }
8097
9144
 
8098
9145
  // src/lint-autonumber-formats.ts
8099
- var import_data7 = require("@objectstack/spec/data");
9146
+ var import_data9 = require("@objectstack/spec/data");
8100
9147
  var AUTONUMBER_UNKNOWN_FIELD = "autonumber-references-unknown-field";
8101
9148
  var AUTONUMBER_OPTIONAL_FIELD = "autonumber-references-optional-field";
8102
9149
  var AUTONUMBER_SELF_REFERENCE = "autonumber-references-self";
8103
9150
  var AUTONUMBER_LITERAL_TOKEN = "autonumber-unrecognized-token";
8104
- function asArray44(v) {
9151
+ function asArray43(v) {
8105
9152
  if (Array.isArray(v)) return v;
8106
9153
  if (v && typeof v === "object") {
8107
9154
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -8110,9 +9157,9 @@ function asArray44(v) {
8110
9157
  }
8111
9158
  function lintAutonumberFormats(stack) {
8112
9159
  const findings = [];
8113
- for (const obj of asArray44(stack.objects)) {
9160
+ for (const obj of asArray43(stack.objects)) {
8114
9161
  const objectName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
8115
- const fields = asArray44(obj.fields);
9162
+ const fields = asArray43(obj.fields);
8116
9163
  const fieldMeta = /* @__PURE__ */ new Map();
8117
9164
  for (const f of fields) {
8118
9165
  if (typeof f.name === "string") fieldMeta.set(f.name, { required: f.required === true });
@@ -8122,8 +9169,8 @@ function lintAutonumberFormats(stack) {
8122
9169
  const name = typeof f.name === "string" ? f.name : "(unnamed field)";
8123
9170
  const fmt = typeof f.autonumberFormat === "string" ? f.autonumberFormat : typeof f.format === "string" ? f.format : "";
8124
9171
  if (!fmt) continue;
8125
- const tokens = (0, import_data7.parseAutonumberFormat)(fmt);
8126
- const refs = (0, import_data7.referencedFields)(tokens);
9172
+ const tokens = (0, import_data9.parseAutonumberFormat)(fmt);
9173
+ const refs = (0, import_data9.referencedFields)(tokens);
8127
9174
  const where = `object '${objectName}' \xB7 field '${name}' (autonumber "${fmt}")`;
8128
9175
  for (const t of tokens) {
8129
9176
  if (t.kind !== "literal") continue;
@@ -8177,8 +9224,8 @@ function lintAutonumberFormats(stack) {
8177
9224
  }
8178
9225
 
8179
9226
  // src/lint-view-refs.ts
8180
- var import_spec3 = require("@objectstack/spec");
8181
- function asArray45(v) {
9227
+ var import_spec5 = require("@objectstack/spec");
9228
+ function asArray44(v) {
8182
9229
  if (Array.isArray(v)) return v;
8183
9230
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
8184
9231
  return [];
@@ -8206,16 +9253,16 @@ function lintViewRefs(stack) {
8206
9253
  s.add(kind);
8207
9254
  };
8208
9255
  const containers = [];
8209
- for (const v of asArray45(stack.views)) {
9256
+ for (const v of asArray44(stack.views)) {
8210
9257
  if (v.viewKind) {
8211
9258
  if (typeof v.name === "string") indexKind(v.name, v.viewKind === "form" ? "form" : "list");
8212
9259
  continue;
8213
9260
  }
8214
- if (!(0, import_spec3.isAggregatedViewContainer)(v)) continue;
9261
+ if (!(0, import_spec5.isAggregatedViewContainer)(v)) continue;
8215
9262
  const object = viewContainerObjectName(v);
8216
9263
  if (object) containers.push({ object, container: v });
8217
9264
  }
8218
- for (const obj of asArray45(stack.objects)) {
9265
+ for (const obj of asArray44(stack.objects)) {
8219
9266
  const object = typeof obj.name === "string" ? obj.name : void 0;
8220
9267
  if (!object) continue;
8221
9268
  if (obj.list || obj.form || obj.listViews || obj.formViews) {
@@ -8223,7 +9270,7 @@ function lintViewRefs(stack) {
8223
9270
  }
8224
9271
  }
8225
9272
  for (const { object, container } of containers) {
8226
- const { items, collisions } = (0, import_spec3.expandViewContainerWithDiagnostics)(object, container);
9273
+ const { items, collisions } = (0, import_spec5.expandViewContainerWithDiagnostics)(object, container);
8227
9274
  for (const it of items) indexKind(it.name, it.viewKind);
8228
9275
  for (const col of collisions) {
8229
9276
  findings.push({
@@ -8269,11 +9316,11 @@ function lintViewRefs(stack) {
8269
9316
  });
8270
9317
  }
8271
9318
  };
8272
- for (const obj of asArray45(stack.objects)) {
9319
+ for (const obj of asArray44(stack.objects)) {
8273
9320
  const object = typeof obj.name === "string" ? obj.name : void 0;
8274
- for (const action of asArray45(obj.actions)) checkAction(action, object);
9321
+ for (const action of asArray44(obj.actions)) checkAction(action, object);
8275
9322
  }
8276
- for (const action of asArray45(stack.actions)) checkAction(action);
9323
+ for (const action of asArray44(stack.actions)) checkAction(action);
8277
9324
  return findings;
8278
9325
  }
8279
9326
 
@@ -8461,13 +9508,14 @@ function lintDataModel(objects) {
8461
9508
  if (!obj?.name) continue;
8462
9509
  const objPath = `objects[${i}]`;
8463
9510
  const fields = fieldEntries2(obj.fields);
8464
- const hasNameField = !!obj.primaryField || !!obj.titleFormat || fields.some((f) => NAME_LIKE_FIELDS.includes(f.name));
9511
+ const hasNameField = !!obj.nameField || fields.some((f) => NAME_LIKE_FIELDS.includes(f.name));
8465
9512
  if (fields.length > 0 && !hasNameField) {
8466
9513
  issues.push({
8467
9514
  severity: "suggestion",
8468
9515
  rule: "object/missing-name-field",
8469
- message: `Object "${obj.name}" has no name/title field or primaryField \u2014 records will display as raw IDs`,
8470
- path: `${objPath}.fields`
9516
+ message: `Object "${obj.name}" has no nameField and no name-like field \u2014 records will display as raw IDs`,
9517
+ path: `${objPath}.fields`,
9518
+ fix: `Set \`nameField: '<field>'\` \u2014 ADR-0079's canonical primary-title pointer \u2014 to a stored text/autonumber field, or to a formula field with \`returnType: 'text'\` for a composite title. A \`titleFormat\` template does NOT count: it is retired (ADR-0079) and render-only, so the server can neither return nor query the title it renders.`
8471
9519
  });
8472
9520
  }
8473
9521
  for (const { name: fieldName, def } of fields) {
@@ -8576,6 +9624,7 @@ var CLI_ONLY = ["cli"];
8576
9624
  var CLI_AND_RUNTIME = ["cli", "runtime-publish"];
8577
9625
  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.";
8578
9626
  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.";
8579
9628
  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.";
8580
9629
  var EXPRESSION_INVALID = "expression-invalid";
8581
9630
  var AUTHORING_RULES = [
@@ -8605,8 +9654,12 @@ var AUTHORING_RULES = [
8605
9654
  }))
8606
9655
  },
8607
9656
  // ADR-0053 — `userFilters`/`quickFilters` on an object list view ("views"
8608
- // mode) are silently dropped: `ObjectListViewSchema` omits them, so this must
8609
- // read the pre-parse tier or the evidence is already gone.
9657
+ // mode). NOT "silently dropped" any more: since #4001 `ObjectListViewSchema`
9658
+ // is strict and refuses `quickFilters` by name, and `ObjectUserFiltersSchema`
9659
+ // refuses `element: 'tabs'` by enum — measured under #6073, `defineStack`
9660
+ // THROWS on both. `normalized` here therefore means "needs no parsed stack"
9661
+ // (so `os lint`, which never parses, can run it), not "sees evidence the
9662
+ // parse would have eaten".
8610
9663
  {
8611
9664
  name: "validateListViewMode",
8612
9665
  tier: "gating",
@@ -8637,9 +9690,13 @@ var AUTHORING_RULES = [
8637
9690
  surfaceReason: RUNTIME_OBJECT_WRITES_P2,
8638
9691
  run: (stack) => validateFunctionalCompleteness(stack)
8639
9692
  },
8640
- // A flat list-view object in `views: []` parses to an EMPTY container
8641
- // (ViewSchema strips unknown keys): the schema step passes, zero views
8642
- // register, and the Console renders nothing. Pre-parse for the same reason.
9693
+ // A view container in `views: []` that registers zero views: nothing appears
9694
+ // in the Console, and the schema step cannot tell it from an intentionally
9695
+ // empty one. The FLAT-list-view arm no longer needs this tier — `ViewSchema`
9696
+ // went strict at #4001, so `defineStack` now REFUSES `{ name, type, columns,
9697
+ // data }` by name with the wrap-it hint (measured under #6073); the arm that
9698
+ // still needs a rule is the all-slots-empty container, whose keys are all
9699
+ // declared and which survives the parse untouched.
8643
9700
  {
8644
9701
  name: "validateViewContainers",
8645
9702
  tier: "gating",
@@ -8689,6 +9746,31 @@ var AUTHORING_RULES = [
8689
9746
  surfaceReason: RUNTIME_NEEDS_FULL_SNAPSHOT,
8690
9747
  run: (stack) => validateFilterTokens(stack)
8691
9748
  },
9749
+ // #5330 — the LITERAL empty combinators (`$and: []`, `$or: []`, `$not: {}`,
9750
+ // `{}`). #5322 ruled their RUNTIME meaning to be the boolean identity, and
9751
+ // this rule does not touch it: it refuses the literal SPELLINGS at authoring
9752
+ // time with a per-shape prescription, which is Prime Directive #12's standard
9753
+ // shape (reject at the producer, never tolerate at the consumer) and #5240's
9754
+ // same-direction precedent one shape over.
9755
+ {
9756
+ name: "validateEmptyCombinators",
9757
+ tier: "gating",
9758
+ input: "parsed",
9759
+ commands: ALL,
9760
+ source: "packages/lint/src/validate-empty-combinators.ts",
9761
+ // The one type #4463's P1 slice opened, and the one this rule most needs:
9762
+ // a flow CRUD node's `config.filter` is where an empty combinator has the
9763
+ // largest blast radius, and the write path is the only door an AI author
9764
+ // uses. This rule needs NO resolution context at all — it judges the filter
9765
+ // literal in isolation — so RUNTIME_NEEDS_FULL_SNAPSHOT does not apply to
9766
+ // it, and widening to the other filter-carrying types (`object`, `view`,
9767
+ // `page`, `dashboard`) is a one-line `runtimeTypes` edit once #4463 P2
9768
+ // opens them at the gate. Making that call here would widen the gate's
9769
+ // dispatch surface on this rule's authority, which is P2's decision.
9770
+ surfaces: CLI_AND_RUNTIME,
9771
+ runtimeTypes: ["flow"],
9772
+ run: (stack) => validateEmptyCombinators(stack)
9773
+ },
8692
9774
  // The reference-integrity suite (#3583 §5 D5) — itself a registry, of the
8693
9775
  // rules that answer "does this name resolve to anything?". It reached all
8694
9776
  // three commands before this file existed; it is an entry here so the two
@@ -8737,6 +9819,16 @@ var AUTHORING_RULES = [
8737
9819
  // `displayField` (#5775) — so gating today would fail the platform's own pages
8738
9820
  // to enforce declarations the platform does not keep. The error upgrade is a
8739
9821
  // separate step, once the warning-period inventory is empty.
9822
+ //
9823
+ // #5775 settled the record picker's half: `displayField` is retired in favour
9824
+ // of the `labelField` the renderer actually reads. Its claim that "the rest of
9825
+ // the keys the renderers honour are declared" did NOT hold — #6776 found five
9826
+ // more (`page:header` `recordChrome`/`showStar`/`showCopyId`,
9827
+ // `page:accordion.variant`, and the tab strip's visual style, whose declared
9828
+ // spelling `page:tabs.type` collided with the component node's own dispatch
9829
+ // key and so was unauthorable in the flat and JSX carriers). All five are
9830
+ // declared as of #6776, the last as the renamed `tabStyle`. What remains
9831
+ // before the error upgrade is #5728 and two page rewrites.
8740
9832
  {
8741
9833
  name: "validateComponentProps",
8742
9834
  tier: "advisory",
@@ -8815,10 +9907,12 @@ var AUTHORING_RULES = [
8815
9907
  //
8816
9908
  // `gating` since #5762, which reviewed the file's rules as one family and
8817
9909
  // split them on a single question: is THIS STACK enough to know the flow is
8818
- // dead? Three rules answer yes and now emit `error` — a `config.timeRelative`
9910
+ // dead? Four rules answer yes and emit `error` — a `config.timeRelative`
8819
9911
  // the spec's own `TimeRelativeTriggerSchema` refuses, one the engine's routing
8820
- // predicate cannot route at all, and a `record-*` triggerType outside the
8821
- // closed token grammar `triggerTypeToHookEvents` maps. None of those verdicts
9912
+ // predicate cannot route at all, a `record-*` triggerType outside the
9913
+ // closed token grammar `triggerTypeToHookEvents` maps, and (#6637) a
9914
+ // `type: 'record_change'` flow whose triggerType the engine's binding resolver
9915
+ // routes nowhere, silently demoting it to a manual flow. None of those verdicts
8822
9916
  // can be changed by installing a package, so there is no reading under which
8823
9917
  // the flow fires. `flow-trigger-unknown-object` deliberately stayed `warning`
8824
9918
  // (the object may come from another installed package — a hedge this rule
@@ -8945,12 +10039,39 @@ var AUTHORING_RULES = [
8945
10039
  surfaceReason: RUNTIME_NEEDS_FULL_SNAPSHOT,
8946
10040
  run: (stack) => validateSeedStateMachine(stack)
8947
10041
  },
8948
- // ADR-0089 D3b — deprecated visibility aliases and a mis-layered binding root.
8949
- // Pre-parse: the schema folds `visibleOn`/`visibility` into `visibleWhen`
8950
- // during parse, so the alias the author wrote is gone from `result.data`.
10042
+ // ADR-0089 D3b — a mis-layered binding root, plus (#6128) the bare-identifier
10043
+ // gate and (#6253) the syntax gate. This entry used to read "pre-parse: the
10044
+ // schema folds `visibleOn`/`visibility` into `visibleWhen` during parse, so
10045
+ // the alias the author wrote is gone from `result.data`". Measured false at
10046
+ // #6073: the ADR-0087 D2 conversions do that fold INSIDE
10047
+ // `normalizeStackInput`, one layer BEFORE this tier, so on every spec-valid
10048
+ // alias site the alias-KEY rule reported zero here too.
10049
+ //
10050
+ // #6318 closed that: `visibility-alias-deprecated` was RETIRED rather than
10051
+ // re-anchored. Re-anchoring would have had to move this entry's input to a
10052
+ // pre-`normalizeStackInput` value that `runAuthoringRules` does not accept —
10053
+ // a change to this package's external input contract, and the maintainer's
10054
+ // call, not a rule file's. Retirement is ADR-0049 (declared ≠ enforced) and
10055
+ // costs no author a signal: the same D2 conversion already shouts through
10056
+ // `warnConversionNotice` in `defineStack`, naming the site, the conversion and
10057
+ // the protocol-16 retirement window — better wording than the rule ever had.
10058
+ //
10059
+ // Every rule left in the family judges the predicate's VALUE, and the value
10060
+ // moves into `visibleWhen` intact, so all three report normally on this tier.
10061
+ // The tier therefore stays `normalized` on its SURVIVING justification (a
10062
+ // finding still reaches the author when an unrelated schema error would stop
10063
+ // the parse — see `AuthoringRuleInputTier`), never on the retired
10064
+ // "pre-parse evidence" one.
10065
+ //
10066
+ // `gating` since #6128: `visibility-bare-identifier` emits `error`. The two
10067
+ // ADR-0089 rules stay advisory findings within it — the tier is a property of
10068
+ // the RULE FUNCTION (can it emit `error`?), and the per-finding severity is
10069
+ // what decides whether any given diagnostic gates, exactly as `lintFlowPatterns`
10070
+ // has worked since #3760. The promotion follows the #5762 precedent: a family
10071
+ // that gains an `error` finding moves its registry tier in the same edit.
8951
10072
  {
8952
10073
  name: "validateVisibilityPredicates",
8953
- tier: "advisory",
10074
+ tier: "gating",
8954
10075
  input: "normalized",
8955
10076
  commands: ALL,
8956
10077
  source: "packages/lint/src/validate-visibility-predicates.ts",
@@ -8958,6 +10079,30 @@ var AUTHORING_RULES = [
8958
10079
  surfaceReason: RUNTIME_NEEDS_FULL_SNAPSHOT,
8959
10080
  run: (stack) => validateVisibilityPredicates(stack)
8960
10081
  },
10082
+ // #7010 — the same predicate surface, one question further in. The three
10083
+ // ADR-0089 D3b rules above judge a predicate's SHAPE (does it parse, is it
10084
+ // rooted, is the root right for the layer) and never open the target schema,
10085
+ // so `data.tpye == 'formula'` passes all three and still resolves to nothing.
10086
+ // This rule resolves the PATH against the schema the form edits — the closed
10087
+ // `getMetadataTypeSchema` key set — and is therefore immune to the CEL
10088
+ // type-name blind spot that made #6248's gate structurally unable to catch
10089
+ // #6254's 16 bare `type ==` predicates.
10090
+ //
10091
+ // Scoped to schema-bound forms (`data: { provider: 'schema', schemaId }`);
10092
+ // the `record.*` layer is deliberately out of scope because an ObjectQL
10093
+ // object's addressable path set is NOT closed (lookup traversal, system
10094
+ // columns, formula outputs), and an `error` gate over an open set generates
10095
+ // false build errors. See the rule's module note.
10096
+ {
10097
+ name: "validatePredicatePathRefs",
10098
+ tier: "gating",
10099
+ input: "normalized",
10100
+ commands: ALL,
10101
+ source: "packages/lint/src/validate-predicate-path-refs.ts",
10102
+ surfaces: CLI_ONLY,
10103
+ surfaceReason: RUNTIME_VISIBILITY_FAMILY_IS_CLI_ONLY,
10104
+ run: (stack) => validatePredicatePathRefs(stack)
10105
+ },
8961
10106
  // #1874 — flow authoring anti-patterns. Advisory by default; a finding marked
8962
10107
  // `error` gates. Three do today: `flow-runas-unscoped` (#3760 — metadata the
8963
10108
  // runtime REFUSES to execute), plus `flow-branch-label-unmatched` and
@@ -9354,10 +10499,13 @@ function runRuntimeAuthoringRules(args) {
9354
10499
  DASHBOARD_ACTION_ROUTE_UNRESOLVED,
9355
10500
  DASHBOARD_ACTION_TARGET_UNDEFINED,
9356
10501
  DASHBOARD_FILTER_FIELD_UNKNOWN,
10502
+ DEFAULT_AGENT_OUTSIDE_ROSTER,
9357
10503
  EXPRESSION_INVALID,
9358
10504
  FIELD_GROUP_EMPTY,
9359
10505
  FIELD_GROUP_SHADOWED,
9360
10506
  FIELD_GROUP_UNDECLARED,
10507
+ FILTER_EMPTY_COMBINATOR,
10508
+ FILTER_EMPTY_NODE,
9361
10509
  FILTER_TOKEN_UNKNOWN,
9362
10510
  FLOW_APPROVAL_REVISE_DEAD_END,
9363
10511
  FLOW_APPROVAL_REVISE_DISABLED,
@@ -9384,6 +10532,7 @@ function runRuntimeAuthoringRules(args) {
9384
10532
  FLOW_TIME_RELATIVE_DESCRIPTOR_UNROUTABLE,
9385
10533
  FLOW_TRIGGER_UNKNOWN_EVENT,
9386
10534
  FLOW_TRIGGER_UNKNOWN_OBJECT,
10535
+ FLOW_TRIGGER_UNROUTABLE,
9387
10536
  FLOW_UPDATE_READONLY_FIELD,
9388
10537
  FLOW_UPDATE_READONLY_WHEN_FIELD,
9389
10538
  FLOW_WRITE_NODE_TYPES,
@@ -9404,19 +10553,25 @@ function runRuntimeAuthoringRules(args) {
9404
10553
  NULL_GUARD_HINT,
9405
10554
  OBJECT_REFERENCE_UNKNOWN,
9406
10555
  OBJECT_REFERENCE_UNREGISTERED_PLATFORM,
10556
+ OPEN_VOCABULARY_PROBES,
9407
10557
  ORG_AXIS_CROSS_ORG_BU_GRANT,
9408
10558
  ORG_AXIS_PERMISSION_INHERITANCE,
9409
10559
  PAGE_FIELD_UNKNOWN,
9410
10560
  PAGE_SOURCE_CLASSNAME,
10561
+ PREDICATE_PATH_UNRESOLVED,
10562
+ PREDICATE_PATH_UNROOTED,
10563
+ PRE_SEAL_PHASES,
9411
10564
  REACT_BLOCK_NEEDS_RECORD_CONTEXT,
9412
10565
  REACT_CHART_AGGREGATE_INVALID,
9413
10566
  REACT_CHART_AXIS_UNKNOWN,
9414
10567
  REACT_CHART_DRILLDOWN_INVALID,
9415
10568
  REACT_CHART_FIELD_UNKNOWN,
9416
10569
  REFERENCE_INTEGRITY_RULES,
10570
+ RLS_PREDICATE_OVER_BUDGET,
9417
10571
  RLS_PREDICATE_UNENFORCEABLE,
9418
10572
  RLS_PREDICATE_UNPARSEABLE,
9419
10573
  RUNTIME_AJV_OPTIONS,
10574
+ SEAL_MARKERS,
9420
10575
  SEARCHABLE_FIELD_UNKNOWN,
9421
10576
  SEARCHABLE_FIELD_UNSEARCHABLE,
9422
10577
  SECURITY_ANCHOR_HIGH_PRIVILEGE,
@@ -9436,6 +10591,9 @@ function runRuntimeAuthoringRules(args) {
9436
10591
  SEMANTIC_ROLE_FIELD_UNKNOWN,
9437
10592
  SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
9438
10593
  SHARING_RULE_UNLOWERABLE_CONDITION,
10594
+ STARTUP_OPEN_VOCABULARY_VERDICT,
10595
+ STARTUP_VERDICT_ASSERTIVE_WORDING,
10596
+ STARTUP_VERDICT_HINT,
9439
10597
  STYLE_CLASSNAME_TAILWIND,
9440
10598
  STYLE_NODE_MISSING_ID,
9441
10599
  STYLE_RESPONSIVE_NO_BASE,
@@ -9457,7 +10615,9 @@ function runRuntimeAuthoringRules(args) {
9457
10615
  VIEW_KEY_COLLISION,
9458
10616
  VIEW_REF_FORM_TARGET_KIND,
9459
10617
  VIEW_REF_FORM_TARGET_MISSING,
9460
- VISIBILITY_ALIAS_DEPRECATED,
10618
+ VISIBILITY_BARE_IDENTIFIER,
10619
+ VISIBILITY_PREDICATE_OVER_BUDGET,
10620
+ VISIBILITY_PREDICATE_SYNTAX,
9461
10621
  VISIBILITY_ROOT_MISLAYERED,
9462
10622
  WIDGET_DATASET_UNKNOWN,
9463
10623
  WIDGET_DIMENSION_UNKNOWN,
@@ -9469,6 +10629,7 @@ function runRuntimeAuthoringRules(args) {
9469
10629
  diffAccessMatrix,
9470
10630
  extractHookBodyWriteSet,
9471
10631
  extractHookBodyWrites,
10632
+ findStartupRegistryVerdicts,
9472
10633
  findUnguardedNullableOperands,
9473
10634
  isSourceAuthoredPage,
9474
10635
  lintAutonumberFormats,
@@ -9498,6 +10659,7 @@ function runRuntimeAuthoringRules(args) {
9498
10659
  validateChartBindings,
9499
10660
  validateComponentProps,
9500
10661
  validateDashboardActionRefs,
10662
+ validateEmptyCombinators,
9501
10663
  validateFilterTokens,
9502
10664
  validateFlowNodeWrites,
9503
10665
  validateFlowTemplatePaths,
@@ -9513,6 +10675,7 @@ function runRuntimeAuthoringRules(args) {
9513
10675
  validateOrgAxisRedLines,
9514
10676
  validatePageFieldBindings,
9515
10677
  validatePageSourceStyling,
10678
+ validatePredicatePathRefs,
9516
10679
  validateReactPageProps,
9517
10680
  validateReactPages,
9518
10681
  validateReadonlyFlowWrites,