@objectstack/lint 17.0.0 → 17.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -20,10 +20,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ ACTION_BODY_SOURCE_UNPARSEABLE: () => ACTION_BODY_SOURCE_UNPARSEABLE,
23
24
  ACTION_BODY_WRITE_EXCLUSIONS: () => ACTION_BODY_WRITE_EXCLUSIONS,
24
25
  ACTION_BODY_WRITE_PATTERNS: () => ACTION_BODY_WRITE_PATTERNS,
25
26
  ACTION_BODY_WRITE_PATTERN_IDS: () => ACTION_BODY_WRITE_PATTERN_IDS,
26
27
  ACTION_BODY_WRITE_UNKNOWN_FIELD: () => ACTION_BODY_WRITE_UNKNOWN_FIELD,
28
+ ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR: () => ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR,
27
29
  ACTION_NAME_UNDEFINED: () => ACTION_NAME_UNDEFINED,
28
30
  ACTION_NO_PLACEMENT: () => ACTION_NO_PLACEMENT,
29
31
  ACTION_RECORD_WRITE_DISCARDED: () => ACTION_RECORD_WRITE_DISCARDED,
@@ -69,6 +71,7 @@ __export(index_exports, {
69
71
  FIELD_GROUP_UNDECLARED: () => FIELD_GROUP_UNDECLARED,
70
72
  FILTER_EMPTY_COMBINATOR: () => FILTER_EMPTY_COMBINATOR,
71
73
  FILTER_EMPTY_NODE: () => FILTER_EMPTY_NODE,
74
+ FILTER_PRESET_COMPARAND: () => FILTER_PRESET_COMPARAND,
72
75
  FILTER_TOKEN_UNKNOWN: () => FILTER_TOKEN_UNKNOWN,
73
76
  FLOW_APPROVAL_REVISE_DEAD_END: () => FLOW_APPROVAL_REVISE_DEAD_END,
74
77
  FLOW_APPROVAL_REVISE_DISABLED: () => FLOW_APPROVAL_REVISE_DISABLED,
@@ -86,6 +89,7 @@ __export(index_exports, {
86
89
  FLOW_MULTIPLE_DEFAULT_EDGES: () => FLOW_MULTIPLE_DEFAULT_EDGES,
87
90
  FLOW_MULTI_WRITE_UNFILTERED: () => FLOW_MULTI_WRITE_UNFILTERED,
88
91
  FLOW_NODE_WRITE_UNKNOWN_FIELD: () => FLOW_NODE_WRITE_UNKNOWN_FIELD,
92
+ FLOW_NODE_WRITE_UNPROVISIONED_ANCHOR: () => FLOW_NODE_WRITE_UNPROVISIONED_ANCHOR,
89
93
  FLOW_PHANTOM_AGGREGATION: () => FLOW_PHANTOM_AGGREGATION,
90
94
  FLOW_RUNAS_UNSCOPED: () => FLOW_RUNAS_UNSCOPED,
91
95
  FLOW_TEMPLATE_FIELD_UNPROVISIONED: () => FLOW_TEMPLATE_FIELD_UNPROVISIONED,
@@ -103,10 +107,12 @@ __export(index_exports, {
103
107
  FLOW_WRITE_NODE_TYPES_DEFERRED: () => FLOW_WRITE_NODE_TYPES_DEFERRED,
104
108
  FORM_COLSPAN_ABSOLUTE: () => FORM_COLSPAN_ABSOLUTE,
105
109
  FORM_FIELD_UNKNOWN: () => FORM_FIELD_UNKNOWN,
110
+ HOOK_BODY_SOURCE_UNPARSEABLE: () => HOOK_BODY_SOURCE_UNPARSEABLE,
106
111
  HOOK_BODY_WRITE_EXCLUSIONS: () => HOOK_BODY_WRITE_EXCLUSIONS,
107
112
  HOOK_BODY_WRITE_PATTERNS: () => HOOK_BODY_WRITE_PATTERNS,
108
113
  HOOK_BODY_WRITE_PATTERN_IDS: () => HOOK_BODY_WRITE_PATTERN_IDS,
109
114
  HOOK_BODY_WRITE_UNKNOWN_FIELD: () => HOOK_BODY_WRITE_UNKNOWN_FIELD,
115
+ HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR: () => HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR,
110
116
  LIST_VIEW_FILTERS_IN_VIEWS_MODE: () => LIST_VIEW_FILTERS_IN_VIEWS_MODE,
111
117
  LIVENESS_DEAD_PROPERTY: () => LIVENESS_DEAD_PROPERTY,
112
118
  LIVENESS_EXPERIMENTAL_PROPERTY: () => LIVENESS_EXPERIMENTAL_PROPERTY,
@@ -125,6 +131,7 @@ __export(index_exports, {
125
131
  PAGE_FIELD_UNKNOWN: () => PAGE_FIELD_UNKNOWN,
126
132
  PAGE_FIELD_UNPROVISIONED: () => PAGE_FIELD_UNPROVISIONED,
127
133
  PAGE_SOURCE_CLASSNAME: () => PAGE_SOURCE_CLASSNAME,
134
+ PARSE_FAILURE_HINT: () => PARSE_FAILURE_HINT,
128
135
  PREDICATE_PATH_UNRESOLVED: () => PREDICATE_PATH_UNRESOLVED,
129
136
  PREDICATE_PATH_UNROOTED: () => PREDICATE_PATH_UNROOTED,
130
137
  PREDICATE_RHS_PATH_SHAPED: () => PREDICATE_RHS_PATH_SHAPED,
@@ -135,6 +142,7 @@ __export(index_exports, {
135
142
  REACT_CHART_DRILLDOWN_INVALID: () => REACT_CHART_DRILLDOWN_INVALID,
136
143
  REACT_CHART_FIELD_UNKNOWN: () => REACT_CHART_FIELD_UNKNOWN,
137
144
  REACT_CHART_FIELD_UNPROVISIONED: () => REACT_CHART_FIELD_UNPROVISIONED,
145
+ REACT_PAGE_SOURCE_UNPARSEABLE: () => REACT_PAGE_SOURCE_UNPARSEABLE,
138
146
  REFERENCE_INTEGRITY_RULES: () => REFERENCE_INTEGRITY_RULES,
139
147
  RLS_PREDICATE_OVER_BUDGET: () => RLS_PREDICATE_OVER_BUDGET,
140
148
  RLS_PREDICATE_UNENFORCEABLE: () => RLS_PREDICATE_UNENFORCEABLE,
@@ -142,6 +150,7 @@ __export(index_exports, {
142
150
  RUNTIME_AJV_OPTIONS: () => RUNTIME_AJV_OPTIONS,
143
151
  SEAL_MARKERS: () => SEAL_MARKERS,
144
152
  SEARCHABLE_FIELD_UNKNOWN: () => SEARCHABLE_FIELD_UNKNOWN,
153
+ SEARCHABLE_FIELD_UNPROVISIONED: () => SEARCHABLE_FIELD_UNPROVISIONED,
145
154
  SEARCHABLE_FIELD_UNSEARCHABLE: () => SEARCHABLE_FIELD_UNSEARCHABLE,
146
155
  SECURITY_ANCHOR_HIGH_PRIVILEGE: () => SECURITY_ANCHOR_HIGH_PRIVILEGE,
147
156
  SECURITY_BOOK_AUDIENCE_UNKNOWN_SET: () => SECURITY_BOOK_AUDIENCE_UNKNOWN_SET,
@@ -160,9 +169,15 @@ __export(index_exports, {
160
169
  SEED_VALUE_OUTSIDE_STATE_MACHINE: () => SEED_VALUE_OUTSIDE_STATE_MACHINE,
161
170
  SEMANTIC_ROLE_FIELD_UNKNOWN: () => SEMANTIC_ROLE_FIELD_UNKNOWN,
162
171
  SEMANTIC_ROLE_FIELD_UNPROVISIONED: () => SEMANTIC_ROLE_FIELD_UNPROVISIONED,
172
+ SHARING_RULE_OBJECT_CONTROLLED_BY_PARENT: () => SHARING_RULE_OBJECT_CONTROLLED_BY_PARENT,
173
+ SHARING_RULE_OBJECT_NOT_SHAREABLE: () => SHARING_RULE_OBJECT_NOT_SHAREABLE,
163
174
  SHARING_RULE_RUNTIME_VARIABLE_CONDITION: () => SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
164
175
  SHARING_RULE_UNLOWERABLE_CONDITION: () => SHARING_RULE_UNLOWERABLE_CONDITION,
176
+ SORT_FIELD_UNKNOWN: () => SORT_FIELD_UNKNOWN,
177
+ SORT_FIELD_UNPROVISIONED: () => SORT_FIELD_UNPROVISIONED,
178
+ SORT_FIELD_UNSORTABLE: () => SORT_FIELD_UNSORTABLE,
165
179
  STARTUP_OPEN_VOCABULARY_VERDICT: () => STARTUP_OPEN_VOCABULARY_VERDICT,
180
+ STARTUP_SOURCE_UNPARSEABLE: () => STARTUP_SOURCE_UNPARSEABLE,
166
181
  STARTUP_VERDICT_ASSERTIVE_WORDING: () => STARTUP_VERDICT_ASSERTIVE_WORDING,
167
182
  STARTUP_VERDICT_HINT: () => STARTUP_VERDICT_HINT,
168
183
  STYLE_CLASSNAME_TAILWIND: () => STYLE_CLASSNAME_TAILWIND,
@@ -198,6 +213,8 @@ __export(index_exports, {
198
213
  authoringRulesFor: () => authoringRulesFor,
199
214
  buildAccessMatrix: () => buildAccessMatrix,
200
215
  buildRuntimeWriteSnapshots: () => buildRuntimeWriteSnapshots,
216
+ checkSortDeclaration: () => checkSortDeclaration,
217
+ describeParseFailure: () => describeParseFailure,
201
218
  diffAccessMatrix: () => diffAccessMatrix,
202
219
  extractHookBodyWriteSet: () => extractHookBodyWriteSet,
203
220
  extractHookBodyWrites: () => extractHookBodyWrites,
@@ -212,6 +229,7 @@ __export(index_exports, {
212
229
  lintUniqueDeclarations: () => lintUniqueDeclarations,
213
230
  lintUnscopedDeclaredIndexes: () => lintUnscopedDeclaredIndexes,
214
231
  lintViewRefs: () => lintViewRefs,
232
+ narrowObjectsToPackageClosure: () => narrowObjectsToPackageClosure,
215
233
  nearestRegisteredFormat: () => nearestRegisteredFormat,
216
234
  nullGuardMessage: () => nullGuardMessage,
217
235
  runAuthoringRules: () => runAuthoringRules,
@@ -250,6 +268,7 @@ __export(index_exports, {
250
268
  validatePageFieldBindings: () => validatePageFieldBindings,
251
269
  validatePageSourceStyling: () => validatePageSourceStyling,
252
270
  validatePredicatePathRefs: () => validatePredicatePathRefs,
271
+ validatePresetComparands: () => validatePresetComparands,
253
272
  validateReactPageProps: () => validateReactPageProps,
254
273
  validateReactPages: () => validateReactPages,
255
274
  validateReadonlyFlowWrites: () => validateReadonlyFlowWrites,
@@ -266,6 +285,7 @@ __export(index_exports, {
266
285
  validateSeedStateMachine: () => validateSeedStateMachine,
267
286
  validateSemanticRoles: () => validateSemanticRoles,
268
287
  validateSharingRuleEnforceability: () => validateSharingRuleEnforceability,
288
+ validateSortableFields: () => validateSortableFields,
269
289
  validateStackExpressions: () => validateStackExpressions,
270
290
  validateTranslatableSections: () => validateTranslatableSections,
271
291
  validateTranslationReferences: () => validateTranslationReferences,
@@ -860,10 +880,10 @@ var OUTCOME_CLAUSE = {
860
880
  "fail-closed": "so the rule enforces nothing and the write is rejected fail-closed (#4649/#4763)",
861
881
  "fail-open": "so the predicate is SKIPPED fail-open \u2014 the field is never actually required, the write proceeds unchecked, and the only trace is a `requiredWhen \u2026 failed to evaluate \u2014 skipped` log line (#4649/#4811)"
862
882
  };
863
- function nullGuardMessage(subject, objectName, finding, outcome = "fail-closed") {
883
+ function nullGuardMessage(subject, objectName, finding2, outcome = "fail-closed") {
864
884
  const owner = objectName ? `'${objectName}'` : "this object";
865
- const hasNote = finding.hasOnlyGuard ? ` \`has(${finding.operand})\` does not guard it.` : "";
866
- return `${subject} applies \`${finding.operator}\` to \`${finding.operand}\`, which ${owner} declares as nullable (no \`required: true\`, no \`defaultValue\`).${hasNote} At runtime the operand is null, CEL has no \`${finding.operator}\` overload for null, and the whole predicate aborts \u2014 ${OUTCOME_CLAUSE[outcome]}. The predicate compares a value that is null. ${NULL_GUARD_HINT}`;
885
+ const hasNote = finding2.hasOnlyGuard ? ` \`has(${finding2.operand})\` does not guard it.` : "";
886
+ return `${subject} applies \`${finding2.operator}\` to \`${finding2.operand}\`, which ${owner} declares as nullable (no \`required: true\`, no \`defaultValue\`).${hasNote} At runtime the operand is null, CEL has no \`${finding2.operator}\` overload for null, and the whole predicate aborts \u2014 ${OUTCOME_CLAUSE[outcome]}. The predicate compares a value that is null. ${NULL_GUARD_HINT}`;
867
887
  }
868
888
 
869
889
  // src/validate-expressions.ts
@@ -1046,10 +1066,10 @@ function validateStackExpressions(stack) {
1046
1066
  if (!nullableFields || nullableFields.size === 0) return;
1047
1067
  const source = celSourceOf(raw);
1048
1068
  if (!source) return;
1049
- for (const finding of findUnguardedNullableOperands(source, { nullableFields })) {
1069
+ for (const finding2 of findUnguardedNullableOperands(source, { nullableFields })) {
1050
1070
  issues.push({
1051
1071
  where,
1052
- message: nullGuardMessage(subject, objectName, finding, outcome),
1072
+ message: nullGuardMessage(subject, objectName, finding2, outcome),
1053
1073
  source,
1054
1074
  severity: "error"
1055
1075
  });
@@ -1073,7 +1093,7 @@ function validateStackExpressions(stack) {
1073
1093
  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";
1074
1094
  const FIELD_RULE_SLOT_CONSEQUENCE = {
1075
1095
  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)",
1076
- 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",
1096
+ 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`). The server is the one that decides: the field looks writable, the save reports success, and the value silently never lands",
1077
1097
  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",
1078
1098
  // Listed rather than left to the `??` below, so the map covers every slot
1079
1099
  // the field walk passes and the default stays unreachable. `FieldSchema`
@@ -1132,14 +1152,15 @@ function validateStackExpressions(stack) {
1132
1152
  if (retired.length > 0) {
1133
1153
  issues.push({
1134
1154
  where: `${at} \xB7 node '${node.id}' (script) callable`,
1135
- 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
1136
- // behaviour, never the retired key's fate — "rewrite it" reads two ways
1137
- // over a branch that DELETES the key (template/recipients/variables/script),
1138
- // "rewrite existing sources" only one. Plain-quoted (not a template literal)
1155
+ 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), reworded under #9529: the house
1156
+ // sentence names the TOOL's behaviour, never the retired key's fate —
1157
+ // "rewrite it" read two ways over a branch that DELETES the key
1158
+ // (template/recipients/variables/script), and the tool never rewrote a
1159
+ // source file at all. Plain-quoted (not a template literal)
1139
1160
  // so this site is a member of `retired-key-migrate-sentence.test.ts`'s
1140
1161
  // widened scan (#7030) on the same textual shape as the spec corpus — no
1141
1162
  // interpolation lives in this clause, so nothing is lost switching quote style.
1142
- "Run `os migrate meta --from 16` to rewrite existing sources automatically.",
1163
+ "Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand.",
1143
1164
  source: JSON.stringify({ id: node.id, type: node.type, config: cfg })
1144
1165
  });
1145
1166
  } else if (!fn) {
@@ -1210,10 +1231,10 @@ function validateStackExpressions(stack) {
1210
1231
  f.expression,
1211
1232
  objectName ? { objectName, fields: fieldIndex.get(objectName), fieldTypes: fieldTypeIndex.get(objectName), scope: "record" } : { scope: "record" }
1212
1233
  );
1213
- const fieldWhere = `object '${objectName}' \xB7 field '${fname}' expression`;
1214
- for (const e of res.errors) issues.push({ where: fieldWhere, message: e.message, source: e.source, severity: "error" });
1215
- for (const w of res.warnings) issues.push({ where: fieldWhere, message: w.message, source: w.source, severity: "warning" });
1216
- warnUnprovisionedAnchors(fieldWhere, f.expression, objectName);
1234
+ const fieldWhere2 = `object '${objectName}' \xB7 field '${fname}' expression`;
1235
+ for (const e of res.errors) issues.push({ where: fieldWhere2, message: e.message, source: e.source, severity: "error" });
1236
+ for (const w of res.warnings) issues.push({ where: fieldWhere2, message: w.message, source: w.source, severity: "warning" });
1237
+ warnUnprovisionedAnchors(fieldWhere2, f.expression, objectName);
1217
1238
  }
1218
1239
  }
1219
1240
  }
@@ -1289,6 +1310,38 @@ function validateStackExpressions(stack) {
1289
1310
 
1290
1311
  // src/lint-startup-registry-verdict.ts
1291
1312
  var import_node_module = require("module");
1313
+
1314
+ // src/checked-parse.ts
1315
+ function createSourceFileChecked(tsc, fileName, source, options) {
1316
+ const sourceFile = tsc.createSourceFile(
1317
+ fileName,
1318
+ source,
1319
+ options.target,
1320
+ options.setParentNodes,
1321
+ options.scriptKind
1322
+ );
1323
+ const diagnostics = sourceFile.parseDiagnostics;
1324
+ if (!diagnostics || diagnostics.length === 0) return { sourceFile };
1325
+ const first = diagnostics[0];
1326
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(first.start ?? 0);
1327
+ const offset = options.synthesizedLinesBefore ?? 0;
1328
+ return {
1329
+ sourceFile,
1330
+ failure: {
1331
+ message: tsc.flattenDiagnosticMessageText(first.messageText, " "),
1332
+ line: Math.max(1, line + 1 - offset),
1333
+ column: character + 1,
1334
+ count: diagnostics.length
1335
+ }
1336
+ };
1337
+ }
1338
+ function describeParseFailure(failure) {
1339
+ const more = failure.count > 1 ? `; ${failure.count} syntax errors in total` : "";
1340
+ return `line ${failure.line}, column ${failure.column}: ${failure.message}${more}`;
1341
+ }
1342
+ var PARSE_FAILURE_HINT = "The source is parsed (never executed) so it can be checked. A source with syntax errors is only partially recovered, so the checks that follow may have skipped real problems \u2014 fix the syntax error and re-run to get a verdict that covers the whole source.";
1343
+
1344
+ // src/lint-startup-registry-verdict.ts
1292
1345
  var import_meta = {};
1293
1346
  var cachedTs = null;
1294
1347
  function loadTypeScript() {
@@ -1305,6 +1358,7 @@ function loadTypeScript() {
1305
1358
  }
1306
1359
  var STARTUP_OPEN_VOCABULARY_VERDICT = "startup-open-vocabulary-verdict";
1307
1360
  var STARTUP_VERDICT_ASSERTIVE_WORDING = "startup-verdict-assertive-wording";
1361
+ var STARTUP_SOURCE_UNPARSEABLE = "startup-source-unparseable";
1308
1362
  var OPEN_VOCABULARY_PROBES = /* @__PURE__ */ new Map([
1309
1363
  [
1310
1364
  "getRegisteredNodeTypes",
@@ -1533,13 +1587,22 @@ function findStartupRegistryVerdicts(source, options = {}) {
1533
1587
  if (!mentionsAny) return [];
1534
1588
  const t = loadTypeScript();
1535
1589
  const fileLabel = options.file ?? "source";
1536
- let sf;
1537
- try {
1538
- sf = t.createSourceFile(fileLabel, source, t.ScriptTarget.Latest, true, t.ScriptKind.TS);
1539
- } catch {
1540
- return [];
1541
- }
1590
+ const { sourceFile: sf, failure } = createSourceFileChecked(t, fileLabel, source, {
1591
+ target: t.ScriptTarget.Latest,
1592
+ setParentNodes: true,
1593
+ scriptKind: t.ScriptKind.TS
1594
+ });
1542
1595
  const findings = [];
1596
+ if (failure) {
1597
+ findings.push({
1598
+ severity: "warning",
1599
+ rule: STARTUP_SOURCE_UNPARSEABLE,
1600
+ where: fileLabel,
1601
+ path: `${fileLabel}:${failure.line}`,
1602
+ message: `source did not parse (${describeParseFailure(failure)}), so this rule read a partially recovered tree \u2014 a startup verdict in the unread part is not reported.`,
1603
+ hint: PARSE_FAILURE_HINT
1604
+ });
1605
+ }
1543
1606
  const moduleBindings = moduleLevelMutableBindings(t, sf);
1544
1607
  const functionBodies = indexFunctionBodies(t, sf);
1545
1608
  const lineOf = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
@@ -2835,6 +2898,7 @@ var import_data5 = require("@objectstack/spec/data");
2835
2898
  var import_data4 = require("@objectstack/spec/data");
2836
2899
  var SEARCHABLE_FIELD_UNKNOWN = "searchable-field-unknown";
2837
2900
  var SEARCHABLE_FIELD_UNSEARCHABLE = "searchable-field-unsearchable";
2901
+ var SEARCHABLE_FIELD_UNPROVISIONED = "searchable-field-unprovisioned";
2838
2902
  function asArray10(v) {
2839
2903
  if (Array.isArray(v)) return v;
2840
2904
  if (v && typeof v === "object") {
@@ -2925,7 +2989,7 @@ function indexObjectSearchTargets(stack) {
2925
2989
  }
2926
2990
  return fieldsByObject;
2927
2991
  }
2928
- function checkSearchableFieldList(declared, objectName, fieldsByObject, where, path, subject, role = "narrowing") {
2992
+ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, path, subject, role = "narrowing", unprovisionedAnchors) {
2929
2993
  const findings = [];
2930
2994
  if (!Array.isArray(declared) || declared.length === 0) return findings;
2931
2995
  if (!objectName) return findings;
@@ -2933,6 +2997,7 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
2933
2997
  const target = fieldsByObject.get(objectName);
2934
2998
  if (!target) return findings;
2935
2999
  const known = target.names;
3000
+ const anchors = unprovisionedAnchors?.get(objectName);
2936
3001
  const resolution = role === "narrowing" ? resolveAllowedSet(target) : void 0;
2937
3002
  for (let i = 0; i < declared.length; i++) {
2938
3003
  const entry = declared[i];
@@ -2950,6 +3015,16 @@ function checkSearchableFieldList(declared, objectName, fieldsByObject, where, p
2950
3015
  });
2951
3016
  continue;
2952
3017
  }
3018
+ if (anchors?.has(name)) {
3019
+ findings.push({
3020
+ severity: "warning",
3021
+ rule: SEARCHABLE_FIELD_UNPROVISIONED,
3022
+ where,
3023
+ path: `${path}[${i}]`,
3024
+ message: `${subject} entry "${name}" resolves on object "${objectName}", but ${unprovisionedAnchorCause(objectName, name)}` + (role === "narrowing" ? ` \u2014 clients echo this declaration verbatim as the '$searchFields' override, so every toolbar search on this list scans a column that is empty on every record: it reads as search coverage and matches nothing.` : ` \u2014 'search' scans it on every record and it can never match, so the object's searchable set is narrower than it declares. Should it be the ONLY entry that resolves, the set scans nothing at all.`),
3025
+ hint: unprovisionedAnchorHint(objectName, name)
3026
+ });
3027
+ }
2953
3028
  if ((0, import_data4.isVirtualSearchField)(target.fields[name])) {
2954
3029
  const vtype = target.fields[name]?.type;
2955
3030
  findings.push({
@@ -3003,9 +3078,19 @@ function validateSearchableFields(stack) {
3003
3078
  if (!isRec4(stack)) return findings;
3004
3079
  const objects = asArray10(stack.objects);
3005
3080
  const fieldsByObject = indexObjectSearchTargets(stack);
3081
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
3006
3082
  const check = (declared, objectName, where, path, subject, role) => {
3007
3083
  findings.push(
3008
- ...checkSearchableFieldList(declared, objectName, fieldsByObject, where, path, subject, role)
3084
+ ...checkSearchableFieldList(
3085
+ declared,
3086
+ objectName,
3087
+ fieldsByObject,
3088
+ where,
3089
+ path,
3090
+ subject,
3091
+ role,
3092
+ unprovisionedAnchors
3093
+ )
3009
3094
  );
3010
3095
  };
3011
3096
  for (let oi = 0; oi < objects.length; oi++) {
@@ -3043,6 +3128,26 @@ function validateSearchableFields(stack) {
3043
3128
  if (!isRec4(view)) continue;
3044
3129
  const viewLabel2 = strName2(view.name) ?? strName2(view.objectName) ?? `#${vi}`;
3045
3130
  const viewObject = strName2(view.objectName) ?? strName2(view.object);
3131
+ if (view.viewKind === "list" && !isRec4(view.config)) {
3132
+ check(
3133
+ view.searchableFields,
3134
+ listViewObject(view) ?? viewObject,
3135
+ `view "${viewLabel2}" (flattened list overlay)`,
3136
+ `views[${vi}].searchableFields`,
3137
+ "list-view searchableFields",
3138
+ "narrowing"
3139
+ );
3140
+ }
3141
+ if (view.viewKind === "list" && isRec4(view.config)) {
3142
+ check(
3143
+ view.config.searchableFields,
3144
+ listViewObject(view.config) ?? viewObject,
3145
+ `view "${viewLabel2}" (ViewItem record)`,
3146
+ `views[${vi}].config.searchableFields`,
3147
+ "list-view searchableFields",
3148
+ "narrowing"
3149
+ );
3150
+ }
3046
3151
  if (isRec4(view.list)) {
3047
3152
  check(
3048
3153
  view.list.searchableFields,
@@ -3192,7 +3297,12 @@ var COMPONENT_FIELD_SPECS = {
3192
3297
  "record:details": { props: ["fields", "hideFields"], nestedSections: ["sections"] },
3193
3298
  "record:path": { props: ["statusField"] },
3194
3299
  "element:number": { props: ["field"] },
3195
- "element:filter": { props: ["fields"] },
3300
+ // `element:filter` had a `{ props: ['fields'] }` entry until #9220 retired the
3301
+ // whole element at element grain (ADR-0049 — no renderer ever shipped for it).
3302
+ // Every `ElementFilterProps` key is a `retiredKey()` tombstone now, so no
3303
+ // spec-conformant page carries `fields` on it, and the #5068 props gate
3304
+ // reports an authored one by name with the element-retirement prescription —
3305
+ // the same #5775/#6629 residue class as the record-picker entries below.
3196
3306
  "element:form": { props: ["fields"] },
3197
3307
  // `labelField` is the one field-bearing prop this element declares. Its former
3198
3308
  // companions `displayField` (renamed to `labelField`, ADR-0087 D2) and
@@ -3485,6 +3595,7 @@ function filterAttrValue(tsc, sf, attr) {
3485
3595
  };
3486
3596
  return perPosition(init.expression);
3487
3597
  }
3598
+ var REACT_PAGE_SOURCE_UNPARSEABLE = "react-page-source-unparseable";
3488
3599
  var REACT_CHART_FIELD_UNKNOWN = "react-chart-field-unknown";
3489
3600
  var REACT_CHART_FIELD_UNPROVISIONED = "react-chart-field-unprovisioned";
3490
3601
  var REACT_CHART_AGGREGATE_INVALID = "react-chart-aggregate-invalid";
@@ -3813,11 +3924,20 @@ function validateReactPageProps(stack) {
3813
3924
  if (typeof source !== "string" || source.trim() === "") continue;
3814
3925
  const name = String(page.name ?? `#${p}`);
3815
3926
  const tsc = loadTypeScript2();
3816
- let sf;
3817
- try {
3818
- sf = tsc.createSourceFile("page.tsx", source, tsc.ScriptTarget.Latest, true, tsc.ScriptKind.TSX);
3819
- } catch {
3820
- continue;
3927
+ const { sourceFile: sf, failure } = createSourceFileChecked(tsc, "page.tsx", source, {
3928
+ target: tsc.ScriptTarget.Latest,
3929
+ setParentNodes: true,
3930
+ scriptKind: tsc.ScriptKind.TSX
3931
+ });
3932
+ if (failure) {
3933
+ findings.push({
3934
+ severity: "warning",
3935
+ rule: REACT_PAGE_SOURCE_UNPARSEABLE,
3936
+ where: `page "${name}"`,
3937
+ path: `pages[${p}].source`,
3938
+ message: `kind:'react' source did not parse (${describeParseFailure(failure)}), so the component-contract checks read a partially recovered tree and may have missed real problems.`,
3939
+ hint: PARSE_FAILURE_HINT
3940
+ });
3821
3941
  }
3822
3942
  const locals = localComponentNames(tsc, sf);
3823
3943
  const visit = (node) => {
@@ -3896,7 +4016,12 @@ function validateReactPageProps(stack) {
3896
4016
  searchTargets,
3897
4017
  where,
3898
4018
  `${path} \u203A searchableFields`,
3899
- "searchableFields"
4019
+ "searchableFields",
4020
+ // A `<ListView>` prop is a view-level narrowing — the checker's
4021
+ // default, spelled out here because the #8404 provenance index
4022
+ // follows it positionally.
4023
+ "narrowing",
4024
+ unprovisionedAnchors
3900
4025
  )
3901
4026
  );
3902
4027
  }
@@ -5468,6 +5593,7 @@ function validateSecurityPosture(stack, opts) {
5468
5593
  }
5469
5594
  }
5470
5595
  const GRANT_SEED_OBJECTS = /* @__PURE__ */ new Set(["sys_user_position", "sys_user_permission_set"]);
5596
+ const DELEGATION_SEED_OBJECTS = /* @__PURE__ */ new Set(["sys_user_position"]);
5471
5597
  const nowMs = opts?.nowMs ?? Date.now();
5472
5598
  for (const [i, seed] of asArray19(stack.data).entries()) {
5473
5599
  const seedObject = typeof seed.object === "string" ? seed.object : "";
@@ -5491,7 +5617,7 @@ function validateSecurityPosture(stack, opts) {
5491
5617
  }
5492
5618
  }
5493
5619
  const delegatedFrom = rec.delegated_from;
5494
- if (delegatedFrom != null && delegatedFrom !== "") {
5620
+ if (DELEGATION_SEED_OBJECTS.has(seedObject) && delegatedFrom != null && delegatedFrom !== "") {
5495
5621
  const reason = rec.reason;
5496
5622
  if (typeof reason !== "string" || reason.trim().length === 0) {
5497
5623
  findings.push({
@@ -5657,6 +5783,8 @@ function validateOrgAxisRedLines(stack) {
5657
5783
  var import_formula6 = require("@objectstack/formula");
5658
5784
  var SHARING_RULE_UNLOWERABLE_CONDITION = "sharing-rule-unlowerable-condition";
5659
5785
  var SHARING_RULE_RUNTIME_VARIABLE_CONDITION = "sharing-rule-runtime-variable-condition";
5786
+ var SHARING_RULE_OBJECT_NOT_SHAREABLE = "sharing-rule-object-not-shareable";
5787
+ var SHARING_RULE_OBJECT_CONTROLLED_BY_PARENT = "sharing-rule-object-controlled-by-parent";
5660
5788
  function asArray21(v) {
5661
5789
  if (Array.isArray(v)) return v;
5662
5790
  if (v && typeof v === "object") {
@@ -5681,10 +5809,67 @@ function sourceOf(condition) {
5681
5809
  return str2(input?.source);
5682
5810
  }
5683
5811
  var PUSHDOWN_SUBSET = "The lowerable subset is: `==` `!=` `>` `<` `>=` `<=`, `in`, `&&` `||` `!`, `== null` / `!= null`, and the string methods `startsWith` / `endsWith` / `contains` \u2014 over SINGLE-column `record.<field>` paths (ADR-0058 D2).";
5812
+ function effectiveSharingModelOf(obj) {
5813
+ const m = obj.sharingModel;
5814
+ if (m === "private") return "private";
5815
+ if (m === "public_read") return "read";
5816
+ if (m === "public_read_write" || m === "controlled_by_parent") return "public";
5817
+ if (m == null) {
5818
+ const isSystem = obj.isSystem === true || str2(obj.name).startsWith("sys_");
5819
+ return isSystem ? "public" : "private";
5820
+ }
5821
+ return "private";
5822
+ }
5823
+ function masterOf(obj) {
5824
+ for (const f of asArray21(obj.fields)) {
5825
+ if (f.type === "master_detail") {
5826
+ const ref = f.reference;
5827
+ if (typeof ref === "string" && ref) return ref;
5828
+ }
5829
+ }
5830
+ return void 0;
5831
+ }
5832
+ function anchorFindings(rule, index, objectsByName) {
5833
+ const object = str2(rule.object);
5834
+ if (!object) return [];
5835
+ const target = objectsByName.get(object);
5836
+ if (!target) return [];
5837
+ const name = str2(rule.name) || String(index);
5838
+ const where = `sharing rule "${name}" on object "${object}"`;
5839
+ const path = `sharingRules[${index}].object`;
5840
+ const owd = target.sharingModel;
5841
+ if (owd === "controlled_by_parent") {
5842
+ const master = masterOf(target);
5843
+ return [{
5844
+ severity: "error",
5845
+ rule: SHARING_RULE_OBJECT_CONTROLLED_BY_PARENT,
5846
+ where,
5847
+ path,
5848
+ message: `Sharing rule "${name}" is anchored on object "${object}", which declares sharingModel 'controlled_by_parent'. A detail record has no record-level access of its own \u2014 its visibility is DERIVED from its master (ADR-0055), so it holds no shares to widen. \`SharingService.assertNotInertGrant\` refuses the grant with SHARING_NOT_ENABLED ("'${object}' is controlled by its parent (master-detail); share the master record instead"), so the rule's boot backfill fails, no \`sys_record_share\` row is ever written, and the recipients this rule names get whatever the MASTER grants them \u2014 which may be nothing. The grant is declared and does not exist.`,
5849
+ hint: `Move the rule onto the MASTER object` + (master ? ` \u2014 "${object}" derives from "${master}" through its master_detail field, so share "${master}" and the detail rows follow` : `, and share that instead; the detail rows follow`) + `. If "${object}" is meant to carry a record-level baseline of its own, that is a different decision: change its sharingModel to 'private' (owner + shares) or 'public_read', and this rule becomes enforceable where it stands.`
5850
+ }];
5851
+ }
5852
+ if (effectiveSharingModelOf(target) !== "public") return [];
5853
+ const declared = owd === "public_read_write" ? `declares sharingModel 'public_read_write'` : `declares no sharingModel and is a system object (\`isSystem: true\` or a \`sys_\` name), which ADR-0090 D1 resolves to public`;
5854
+ return [{
5855
+ severity: "error",
5856
+ rule: SHARING_RULE_OBJECT_NOT_SHAREABLE,
5857
+ where,
5858
+ path,
5859
+ message: `Sharing rule "${name}" is anchored on object "${object}", which ${declared}. Its effective sharing model is therefore \`public\`, and sharing only ever WIDENS an OWD baseline \u2014 on the widest baseline there is nothing left to widen. \`SharingService.assertNotInertGrant\` refuses the grant with SHARING_NOT_ENABLED ("'${object}' is not under record-sharing enforcement"), so the rule's boot backfill fails and no \`sys_record_share\` row is ever written. Measured: \`buildReadFilter\` returns \`null\` for this object, i.e. NO record-level filter at all \u2014 every principal already reads every row, so this rule advertises a restriction that does not exist.`,
5860
+ hint: `Decide which half is wrong. If the ACCESS is right \u2014 everyone should read and write these records \u2014 the rule is dead metadata: delete it (ADR-0049 enforce-or-remove). If the RULE is right \u2014 only the named audience should reach these records \u2014 then "${object}"'s OWD is the defect: set sharingModel: 'private' (owner + shares) or 'public_read', and this rule starts enforcing. Do NOT re-home the rule onto another public object; that moves the inertness instead of removing it.`
5861
+ }];
5862
+ }
5684
5863
  function validateSharingRuleEnforceability(stack) {
5685
5864
  const findings = [];
5686
5865
  const cfg = stack ?? {};
5866
+ const objectsByName = /* @__PURE__ */ new Map();
5867
+ for (const obj of asArray21(cfg.objects)) {
5868
+ const name = str2(obj.name);
5869
+ if (name) objectsByName.set(name, obj);
5870
+ }
5687
5871
  asArray21(cfg.sharingRules).forEach((rule, index) => {
5872
+ anchorFindings(rule, index, objectsByName).forEach((f) => findings.push(f));
5688
5873
  const input = toCompilerInput(rule.condition);
5689
5874
  if (input === null) return;
5690
5875
  const result = (0, import_formula6.compileCelToFilter)(input, { variables: {} });
@@ -5817,7 +6002,6 @@ function asArray23(v) {
5817
6002
  function strName7(v) {
5818
6003
  return typeof v === "string" && v.length > 0 ? v : void 0;
5819
6004
  }
5820
- var MODAL_VERB_RE = /^(?:create|new|add|edit|update)_(.+)$/;
5821
6005
  var URL_COLLECTION_TO_STACK_KEY = {
5822
6006
  object: "objects",
5823
6007
  objects: "objects",
@@ -5862,13 +6046,10 @@ function collectKnownTargets(stack) {
5862
6046
  return { actions, objects, reports, dashboards, pages, views };
5863
6047
  }
5864
6048
  function resolveActionTarget(actionType, target, known) {
5865
- if (known.actions.has(target)) return true;
5866
6049
  if (actionType === "modal") {
5867
- if (known.objects.has(target)) return true;
5868
- const m = MODAL_VERB_RE.exec(target);
5869
- if (m && known.objects.has(m[1])) return true;
6050
+ return known.pages.has(target);
5870
6051
  }
5871
- return false;
6052
+ return known.actions.has(target);
5872
6053
  }
5873
6054
  function resolveUrlRoute(target, known) {
5874
6055
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(target) || target.startsWith("//")) return null;
@@ -5898,14 +6079,13 @@ function validateDashboardActionRefs(stack) {
5898
6079
  const actionType = strName7(action.actionType) ?? "url";
5899
6080
  if (actionType === "script" || actionType === "modal") {
5900
6081
  if (resolveActionTarget(actionType, target, known)) return;
5901
- const kindWord = actionType === "script" ? "script" : "modal";
5902
6082
  findings.push({
5903
6083
  severity: "error",
5904
6084
  rule: DASHBOARD_ACTION_TARGET_UNDEFINED,
5905
6085
  where,
5906
6086
  path,
5907
- message: `${kindWord} action target "${target}" resolves to no defined action` + (actionType === "modal" ? " or object" : "") + `. The button renders but does nothing when clicked \u2014 a dangling reference the runtime cannot dispatch (ADR-0049: a declared reference must resolve).`,
5908
- hint: actionType === "modal" ? `Define an action named "${target}" (stack.actions or the object's actions), use the "<verb>_<object>" convention against a real object (e.g. "create_<object>"), point actionUrl at an existing object, or remove the button.` : `Define a script action named "${target}" (stack.actions or the object's actions) with an inline body or a registered handler, or remove the button.`
6087
+ message: actionType === "modal" ? `modal action target "${target}" names no declared page \u2014 a modal target names a PAGE, only (objectstack#6739). The button renders but the runtime refuses the dispatch when clicked \u2014 a dangling reference (ADR-0049: a declared reference must resolve).` : `script action target "${target}" resolves to no defined action. The button renders but does nothing when clicked \u2014 a dangling reference the runtime cannot dispatch (ADR-0049: a declared reference must resolve).`,
6088
+ hint: actionType === "modal" ? `Point actionUrl at a declared page (stack.pages), or use actionType: 'form' with an "<object>.<view>" form-view target to open an object's form, or remove the button.` : `Define a script action named "${target}" (stack.actions or the object's actions) with an inline body or a registered handler, or remove the button.`
5909
6089
  });
5910
6090
  return;
5911
6091
  }
@@ -5947,7 +6127,7 @@ function validateDashboardActionRefs(stack) {
5947
6127
  var import_data7 = require("@objectstack/spec/data");
5948
6128
 
5949
6129
  // src/filter-walk.ts
5950
- var FILTER_KEYS = /* @__PURE__ */ new Set(["filter", "filters", "runtimeFilter"]);
6130
+ var FILTER_KEYS = /* @__PURE__ */ new Set(["filter", "filters", "runtimeFilter", "relatedListFilter"]);
5951
6131
  function asArray24(v) {
5952
6132
  if (Array.isArray(v)) return v;
5953
6133
  if (v && typeof v === "object") {
@@ -6051,8 +6231,146 @@ function validateFilterTokens(stack) {
6051
6231
  return out;
6052
6232
  }
6053
6233
 
6054
- // src/validate-empty-combinators.ts
6234
+ // src/validate-preset-comparands.ts
6055
6235
  var import_data8 = require("@objectstack/spec/data");
6236
+ var import_ui3 = require("@objectstack/spec/ui");
6237
+ var FILTER_PRESET_COMPARAND = "filter-preset-comparand";
6238
+ var PRESET_COMPARAND_SURFACES = [
6239
+ { key: "dashboards", kind: "dashboard" },
6240
+ { key: "objects", kind: "object" },
6241
+ { key: "views", kind: "view" },
6242
+ { key: "reports", kind: "report" },
6243
+ { key: "datasets", kind: "dataset" },
6244
+ { key: "pages", kind: "page" },
6245
+ { key: "apps", kind: "app" },
6246
+ { key: "flows", kind: "flow" }
6247
+ ];
6248
+ var ORDERING_DOLLAR_OPS = /* @__PURE__ */ new Set(["$gt", "$gte", "$lt", "$lte"]);
6249
+ var ORDERING_INFIX_OPS = /* @__PURE__ */ new Set([">", ">=", "<", "<="]);
6250
+ var ORDERING_RULE_OPS = /* @__PURE__ */ new Set([
6251
+ "greater_than",
6252
+ "greater_than_or_equal",
6253
+ "less_than",
6254
+ "less_than_or_equal",
6255
+ "before",
6256
+ "after"
6257
+ ]);
6258
+ var MAX_DEPTH = 32;
6259
+ function isPlainObject(v) {
6260
+ return !!v && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date);
6261
+ }
6262
+ function finding(where, path, preset, operator) {
6263
+ return {
6264
+ severity: "error",
6265
+ rule: FILTER_PRESET_COMPARAND,
6266
+ where,
6267
+ path,
6268
+ message: (0, import_data8.bareDateRangePresetComparandMessage)(preset, operator),
6269
+ hint: "Presets belong to the dashboard date-filter bar (dateRange.defaultRange, a date global filter's defaultValue). In a filter comparand, write the {date-macro} window the message names, or an ISO date."
6270
+ };
6271
+ }
6272
+ function judgeConditionNode(node, path, where, out, depth) {
6273
+ if (depth > MAX_DEPTH) return;
6274
+ for (const [key, value] of Object.entries(node)) {
6275
+ const here = `${path}.${key}`;
6276
+ if (key === "$and" || key === "$or") {
6277
+ if (Array.isArray(value)) {
6278
+ value.forEach((arm, i) => {
6279
+ if (isPlainObject(arm)) judgeConditionNode(arm, `${here}[${i}]`, where, out, depth + 1);
6280
+ });
6281
+ }
6282
+ continue;
6283
+ }
6284
+ if (key === "$not") {
6285
+ if (isPlainObject(value)) judgeConditionNode(value, here, where, out, depth + 1);
6286
+ continue;
6287
+ }
6288
+ if (key.startsWith("$")) continue;
6289
+ if (!isPlainObject(value)) continue;
6290
+ const hasOps = Object.keys(value).some((k) => k.startsWith("$"));
6291
+ if (!hasOps) {
6292
+ judgeConditionNode(value, here, where, out, depth + 1);
6293
+ continue;
6294
+ }
6295
+ for (const [op, comparand] of Object.entries(value)) {
6296
+ if (ORDERING_DOLLAR_OPS.has(op) && (0, import_data8.isDateRangePresetName)(comparand)) {
6297
+ out.push(finding(where, `${here}.${op}`, comparand, op));
6298
+ continue;
6299
+ }
6300
+ if (op === "$between" && Array.isArray(comparand)) {
6301
+ comparand.forEach((endpoint, i) => {
6302
+ if ((0, import_data8.isDateRangePresetName)(endpoint)) {
6303
+ out.push(finding(where, `${here}.${op}[${i}]`, endpoint, op));
6304
+ }
6305
+ });
6306
+ }
6307
+ }
6308
+ }
6309
+ }
6310
+ function judgeFilterRule(rule, path, where, out) {
6311
+ const operator = (0, import_ui3.normalizeFilterOperator)(rule.operator);
6312
+ if (typeof operator !== "string") return;
6313
+ const value = rule.value;
6314
+ if (ORDERING_RULE_OPS.has(operator) && (0, import_data8.isDateRangePresetName)(value)) {
6315
+ out.push(finding(where, `${path}.value`, value, operator));
6316
+ return;
6317
+ }
6318
+ if (operator === "between" && Array.isArray(value)) {
6319
+ value.forEach((endpoint, i) => {
6320
+ if ((0, import_data8.isDateRangePresetName)(endpoint)) {
6321
+ out.push(finding(where, `${path}.value[${i}]`, endpoint, operator));
6322
+ }
6323
+ });
6324
+ }
6325
+ }
6326
+ function judgeTriple(triple, path, where, out) {
6327
+ const op = triple[1];
6328
+ if (typeof op !== "string") return;
6329
+ const canonical = (0, import_data8.canonicalAstOperator)(op);
6330
+ const value = triple[2];
6331
+ if (ORDERING_INFIX_OPS.has(canonical) && (0, import_data8.isDateRangePresetName)(value)) {
6332
+ out.push(finding(where, `${path}[2]`, value, op));
6333
+ return;
6334
+ }
6335
+ if (canonical === "between" && Array.isArray(value)) {
6336
+ value.forEach((endpoint, i) => {
6337
+ if ((0, import_data8.isDateRangePresetName)(endpoint)) {
6338
+ out.push(finding(where, `${path}[2][${i}]`, endpoint, op));
6339
+ }
6340
+ });
6341
+ }
6342
+ }
6343
+ function judgeFilterValue(node, path, where, out, depth) {
6344
+ if (depth > MAX_DEPTH) return;
6345
+ if (Array.isArray(node)) {
6346
+ if (typeof node[0] === "string" && typeof node[1] === "string" && !["and", "or"].includes(node[0].toLowerCase()) && import_data8.VALID_AST_OPERATORS.has(node[1].toLowerCase())) {
6347
+ judgeTriple(node, path, where, out);
6348
+ return;
6349
+ }
6350
+ node.forEach((member, i) => {
6351
+ if (typeof member === "string") return;
6352
+ judgeFilterValue(member, `${path}[${i}]`, where, out, depth + 1);
6353
+ });
6354
+ return;
6355
+ }
6356
+ if (!isPlainObject(node)) return;
6357
+ if (typeof node.field === "string" && typeof node.operator === "string") {
6358
+ judgeFilterRule(node, path, where, out);
6359
+ return;
6360
+ }
6361
+ judgeConditionNode(node, path, where, out, depth);
6362
+ }
6363
+ function validatePresetComparands(stack) {
6364
+ if (!stack || typeof stack !== "object") return [];
6365
+ const out = [];
6366
+ walkAuthoredFilters(stack, PRESET_COMPARAND_SURFACES, ({ value, path, where }) => {
6367
+ judgeFilterValue(value, path, where, out, 0);
6368
+ });
6369
+ return out;
6370
+ }
6371
+
6372
+ // src/validate-empty-combinators.ts
6373
+ var import_data9 = require("@objectstack/spec/data");
6056
6374
  var FILTER_EMPTY_COMBINATOR = "filter-empty-combinator";
6057
6375
  var FILTER_EMPTY_NODE = "filter-empty-node";
6058
6376
  var EMPTY_COMBINATOR_SURFACES = [
@@ -6071,12 +6389,12 @@ function isFilterNode(value) {
6071
6389
  return proto === Object.prototype || proto === null;
6072
6390
  }
6073
6391
  var VERDICT_OF = {
6074
- $and: (0, import_data8.reduceFilterVerdict)({ $and: [] }),
6075
- $or: (0, import_data8.reduceFilterVerdict)({ $or: [] }),
6076
- $not: (0, import_data8.reduceFilterVerdict)({ $not: {} }),
6077
- node: (0, import_data8.reduceFilterVerdict)({}),
6392
+ $and: (0, import_data9.reduceFilterVerdict)({ $and: [] }),
6393
+ $or: (0, import_data9.reduceFilterVerdict)({ $or: [] }),
6394
+ $not: (0, import_data9.reduceFilterVerdict)({ $not: {} }),
6395
+ node: (0, import_data9.reduceFilterVerdict)({}),
6078
6396
  /** One TRUE disjunct absorbs its `$or`: the sibling branches stop mattering. */
6079
- orWithEmptyBranch: (0, import_data8.reduceFilterVerdict)({ $or: [{ status: "open" }, {}] })
6397
+ orWithEmptyBranch: (0, import_data9.reduceFilterVerdict)({ $or: [{ status: "open" }, {}] })
6080
6398
  };
6081
6399
  function rows(verdict) {
6082
6400
  if (verdict === "true") return "matches EVERY row";
@@ -6431,7 +6749,7 @@ function validateNavTargetRefs(stack) {
6431
6749
  }
6432
6750
 
6433
6751
  // src/validate-nav-object-servability.ts
6434
- var import_data9 = require("@objectstack/spec/data");
6752
+ var import_data10 = require("@objectstack/spec/data");
6435
6753
  var NAV_OBJECT_UNSERVABLE = "nav-object-unservable";
6436
6754
  var isRec15 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
6437
6755
  function asArray27(v) {
@@ -6467,7 +6785,7 @@ function validateNavObjectServability(stack) {
6467
6785
  if (nav.type === "object") {
6468
6786
  const target = strName10(nav.objectName);
6469
6787
  const declared = target && !isInterpolated3(target) ? ownEnable.get(target) : void 0;
6470
- if (target && declared && !(0, import_data9.canServeApiOperation)(declared.enable, "list")) {
6788
+ if (target && declared && !(0, import_data10.canServeApiOperation)(declared.enable, "list")) {
6471
6789
  const enable = isRec15(declared.enable) ? declared.enable : {};
6472
6790
  const apiDisabled = enable.apiEnabled === false;
6473
6791
  const condition = apiDisabled ? "`enable.apiEnabled: false`" : "`enable.apiMethods` does not grant `list`" + (Array.isArray(enable.apiMethods) ? ` (declared: ${enable.apiMethods.length === 0 ? "[] \u2014 deny-all" : enable.apiMethods.map((m) => `\`${String(m)}\``).join(", ")})` : "");
@@ -6498,6 +6816,217 @@ function validateNavObjectServability(stack) {
6498
6816
  return findings;
6499
6817
  }
6500
6818
 
6819
+ // src/validate-sortable-fields.ts
6820
+ var import_data11 = require("@objectstack/spec/data");
6821
+ var SORT_FIELD_UNKNOWN = "sort-field-unknown";
6822
+ var SORT_FIELD_UNSORTABLE = "sort-field-unsortable";
6823
+ var SORT_FIELD_UNPROVISIONED = "sort-field-unprovisioned";
6824
+ function isRec16(v) {
6825
+ return !!v && typeof v === "object" && !Array.isArray(v);
6826
+ }
6827
+ function strName11(v) {
6828
+ return typeof v === "string" && v.length > 0 ? v : void 0;
6829
+ }
6830
+ function readSortKeys(declared) {
6831
+ const fromShorthand = (raw, at) => {
6832
+ const trimmed = raw.trim();
6833
+ if (!trimmed) return void 0;
6834
+ const bare = trimmed.startsWith("-") ? trimmed.slice(1).trim() : trimmed.split(/\s+/)[0];
6835
+ return bare ? { field: bare, at } : void 0;
6836
+ };
6837
+ if (typeof declared === "string") {
6838
+ return declared.split(",").map((part, i) => fromShorthand(part, declared.includes(",") ? `[${i}]` : "")).filter((k) => !!k);
6839
+ }
6840
+ if (Array.isArray(declared)) {
6841
+ const keys = [];
6842
+ for (let i = 0; i < declared.length; i++) {
6843
+ const el = declared[i];
6844
+ if (typeof el === "string") {
6845
+ const k = fromShorthand(el, `[${i}]`);
6846
+ if (k) keys.push(k);
6847
+ continue;
6848
+ }
6849
+ if (!isRec16(el)) continue;
6850
+ const field = strName11(el.field);
6851
+ if (field) keys.push({ field: field.trim(), at: `[${i}]` });
6852
+ }
6853
+ return keys;
6854
+ }
6855
+ return [];
6856
+ }
6857
+ function suggest4(target, known) {
6858
+ let best;
6859
+ let bestScore = Infinity;
6860
+ for (const candidate of known) {
6861
+ const d = distance3(target, candidate);
6862
+ if (d < bestScore) {
6863
+ bestScore = d;
6864
+ best = candidate;
6865
+ }
6866
+ }
6867
+ const limit = Math.max(2, Math.floor(target.length / 3));
6868
+ return best && bestScore <= limit ? ` Did you mean "${best}"?` : "";
6869
+ }
6870
+ function distance3(a, b) {
6871
+ const m = a.length;
6872
+ const n = b.length;
6873
+ if (m === 0) return n;
6874
+ if (n === 0) return m;
6875
+ let prev = Array.from({ length: n + 1 }, (_, j) => j);
6876
+ for (let i = 1; i <= m; i++) {
6877
+ const curr = [i, ...new Array(n).fill(0)];
6878
+ for (let j = 1; j <= n; j++) {
6879
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
6880
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
6881
+ }
6882
+ prev = curr;
6883
+ }
6884
+ return prev[n];
6885
+ }
6886
+ function checkSortDeclaration(declared, objectName, fieldsByObject, where, path, subject, unprovisionedAnchors) {
6887
+ const findings = [];
6888
+ if (declared === void 0 || declared === null) return findings;
6889
+ if (!objectName) return findings;
6890
+ if (!fieldsByObject.has(objectName)) return findings;
6891
+ const target = fieldsByObject.get(objectName);
6892
+ if (!target) return findings;
6893
+ const known = target.names;
6894
+ const anchors = unprovisionedAnchors?.get(objectName);
6895
+ for (const key of readSortKeys(declared)) {
6896
+ const name = key.field;
6897
+ const head = name.split(".")[0];
6898
+ if (SYSTEM_FIELDS.has(head)) {
6899
+ if (!name.includes(".") && anchors?.has(head)) {
6900
+ findings.push({
6901
+ severity: "warning",
6902
+ rule: SORT_FIELD_UNPROVISIONED,
6903
+ where,
6904
+ path: `${path}${key.at}`,
6905
+ message: `${subject} orders by "${name}", which resolves on object "${objectName}", but ${unprovisionedAnchorCause(objectName, name)} \u2014 so the ORDER BY reaches the driver, finds no column, and is dropped. Measured on a federated object over a real remote table, 'asc' and 'desc' return BYTE-IDENTICAL row order under a 200 while the same query on a real column reverses. Unlike this rule's other two verdicts nothing refuses it: the REST ingress (#6994) and the engine (#7095) both judge only 'formula', so the view's FIRST fetch \u2014 and every fetch after it \u2014 silently answers in an arbitrary order, which 'limit'/'offset' then slice into an arbitrary page.`,
6906
+ hint: unprovisionedAnchorHint(objectName, name)
6907
+ });
6908
+ }
6909
+ continue;
6910
+ }
6911
+ if (!known.has(head)) {
6912
+ const dotted = name.includes(".");
6913
+ findings.push({
6914
+ severity: "error",
6915
+ rule: SORT_FIELD_UNKNOWN,
6916
+ where,
6917
+ path: `${path}${key.at}`,
6918
+ message: `${subject} orders by "${name}", which is not a field on object "${objectName}". The runtime refuses the sort rather than dropping it: every load of this view answers 400 INVALID_SORT (#6994), because a sort is the view's FIRST fetch and not an optional interaction.` + (dotted ? "" : suggest4(head, known)),
6919
+ hint: (dotted ? `'sort' reaches only whole columns of "${objectName}" itself, never a related record's column \u2014 denormalise the value onto "${objectName}" (a stored field, written when the source changes) and sort by that. ` : `Fix the name, or add "${name}" to ${objectName}.fields. `) + (known.size > 0 ? `Object fields: ${[...known].sort().join(", ")}.` : "")
6920
+ });
6921
+ continue;
6922
+ }
6923
+ const meta = target.fields[name];
6924
+ if ((0, import_data11.isVirtualSearchField)(meta)) {
6925
+ const vtype = meta?.type;
6926
+ findings.push({
6927
+ severity: "error",
6928
+ rule: SORT_FIELD_UNSORTABLE,
6929
+ where,
6930
+ path: `${path}${key.at}`,
6931
+ message: `${subject} orders by "${name}" on object "${objectName}", a virtual '${vtype}' field: its value is computed on read and never stored, so no driver materialises a column to ORDER BY. Measured, an unrefused sort on one returns 'asc' and 'desc' in byte-identical order \u2014 the rows carry the values they were asked to be ordered by, unordered, under a success.`,
6932
+ hint: `Denormalise the value onto "${objectName}" (a stored field, written when the source changes) and sort by that, or drop "${name}" from this sort. At runtime both doors now refuse it with 400 INVALID_SORT \u2014 the REST ingress (#6994) and the engine itself (#7095) \u2014 so the declaration breaks the view's first fetch, and every fetch after it.`
6933
+ });
6934
+ }
6935
+ }
6936
+ return findings;
6937
+ }
6938
+ function validateSortableFields(stack) {
6939
+ const findings = [];
6940
+ if (!isRec16(stack)) return findings;
6941
+ const objects = Array.isArray(stack.objects) ? stack.objects : isRec16(stack.objects) ? Object.entries(stack.objects).map(([name, def]) => ({ name, ...def })) : [];
6942
+ const fieldsByObject = indexObjectSearchTargets(stack);
6943
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
6944
+ const check = (declared, objectName, where, path, subject) => {
6945
+ findings.push(
6946
+ ...checkSortDeclaration(
6947
+ declared,
6948
+ objectName,
6949
+ fieldsByObject,
6950
+ where,
6951
+ path,
6952
+ subject,
6953
+ unprovisionedAnchors
6954
+ )
6955
+ );
6956
+ };
6957
+ for (let oi = 0; oi < objects.length; oi++) {
6958
+ const obj = objects[oi];
6959
+ if (!isRec16(obj)) continue;
6960
+ const objName = strName11(obj.name);
6961
+ const label2 = objName ? `object "${objName}"` : `objects[${oi}]`;
6962
+ if (isRec16(obj.listViews)) {
6963
+ for (const [key, lv] of Object.entries(obj.listViews)) {
6964
+ if (!isRec16(lv)) continue;
6965
+ check(
6966
+ lv.sort,
6967
+ // A built-in list view belongs to its object; an inline `data.object`
6968
+ // may still retarget it (ADR-0047 allows the explicit binding).
6969
+ listViewObject2(lv) ?? objName,
6970
+ `${label2} \u203A listViews.${key}`,
6971
+ `objects[${oi}].listViews.${key}.sort`,
6972
+ "list-view sort"
6973
+ );
6974
+ }
6975
+ }
6976
+ }
6977
+ const views = Array.isArray(stack.views) ? stack.views : [];
6978
+ for (let vi = 0; vi < views.length; vi++) {
6979
+ const view = views[vi];
6980
+ if (!isRec16(view)) continue;
6981
+ const viewLabel2 = strName11(view.name) ?? strName11(view.objectName) ?? `#${vi}`;
6982
+ const viewObject = strName11(view.objectName) ?? strName11(view.object);
6983
+ if (view.viewKind === "list" && !isRec16(view.config)) {
6984
+ check(
6985
+ view.sort,
6986
+ listViewObject2(view) ?? viewObject,
6987
+ `view "${viewLabel2}" (flattened list overlay)`,
6988
+ `views[${vi}].sort`,
6989
+ "list-view sort"
6990
+ );
6991
+ }
6992
+ if (view.viewKind === "list" && isRec16(view.config)) {
6993
+ check(
6994
+ view.config.sort,
6995
+ listViewObject2(view.config) ?? viewObject,
6996
+ `view "${viewLabel2}" (ViewItem record)`,
6997
+ `views[${vi}].config.sort`,
6998
+ "list-view sort"
6999
+ );
7000
+ }
7001
+ if (isRec16(view.list)) {
7002
+ check(
7003
+ view.list.sort,
7004
+ listViewObject2(view.list) ?? viewObject,
7005
+ `view "${viewLabel2}" \u203A list`,
7006
+ `views[${vi}].list.sort`,
7007
+ "list-view sort"
7008
+ );
7009
+ }
7010
+ if (isRec16(view.listViews)) {
7011
+ for (const [key, lv] of Object.entries(view.listViews)) {
7012
+ if (!isRec16(lv)) continue;
7013
+ check(
7014
+ lv.sort,
7015
+ listViewObject2(lv) ?? viewObject,
7016
+ `view "${viewLabel2}" \u203A listViews.${key}`,
7017
+ `views[${vi}].listViews.${key}.sort`,
7018
+ "list-view sort"
7019
+ );
7020
+ }
7021
+ }
7022
+ }
7023
+ return findings;
7024
+ }
7025
+ function listViewObject2(listView) {
7026
+ const data = listView.data;
7027
+ return isRec16(data) ? strName11(data.object) : void 0;
7028
+ }
7029
+
6501
7030
  // src/validate-action-name-refs.ts
6502
7031
  var ACTION_NAME_UNDEFINED = "action-name-undefined";
6503
7032
  function asArray28(v) {
@@ -6507,13 +7036,13 @@ function asArray28(v) {
6507
7036
  }
6508
7037
  return [];
6509
7038
  }
6510
- function strName11(v) {
7039
+ function strName12(v) {
6511
7040
  return typeof v === "string" && v.length > 0 ? v : void 0;
6512
7041
  }
6513
7042
  function strList(v) {
6514
7043
  return Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.length > 0) : [];
6515
7044
  }
6516
- function distance3(a, b) {
7045
+ function distance4(a, b) {
6517
7046
  const m = a.length;
6518
7047
  const n = b.length;
6519
7048
  if (m === 0) return n;
@@ -6529,11 +7058,11 @@ function distance3(a, b) {
6529
7058
  }
6530
7059
  return prev[n];
6531
7060
  }
6532
- function suggest4(target, known) {
7061
+ function suggest5(target, known) {
6533
7062
  let best;
6534
7063
  let bestScore = Infinity;
6535
7064
  for (const candidate of known) {
6536
- const d = distance3(target, candidate);
7065
+ const d = distance4(target, candidate);
6537
7066
  if (d < bestScore) {
6538
7067
  bestScore = d;
6539
7068
  best = candidate;
@@ -6545,13 +7074,13 @@ function suggest4(target, known) {
6545
7074
  function collectActionNames(stack) {
6546
7075
  const names = /* @__PURE__ */ new Set();
6547
7076
  for (const action of asArray28(stack.actions)) {
6548
- const n = strName11(action?.name);
7077
+ const n = strName12(action?.name);
6549
7078
  if (n) names.add(n);
6550
7079
  }
6551
7080
  for (const obj of asArray28(stack.objects)) {
6552
7081
  if (!obj || typeof obj !== "object") continue;
6553
7082
  for (const action of asArray28(obj.actions)) {
6554
- const n = strName11(action?.name);
7083
+ const n = strName12(action?.name);
6555
7084
  if (n) names.add(n);
6556
7085
  }
6557
7086
  }
@@ -6568,7 +7097,7 @@ function validateActionNameRefs(stack) {
6568
7097
  rule: ACTION_NAME_UNDEFINED,
6569
7098
  where,
6570
7099
  path,
6571
- message: `${surface} names action "${name}", which is defined by no action in this stack (neither \`stack.actions\` nor any object's \`actions\`). The button renders and does nothing when clicked \u2014 a dead affordance the runtime cannot dispatch.` + suggest4(name, known),
7100
+ message: `${surface} names action "${name}", which is defined by no action in this stack (neither \`stack.actions\` nor any object's \`actions\`). The button renders and does nothing when clicked \u2014 a dead affordance the runtime cannot dispatch.` + suggest5(name, known),
6572
7101
  hint: `Define an action named "${name}" (in \`stack.actions\` or the object's \`actions\`) ${placement}, remove the reference, or ignore this if the action is contributed by another installed package.` + (known.size > 0 ? ` Defined actions: ${[...known].sort().join(", ")}.` : "")
6573
7102
  });
6574
7103
  };
@@ -6594,7 +7123,7 @@ function validateActionNameRefs(stack) {
6594
7123
  if (!def || typeof def !== "object") continue;
6595
7124
  if (def.execution !== "aggregate") continue;
6596
7125
  if (def.actionDef !== void 0) continue;
6597
- const name = strName11(def.name);
7126
+ const name = strName12(def.name);
6598
7127
  if (!name) continue;
6599
7128
  check(
6600
7129
  name,
@@ -6609,7 +7138,7 @@ function validateActionNameRefs(stack) {
6609
7138
  for (let vi = 0; vi < views.length; vi++) {
6610
7139
  const view = views[vi];
6611
7140
  if (!view || typeof view !== "object") continue;
6612
- const viewName = strName11(view.name) ?? strName11(view.object) ?? `#${vi}`;
7141
+ const viewName = strName12(view.name) ?? strName12(view.object) ?? `#${vi}`;
6613
7142
  const owner = `view "${viewName}"`;
6614
7143
  checkListContainer(view.list, owner, "list", `views[${vi}].list`);
6615
7144
  const listViews = view.listViews;
@@ -6625,7 +7154,7 @@ function validateActionNameRefs(stack) {
6625
7154
  if (!obj || typeof obj !== "object") continue;
6626
7155
  const objListViews = obj.listViews;
6627
7156
  if (!objListViews || typeof objListViews !== "object" || Array.isArray(objListViews)) continue;
6628
- const owner = `object "${strName11(obj.name) ?? `#${oi}`}"`;
7157
+ const owner = `object "${strName12(obj.name) ?? `#${oi}`}"`;
6629
7158
  for (const [key, lv] of Object.entries(objListViews)) {
6630
7159
  checkListContainer(lv, owner, `listViews.${key}`, `objects[${oi}].listViews.${key}`);
6631
7160
  }
@@ -6634,7 +7163,7 @@ function validateActionNameRefs(stack) {
6634
7163
  for (let pi = 0; pi < pages.length; pi++) {
6635
7164
  const page = pages[pi];
6636
7165
  if (!page || typeof page !== "object") continue;
6637
- const pageName = strName11(page.name) ?? `#${pi}`;
7166
+ const pageName = strName12(page.name) ?? `#${pi}`;
6638
7167
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
6639
7168
  const props = component.properties;
6640
7169
  if (!props || typeof props !== "object") continue;
@@ -6642,7 +7171,7 @@ function validateActionNameRefs(stack) {
6642
7171
  for (let ai = 0; ai < names.length; ai++) {
6643
7172
  check(
6644
7173
  names[ai],
6645
- `page "${pageName}" \xB7 component "${strName11(component.type) ?? "?"}"`,
7174
+ `page "${pageName}" \xB7 component "${strName12(component.type) ?? "?"}"`,
6646
7175
  `${path}.properties.actionNames[${ai}]`,
6647
7176
  "Quick-actions bar"
6648
7177
  );
@@ -6653,7 +7182,7 @@ function validateActionNameRefs(stack) {
6653
7182
  for (let ai = 0; ai < apps.length; ai++) {
6654
7183
  const app = apps[ai];
6655
7184
  if (!app || typeof app !== "object") continue;
6656
- const appName = strName11(app.name) ?? `#${ai}`;
7185
+ const appName = strName12(app.name) ?? `#${ai}`;
6657
7186
  const walkNav = (items, basePath) => {
6658
7187
  const navItems = asArray28(items);
6659
7188
  for (let ni = 0; ni < navItems.length; ni++) {
@@ -6661,20 +7190,20 @@ function validateActionNameRefs(stack) {
6661
7190
  if (!nav || typeof nav !== "object") continue;
6662
7191
  const navPath = `${basePath}[${ni}]`;
6663
7192
  const actionDef = nav.actionDef;
6664
- const actionName = strName11(actionDef?.actionName);
7193
+ const actionName = strName12(actionDef?.actionName);
6665
7194
  if (nav.type === "action" && actionName) {
6666
7195
  check(
6667
7196
  actionName,
6668
- `app "${appName}" \xB7 nav "${strName11(nav.id) ?? `#${ni}`}"`,
7197
+ `app "${appName}" \xB7 nav "${strName12(nav.id) ?? `#${ni}`}"`,
6669
7198
  `${navPath}.actionDef.actionName`,
6670
7199
  "Navigation action item"
6671
7200
  );
6672
7201
  }
6673
- const runAction = strName11(nav.runAction);
7202
+ const runAction = strName12(nav.runAction);
6674
7203
  if (nav.type === "object" && runAction) {
6675
7204
  check(
6676
7205
  runAction,
6677
- `app "${appName}" \xB7 nav "${strName11(nav.id) ?? `#${ni}`}"`,
7206
+ `app "${appName}" \xB7 nav "${strName12(nav.id) ?? `#${ni}`}"`,
6678
7207
  `${navPath}.runAction`,
6679
7208
  "Navigation deep-link auto-run"
6680
7209
  );
@@ -6700,7 +7229,7 @@ function asArray29(v) {
6700
7229
  }
6701
7230
  return [];
6702
7231
  }
6703
- function strName12(v) {
7232
+ function strName13(v) {
6704
7233
  return typeof v === "string" && v.length > 0 ? v : void 0;
6705
7234
  }
6706
7235
  function strList2(v) {
@@ -6715,7 +7244,7 @@ function collectNamePlacedActions(stack) {
6715
7244
  for (const n of strList2(list3[key])) placed.add(n);
6716
7245
  }
6717
7246
  for (const def of asArray29(list3.bulkActionDefs)) {
6718
- const n = strName12(def?.name);
7247
+ const n = strName13(def?.name);
6719
7248
  if (n) placed.add(n);
6720
7249
  }
6721
7250
  };
@@ -6741,7 +7270,7 @@ function validateActionLocations(stack) {
6741
7270
  const check = (action, path) => {
6742
7271
  if (!action || typeof action !== "object") return;
6743
7272
  if ("locations" in action) return;
6744
- const name = strName12(action.name);
7273
+ const name = strName13(action.name);
6745
7274
  if (!name) return;
6746
7275
  if (namePlaced.has(name)) return;
6747
7276
  findings.push({
@@ -6766,14 +7295,14 @@ function validateActionLocations(stack) {
6766
7295
  }
6767
7296
 
6768
7297
  // src/validate-component-props.ts
6769
- var import_ui3 = require("@objectstack/spec/ui");
7298
+ var import_ui4 = require("@objectstack/spec/ui");
6770
7299
  var import_spec3 = require("@objectstack/spec");
6771
7300
  var COMPONENT_PROPS_UNKNOWN_KEY = "component-props-unknown-key";
6772
7301
  var COMPONENT_PROPS_INVALID = "component-props-invalid";
6773
- function isRec16(v) {
7302
+ function isRec17(v) {
6774
7303
  return !!v && typeof v === "object" && !Array.isArray(v);
6775
7304
  }
6776
- function strName13(v) {
7305
+ function strName14(v) {
6777
7306
  return typeof v === "string" && v.length > 0 ? v : void 0;
6778
7307
  }
6779
7308
  function asArray30(v) {
@@ -6783,12 +7312,12 @@ function asArray30(v) {
6783
7312
  }
6784
7313
  return [];
6785
7314
  }
6786
- var PROPS_SCHEMAS = import_ui3.ComponentPropsMap;
7315
+ var PROPS_SCHEMAS = import_ui4.ComponentPropsMap;
6787
7316
  var DATASOURCE_SUPPLIED_PROP = "object";
6788
7317
  function suppliedByDataSource(issue, component) {
6789
7318
  if (issue.path.length !== 1 || issue.path[0] !== DATASOURCE_SUPPLIED_PROP) return false;
6790
- const dataSource = isRec16(component.dataSource) ? component.dataSource : void 0;
6791
- return strName13(dataSource?.object) !== void 0;
7319
+ const dataSource = isRec17(component.dataSource) ? component.dataSource : void 0;
7320
+ return strName14(dataSource?.object) !== void 0;
6792
7321
  }
6793
7322
  function unrecognizedKeysFromUnionArm(issue) {
6794
7323
  if (issue.code !== "invalid_union") return void 0;
@@ -6805,18 +7334,18 @@ function unrecognizedKeysFromUnionArm(issue) {
6805
7334
  }
6806
7335
  function validateComponentProps(stack) {
6807
7336
  const findings = [];
6808
- if (!isRec16(stack)) return findings;
7337
+ if (!isRec17(stack)) return findings;
6809
7338
  const pages = asArray30(stack.pages);
6810
7339
  for (let pi = 0; pi < pages.length; pi++) {
6811
7340
  const page = pages[pi];
6812
- if (!isRec16(page)) continue;
6813
- const pageName = strName13(page.name) ?? `#${pi}`;
7341
+ if (!isRec17(page)) continue;
7342
+ const pageName = strName14(page.name) ?? `#${pi}`;
6814
7343
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
6815
- const type = strName13(component.type);
7344
+ const type = strName14(component.type);
6816
7345
  if (!type) continue;
6817
7346
  const schema = PROPS_SCHEMAS[type];
6818
7347
  if (!schema) continue;
6819
- const props = isRec16(component.properties) ? component.properties : void 0;
7348
+ const props = isRec17(component.properties) ? component.properties : void 0;
6820
7349
  if (!props) continue;
6821
7350
  const where = `page "${pageName}" \xB7 ${type}`;
6822
7351
  const base = `${path}.properties`;
@@ -6888,16 +7417,16 @@ function asArray31(v) {
6888
7417
  }
6889
7418
  return [];
6890
7419
  }
6891
- function strName14(v) {
7420
+ function strName15(v) {
6892
7421
  return typeof v === "string" && v.length > 0 ? v : void 0;
6893
7422
  }
6894
7423
  function strList3(v) {
6895
7424
  return Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.length > 0) : [];
6896
7425
  }
6897
- function isRec17(v) {
7426
+ function isRec18(v) {
6898
7427
  return !!v && typeof v === "object" && !Array.isArray(v);
6899
7428
  }
6900
- function distance4(a, b) {
7429
+ function distance5(a, b) {
6901
7430
  const m = a.length;
6902
7431
  const n = b.length;
6903
7432
  if (m === 0) return n;
@@ -6913,11 +7442,11 @@ function distance4(a, b) {
6913
7442
  }
6914
7443
  return prev[n];
6915
7444
  }
6916
- function suggest5(target, known) {
7445
+ function suggest6(target, known) {
6917
7446
  let best;
6918
7447
  let bestScore = Infinity;
6919
7448
  for (const c of known) {
6920
- const d = distance4(target, c);
7449
+ const d = distance5(target, c);
6921
7450
  if (d < bestScore) {
6922
7451
  bestScore = d;
6923
7452
  best = c;
@@ -6933,16 +7462,16 @@ function list2(names) {
6933
7462
  function indexDatasets(stack) {
6934
7463
  const out = /* @__PURE__ */ new Map();
6935
7464
  for (const ds of asArray31(stack.datasets)) {
6936
- const name = strName14(ds.name);
7465
+ const name = strName15(ds.name);
6937
7466
  if (!name) continue;
6938
7467
  const dimensions = /* @__PURE__ */ new Set();
6939
7468
  for (const d of asArray31(ds.dimensions)) {
6940
- const n = strName14(d.name);
7469
+ const n = strName15(d.name);
6941
7470
  if (n) dimensions.add(n);
6942
7471
  }
6943
7472
  const measures = /* @__PURE__ */ new Set();
6944
7473
  for (const m of asArray31(ds.measures)) {
6945
- const n = strName14(m.name);
7474
+ const n = strName15(m.name);
6946
7475
  if (n) measures.add(n);
6947
7476
  }
6948
7477
  out.set(name, { dimensions, measures });
@@ -6965,7 +7494,7 @@ function validateChartBindings(stack) {
6965
7494
  where: binding.where,
6966
7495
  path: `${binding.path}.dataset`,
6967
7496
  message: `binds dataset "${dsName}", which resolves to no declared dataset \u2014 the chart has no data to render.`,
6968
- hint: `Declared datasets: ${list2(datasets.keys())}.${suggest5(dsName, datasets.keys())} Define it with defineDataset() or fix the reference (ADR-0021).`
7497
+ hint: `Declared datasets: ${list2(datasets.keys())}.${suggest6(dsName, datasets.keys())} Define it with defineDataset() or fix the reference (ADR-0021).`
6969
7498
  });
6970
7499
  return;
6971
7500
  }
@@ -6977,7 +7506,7 @@ function validateChartBindings(stack) {
6977
7506
  where: binding.where,
6978
7507
  path,
6979
7508
  message: `"${name}" is not a dimension declared by dataset "${dsName}". Post-ADR-0021 result rows are keyed by DIMENSION NAME, not the base field, so this axis renders with no categories.`,
6980
- hint: `Dataset dimensions: ${list2(ds.dimensions)}.${suggest5(name, ds.dimensions)} Declare the dimension on the dataset, or bind an existing one.`
7509
+ hint: `Dataset dimensions: ${list2(ds.dimensions)}.${suggest6(name, ds.dimensions)} Declare the dimension on the dataset, or bind an existing one.`
6981
7510
  });
6982
7511
  };
6983
7512
  const measureRef = (name, path, selected2) => {
@@ -6988,7 +7517,7 @@ function validateChartBindings(stack) {
6988
7517
  where: binding.where,
6989
7518
  path,
6990
7519
  message: `"${name}" is not a measure declared by dataset "${dsName}". Post-ADR-0021 result rows are keyed by MEASURE NAME (e.g. "sum_amount"), not the base field (e.g. "amount"), so this series comes back empty.`,
6991
- hint: `Dataset measures: ${list2(ds.measures)}.${suggest5(name, ds.measures)} Declare the measure on the dataset, or bind an existing one.`
7520
+ hint: `Dataset measures: ${list2(ds.measures)}.${suggest6(name, ds.measures)} Declare the measure on the dataset, or bind an existing one.`
6992
7521
  });
6993
7522
  return;
6994
7523
  }
@@ -7023,26 +7552,26 @@ function validateChartBindings(stack) {
7023
7552
  const reports = asArray31(stack.reports);
7024
7553
  for (let ri = 0; ri < reports.length; ri++) {
7025
7554
  const report = reports[ri];
7026
- if (!isRec17(report)) continue;
7027
- const reportName = strName14(report.name) ?? `#${ri}`;
7555
+ if (!isRec18(report)) continue;
7556
+ const reportName = strName15(report.name) ?? `#${ri}`;
7028
7557
  const checkReportChart = (chart, dataset, values, where, path) => {
7029
- if (!isRec17(chart)) return;
7558
+ if (!isRec18(chart)) return;
7030
7559
  check({
7031
7560
  dataset,
7032
7561
  // `values` is the report's measure SELECTION, not a chart ref; feeding
7033
7562
  // it in lets the yAxis "declared but not selected" check work without
7034
7563
  // reporting the selection itself twice.
7035
7564
  values: { names: values, path: `${path}.values` },
7036
- xAxis: strName14(chart.xAxis) ? { name: strName14(chart.xAxis), path: `${path}.chart.xAxis` } : void 0,
7037
- yAxis: strName14(chart.yAxis) ? { name: strName14(chart.yAxis), path: `${path}.chart.yAxis` } : void 0,
7038
- series: asArray31(chart.series).map((s, si) => ({ name: strName14(s.name), path: `${path}.chart.series[${si}].name` })).filter((s) => !!s.name),
7565
+ xAxis: strName15(chart.xAxis) ? { name: strName15(chart.xAxis), path: `${path}.chart.xAxis` } : void 0,
7566
+ yAxis: strName15(chart.yAxis) ? { name: strName15(chart.yAxis), path: `${path}.chart.yAxis` } : void 0,
7567
+ series: asArray31(chart.series).map((s, si) => ({ name: strName15(s.name), path: `${path}.chart.series[${si}].name` })).filter((s) => !!s.name),
7039
7568
  where,
7040
7569
  path: `${path}.chart`
7041
7570
  });
7042
7571
  };
7043
7572
  checkReportChart(
7044
7573
  report.chart,
7045
- strName14(report.dataset),
7574
+ strName15(report.dataset),
7046
7575
  strList3(report.values),
7047
7576
  `report "${reportName}" \xB7 chart`,
7048
7577
  `reports[${ri}]`
@@ -7050,22 +7579,22 @@ function validateChartBindings(stack) {
7050
7579
  const blocks = Array.isArray(report.blocks) ? report.blocks : [];
7051
7580
  for (let bi = 0; bi < blocks.length; bi++) {
7052
7581
  const block = blocks[bi];
7053
- if (!isRec17(block)) continue;
7582
+ if (!isRec18(block)) continue;
7054
7583
  checkReportChart(
7055
7584
  block.chart,
7056
- strName14(block.dataset),
7585
+ strName15(block.dataset),
7057
7586
  strList3(block.values),
7058
- `report "${reportName}" \xB7 block "${strName14(block.name) ?? `#${bi}`}" chart`,
7587
+ `report "${reportName}" \xB7 block "${strName15(block.name) ?? `#${bi}`}" chart`,
7059
7588
  `reports[${ri}].blocks[${bi}]`
7060
7589
  );
7061
7590
  }
7062
7591
  }
7063
7592
  const checkListChart = (container, where, path) => {
7064
- if (!isRec17(container)) return;
7593
+ if (!isRec18(container)) return;
7065
7594
  const chart = container.chart;
7066
- if (!isRec17(chart)) return;
7595
+ if (!isRec18(chart)) return;
7067
7596
  check({
7068
- dataset: strName14(chart.dataset),
7597
+ dataset: strName15(chart.dataset),
7069
7598
  dimensions: { names: strList3(chart.dimensions), path: `${path}.chart.dimensions` },
7070
7599
  values: { names: strList3(chart.values), path: `${path}.chart.values` },
7071
7600
  where,
@@ -7075,10 +7604,10 @@ function validateChartBindings(stack) {
7075
7604
  const views = asArray31(stack.views);
7076
7605
  for (let vi = 0; vi < views.length; vi++) {
7077
7606
  const view = views[vi];
7078
- if (!isRec17(view)) continue;
7079
- const viewName = strName14(view.name) ?? strName14(view.objectName) ?? `#${vi}`;
7607
+ if (!isRec18(view)) continue;
7608
+ const viewName = strName15(view.name) ?? strName15(view.objectName) ?? `#${vi}`;
7080
7609
  checkListChart(view.list, `view "${viewName}" \xB7 list chart`, `views[${vi}].list`);
7081
- if (isRec17(view.listViews)) {
7610
+ if (isRec18(view.listViews)) {
7082
7611
  for (const [key, lv] of Object.entries(view.listViews)) {
7083
7612
  checkListChart(lv, `view "${viewName}" \xB7 listViews.${key} chart`, `views[${vi}].listViews.${key}`);
7084
7613
  }
@@ -7087,8 +7616,8 @@ function validateChartBindings(stack) {
7087
7616
  const objects = asArray31(stack.objects);
7088
7617
  for (let oi = 0; oi < objects.length; oi++) {
7089
7618
  const obj = objects[oi];
7090
- if (!isRec17(obj) || !isRec17(obj.listViews)) continue;
7091
- const objName = strName14(obj.name) ?? `#${oi}`;
7619
+ if (!isRec18(obj) || !isRec18(obj.listViews)) continue;
7620
+ const objName = strName15(obj.name) ?? `#${oi}`;
7092
7621
  for (const [key, lv] of Object.entries(obj.listViews)) {
7093
7622
  checkListChart(
7094
7623
  lv,
@@ -7100,19 +7629,19 @@ function validateChartBindings(stack) {
7100
7629
  const pages = asArray31(stack.pages);
7101
7630
  for (let pi = 0; pi < pages.length; pi++) {
7102
7631
  const page = pages[pi];
7103
- if (!isRec17(page)) continue;
7104
- const pageName = strName14(page.name) ?? `#${pi}`;
7632
+ if (!isRec18(page)) continue;
7633
+ const pageName = strName15(page.name) ?? `#${pi}`;
7105
7634
  for (const { component, path } of walkPageComponents(page, `pages[${pi}]`)) {
7106
- const props = isRec17(component.properties) ? component.properties : void 0;
7107
- if (!props || !strName14(props.dataset)) continue;
7108
- const axisRefs = asArray31(props.yAxis).map((a, ai) => ({ name: strName14(a.field), path: `${path}.properties.yAxis[${ai}].field` })).filter((a) => !!a.name);
7109
- const seriesRefs = asArray31(props.series).map((s, si) => ({ name: strName14(s.name), path: `${path}.properties.series[${si}].name` })).filter((s) => !!s.name);
7635
+ const props = isRec18(component.properties) ? component.properties : void 0;
7636
+ if (!props || !strName15(props.dataset)) continue;
7637
+ const axisRefs = asArray31(props.yAxis).map((a, ai) => ({ name: strName15(a.field), path: `${path}.properties.yAxis[${ai}].field` })).filter((a) => !!a.name);
7638
+ const seriesRefs = asArray31(props.series).map((s, si) => ({ name: strName15(s.name), path: `${path}.properties.series[${si}].name` })).filter((s) => !!s.name);
7110
7639
  check({
7111
- dataset: strName14(props.dataset),
7640
+ dataset: strName15(props.dataset),
7112
7641
  dimensions: { names: strList3(props.dimensions), path: `${path}.properties.dimensions` },
7113
7642
  values: { names: strList3(props.values), path: `${path}.properties.values` },
7114
7643
  series: [...axisRefs, ...seriesRefs],
7115
- where: `page "${pageName}" \xB7 ${strName14(component.type) ?? "chart"}`,
7644
+ where: `page "${pageName}" \xB7 ${strName15(component.type) ?? "chart"}`,
7116
7645
  path: `${path}.properties`
7117
7646
  });
7118
7647
  }
@@ -7126,11 +7655,11 @@ var import_meta4 = {};
7126
7655
  var VALIDATION_RULE_REGEX_UNCOMPILABLE = "validation-rule-regex-uncompilable";
7127
7656
  var VALIDATION_RULE_SCHEMA_UNCOMPILABLE = "validation-rule-json-schema-uncompilable";
7128
7657
  var RUNTIME_AJV_OPTIONS = { allErrors: true, strict: false };
7129
- var isRec18 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7658
+ var isRec19 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7130
7659
  function asArray32(v) {
7131
- if (Array.isArray(v)) return v.filter(isRec18);
7132
- if (isRec18(v)) {
7133
- return Object.entries(v).filter(([, def]) => isRec18(def)).map(([name, def]) => ({ name, ...def }));
7660
+ if (Array.isArray(v)) return v.filter(isRec19);
7661
+ if (isRec19(v)) {
7662
+ return Object.entries(v).filter(([, def]) => isRec19(def)).map(([name, def]) => ({ name, ...def }));
7134
7663
  }
7135
7664
  return [];
7136
7665
  }
@@ -7147,7 +7676,7 @@ function loadAjv() {
7147
7676
  `@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.`
7148
7677
  );
7149
7678
  }
7150
- const ctor = isRec18(mod) && "default" in mod ? mod.default : mod;
7679
+ const ctor = isRec19(mod) && "default" in mod ? mod.default : mod;
7151
7680
  cachedAjv = ctor;
7152
7681
  return ctor;
7153
7682
  }
@@ -7162,7 +7691,7 @@ function loadAddFormats() {
7162
7691
  `@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.`
7163
7692
  );
7164
7693
  }
7165
- const plugin = isRec18(mod) && "default" in mod ? mod.default : mod;
7694
+ const plugin = isRec19(mod) && "default" in mod ? mod.default : mod;
7166
7695
  cachedAddFormats = plugin;
7167
7696
  return plugin;
7168
7697
  }
@@ -7192,13 +7721,13 @@ function flattenRules(rule, labelTrail, pathTrail, depth = 0) {
7192
7721
  if (depth >= MAX_RULE_NESTING_DEPTH) return out;
7193
7722
  for (const branch of ["then", "otherwise"]) {
7194
7723
  const nested = rule[branch];
7195
- if (isRec18(nested)) out.push(...flattenRules(nested, label2, `${path}.${branch}`, depth + 1));
7724
+ if (isRec19(nested)) out.push(...flattenRules(nested, label2, `${path}.${branch}`, depth + 1));
7196
7725
  }
7197
7726
  return out;
7198
7727
  }
7199
7728
  function walkObjectValidationRules(stack) {
7200
7729
  const walked = [];
7201
- if (!isRec18(stack)) return walked;
7730
+ if (!isRec19(stack)) return walked;
7202
7731
  for (const obj of asArray32(stack.objects)) {
7203
7732
  const objectName = typeof obj.name === "string" ? obj.name : "(unnamed object)";
7204
7733
  const validations = obj.validations;
@@ -7233,7 +7762,7 @@ function validateRuleCompilability(stack) {
7233
7762
  });
7234
7763
  }
7235
7764
  }
7236
- if (rule.type === "json_schema" && isRec18(rule.schema)) {
7765
+ if (rule.type === "json_schema" && isRec19(rule.schema)) {
7237
7766
  try {
7238
7767
  createRuntimeAjv().compile(rule.schema);
7239
7768
  } catch (err) {
@@ -7253,7 +7782,7 @@ function validateRuleCompilability(stack) {
7253
7782
 
7254
7783
  // src/validate-rule-schema-formats.ts
7255
7784
  var VALIDATION_RULE_SCHEMA_UNKNOWN_FORMAT = "validation-rule-json-schema-unknown-format";
7256
- var isRec19 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7785
+ var isRec20 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7257
7786
  var SUBSCHEMA_KEYS = [
7258
7787
  "additionalItems",
7259
7788
  "additionalProperties",
@@ -7277,7 +7806,7 @@ var SUBSCHEMA_MAP_KEYS = [
7277
7806
  var MAX_SCHEMA_WALK_DEPTH = 32;
7278
7807
  var escapePointerSegment = (segment) => segment.replace(/~/g, "~0").replace(/\//g, "~1");
7279
7808
  function collectFormatUses(schema, pointer, out, depth) {
7280
- if (!isRec19(schema)) return;
7809
+ if (!isRec20(schema)) return;
7281
7810
  if (typeof schema.format === "string") {
7282
7811
  out.push({ pointer: `${pointer}/format`, name: schema.format });
7283
7812
  }
@@ -7296,7 +7825,7 @@ function collectFormatUses(schema, pointer, out, depth) {
7296
7825
  }
7297
7826
  for (const key of SUBSCHEMA_MAP_KEYS) {
7298
7827
  const value = schema[key];
7299
- if (!isRec19(value)) continue;
7828
+ if (!isRec20(value)) continue;
7300
7829
  for (const [name, entry] of Object.entries(value)) {
7301
7830
  collectFormatUses(entry, `${pointer}/${escapePointerSegment(key)}/${escapePointerSegment(name)}`, out, depth + 1);
7302
7831
  }
@@ -7304,13 +7833,13 @@ function collectFormatUses(schema, pointer, out, depth) {
7304
7833
  const items = schema.items;
7305
7834
  if (Array.isArray(items)) {
7306
7835
  items.forEach((entry, index) => collectFormatUses(entry, `${pointer}/items/${index}`, out, depth + 1));
7307
- } else if (isRec19(items)) {
7836
+ } else if (isRec20(items)) {
7308
7837
  collectFormatUses(items, `${pointer}/items`, out, depth + 1);
7309
7838
  }
7310
7839
  const dependencies = schema.dependencies;
7311
- if (isRec19(dependencies)) {
7840
+ if (isRec20(dependencies)) {
7312
7841
  for (const [name, entry] of Object.entries(dependencies)) {
7313
- if (!isRec19(entry)) continue;
7842
+ if (!isRec20(entry)) continue;
7314
7843
  collectFormatUses(entry, `${pointer}/dependencies/${escapePointerSegment(name)}`, out, depth + 1);
7315
7844
  }
7316
7845
  }
@@ -7337,9 +7866,9 @@ function nearestRegisteredFormat(name, registered) {
7337
7866
  let best = null;
7338
7867
  let bestDistance = Number.POSITIVE_INFINITY;
7339
7868
  for (const candidate of [...registered].sort()) {
7340
- const distance7 = editDistance2(authored, candidate);
7341
- if (distance7 < bestDistance) {
7342
- bestDistance = distance7;
7869
+ const distance8 = editDistance2(authored, candidate);
7870
+ if (distance8 < bestDistance) {
7871
+ bestDistance = distance8;
7343
7872
  best = candidate;
7344
7873
  }
7345
7874
  }
@@ -7349,7 +7878,7 @@ function validateRuleSchemaFormats(stack) {
7349
7878
  const findings = [];
7350
7879
  const pending = [];
7351
7880
  for (const { rule, objectName, label: label2, where, basePath } of walkObjectValidationRules(stack)) {
7352
- if (rule.type !== "json_schema" || !isRec19(rule.schema)) continue;
7881
+ if (rule.type !== "json_schema" || !isRec20(rule.schema)) continue;
7353
7882
  const uses = [];
7354
7883
  collectFormatUses(rule.schema, "", uses, 0);
7355
7884
  for (const use of uses) pending.push({ use, where, label: label2, objectName, basePath });
@@ -7474,7 +8003,7 @@ function asArray34(v) {
7474
8003
  }
7475
8004
  return [];
7476
8005
  }
7477
- function strName15(v) {
8006
+ function strName16(v) {
7478
8007
  return typeof v === "string" && v.length > 0 ? v : void 0;
7479
8008
  }
7480
8009
  function collectNavExposures(stack) {
@@ -7483,18 +8012,18 @@ function collectNavExposures(stack) {
7483
8012
  for (let ai = 0; ai < apps.length; ai++) {
7484
8013
  const app = apps[ai];
7485
8014
  if (!app || typeof app !== "object") continue;
7486
- const appName = strName15(app.name) ?? `#${ai}`;
8015
+ const appName = strName16(app.name) ?? `#${ai}`;
7487
8016
  const walk = (items, basePath) => {
7488
8017
  const navItems = asArray34(items);
7489
8018
  for (let ni = 0; ni < navItems.length; ni++) {
7490
8019
  const nav = navItems[ni];
7491
8020
  if (!nav || typeof nav !== "object") continue;
7492
8021
  const navPath = `${basePath}[${ni}]`;
7493
- const objectName = strName15(nav.objectName);
8022
+ const objectName = strName16(nav.objectName);
7494
8023
  if (nav.type === "object" && objectName) {
7495
8024
  out.push({
7496
8025
  objectName,
7497
- where: `app "${appName}" \xB7 nav "${strName15(nav.id) ?? `#${ni}`}"`,
8026
+ where: `app "${appName}" \xB7 nav "${strName16(nav.id) ?? `#${ni}`}"`,
7498
8027
  path: `${navPath}.objectName`
7499
8028
  });
7500
8029
  }
@@ -7518,7 +8047,7 @@ function validateNavAccess(stack) {
7518
8047
  if (exposures.length === 0) return findings;
7519
8048
  const ownObjects = /* @__PURE__ */ new Set();
7520
8049
  for (const obj of asArray34(stack.objects)) {
7521
- const n = strName15(obj.name);
8050
+ const n = strName16(obj.name);
7522
8051
  if (n) ownObjects.add(n);
7523
8052
  }
7524
8053
  const readable = /* @__PURE__ */ new Set();
@@ -7551,18 +8080,18 @@ var import_spec4 = require("@objectstack/spec");
7551
8080
  var import_system4 = require("@objectstack/spec/system");
7552
8081
  var TRANSLATION_TARGET_UNKNOWN = "translation-target-unknown";
7553
8082
  var TRANSLATION_OPTION_KEY_UNKNOWN = "translation-option-key-unknown";
7554
- function isRec20(v) {
8083
+ function isRec21(v) {
7555
8084
  return !!v && typeof v === "object" && !Array.isArray(v);
7556
8085
  }
7557
8086
  function asArray35(v) {
7558
8087
  if (Array.isArray(v)) return v;
7559
- if (isRec20(v)) return Object.entries(v).map(([name, def]) => ({ name, ...isRec20(def) ? def : {} }));
8088
+ if (isRec21(v)) return Object.entries(v).map(([name, def]) => ({ name, ...isRec21(def) ? def : {} }));
7560
8089
  return [];
7561
8090
  }
7562
- function strName16(v) {
8091
+ function strName17(v) {
7563
8092
  return typeof v === "string" && v.length > 0 ? v : void 0;
7564
8093
  }
7565
- function distance5(a, b) {
8094
+ function distance6(a, b) {
7566
8095
  const m = a.length;
7567
8096
  const n = b.length;
7568
8097
  if (m === 0) return n;
@@ -7578,7 +8107,7 @@ function distance5(a, b) {
7578
8107
  }
7579
8108
  return prev[n];
7580
8109
  }
7581
- function suggest6(target, known) {
8110
+ function suggest7(target, known) {
7582
8111
  const names = [...known];
7583
8112
  const segmentMatch = names.find(
7584
8113
  (candidate) => candidate.endsWith(`_${target}`) || candidate.startsWith(`${target}_`)
@@ -7587,7 +8116,7 @@ function suggest6(target, known) {
7587
8116
  let best;
7588
8117
  let bestScore = Infinity;
7589
8118
  for (const candidate of names) {
7590
- const d = distance5(target, candidate);
8119
+ const d = distance6(target, candidate);
7591
8120
  if (d < bestScore) {
7592
8121
  bestScore = d;
7593
8122
  best = candidate;
@@ -7620,33 +8149,33 @@ function collectViewRecord(view, factsFor) {
7620
8149
  const addSections = (container, binding) => {
7621
8150
  if (!binding) return;
7622
8151
  for (const section of asArray35(container.sections)) {
7623
- const sectionName = strName16(section.name);
8152
+ const sectionName = strName17(section.name);
7624
8153
  if (sectionName) factsFor(binding).sections.add(sectionName);
7625
8154
  }
7626
8155
  };
7627
- const listBinding = isRec20(view.list) ? bindingOf(view.list) : void 0;
7628
- if (isRec20(view.list)) addView(listBinding, defaultListViewKey(listBinding, view));
7629
- addView(recordObject ?? listBinding, strName16(view.name));
8156
+ const listBinding = isRec21(view.list) ? bindingOf(view.list) : void 0;
8157
+ if (isRec21(view.list)) addView(listBinding, defaultListViewKey(listBinding, view));
8158
+ addView(recordObject ?? listBinding, strName17(view.name));
7630
8159
  const named = namedViewKeys(view);
7631
8160
  for (const family of ["listViews", "formViews"]) {
7632
8161
  const container = view[family];
7633
- if (!isRec20(container)) continue;
8162
+ if (!isRec21(container)) continue;
7634
8163
  const registryKeys = family === "listViews" ? named.list : named.form;
7635
8164
  let at = 0;
7636
8165
  for (const sub of Object.values(container)) {
7637
8166
  if (!sub || typeof sub !== "object") continue;
7638
8167
  const registryKey = registryKeys[at++];
7639
- if (!isRec20(sub)) continue;
8168
+ if (!isRec21(sub)) continue;
7640
8169
  const binding = bindingOf(sub) ?? listBinding;
7641
8170
  addView(binding, registryKey);
7642
8171
  addSections(sub, binding);
7643
8172
  }
7644
8173
  }
7645
- if (isRec20(view.form)) addSections(view.form, bindingOf(view.form) ?? listBinding);
8174
+ if (isRec21(view.form)) addSections(view.form, bindingOf(view.form) ?? listBinding);
7646
8175
  addSections(view, recordObject ?? listBinding);
7647
8176
  }
7648
8177
  function defaultListViewKey(object, container) {
7649
- if (!object || !isRec20(container.list)) return void 0;
8178
+ if (!object || !isRec21(container.list)) return void 0;
7650
8179
  const item = (0, import_spec4.expandViewContainer)(object, container).find(
7651
8180
  (i) => i.viewKind === "list" && i.isDefault
7652
8181
  );
@@ -7658,7 +8187,7 @@ function namedViewKeys(container) {
7658
8187
  const object = "probe";
7659
8188
  const prefix = `${object}.`;
7660
8189
  const bare = (name) => name.startsWith(prefix) ? name.slice(prefix.length) : name;
7661
- const countEntries = (v) => isRec20(v) ? Object.values(v).filter((e) => !!e && typeof e === "object").length : 0;
8190
+ const countEntries = (v) => isRec21(v) ? Object.values(v).filter((e) => !!e && typeof e === "object").length : 0;
7662
8191
  const listCount = countEntries(container.listViews);
7663
8192
  const formCount = countEntries(container.formViews);
7664
8193
  if (!listCount && !formCount) return { list: [], form: [] };
@@ -7676,14 +8205,14 @@ function readOptions(field) {
7676
8205
  values.add(opt);
7677
8206
  continue;
7678
8207
  }
7679
- if (!isRec20(opt)) continue;
7680
- const value = strName16(opt.value);
8208
+ if (!isRec21(opt)) continue;
8209
+ const value = strName17(opt.value);
7681
8210
  if (!value) continue;
7682
8211
  values.add(value);
7683
- const label2 = strName16(opt.label);
8212
+ const label2 = strName17(opt.label);
7684
8213
  if (label2) byLabel.set(label2.toLowerCase(), value);
7685
8214
  }
7686
- } else if (isRec20(raw)) {
8215
+ } else if (isRec21(raw)) {
7687
8216
  for (const [value, label2] of Object.entries(raw)) {
7688
8217
  values.add(value);
7689
8218
  if (typeof label2 === "string" && label2.length > 0) byLabel.set(label2.toLowerCase(), value);
@@ -7704,23 +8233,23 @@ function buildUniverse(stack) {
7704
8233
  return facts;
7705
8234
  };
7706
8235
  for (const obj of asArray35(stack.objects)) {
7707
- const objectName = strName16(obj.name);
8236
+ const objectName = strName17(obj.name);
7708
8237
  if (!objectName) continue;
7709
8238
  const facts = factsFor(objectName);
7710
8239
  for (const field of asArray35(obj.fields)) {
7711
- const fieldName = strName16(field.name);
8240
+ const fieldName = strName17(field.name);
7712
8241
  if (fieldName) facts.fields.set(fieldName, field);
7713
8242
  }
7714
8243
  for (const action of asArray35(obj.actions)) {
7715
- const actionName = strName16(action.name);
8244
+ const actionName = strName17(action.name);
7716
8245
  if (actionName) facts.actions.set(actionName, action);
7717
8246
  }
7718
8247
  for (const view of asArray35(obj.views)) {
7719
- collectViewRecord({ ...view, object: strName16(view.object) ?? objectName }, factsFor);
8248
+ collectViewRecord({ ...view, object: strName17(view.object) ?? objectName }, factsFor);
7720
8249
  }
7721
8250
  collectViewRecord({ object: objectName, listViews: obj.listViews }, factsFor);
7722
8251
  for (const group of asArray35(obj.fieldGroups)) {
7723
- const key = strName16(group.key) ?? strName16(group.name);
8252
+ const key = strName17(group.key) ?? strName17(group.name);
7724
8253
  if (key) facts.sections.add(key);
7725
8254
  }
7726
8255
  }
@@ -7731,10 +8260,10 @@ function buildUniverse(stack) {
7731
8260
  for (let pi = 0; pi < pages.length; pi++) {
7732
8261
  for (const walked of walkPageComponents(pages[pi], `pages[${pi}]`)) {
7733
8262
  if (!walked.objectName) continue;
7734
- const props = isRec20(walked.component.properties) ? walked.component.properties : void 0;
8263
+ const props = isRec21(walked.component.properties) ? walked.component.properties : void 0;
7735
8264
  if (!props) continue;
7736
8265
  for (const section of asArray35(props.sections)) {
7737
- const sectionName = strName16(section.name);
8266
+ const sectionName = strName17(section.name);
7738
8267
  if (sectionName) factsFor(walked.objectName).sections.add(sectionName);
7739
8268
  }
7740
8269
  }
@@ -7742,9 +8271,9 @@ function buildUniverse(stack) {
7742
8271
  const globalActions = /* @__PURE__ */ new Map();
7743
8272
  const actionOwners = /* @__PURE__ */ new Map();
7744
8273
  for (const action of asArray35(stack.actions)) {
7745
- const actionName = strName16(action.name);
8274
+ const actionName = strName17(action.name);
7746
8275
  if (!actionName) continue;
7747
- const owner = strName16(action.objectName) ?? strName16(action.object);
8276
+ const owner = strName17(action.objectName) ?? strName17(action.object);
7748
8277
  if (owner) {
7749
8278
  factsFor(owner).actions.set(actionName, action);
7750
8279
  actionOwners.set(actionName, owner);
@@ -7759,19 +8288,19 @@ function buildUniverse(stack) {
7759
8288
  }
7760
8289
  const apps = /* @__PURE__ */ new Map();
7761
8290
  for (const app of asArray35(stack.apps)) {
7762
- const appName = strName16(app.name);
8291
+ const appName = strName17(app.name);
7763
8292
  if (!appName) continue;
7764
8293
  const navIds = apps.get(appName) ?? /* @__PURE__ */ new Set();
7765
8294
  const walkNav = (items) => {
7766
8295
  for (const item of asArray35(items)) {
7767
- const id = strName16(item.id);
8296
+ const id = strName17(item.id);
7768
8297
  if (id) navIds.add(id);
7769
8298
  if (item.children) walkNav(item.children);
7770
8299
  }
7771
8300
  };
7772
8301
  walkNav(app.navigation);
7773
8302
  for (const area of asArray35(app.areas)) {
7774
- const areaId = strName16(area.id);
8303
+ const areaId = strName17(area.id);
7775
8304
  if (areaId) navIds.add(areaId);
7776
8305
  walkNav(area.navigation);
7777
8306
  }
@@ -7779,20 +8308,20 @@ function buildUniverse(stack) {
7779
8308
  }
7780
8309
  const dashboards = /* @__PURE__ */ new Map();
7781
8310
  for (const dash of asArray35(stack.dashboards)) {
7782
- const dashName = strName16(dash.name);
8311
+ const dashName = strName17(dash.name);
7783
8312
  if (!dashName) continue;
7784
8313
  const widgets = /* @__PURE__ */ new Set();
7785
8314
  for (const widget of asArray35(dash.widgets)) {
7786
- const id = strName16(widget.id) ?? strName16(widget.name);
8315
+ const id = strName17(widget.id) ?? strName17(widget.name);
7787
8316
  if (id) widgets.add(id);
7788
8317
  }
7789
8318
  const actions = /* @__PURE__ */ new Set();
7790
8319
  const headerActions = [
7791
- ...asArray35(isRec20(dash.header) ? dash.header.actions : void 0),
8320
+ ...asArray35(isRec21(dash.header) ? dash.header.actions : void 0),
7792
8321
  ...asArray35(dash.actions)
7793
8322
  ];
7794
8323
  for (const action of headerActions) {
7795
- const key = strName16(action.actionUrl) ?? strName16(action.url) ?? strName16(action.name);
8324
+ const key = strName17(action.actionUrl) ?? strName17(action.url) ?? strName17(action.name);
7796
8325
  if (key) actions.add(key);
7797
8326
  }
7798
8327
  dashboards.set(dashName, { widgets, actions });
@@ -7804,7 +8333,7 @@ function localePath(bundleIndex, locale) {
7804
8333
  }
7805
8334
  function validateTranslationReferences(stack) {
7806
8335
  const findings = [];
7807
- if (!isRec20(stack)) return findings;
8336
+ if (!isRec21(stack)) return findings;
7808
8337
  const bundles = Array.isArray(stack.translations) ? stack.translations : [];
7809
8338
  if (bundles.length === 0) return findings;
7810
8339
  const universe = buildUniverse(stack);
@@ -7813,13 +8342,13 @@ function validateTranslationReferences(stack) {
7813
8342
  };
7814
8343
  for (let bi = 0; bi < bundles.length; bi++) {
7815
8344
  const bundle = bundles[bi];
7816
- if (!isRec20(bundle)) continue;
8345
+ if (!isRec21(bundle)) continue;
7817
8346
  for (const [locale, rawData] of Object.entries(bundle)) {
7818
- if (!isRec20(rawData)) continue;
8347
+ if (!isRec21(rawData)) continue;
7819
8348
  const base = localePath(bi, locale);
7820
8349
  const inLocale = `locale "${locale}"`;
7821
8350
  for (const [objectName, rawNode] of Object.entries(asRecord(rawData.objects))) {
7822
- if (!isRec20(rawNode)) continue;
8351
+ if (!isRec21(rawNode)) continue;
7823
8352
  const objPath = `${base}.objects.${objectName}`;
7824
8353
  const facts = universe.objects.get(objectName);
7825
8354
  if (!facts) {
@@ -7827,7 +8356,7 @@ function validateTranslationReferences(stack) {
7827
8356
  orphan(
7828
8357
  `${inLocale} \xB7 object "${objectName}"`,
7829
8358
  objPath,
7830
- (0, import_system4.hasPlatformObjectPrefix)(objectName) ? `Translations are keyed to "${objectName}", which carries a platform namespace prefix but is registered by no platform package, official plugin, or cloud runtime object \u2014 and this stack does not define it either. Nothing resolves these keys.` + suggest6(objectName, universe.objects.keys()) : `Translations are keyed to "${objectName}", which no object in this stack defines. The resolver looks up keys derived from the metadata, so this whole subtree is dead weight \u2014 every label it carries renders untranslated.` + suggest6(objectName, universe.objects.keys()),
8359
+ (0, import_system4.hasPlatformObjectPrefix)(objectName) ? `Translations are keyed to "${objectName}", which carries a platform namespace prefix but is registered by no platform package, official plugin, or cloud runtime object \u2014 and this stack does not define it either. Nothing resolves these keys.` + suggest7(objectName, universe.objects.keys()) : `Translations are keyed to "${objectName}", which no object in this stack defines. The resolver looks up keys derived from the metadata, so this whole subtree is dead weight \u2014 every label it carries renders untranslated.` + suggest7(objectName, universe.objects.keys()),
7831
8360
  `Rename the key to the object it was written for, drop it, or ignore this if the object is contributed by another installed package.` + (universe.objects.size > 0 ? ` Defined objects: ${listNames(universe.objects.keys())}.` : "")
7832
8361
  );
7833
8362
  continue;
@@ -7840,12 +8369,12 @@ function validateTranslationReferences(stack) {
7840
8369
  orphan(
7841
8370
  `${inLocale} \xB7 object "${objectName}" \xB7 field "${fieldName}"`,
7842
8371
  fieldPath,
7843
- `Translations are keyed to field "${fieldName}", which object "${objectName}" does not declare. The label renders untranslated in this locale \u2014 and because every neighbouring field DOES resolve, the hole reads as a styling quirk rather than a missing translation.` + suggest6(fieldName, facts.fields.keys()),
8372
+ `Translations are keyed to field "${fieldName}", which object "${objectName}" does not declare. The label renders untranslated in this locale \u2014 and because every neighbouring field DOES resolve, the hole reads as a styling quirk rather than a missing translation.` + suggest7(fieldName, facts.fields.keys()),
7844
8373
  `Point the key at a declared field, or drop it if the field was removed or renamed.` + (facts.fields.size > 0 ? ` Declared fields: ${listNames(facts.fields.keys())}.` : "")
7845
8374
  );
7846
8375
  continue;
7847
8376
  }
7848
- if (!isRec20(rawField)) continue;
8377
+ if (!isRec21(rawField)) continue;
7849
8378
  checkOptionKeys(findings, {
7850
8379
  optionMap: rawField.options,
7851
8380
  field,
@@ -7860,7 +8389,7 @@ function validateTranslationReferences(stack) {
7860
8389
  orphan(
7861
8390
  `${inLocale} \xB7 object "${objectName}" \xB7 view "${viewName}"`,
7862
8391
  `${objPath}._views.${viewName}`,
7863
- `Translations are keyed to view "${viewName}", which no view of object "${objectName}" declares. The view tab keeps its source-locale label.` + suggest6(viewName, facts.views),
8392
+ `Translations are keyed to view "${viewName}", which no view of object "${objectName}" declares. The view tab keeps its source-locale label.` + suggest7(viewName, facts.views),
7864
8393
  `Match the key to the view's \`name\` (not its label), or drop it.` + (facts.views.size > 0 ? ` Declared views: ${listNames(facts.views)}.` : "")
7865
8394
  );
7866
8395
  }
@@ -7869,7 +8398,7 @@ function validateTranslationReferences(stack) {
7869
8398
  orphan(
7870
8399
  `${inLocale} \xB7 object "${objectName}" \xB7 section "${sectionName}"`,
7871
8400
  `${objPath}._sections.${sectionName}`,
7872
- `Translations are keyed to section "${sectionName}", which nothing on object "${objectName}" declares \u2014 no \`fieldGroups[].key\`, no named form-view section, no named \`record:details\` section. The section heading stays in the source locale.` + suggest6(sectionName, facts.sections),
8401
+ `Translations are keyed to section "${sectionName}", which nothing on object "${objectName}" declares \u2014 no \`fieldGroups[].key\`, no named form-view section, no named \`record:details\` section. The section heading stays in the source locale.` + suggest7(sectionName, facts.sections),
7873
8402
  `Sections are translatable only through a STABLE NAME: give the group/section a \`key\`/\`name\` and use it here, or drop the translation.` + (facts.sections.size > 0 ? ` Declared sections: ${listNames(facts.sections)}.` : ` Object "${objectName}" declares no named section at all.`)
7874
8403
  );
7875
8404
  }
@@ -7880,7 +8409,7 @@ function validateTranslationReferences(stack) {
7880
8409
  orphan(
7881
8410
  `${inLocale} \xB7 object "${objectName}" \xB7 action "${actionName}"`,
7882
8411
  actionPath,
7883
- `Translations are keyed to action "${actionName}", which is defined by neither object "${objectName}"'s \`actions\` nor a \`stack.actions\` entry bound to it. The button keeps its source-locale label.` + suggest6(actionName, facts.actions.keys()),
8412
+ `Translations are keyed to action "${actionName}", which is defined by neither object "${objectName}"'s \`actions\` nor a \`stack.actions\` entry bound to it. The button keeps its source-locale label.` + suggest7(actionName, facts.actions.keys()),
7884
8413
  `Match the key to a defined action name, move it under the object that owns the action, or drop it.` + (facts.actions.size > 0 ? ` Actions on this object: ${listNames(facts.actions.keys())}.` : "")
7885
8414
  );
7886
8415
  continue;
@@ -7902,7 +8431,7 @@ function validateTranslationReferences(stack) {
7902
8431
  orphan(
7903
8432
  `${inLocale} \xB7 global action "${actionName}"`,
7904
8433
  actionPath,
7905
- owner ? `Action "${actionName}" is bound to object "${owner}", so the resolver looks it up under \`objects.${owner}._actions.${actionName}\` \u2014 never under \`globalActions\`, which is only consulted for object-less actions. This key is never read.` : `Translations are keyed to global action "${actionName}", which no object-less action in this stack defines. The button keeps its source-locale label.` + suggest6(actionName, universe.globalActions.keys()),
8434
+ owner ? `Action "${actionName}" is bound to object "${owner}", so the resolver looks it up under \`objects.${owner}._actions.${actionName}\` \u2014 never under \`globalActions\`, which is only consulted for object-less actions. This key is never read.` : `Translations are keyed to global action "${actionName}", which no object-less action in this stack defines. The button keeps its source-locale label.` + suggest7(actionName, universe.globalActions.keys()),
7906
8435
  owner ? `Move these keys under \`objects.${owner}._actions.${actionName}\`.` : `Match the key to an object-less action's name, or drop it.` + (universe.globalActions.size > 0 ? ` Object-less actions: ${listNames(universe.globalActions.keys())}.` : "")
7907
8436
  );
7908
8437
  continue;
@@ -7922,18 +8451,18 @@ function validateTranslationReferences(stack) {
7922
8451
  orphan(
7923
8452
  `${inLocale} \xB7 app "${appName}"`,
7924
8453
  appPath,
7925
- `Translations are keyed to app "${appName}", which this stack does not define. The app launcher shows the source-locale label.` + suggest6(appName, universe.apps.keys()),
8454
+ `Translations are keyed to app "${appName}", which this stack does not define. The app launcher shows the source-locale label.` + suggest7(appName, universe.apps.keys()),
7926
8455
  `Match the key to an app's \`name\`, or drop it.` + (universe.apps.size > 0 ? ` Defined apps: ${listNames(universe.apps.keys())}.` : "")
7927
8456
  );
7928
8457
  continue;
7929
8458
  }
7930
- if (!isRec20(rawApp)) continue;
8459
+ if (!isRec21(rawApp)) continue;
7931
8460
  for (const navId of Object.keys(asRecord(rawApp.navigation))) {
7932
8461
  if (navIds.has(navId)) continue;
7933
8462
  orphan(
7934
8463
  `${inLocale} \xB7 app "${appName}" \xB7 navigation "${navId}"`,
7935
8464
  `${appPath}.navigation.${navId}`,
7936
- `Translations are keyed to navigation item "${navId}", which app "${appName}" does not declare. The menu entry keeps its source-locale label.` + suggest6(navId, navIds),
8465
+ `Translations are keyed to navigation item "${navId}", which app "${appName}" does not declare. The menu entry keeps its source-locale label.` + suggest7(navId, navIds),
7937
8466
  `Match the key to the navigation item's \`id\`, or drop it.` + (navIds.size > 0 ? ` Declared navigation ids: ${listNames(navIds)}.` : "")
7938
8467
  );
7939
8468
  }
@@ -7945,18 +8474,18 @@ function validateTranslationReferences(stack) {
7945
8474
  orphan(
7946
8475
  `${inLocale} \xB7 dashboard "${dashName}"`,
7947
8476
  dashPath,
7948
- `Translations are keyed to dashboard "${dashName}", which this stack does not define. The dashboard title stays in the source locale.` + suggest6(dashName, universe.dashboards.keys()),
8477
+ `Translations are keyed to dashboard "${dashName}", which this stack does not define. The dashboard title stays in the source locale.` + suggest7(dashName, universe.dashboards.keys()),
7949
8478
  `Match the key to a dashboard's \`name\`, or drop it.` + (universe.dashboards.size > 0 ? ` Defined dashboards: ${listNames(universe.dashboards.keys())}.` : "")
7950
8479
  );
7951
8480
  continue;
7952
8481
  }
7953
- if (!isRec20(rawDash)) continue;
8482
+ if (!isRec21(rawDash)) continue;
7954
8483
  for (const widgetId of Object.keys(asRecord(rawDash.widgets))) {
7955
8484
  if (dash.widgets.has(widgetId)) continue;
7956
8485
  orphan(
7957
8486
  `${inLocale} \xB7 dashboard "${dashName}" \xB7 widget "${widgetId}"`,
7958
8487
  `${dashPath}.widgets.${widgetId}`,
7959
- `Translations are keyed to widget "${widgetId}", which dashboard "${dashName}" does not declare. The widget title stays in the source locale.` + suggest6(widgetId, dash.widgets),
8488
+ `Translations are keyed to widget "${widgetId}", which dashboard "${dashName}" does not declare. The widget title stays in the source locale.` + suggest7(widgetId, dash.widgets),
7960
8489
  `Match the key to the widget's \`id\`, or drop it.` + (dash.widgets.size > 0 ? ` Declared widget ids: ${listNames(dash.widgets)}.` : "")
7961
8490
  );
7962
8491
  }
@@ -7965,7 +8494,7 @@ function validateTranslationReferences(stack) {
7965
8494
  orphan(
7966
8495
  `${inLocale} \xB7 dashboard "${dashName}" \xB7 action "${actionKey}"`,
7967
8496
  `${dashPath}.actions.${actionKey}`,
7968
- `Translations are keyed to header action "${actionKey}", which dashboard "${dashName}" does not declare. The button keeps its source-locale label.` + suggest6(actionKey, dash.actions),
8497
+ `Translations are keyed to header action "${actionKey}", which dashboard "${dashName}" does not declare. The button keeps its source-locale label.` + suggest7(actionKey, dash.actions),
7969
8498
  `Header-action translations are keyed by the action's \`actionUrl\`, not its label.` + (dash.actions.size > 0 ? ` Declared header actions: ${listNames(dash.actions)}.` : "")
7970
8499
  );
7971
8500
  }
@@ -7975,7 +8504,7 @@ function validateTranslationReferences(stack) {
7975
8504
  return findings;
7976
8505
  }
7977
8506
  function asRecord(v) {
7978
- return isRec20(v) ? v : {};
8507
+ return isRec21(v) ? v : {};
7979
8508
  }
7980
8509
  function checkOptionKeys(findings, ctx) {
7981
8510
  const optionKeys = Object.keys(asRecord(ctx.optionMap));
@@ -7987,7 +8516,7 @@ function checkOptionKeys(findings, ctx) {
7987
8516
  rule: TRANSLATION_OPTION_KEY_UNKNOWN,
7988
8517
  where: ctx.where,
7989
8518
  path: ctx.path,
7990
- message: `Option translations are keyed under field "${ctx.fieldName}" of object "${ctx.objectName}", which declares no \`options\` at all (field type "${strName16(ctx.field.type) ?? "unknown"}"). Nothing reads this map.`,
8519
+ message: `Option translations are keyed under field "${ctx.fieldName}" of object "${ctx.objectName}", which declares no \`options\` at all (field type "${strName17(ctx.field.type) ?? "unknown"}"). Nothing reads this map.`,
7991
8520
  hint: `Declare the options on the field, move the translations to the field that owns them, or drop them.`
7992
8521
  });
7993
8522
  return;
@@ -8000,17 +8529,17 @@ function checkOptionKeys(findings, ctx) {
8000
8529
  rule: TRANSLATION_OPTION_KEY_UNKNOWN,
8001
8530
  where: ctx.where,
8002
8531
  path: `${ctx.path}.${key}`,
8003
- message: byLabel ? `Option translation is keyed by the DISPLAY LABEL "${key}" instead of the stored value "${byLabel}". The resolver looks the option up by value, so this entry is never found and the option renders with its source-locale label.` : `Option translation is keyed by "${key}", which is not one of the values declared by field "${ctx.objectName}.${ctx.fieldName}". The option renders untranslated.` + suggest6(key, declared.values),
8532
+ message: byLabel ? `Option translation is keyed by the DISPLAY LABEL "${key}" instead of the stored value "${byLabel}". The resolver looks the option up by value, so this entry is never found and the option renders with its source-locale label.` : `Option translation is keyed by "${key}", which is not one of the values declared by field "${ctx.objectName}.${ctx.fieldName}". The option renders untranslated.` + suggest7(key, declared.values),
8004
8533
  hint: byLabel ? `Rename the key to "${byLabel}".` : `Option keys are the stored \`value\`, not the label and not a variant spelling (\`direct_mail\`, not \`direct-mail\`). Declared values: ${listNames(declared.values)}.`
8005
8534
  });
8006
8535
  }
8007
8536
  }
8008
8537
  function checkActionParams(findings, ctx) {
8009
- const rawParams = Object.keys(asRecord(isRec20(ctx.rawAction) ? ctx.rawAction.params : void 0));
8538
+ const rawParams = Object.keys(asRecord(isRec21(ctx.rawAction) ? ctx.rawAction.params : void 0));
8010
8539
  if (rawParams.length === 0) return;
8011
8540
  const declared = /* @__PURE__ */ new Set();
8012
8541
  for (const param of asArray35(ctx.action.params)) {
8013
- const name = strName16(param.name) ?? strName16(param.field);
8542
+ const name = strName17(param.name) ?? strName17(param.field);
8014
8543
  if (name) declared.add(name);
8015
8544
  }
8016
8545
  for (const paramName of rawParams) {
@@ -8020,7 +8549,7 @@ function checkActionParams(findings, ctx) {
8020
8549
  rule: TRANSLATION_TARGET_UNKNOWN,
8021
8550
  where: `${ctx.where} \xB7 param "${paramName}"`,
8022
8551
  path: `${ctx.path}.params.${paramName}`,
8023
- message: `Translations are keyed to parameter "${paramName}", which ${ctx.subject} does not declare. The parameter's label and help text render untranslated in the action dialog.` + suggest6(paramName, declared),
8552
+ message: `Translations are keyed to parameter "${paramName}", which ${ctx.subject} does not declare. The parameter's label and help text render untranslated in the action dialog.` + suggest7(paramName, declared),
8024
8553
  hint: `Match the key to a declared param \`name\`, or drop it.` + (declared.size > 0 ? ` Declared params: ${listNames(declared)}.` : "")
8025
8554
  });
8026
8555
  }
@@ -8028,14 +8557,14 @@ function checkActionParams(findings, ctx) {
8028
8557
 
8029
8558
  // src/validate-translatable-sections.ts
8030
8559
  var TRANSLATION_SECTION_NAME_MISSING = "translation-section-name-missing";
8031
- function isRec21(v) {
8560
+ function isRec22(v) {
8032
8561
  return !!v && typeof v === "object" && !Array.isArray(v);
8033
8562
  }
8034
- function strName17(v) {
8563
+ function strName18(v) {
8035
8564
  return typeof v === "string" && v.length > 0 ? v : void 0;
8036
8565
  }
8037
8566
  function viewLabel(view) {
8038
- const name = strName17(view.name);
8567
+ const name = strName18(view.name);
8039
8568
  return name ? `view "${name}"` : "";
8040
8569
  }
8041
8570
  function joinWhere(...parts) {
@@ -8043,7 +8572,7 @@ function joinWhere(...parts) {
8043
8572
  }
8044
8573
  function collectViewSites(view, basePath, label2, sites) {
8045
8574
  const recordObject = viewObjectName(view);
8046
- const listBinding = isRec21(view.list) ? viewObjectName(view.list) ?? recordObject : void 0;
8575
+ const listBinding = isRec22(view.list) ? viewObjectName(view.list) ?? recordObject : void 0;
8047
8576
  for (const site of viewContainerSites(view, basePath)) {
8048
8577
  sites.push({
8049
8578
  path: `${site.path}.sections`,
@@ -8057,11 +8586,11 @@ function translatedObjectNames(stack) {
8057
8586
  const out = /* @__PURE__ */ new Set();
8058
8587
  const bundles = Array.isArray(stack.translations) ? stack.translations : [];
8059
8588
  for (const bundle of bundles) {
8060
- if (!isRec21(bundle)) continue;
8589
+ if (!isRec22(bundle)) continue;
8061
8590
  for (const data of Object.values(bundle)) {
8062
- if (!isRec21(data) || !isRec21(data.objects)) continue;
8591
+ if (!isRec22(data) || !isRec22(data.objects)) continue;
8063
8592
  for (const [objectName, node] of Object.entries(data.objects)) {
8064
- if (isRec21(node)) out.add(objectName);
8593
+ if (isRec22(node)) out.add(objectName);
8065
8594
  }
8066
8595
  }
8067
8596
  }
@@ -8073,22 +8602,22 @@ function suggestedName(label2) {
8073
8602
  }
8074
8603
  function validateTranslatableSections(stack) {
8075
8604
  const findings = [];
8076
- if (!isRec21(stack)) return findings;
8605
+ if (!isRec22(stack)) return findings;
8077
8606
  const translated = translatedObjectNames(stack);
8078
8607
  if (translated.size === 0) return findings;
8079
8608
  const sites = [];
8080
8609
  for (const { rec: obj, path: objPath } of collectionEntries(stack.objects, "objects")) {
8081
- const objectName = strName17(obj.name);
8610
+ const objectName = strName18(obj.name);
8082
8611
  if (!objectName) continue;
8083
8612
  for (const { rec: view, path } of collectionEntries(obj.views, `${objPath}.views`)) {
8084
8613
  collectViewSites(
8085
- { ...view, object: strName17(view.object) ?? objectName },
8614
+ { ...view, object: strName18(view.object) ?? objectName },
8086
8615
  path,
8087
8616
  viewLabel(view),
8088
8617
  sites
8089
8618
  );
8090
8619
  }
8091
- if (isRec21(obj.listViews)) {
8620
+ if (isRec22(obj.listViews)) {
8092
8621
  collectViewSites({ object: objectName, listViews: obj.listViews }, objPath, "", sites);
8093
8622
  }
8094
8623
  }
@@ -8096,13 +8625,13 @@ function validateTranslatableSections(stack) {
8096
8625
  collectViewSites(view, path, viewLabel(view), sites);
8097
8626
  }
8098
8627
  for (const { rec: page, path: pagePath } of collectionEntries(stack.pages, "pages")) {
8099
- const pageName = strName17(page.name);
8628
+ const pageName = strName18(page.name);
8100
8629
  const pageLabel = pageName ? `page "${pageName}"` : "";
8101
8630
  for (const walked of walkPageComponents(page, pagePath)) {
8102
8631
  if (!walked.objectName) continue;
8103
- const props = isRec21(walked.component.properties) ? walked.component.properties : void 0;
8632
+ const props = isRec22(walked.component.properties) ? walked.component.properties : void 0;
8104
8633
  if (!props) continue;
8105
- const type = strName17(walked.component.type) ?? "component";
8634
+ const type = strName18(walked.component.type) ?? "component";
8106
8635
  sites.push({
8107
8636
  path: `${walked.path}.properties.sections`,
8108
8637
  surface: joinWhere(pageLabel, type),
@@ -8117,9 +8646,9 @@ function validateTranslatableSections(stack) {
8117
8646
  if (!Array.isArray(site.sections)) continue;
8118
8647
  for (let i = 0; i < site.sections.length; i++) {
8119
8648
  const section = site.sections[i];
8120
- if (!isRec21(section)) continue;
8121
- if (strName17(section.name)) continue;
8122
- const heading = strName17(section.label);
8649
+ if (!isRec22(section)) continue;
8650
+ if (strName18(section.name)) continue;
8651
+ const heading = strName18(section.label);
8123
8652
  if (!heading) continue;
8124
8653
  const slug = suggestedName(heading);
8125
8654
  findings.push({
@@ -8144,7 +8673,7 @@ function asArray36(v) {
8144
8673
  }
8145
8674
  return [];
8146
8675
  }
8147
- function strName18(v) {
8676
+ function strName19(v) {
8148
8677
  return typeof v === "string" && v.length > 0 ? v : void 0;
8149
8678
  }
8150
8679
  function surfaceOf(v) {
@@ -8155,17 +8684,17 @@ function validateAiSurfaceAffinity(stack) {
8155
8684
  if (!stack || typeof stack !== "object") return findings;
8156
8685
  const skillsByName = /* @__PURE__ */ new Map();
8157
8686
  for (const skill of asArray36(stack.skills)) {
8158
- const n = strName18(skill.name);
8687
+ const n = strName19(skill.name);
8159
8688
  if (n) skillsByName.set(n, skill);
8160
8689
  }
8161
8690
  const agents = asArray36(stack.agents);
8162
8691
  for (let ai = 0; ai < agents.length; ai++) {
8163
8692
  const agent = agents[ai];
8164
- const agentName = strName18(agent.name) ?? `#${ai}`;
8693
+ const agentName = strName19(agent.name) ?? `#${ai}`;
8165
8694
  const agentSurface = surfaceOf(agent.surface);
8166
8695
  const skillRefs = Array.isArray(agent.skills) ? agent.skills : [];
8167
8696
  for (let si = 0; si < skillRefs.length; si++) {
8168
- const ref = strName18(skillRefs[si]);
8697
+ const ref = strName19(skillRefs[si]);
8169
8698
  if (!ref) continue;
8170
8699
  const skill = skillsByName.get(ref);
8171
8700
  if (!skill) continue;
@@ -8194,10 +8723,10 @@ function asArray37(v) {
8194
8723
  }
8195
8724
  return [];
8196
8725
  }
8197
- function strName19(v) {
8726
+ function strName20(v) {
8198
8727
  return typeof v === "string" && v.length > 0 ? v : void 0;
8199
8728
  }
8200
- function distance6(a, b) {
8729
+ function distance7(a, b) {
8201
8730
  const m = a.length;
8202
8731
  const n = b.length;
8203
8732
  if (m === 0) return n;
@@ -8213,14 +8742,14 @@ function distance6(a, b) {
8213
8742
  }
8214
8743
  return prev[n];
8215
8744
  }
8216
- function suggest7(target, known) {
8745
+ function suggest8(target, known) {
8217
8746
  for (const prefix of import_system5.PLATFORM_TOOL_FAMILY_PREFIXES) {
8218
8747
  if (known.has(`${prefix}${target}`)) return ` Did you mean "${prefix}${target}"?`;
8219
8748
  }
8220
8749
  let best;
8221
8750
  let bestScore = Infinity;
8222
8751
  for (const candidate of known) {
8223
- const d = distance6(target, candidate);
8752
+ const d = distance7(target, candidate);
8224
8753
  if (d < bestScore) {
8225
8754
  bestScore = d;
8226
8755
  best = candidate;
@@ -8235,8 +8764,8 @@ function materialisesAsTool(action) {
8235
8764
  if (!ai || typeof ai !== "object") return false;
8236
8765
  const aiRec = ai;
8237
8766
  if (aiRec.exposed !== true) return false;
8238
- if (!strName19(aiRec.description)) return false;
8239
- const type = strName19(action.type);
8767
+ if (!strName20(aiRec.description)) return false;
8768
+ const type = strName20(action.type);
8240
8769
  if (!type || !HEADLESS_ACTION_TYPES.has(type)) return false;
8241
8770
  if (type === "script") return Boolean(action.target || action.body);
8242
8771
  return Boolean(action.target);
@@ -8244,12 +8773,12 @@ function materialisesAsTool(action) {
8244
8773
  function collectToolUniverse(stack) {
8245
8774
  const universe = new Set(import_system5.PLATFORM_PROVIDED_TOOL_NAMES);
8246
8775
  for (const tool of asArray37(stack.tools)) {
8247
- const n = strName19(tool.name);
8776
+ const n = strName20(tool.name);
8248
8777
  if (n) universe.add(n);
8249
8778
  }
8250
8779
  const addActionFamily = (actions) => {
8251
8780
  for (const action of asArray37(actions)) {
8252
- const n = strName19(action.name);
8781
+ const n = strName20(action.name);
8253
8782
  if (n && materialisesAsTool(action)) universe.add(`action_${n}`);
8254
8783
  }
8255
8784
  };
@@ -8263,7 +8792,7 @@ function collectUnexposedActionNames(stack) {
8263
8792
  const names = /* @__PURE__ */ new Set();
8264
8793
  const scan = (actions) => {
8265
8794
  for (const action of asArray37(actions)) {
8266
- const n = strName19(action.name);
8795
+ const n = strName20(action.name);
8267
8796
  if (n && !materialisesAsTool(action)) names.add(n);
8268
8797
  }
8269
8798
  };
@@ -8289,10 +8818,10 @@ function validateAiToolReferences(stack) {
8289
8818
  const skills = asArray37(stack.skills);
8290
8819
  for (let si = 0; si < skills.length; si++) {
8291
8820
  const skill = skills[si];
8292
- const skillName = strName19(skill.name) ?? `#${si}`;
8821
+ const skillName = strName20(skill.name) ?? `#${si}`;
8293
8822
  const refs = Array.isArray(skill.tools) ? skill.tools : [];
8294
8823
  for (let ti = 0; ti < refs.length; ti++) {
8295
- const ref = strName19(refs[ti]);
8824
+ const ref = strName20(refs[ti]);
8296
8825
  if (!ref || resolves(ref)) continue;
8297
8826
  const isPattern = ref.endsWith("*");
8298
8827
  const unexposed = !isPattern && ref.startsWith("action_") && unexposedActions.has(ref.slice("action_".length)) ? ref.slice("action_".length) : void 0;
@@ -8301,7 +8830,7 @@ function validateAiToolReferences(stack) {
8301
8830
  rule: AI_SKILL_TOOL_UNRESOLVED,
8302
8831
  where: `skill "${skillName}" \xB7 tools`,
8303
8832
  path: `skills[${si}].tools[${ti}]`,
8304
- message: isPattern ? `Skill "${skillName}" subscribes to tool family "${ref}", which matches nothing this stack can resolve (no declared tool, no platform tool, and no AI-exposed declarative action materialises into it). The subscription contributes zero tools at runtime.` : unexposed ? `Skill "${skillName}" references tool "${ref}", but the action "${unexposed}" does not become an AI tool: the runtime materialises \`action_<name>\` only for an action that opts in with \`ai.exposed: true\` + \`ai.description\` (ADR-0011) AND has a headless path (type \`script\`/\`api\`/\`flow\` with a target or body \u2014 \`url\`/\`modal\`/\`form\` are UI-only). The reference is dropped at runtime, so the skill promises a capability the agent cannot call.` : `Skill "${skillName}" references tool "${ref}", which resolves to nothing this stack can see: not a \`stack.tools\` record, not a platform-registered tool, and not a materialised action tool (\`action_<name>\`). The runtime silently drops the reference, so the skill's instructions claim a capability the agent does not have \u2014 the assistant will improvise or fail when asked to use it.` + suggest7(ref, universe),
8833
+ message: isPattern ? `Skill "${skillName}" subscribes to tool family "${ref}", which matches nothing this stack can resolve (no declared tool, no platform tool, and no AI-exposed declarative action materialises into it). The subscription contributes zero tools at runtime.` : unexposed ? `Skill "${skillName}" references tool "${ref}", but the action "${unexposed}" does not become an AI tool: the runtime materialises \`action_<name>\` only for an action that opts in with \`ai.exposed: true\` + \`ai.description\` (ADR-0011) AND has a headless path (type \`script\`/\`api\`/\`flow\` with a target or body \u2014 \`url\`/\`modal\`/\`form\` are UI-only). The reference is dropped at runtime, so the skill promises a capability the agent cannot call.` : `Skill "${skillName}" references tool "${ref}", which resolves to nothing this stack can see: not a \`stack.tools\` record, not a platform-registered tool, and not a materialised action tool (\`action_<name>\`). The runtime silently drops the reference, so the skill's instructions claim a capability the agent does not have \u2014 the assistant will improvise or fail when asked to use it.` + suggest8(ref, universe),
8305
8834
  hint: unexposed ? `Either opt "${unexposed}" in \u2014 set \`ai: { exposed: true, description: '\u2026' }\` (\u226540 chars, LLM-facing) and give it a headless type \u2014 or drop the reference and have the skill's instructions recommend the UI action instead. A \`modal\`/\`form\`/\`url\` action stays human-driven by design; that is a legitimate answer, not a gap.` : `Back "${ref}" with a real executable: declare a declarative action (or flow), opt it in with \`ai.exposed: true\` + \`ai.description\`, and reference its materialised tool (\`action_<name>\` \u2014 the ADR-0109 default path, no tool record needed); or reference a platform tool by its registered name; or remove the reference and the instructions that mention it. Ignore this only if a runtime plugin outside the platform registry provides "${ref}". Family prefixes materialised by the runtime: ${import_system5.PLATFORM_TOOL_FAMILY_PREFIXES.join(", ")}.`
8306
8835
  });
8307
8836
  }
@@ -8319,7 +8848,7 @@ function asArray38(v) {
8319
8848
  }
8320
8849
  return [];
8321
8850
  }
8322
- function strName20(v) {
8851
+ function strName21(v) {
8323
8852
  return typeof v === "string" && v.length > 0 ? v : void 0;
8324
8853
  }
8325
8854
  var PLATFORM_AGENT_NAMES = /* @__PURE__ */ new Set(["ask", "build", "data_chat", "metadata_assistant"]);
@@ -8329,7 +8858,7 @@ function validateAiAgentAuthoring(stack) {
8329
8858
  const agents = asArray38(stack.agents);
8330
8859
  for (let ai = 0; ai < agents.length; ai++) {
8331
8860
  const agent = agents[ai];
8332
- const name = strName20(agent.name) ?? `#${ai}`;
8861
+ const name = strName21(agent.name) ?? `#${ai}`;
8333
8862
  const isPlatformName = PLATFORM_AGENT_NAMES.has(name);
8334
8863
  const skillCount = Array.isArray(agent.skills) ? agent.skills.length : 0;
8335
8864
  findings.push({
@@ -8345,9 +8874,9 @@ function validateAiAgentAuthoring(stack) {
8345
8874
  const apps = asArray38(stack.apps);
8346
8875
  for (let appIdx = 0; appIdx < apps.length; appIdx++) {
8347
8876
  const app = apps[appIdx];
8348
- const defaultAgent = strName20(app.defaultAgent);
8877
+ const defaultAgent = strName21(app.defaultAgent);
8349
8878
  if (!defaultAgent || PLATFORM_AGENT_NAMES.has(defaultAgent)) continue;
8350
- const appName = strName20(app.name) ?? `#${appIdx}`;
8879
+ const appName = strName21(app.name) ?? `#${appIdx}`;
8351
8880
  findings.push({
8352
8881
  severity: "warning",
8353
8882
  rule: DEFAULT_AGENT_OUTSIDE_ROSTER,
@@ -8378,6 +8907,8 @@ function loadTypeScript3() {
8378
8907
  return cachedTs3;
8379
8908
  }
8380
8909
  var HOOK_BODY_WRITE_UNKNOWN_FIELD = "hook-body-write-unknown-field";
8910
+ var HOOK_BODY_SOURCE_UNPARSEABLE = "hook-body-source-unparseable";
8911
+ var HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR = "hook-body-write-unprovisioned-anchor";
8381
8912
  var HOOK_BODY_WRITE_PATTERNS = [
8382
8913
  {
8383
8914
  id: "input-property-assign",
@@ -8450,13 +8981,16 @@ var IMPLICIT_FIELDS2 = /* @__PURE__ */ new Set([
8450
8981
  "owner",
8451
8982
  "record_type"
8452
8983
  ]);
8453
- var isRec22 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8984
+ function unprovisionedAnchorWriteConsequence() {
8985
+ return `so the value can never land: the anchor exists only in the registered schema, which is what carries it PAST the write-path validator that refuses an undeclared name outright (INVALID_FIELD). The remote database is what rejects it \u2014 on a SQL remote with an untyped driver error ('no such column') that aborts the whole statement, so the correctly named fields in the same payload never land either; on a schemaless remote the key is persisted into a column no read surface returns (#4271).`;
8986
+ }
8987
+ var isRec23 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8454
8988
  function asArray39(v) {
8455
- if (Array.isArray(v)) return v.filter((x) => isRec22(x));
8456
- if (isRec22(v)) {
8989
+ if (Array.isArray(v)) return v.filter((x) => isRec23(x));
8990
+ if (isRec23(v)) {
8457
8991
  return Object.entries(v).map(([name, def]) => ({
8458
8992
  name,
8459
- ...isRec22(def) ? def : {}
8993
+ ...isRec23(def) ? def : {}
8460
8994
  }));
8461
8995
  }
8462
8996
  return [];
@@ -8487,15 +9021,18 @@ function extractHookBodyWriteSet(source) {
8487
9021
  return { writes: [], ctxRecordEscapes: false };
8488
9022
  }
8489
9023
  const tsc = loadTypeScript3();
8490
- const sf = tsc.createSourceFile(
9024
+ const { sourceFile: sf, failure: parseFailure } = createSourceFileChecked(
9025
+ tsc,
8491
9026
  "hook-body.ts",
8492
9027
  `async function __body(ctx) {
8493
9028
  ${source}
8494
9029
  }`,
8495
- tsc.ScriptTarget.Latest,
8496
- /* setParentNodes */
8497
- false,
8498
- tsc.ScriptKind.TS
9030
+ {
9031
+ target: tsc.ScriptTarget.Latest,
9032
+ setParentNodes: false,
9033
+ scriptKind: tsc.ScriptKind.TS,
9034
+ synthesizedLinesBefore: 1
9035
+ }
8499
9036
  );
8500
9037
  const writes = [];
8501
9038
  const recordRefs = [];
@@ -8592,7 +9129,8 @@ ${source}
8592
9129
  visit(sf);
8593
9130
  return {
8594
9131
  writes,
8595
- ctxRecordEscapes: recordRefs.some((ref) => !consumedRecordRefs.has(ref))
9132
+ ctxRecordEscapes: recordRefs.some((ref) => !consumedRecordRefs.has(ref)),
9133
+ ...parseFailure ? { parseFailure } : {}
8596
9134
  };
8597
9135
  }
8598
9136
  function validateHookBodyWrites(stack) {
@@ -8600,15 +9138,28 @@ function validateHookBodyWrites(stack) {
8600
9138
  const hooks = asArray39(stack.hooks);
8601
9139
  if (hooks.length === 0) return findings;
8602
9140
  let objectFields = null;
9141
+ let anchors = null;
8603
9142
  hooks.forEach((hook, hookIndex) => {
8604
9143
  const body = hook.body;
8605
- if (!isRec22(body) || body.language !== "js") return;
9144
+ if (!isRec23(body) || body.language !== "js") return;
8606
9145
  const source = body.source;
8607
9146
  if (typeof source !== "string" || source.trim() === "") return;
8608
- const writes = extractHookBodyWrites(source).filter((w) => HOOK_APPLICABLE_IDS.has(w.patternId));
9147
+ const extracted = extractHookBodyWriteSet(source);
9148
+ const hookName = typeof hook.name === "string" && hook.name ? hook.name : `#${hookIndex}`;
9149
+ if (extracted.parseFailure) {
9150
+ findings.push({
9151
+ severity: "warning",
9152
+ rule: HOOK_BODY_SOURCE_UNPARSEABLE,
9153
+ where: `hook "${hookName}" \u203A body`,
9154
+ path: `hooks[${hookIndex}].body.source`,
9155
+ message: `L2 body did not parse (${describeParseFailure(extracted.parseFailure)}), so its write set was read from a partially recovered tree \u2014 an undeclared field write in the unread part is not reported.`,
9156
+ hint: PARSE_FAILURE_HINT
9157
+ });
9158
+ }
9159
+ const writes = extracted.writes.filter((w) => HOOK_APPLICABLE_IDS.has(w.patternId));
8609
9160
  if (writes.length === 0) return;
8610
9161
  objectFields ?? (objectFields = indexObjectFields2(stack));
8611
- const hookName = typeof hook.name === "string" && hook.name ? hook.name : `#${hookIndex}`;
9162
+ anchors ?? (anchors = indexUnprovisionedAnchors(stack));
8612
9163
  const targets = (Array.isArray(hook.object) ? hook.object : [hook.object]).filter(
8613
9164
  (o) => typeof o === "string" && o.trim() !== ""
8614
9165
  );
@@ -8622,7 +9173,20 @@ function validateHookBodyWrites(stack) {
8622
9173
  if (reported.has(dedupeKey)) continue;
8623
9174
  if (w.object === void 0) {
8624
9175
  if (!inputJudgeable) continue;
8625
- if (IMPLICIT_FIELDS2.has(w.field)) continue;
9176
+ if (IMPLICIT_FIELDS2.has(w.field)) {
9177
+ if (!targets.every((t) => anchors.get(t)?.has(w.field))) continue;
9178
+ reported.add(dedupeKey);
9179
+ const anchorObj = targets.length === 1 ? targets[0] : targets.join(", ");
9180
+ findings.push({
9181
+ severity: "warning",
9182
+ rule: HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR,
9183
+ where,
9184
+ path,
9185
+ message: `body writes '${w.field}' to its input, and ${unprovisionedAnchorCause(anchorObj, w.field)} \u2014 ` + unprovisionedAnchorWriteConsequence(),
9186
+ hint: unprovisionedAnchorHint(anchorObj, w.field)
9187
+ });
9188
+ continue;
9189
+ }
8626
9190
  if (targetSets.some((s) => s.has(w.field))) continue;
8627
9191
  reported.add(dedupeKey);
8628
9192
  const objDesc = targets.length === 1 ? `object '${targets[0]}'` : `none of its target objects (${targets.join(", ")})`;
@@ -8638,7 +9202,20 @@ function validateHookBodyWrites(stack) {
8638
9202
  } else {
8639
9203
  const known = judgeableFieldsOf(objectFields, w.object);
8640
9204
  if (!known) continue;
8641
- if (IMPLICIT_FIELDS2.has(w.field) || known.has(w.field)) continue;
9205
+ if (known.has(w.field)) continue;
9206
+ if (IMPLICIT_FIELDS2.has(w.field)) {
9207
+ if (!anchors.get(w.object)?.has(w.field)) continue;
9208
+ reported.add(dedupeKey);
9209
+ findings.push({
9210
+ severity: "warning",
9211
+ rule: HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR,
9212
+ where,
9213
+ path,
9214
+ message: `body calls ctx.api.object('${w.object}').${w.method ?? "update"}(\u2026) writing '${w.field}', and ${unprovisionedAnchorCause(w.object, w.field)} \u2014 ${unprovisionedAnchorWriteConsequence()}`,
9215
+ hint: unprovisionedAnchorHint(w.object, w.field)
9216
+ });
9217
+ continue;
9218
+ }
8642
9219
  reported.add(dedupeKey);
8643
9220
  findings.push({
8644
9221
  severity: "warning",
@@ -8667,6 +9244,8 @@ function fixHint(field, declared) {
8667
9244
  var import_shared2 = require("@objectstack/spec/shared");
8668
9245
  var ACTION_BODY_WRITE_UNKNOWN_FIELD = "action-body-write-unknown-field";
8669
9246
  var ACTION_RECORD_WRITE_DISCARDED = "action-record-write-discarded";
9247
+ var ACTION_BODY_SOURCE_UNPARSEABLE = "action-body-source-unparseable";
9248
+ var ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR = "action-body-write-unprovisioned-anchor";
8670
9249
  var ACTION_BODY_WRITE_PATTERN_IDS = ["api-crud-literal"];
8671
9250
  var ACTION_RECORD_WRITE_PATTERN_IDS = ["record-property-assign"];
8672
9251
  var ACTION_BODY_WRITE_EXCLUSIONS = [
@@ -8683,13 +9262,13 @@ var ACTION_BODY_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_B
8683
9262
  var ACTION_RECORD_WRITE_PATTERNS = HOOK_BODY_WRITE_PATTERNS.filter((p) => ACTION_RECORD_WRITE_PATTERN_IDS.includes(p.id));
8684
9263
  var APPLICABLE_IDS = new Set(ACTION_BODY_WRITE_PATTERN_IDS);
8685
9264
  var RECORD_WRITE_IDS = new Set(ACTION_RECORD_WRITE_PATTERN_IDS);
8686
- var isRec23 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
9265
+ var isRec24 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8687
9266
  function asArray40(v) {
8688
- if (Array.isArray(v)) return v.filter((x) => isRec23(x));
8689
- if (isRec23(v)) {
9267
+ if (Array.isArray(v)) return v.filter((x) => isRec24(x));
9268
+ if (isRec24(v)) {
8690
9269
  return Object.entries(v).map(([name, def]) => ({
8691
9270
  name,
8692
- ...isRec23(def) ? def : {}
9271
+ ...isRec24(def) ? def : {}
8693
9272
  }));
8694
9273
  }
8695
9274
  return [];
@@ -8707,7 +9286,7 @@ function collectActionBodies(stack) {
8707
9286
  const type = typeof action.type === "string" ? action.type : "script";
8708
9287
  if (type !== "script") return;
8709
9288
  const body = action.body;
8710
- if (!isRec23(body) || body.language !== "js") return;
9289
+ if (!isRec24(body) || body.language !== "js") return;
8711
9290
  const source = body.source;
8712
9291
  if (typeof source !== "string" || source.trim() === "") return;
8713
9292
  const name = typeof action.name === "string" && action.name ? action.name : `#${index}`;
@@ -8726,17 +9305,28 @@ function collectActionBodies(stack) {
8726
9305
  }
8727
9306
  function validateActionBodyWrites(stack) {
8728
9307
  const findings = [];
8729
- if (!isRec23(stack)) return findings;
9308
+ if (!isRec24(stack)) return findings;
8730
9309
  const sites = collectActionBodies(stack);
8731
9310
  if (sites.length === 0) return findings;
8732
9311
  let objectFields = null;
9312
+ let anchors = null;
8733
9313
  for (const site of sites) {
8734
9314
  if (!/\bapi\b/.test(site.source) && !/\brecord\b/.test(site.source)) continue;
8735
- const { writes: allWrites, ctxRecordEscapes } = extractHookBodyWriteSet(site.source);
9315
+ const { writes: allWrites, ctxRecordEscapes, parseFailure } = extractHookBodyWriteSet(site.source);
8736
9316
  const writes = allWrites.filter((w) => APPLICABLE_IDS.has(w.patternId));
8737
9317
  const recordWrites = allWrites.filter((w) => RECORD_WRITE_IDS.has(w.patternId));
8738
- if (writes.length === 0 && recordWrites.length === 0) continue;
8739
9318
  const where = `action "${site.name}" \u203A body`;
9319
+ if (parseFailure) {
9320
+ findings.push({
9321
+ severity: "warning",
9322
+ rule: ACTION_BODY_SOURCE_UNPARSEABLE,
9323
+ where,
9324
+ path: site.path,
9325
+ message: `L2 body did not parse (${describeParseFailure(parseFailure)}), so its write set was read from a partially recovered tree \u2014 an undeclared field write in the unread part is not reported.`,
9326
+ hint: PARSE_FAILURE_HINT
9327
+ });
9328
+ }
9329
+ if (writes.length === 0 && recordWrites.length === 0) continue;
8740
9330
  if (recordWrites.length > 0 && !ctxRecordEscapes) {
8741
9331
  const reportedFields = /* @__PURE__ */ new Set();
8742
9332
  for (const w of recordWrites) {
@@ -8754,6 +9344,7 @@ function validateActionBodyWrites(stack) {
8754
9344
  }
8755
9345
  if (writes.length === 0) continue;
8756
9346
  objectFields ?? (objectFields = indexObjectFields2(stack));
9347
+ anchors ?? (anchors = indexUnprovisionedAnchors(stack));
8757
9348
  const reported = /* @__PURE__ */ new Set();
8758
9349
  for (const w of writes) {
8759
9350
  if (w.object === void 0) continue;
@@ -8761,7 +9352,20 @@ function validateActionBodyWrites(stack) {
8761
9352
  if (reported.has(dedupeKey)) continue;
8762
9353
  const known = judgeableFieldsOf(objectFields, w.object);
8763
9354
  if (!known) continue;
8764
- if (IMPLICIT_FIELDS2.has(w.field) || known.has(w.field)) continue;
9355
+ if (known.has(w.field)) continue;
9356
+ if (IMPLICIT_FIELDS2.has(w.field)) {
9357
+ if (!anchors.get(w.object)?.has(w.field)) continue;
9358
+ reported.add(dedupeKey);
9359
+ findings.push({
9360
+ severity: "warning",
9361
+ rule: ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR,
9362
+ where,
9363
+ path: site.path,
9364
+ message: `body calls ctx.api.object('${w.object}').${w.method ?? "update"}(\u2026) writing '${w.field}', and ${unprovisionedAnchorCause(w.object, w.field)} \u2014 ${unprovisionedAnchorWriteConsequence()}`,
9365
+ hint: unprovisionedAnchorHint(w.object, w.field)
9366
+ });
9367
+ continue;
9368
+ }
8765
9369
  reported.add(dedupeKey);
8766
9370
  findings.push({
8767
9371
  severity: "warning",
@@ -8783,15 +9387,16 @@ function fixHint2(field, declared) {
8783
9387
  // src/validate-flow-node-writes.ts
8784
9388
  var import_shared3 = require("@objectstack/spec/shared");
8785
9389
  var FLOW_NODE_WRITE_UNKNOWN_FIELD = "flow-node-write-unknown-field";
9390
+ var FLOW_NODE_WRITE_UNPROVISIONED_ANCHOR = "flow-node-write-unprovisioned-anchor";
8786
9391
  var FLOW_WRITE_NODE_TYPES = ["update_record", "create_record"];
8787
9392
  var FLOW_WRITE_NODE_TYPES_DEFERRED = [];
8788
- var isRec24 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
9393
+ var isRec25 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
8789
9394
  function asArray41(v) {
8790
- if (Array.isArray(v)) return v.filter((x) => isRec24(x));
8791
- if (isRec24(v)) {
9395
+ if (Array.isArray(v)) return v.filter((x) => isRec25(x));
9396
+ if (isRec25(v)) {
8792
9397
  return Object.entries(v).map(([name, def]) => ({
8793
9398
  name,
8794
- ...isRec24(def) ? def : {}
9399
+ ...isRec25(def) ? def : {}
8795
9400
  }));
8796
9401
  }
8797
9402
  return [];
@@ -8804,30 +9409,44 @@ function readLiteralObjectName2(config) {
8804
9409
  var COVERED_TYPES = new Set(FLOW_WRITE_NODE_TYPES);
8805
9410
  function validateFlowNodeWrites(stack) {
8806
9411
  const findings = [];
8807
- if (!isRec24(stack)) return findings;
9412
+ if (!isRec25(stack)) return findings;
8808
9413
  const flows = asArray41(stack.flows);
8809
9414
  if (flows.length === 0) return findings;
8810
9415
  let objectFields = null;
9416
+ let anchors = null;
8811
9417
  flows.forEach((flow, flowIndex) => {
8812
9418
  const flowName = typeof flow.name === "string" && flow.name ? flow.name : `#${flowIndex}`;
8813
9419
  const walked = walkFlowNodes(flow, `flows[${flowIndex}]`);
8814
9420
  walked.forEach(({ node, path: nodePath, regionTrail }, walkIndex) => {
8815
9421
  if (typeof node.type !== "string" || !COVERED_TYPES.has(node.type)) return;
8816
- const config = isRec24(node.config) ? node.config : void 0;
9422
+ const config = isRec25(node.config) ? node.config : void 0;
8817
9423
  if (!config) return;
8818
9424
  const fields = config.fields;
8819
- if (!isRec24(fields)) return;
9425
+ if (!isRec25(fields)) return;
8820
9426
  const written = Object.keys(fields);
8821
9427
  if (written.length === 0) return;
8822
9428
  const objectName = readLiteralObjectName2(config);
8823
9429
  if (!objectName) return;
8824
9430
  objectFields ?? (objectFields = indexObjectFields2(stack));
9431
+ anchors ?? (anchors = indexUnprovisionedAnchors(stack));
8825
9432
  const known = judgeableFieldsOf(objectFields, objectName);
8826
9433
  if (!known) return;
8827
9434
  const nodeName = flowNodeLabel(node, walkIndex);
8828
9435
  const nodeWhere = regionTrail ? `${regionTrail} \u203A node "${nodeName}"` : `node "${nodeName}"`;
8829
9436
  for (const fieldName of written) {
8830
- if (known.has(fieldName) || IMPLICIT_FIELDS2.has(fieldName)) continue;
9437
+ if (known.has(fieldName)) continue;
9438
+ if (IMPLICIT_FIELDS2.has(fieldName)) {
9439
+ if (!anchors.get(objectName)?.has(fieldName)) continue;
9440
+ findings.push({
9441
+ severity: "warning",
9442
+ rule: FLOW_NODE_WRITE_UNPROVISIONED_ANCHOR,
9443
+ where: `flow "${flowName}" \u203A ${nodeWhere}`,
9444
+ path: `${nodePath}.config.fields.${fieldName}`,
9445
+ message: `${node.type} writes '${fieldName}', and ${unprovisionedAnchorCause(objectName, fieldName)} \u2014 ` + unprovisionedAnchorWriteConsequence(),
9446
+ hint: unprovisionedAnchorHint(objectName, fieldName)
9447
+ });
9448
+ continue;
9449
+ }
8831
9450
  if (fieldName.includes(".")) continue;
8832
9451
  findings.push({
8833
9452
  severity: "error",
@@ -8848,9 +9467,26 @@ function fixHint3(field, declared) {
8848
9467
  }
8849
9468
 
8850
9469
  // src/reference-integrity-suite.ts
9470
+ var DEFAULT_MEMBER_RUNTIME_TYPES = ["flow"];
8851
9471
  var REFERENCE_INTEGRITY_RULES = [
8852
9472
  { name: "validateObjectReferences", run: validateObjectReferences },
8853
- { name: "validateSearchableFields", run: validateSearchableFields },
9473
+ // [#9313] `runtimeTypes` gains `view` on this member and its sort sibling:
9474
+ // both judge a LIST VIEW's field references, and a standalone list view is
9475
+ // written through `PUT /api/v1/meta/view` — the only door a Studio tenant or
9476
+ // an MCP/AI author has. Their walks read the flattened overlay shape that
9477
+ // door carries (see each rule's `views[]` self rung), and they resolve only
9478
+ // against `stack.objects`, which the per-write snapshot DOES carry — so the
9479
+ // crossing has no missing-collection false-positive channel. Measured over
9480
+ // the shipped view corpus before crossing (0 refusals; population in the
9481
+ // #9313 PR).
9482
+ { name: "validateSearchableFields", runtimeTypes: ["flow", "view"], run: validateSearchableFields },
9483
+ // [#9257] The same reading, one axis over: a list view's `sort` is a field
9484
+ // name written in metadata, resolved against the object's declared fields. It
9485
+ // gates (`error`) because the runtime does not tolerate a bad one at all —
9486
+ // `assertSortFieldsExist` (#6994) and `assertOrderByIsMaterializable` (#7095)
9487
+ // both answer `400 INVALID_SORT` — and a view's sort is its FIRST fetch, so
9488
+ // the refusal is the whole view, on every load, traced to nothing.
9489
+ { name: "validateSortableFields", runtimeTypes: ["flow", "view"], run: validateSortableFields },
8854
9490
  { name: "validateActionNameRefs", run: validateActionNameRefs },
8855
9491
  { name: "validatePageFieldBindings", run: validatePageFieldBindings },
8856
9492
  { name: "validateChartBindings", run: validateChartBindings },
@@ -8956,9 +9592,11 @@ var REFERENCE_INTEGRITY_RULES = [
8956
9592
  // — only a page that is actually `kind:'react'` loads the compiler.
8957
9593
  { name: "validateReactPageProps", run: validateReactPageProps }
8958
9594
  ];
8959
- function validateReferenceIntegrity(stack) {
9595
+ function validateReferenceIntegrity(stack, options) {
8960
9596
  const findings = [];
9597
+ const writeType = options?.runtimeWriteType;
8961
9598
  for (const rule of REFERENCE_INTEGRITY_RULES) {
9599
+ if (writeType !== void 0 && !(rule.runtimeTypes ?? DEFAULT_MEMBER_RUNTIME_TYPES).includes(writeType)) continue;
8962
9600
  findings.push(...rule.run(stack));
8963
9601
  }
8964
9602
  return findings;
@@ -8966,7 +9604,7 @@ function validateReferenceIntegrity(stack) {
8966
9604
 
8967
9605
  // src/lint-flow-patterns.ts
8968
9606
  var import_automation5 = require("@objectstack/spec/automation");
8969
- var import_data10 = require("@objectstack/spec/data");
9607
+ var import_data12 = require("@objectstack/spec/data");
8970
9608
  function asArray42(v) {
8971
9609
  if (Array.isArray(v)) return v;
8972
9610
  if (v && typeof v === "object") return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -9241,7 +9879,7 @@ function scanBranchRouting(at, nodes, edges, findings) {
9241
9879
  function filterCarriesNoCondition(filter) {
9242
9880
  if (filter === void 0 || filter === null) return true;
9243
9881
  if (typeof filter !== "object" || Array.isArray(filter)) return false;
9244
- return (0, import_data10.reduceFilterVerdict)(filter) === "true";
9882
+ return (0, import_data12.reduceFilterVerdict)(filter) === "true";
9245
9883
  }
9246
9884
  function describeUnboundedFilter(filter) {
9247
9885
  if (filter === void 0 || filter === null) return "no `filter` key";
@@ -9608,7 +10246,7 @@ function lintLivenessProperties(stack) {
9608
10246
  }
9609
10247
 
9610
10248
  // src/lint-autonumber-formats.ts
9611
- var import_data11 = require("@objectstack/spec/data");
10249
+ var import_data13 = require("@objectstack/spec/data");
9612
10250
  var AUTONUMBER_UNKNOWN_FIELD = "autonumber-references-unknown-field";
9613
10251
  var AUTONUMBER_OPTIONAL_FIELD = "autonumber-references-optional-field";
9614
10252
  var AUTONUMBER_SELF_REFERENCE = "autonumber-references-self";
@@ -9634,8 +10272,8 @@ function lintAutonumberFormats(stack) {
9634
10272
  const name = typeof f.name === "string" ? f.name : "(unnamed field)";
9635
10273
  const fmt = typeof f.autonumberFormat === "string" ? f.autonumberFormat : typeof f.format === "string" ? f.format : "";
9636
10274
  if (!fmt) continue;
9637
- const tokens = (0, import_data11.parseAutonumberFormat)(fmt);
9638
- const refs = (0, import_data11.referencedFields)(tokens);
10275
+ const tokens = (0, import_data13.parseAutonumberFormat)(fmt);
10276
+ const refs = (0, import_data13.referencedFields)(tokens);
9639
10277
  const where = `object '${objectName}' \xB7 field '${name}' (autonumber "${fmt}")`;
9640
10278
  for (const t of tokens) {
9641
10279
  if (t.kind !== "literal") continue;
@@ -9790,6 +10428,17 @@ function lintViewRefs(stack) {
9790
10428
  }
9791
10429
 
9792
10430
  // src/data-model-rules.ts
10431
+ function objectWhere(obj) {
10432
+ return `object "${obj?.name}"`;
10433
+ }
10434
+ function indexWhere(obj, idx, j, cols) {
10435
+ const named = typeof idx?.name === "string" && idx.name.trim() ? `'${idx.name.trim()}'` : "";
10436
+ const label2 = named || (cols.length > 0 ? `[${cols.join(", ")}]` : `#${j}`);
10437
+ return `${objectWhere(obj)} \xB7 index ${label2}`;
10438
+ }
10439
+ function fieldWhere(obj, fieldName) {
10440
+ return `${objectWhere(obj)} \xB7 field '${fieldName}'`;
10441
+ }
9793
10442
  var RELATIONSHIP_TYPES = /* @__PURE__ */ new Set(["lookup", "master_detail"]);
9794
10443
  var NUMERIC_TYPES = /* @__PURE__ */ new Set([
9795
10444
  "number",
@@ -9862,11 +10511,13 @@ function lintUnscopedDeclaredIndexes(objects) {
9862
10511
  for (let j = 0; j < declaredIndexes.length; j++) {
9863
10512
  const idx = declaredIndexes[j];
9864
10513
  if (idx?.unique !== true) continue;
9865
- const cols = Array.isArray(idx?.fields) ? idx.fields.filter((f) => typeof f === "string").join(", ") : "";
10514
+ const colList = Array.isArray(idx?.fields) ? idx.fields.filter((f) => typeof f === "string") : [];
10515
+ const cols = colList.join(", ");
9866
10516
  const indexLabel = typeof idx?.name === "string" && idx.name.trim() ? ` '${idx.name.trim()}'` : "";
9867
10517
  issues.push({
9868
10518
  severity: "warning",
9869
10519
  rule: UNIQUE_UNSCOPED_DECLARED_INDEX,
10520
+ where: indexWhere(obj, idx, j, colList),
9870
10521
  message: `"${obj.name}" declares index${indexLabel} [${cols}] with bare \`unique: true\` \u2014 a unique index whose scope is unstated (ADR-0120). Today the bare spelling materializes over exactly its \`fields\`, i.e. installation-wide; an author who meant "unique per organization" gets no per-organization constraint and no error. Protocol 18 rejects this spelling (#5082).`,
9871
10522
  path: `objects[${i}].indexes[${j}]`,
9872
10523
  fix: `State the scope: \`unique: 'global'\` (installation-wide \u2014 exactly today's behavior) or \`unique: 'organization'\` (one holder per organization \u2014 the driver prepends the NULL-safe organization key part at registration).`
@@ -9915,6 +10566,11 @@ function lintUniqueDeclarations(objects) {
9915
10566
  issues.push({
9916
10567
  severity: "warning",
9917
10568
  rule: UNIQUE_DOUBLE_DECLARATION,
10569
+ // More specific than `path` on purpose: this rule's finding is about ONE
10570
+ // column, but its `path` has always been the whole object (`objects[i]`)
10571
+ // because the defect straddles `fields.<name>.unique` and an entry of
10572
+ // `indexes`. `where` can name the column without picking one of the two.
10573
+ where: fieldWhere(obj, name),
9918
10574
  message,
9919
10575
  path: `objects[${i}]`,
9920
10576
  fix
@@ -9943,6 +10599,7 @@ function lintLegacyOrganizationComposites(objects) {
9943
10599
  issues.push({
9944
10600
  severity: "warning",
9945
10601
  rule: UNIQUE_LEGACY_ORGANIZATION_COMPOSITE,
10602
+ where: indexWhere(obj, idx, j, cols),
9946
10603
  message: `"${obj.name}" declares index${indexLabel} [${cols.join(", ")}] with ${spelling} and lists the organization column '${tenantColumn}' itself \u2014 the hand-written per-organization composite that predates the scope vocabulary (ADR-0120 S6). It reads as "unique per organization" but materializes as a plain composite, and SQL UNIQUE is NULL-distinct: on every row whose '${tenantColumn}' is NULL it enforces nothing (#5030) \u2014 which on a single-organization deployment is every row.`,
9947
10604
  path: `objects[${i}].indexes[${j}]`,
9948
10605
  fix: `State the scope instead: \`unique: 'organization'\` on this index (keep \`fields\` exactly as they are \u2014 the driver makes the listed '${tenantColumn}' NULL-safe in place rather than prepending a second organization key part). ${rest.length > 0 ? `The constraint then really is "one ${rest.join(" + ")} per organization". ` : ""}Opting in is a physical tightening: it surfaces as a \`recreate_index\` drift op gated by the duplicate pre-flight probe (ADR-0120 D4), so pre-existing duplicate NULL-organization rows block it with a report rather than failing a boot. Leaving it as-is stays valid indefinitely and forces no drift.`
@@ -10089,7 +10746,7 @@ var CLI_ONLY = ["cli"];
10089
10746
  var CLI_AND_RUNTIME = ["cli", "runtime-publish"];
10090
10747
  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.";
10091
10748
  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.";
10092
- var RUNTIME_OBJECT_WRITES_P2 = "P2 (#4463): judges an object/field declaration. Object writes are the hottest metadata path in the product, so P1 gates `flow` first and widens once the gate has real traffic behind it.";
10749
+ var RUNTIME_OBJECT_ADVISORY_VOLUME = "Advisory-tier object rule: it cannot refuse a write, and it is held off the runtime door for advisory VOLUME (~8 findings per object write measured on unswept metadata, rendered in Studio since #4717), not refusal risk. Crossing it is a UX decision with its own card (#4716).";
10093
10750
  var EXPRESSION_INVALID = "expression-invalid";
10094
10751
  var AUTHORING_RULES = [
10095
10752
  // ADR-0032 §1a/1b — CEL predicates in actions/validations/flows/sharing/hooks
@@ -10150,8 +10807,14 @@ var AUTHORING_RULES = [
10150
10807
  input: "normalized",
10151
10808
  commands: ALL,
10152
10809
  source: "packages/lint/src/validate-functional-completeness.ts",
10153
- surfaces: CLI_ONLY,
10154
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
10810
+ // Runtime publish gate (#4716): the OBJECT write door — the five gating
10811
+ // object rules cross together under the 2026-08-18 adjudication. The
10812
+ // false-positive budget the crossing owed was exempted on a measured
10813
+ // 0 refusals / 75 real object declarations (authored config-file metadata,
10814
+ // so a lower bound — see RUNTIME_OBJECT_ADVISORY_VOLUME's note); the six
10815
+ // advisory-tier object rules deliberately do NOT ride.
10816
+ surfaces: CLI_AND_RUNTIME,
10817
+ runtimeTypes: ["object"],
10155
10818
  run: (stack) => validateFunctionalCompleteness(stack)
10156
10819
  },
10157
10820
  // [#7521, via cloud#1225] A managed object advertising a generic write verb
@@ -10175,8 +10838,11 @@ var AUTHORING_RULES = [
10175
10838
  input: "normalized",
10176
10839
  commands: ALL,
10177
10840
  source: "packages/lint/src/validate-managed-api-methods.ts",
10178
- surfaces: CLI_ONLY,
10179
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
10841
+ // Runtime publish gate (#4716): a managed object advertising a verb its
10842
+ // own affordances refuse is exactly the contradiction a Studio/MCP author
10843
+ // can save today — the CLI sweep (#7934) never sees an overlay row.
10844
+ surfaces: CLI_AND_RUNTIME,
10845
+ runtimeTypes: ["object"],
10180
10846
  run: (stack) => validateManagedApiMethods(stack)
10181
10847
  },
10182
10848
  // A view container in `views: []` that registers zero views: nothing appears
@@ -10198,19 +10864,38 @@ var AUTHORING_RULES = [
10198
10864
  },
10199
10865
  // ADR-0021 (#1719/#1721) — a widget's `dataset`/`dimensions`/`values` and its
10200
10866
  // chartConfig axis/series must resolve against the declared datasets.
10867
+ //
10868
+ // Runtime publish gate (#7529, maintainer-ruled 2026-08-12): a dashboard
10869
+ // widget bound to a dataset that resolves to nothing sailed through save AND
10870
+ // publish — this rule provably caught the body and was simply never invoked,
10871
+ // because `dashboard` was not a runtime-gated type. Option B: a DRAFT may
10872
+ // hold a forward reference; publishing refuses it with the key path named.
10873
+ // The snapshot carries `datasets` for exactly this rule (`RuntimeStackContext`
10874
+ // — without it every legitimate board reads as dangling, the 3-phantom
10875
+ // measurement). `surfaces` is per-RULE, so this flip puts all SIX of the
10876
+ // rule's error ids on the publish gate, not just `widget-dataset-unknown` —
10877
+ // ruled 2026-08-15: they are one coherent "this board cannot render"
10878
+ // reference-integrity class, and the ~6× wider accept-set narrowing was
10879
+ // accepted knowingly rather than splitting the dataset limb into its own
10880
+ // rule (traversal-duplication drift) or adding a per-finding-id surface
10881
+ // filter (registry machinery that weakens "delete a rule from the table and
10882
+ // enforcement stops in the same commit"). Warning-tier ids ride along on the
10883
+ // advisory channel and never block (#4463 P1).
10201
10884
  {
10202
10885
  name: "validateWidgetBindings",
10203
10886
  tier: "gating",
10204
10887
  input: "parsed",
10205
10888
  commands: ALL,
10206
10889
  source: "packages/lint/src/validate-widget-bindings.ts",
10207
- surfaces: CLI_ONLY,
10208
- surfaceReason: RUNTIME_NEEDS_FULL_SNAPSHOT,
10890
+ surfaces: CLI_AND_RUNTIME,
10891
+ runtimeTypes: ["dashboard"],
10209
10892
  run: (stack) => validateWidgetBindings(stack)
10210
10893
  },
10211
- // ADR-0049 / #3367 — a header or widget action naming a `script`/`modal`
10212
- // target that resolves to no defined action ships a button that renders and
10213
- // silently does nothing on click. Unresolved `url` routes stay advisory.
10894
+ // ADR-0049 / #3367 — a dashboard header action naming a dead target ships a
10895
+ // button that renders and refuses (or does nothing) on click: a `script`
10896
+ // target must name a defined action, a `modal` target must name a declared
10897
+ // page (objectstack#6739-A — a modal string target names a PAGE, only).
10898
+ // Unresolved `url` routes stay advisory.
10214
10899
  {
10215
10900
  name: "validateDashboardActionRefs",
10216
10901
  tier: "gating",
@@ -10235,6 +10920,28 @@ var AUTHORING_RULES = [
10235
10920
  surfaceReason: RUNTIME_NEEDS_FULL_SNAPSHOT,
10236
10921
  run: (stack) => validateFilterTokens(stack)
10237
10922
  },
10923
+ // #8793 (the ruled C half of #8690) — a declared dashboard date-range preset
10924
+ // name (`last_30_days`, …) authored as a bare ORDERING comparand resolves in
10925
+ // no layer: the engine refuses it on a declared temporal field at query time
10926
+ // (INVALID_FILTER / 400, PR #8808), and anywhere else it compares as a
10927
+ // literal string. This is the authoring-time refusal the ruling shipped
10928
+ // alongside the engine door, judging the filter literal in isolation —
10929
+ // ordering positions only, all three authored filter shapes. Like
10930
+ // `validateEmptyCombinators` it needs NO resolution context, so
10931
+ // RUNTIME_NEEDS_FULL_SNAPSHOT does not apply and the runtime gate runs it
10932
+ // for every filter-carrying type the gate already maps: the write path is
10933
+ // the one door an AI author uses, and dashboards/views are where the preset
10934
+ // vocabulary is near enough to reach for.
10935
+ {
10936
+ name: "validatePresetComparands",
10937
+ tier: "gating",
10938
+ input: "parsed",
10939
+ commands: ALL,
10940
+ source: "packages/lint/src/validate-preset-comparands.ts",
10941
+ surfaces: CLI_AND_RUNTIME,
10942
+ runtimeTypes: ["dashboard", "view", "object", "page", "flow"],
10943
+ run: (stack) => validatePresetComparands(stack)
10944
+ },
10238
10945
  // #5330 — the LITERAL empty combinators (`$and: []`, `$or: []`, `$not: {}`,
10239
10946
  // `{}`). #5322 ruled their RUNTIME meaning to be the boolean identity, and
10240
10947
  // this rule does not touch it: it refuses the literal SPELLINGS at authoring
@@ -10278,9 +10985,31 @@ var AUTHORING_RULES = [
10278
10985
  // collection in the snapshot and return nothing, and the two that would
10279
10986
  // load `typescript` need a hook/action/react body the snapshot never
10280
10987
  // carries — which is what `runtime-lazy-deps.test.ts` pins.
10988
+ //
10989
+ // [#9313] `view` joins, and the granularity decision #4463 P2 reserved is
10990
+ // taken HERE, in writing: the entry-level `runtimeTypes` says which WRITES
10991
+ // dispatch the suite, and the suite's own per-member `runtimeTypes`
10992
+ // (`ReferenceIntegrityRule`, default `['flow']`) says which MEMBERS judge
10993
+ // that snapshot — the gate passes the written type through
10994
+ // `ctx.runtimeWriteType`. A `view` write therefore reaches exactly the two
10995
+ // members that judge a list view's field references and resolve only
10996
+ // against the `objects` collection the snapshot carries
10997
+ // (`validateSearchableFields`, `validateSortableFields`). The whole suite
10998
+ // is NOT the right granularity for this door, measured not assumed, and
10999
+ // the crossing fails differently per body shape — both ways wrong:
11000
+ // `validateActionNameRefs` (error-tier) resolves `views[].list` /
11001
+ // `views[].listViews.*` action names against `stack.actions`, a collection
11002
+ // no per-write snapshot carries, so on a CONTAINER view write it would
11003
+ // refuse every stack-level action the body names (measured:
11004
+ // `action-name-undefined` on the snapshot shape, clean on the full stack)
11005
+ // — RUNTIME_NEEDS_FULL_SNAPSHOT's exact sentence, on the hottest write
11006
+ // type the gate has. On a FLATTENED overlay it has no rung at all, so the
11007
+ // crossing would be a silent no-op that reads as coverage — the very shape
11008
+ // #9313 was filed about. Flow snapshots are unchanged: every member keeps
11009
+ // the default `flow` declaration.
10281
11010
  surfaces: CLI_AND_RUNTIME,
10282
- runtimeTypes: ["flow"],
10283
- run: (stack) => validateReferenceIntegrity(stack)
11011
+ runtimeTypes: ["flow", "view"],
11012
+ run: (stack, ctx) => validateReferenceIntegrity(stack, ctx)
10284
11013
  },
10285
11014
  // ADR-0078 / #5068 — the SDUI component-props gate. `PageComponent.properties`
10286
11015
  // is `z.record(z.string(), z.unknown())` and ADR-0089 D3a strictness does not
@@ -10453,7 +11182,7 @@ var AUTHORING_RULES = [
10453
11182
  commands: ALL,
10454
11183
  source: "packages/lint/src/validate-record-title.ts",
10455
11184
  surfaces: CLI_ONLY,
10456
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
11185
+ surfaceReason: RUNTIME_OBJECT_ADVISORY_VOLUME,
10457
11186
  run: (stack) => validateRecordTitle(stack)
10458
11187
  },
10459
11188
  // ADR-0085 — `stageField` / `highlightFields` / `Field.group` are pointers
@@ -10466,7 +11195,7 @@ var AUTHORING_RULES = [
10466
11195
  commands: ALL,
10467
11196
  source: "packages/lint/src/validate-semantic-roles.ts",
10468
11197
  surfaces: CLI_ONLY,
10469
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
11198
+ surfaceReason: RUNTIME_OBJECT_ADVISORY_VOLUME,
10470
11199
  run: (stack) => validateSemanticRoles(stack)
10471
11200
  },
10472
11201
  // #2578 / #4449 — a form section's field reference that resolves to nothing
@@ -10677,7 +11406,7 @@ var AUTHORING_RULES = [
10677
11406
  commands: ALL,
10678
11407
  source: "packages/lint/src/lint-liveness-properties.ts",
10679
11408
  surfaces: CLI_ONLY,
10680
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
11409
+ surfaceReason: RUNTIME_OBJECT_ADVISORY_VOLUME,
10681
11410
  run: (stack) => lintLivenessProperties(stack).map((f) => ({
10682
11411
  severity: "warning",
10683
11412
  rule: f.rule,
@@ -10696,8 +11425,12 @@ var AUTHORING_RULES = [
10696
11425
  input: "parsed",
10697
11426
  commands: ALL,
10698
11427
  source: "packages/lint/src/lint-autonumber-formats.ts",
10699
- surfaces: CLI_ONLY,
10700
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
11428
+ // Runtime publish gate (#4716): an autonumber referencing a field the
11429
+ // object does not carry is broken from the first record; only the error
11430
+ // arm blocks — the optional-field arm is `warning` and rides the
11431
+ // advisory channel like every other non-error finding (#4463 P1).
11432
+ surfaces: CLI_AND_RUNTIME,
11433
+ runtimeTypes: ["object"],
10701
11434
  run: (stack) => lintAutonumberFormats(stack).map((f) => ({
10702
11435
  severity: f.severity,
10703
11436
  rule: f.rule,
@@ -10738,12 +11471,12 @@ var AUTHORING_RULES = [
10738
11471
  commands: ["validate", "build"],
10739
11472
  source: "packages/lint/src/data-model-rules.ts",
10740
11473
  surfaces: CLI_ONLY,
10741
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
11474
+ surfaceReason: RUNTIME_OBJECT_ADVISORY_VOLUME,
10742
11475
  scopeReason: "`os lint` already reports this rule through `lintDataModel`, which calls it directly ahead of R10 in its best-practice sweep \u2014 registering it for `lint` as well would report every finding twice. This is coverage recorded, not coverage missing: all three commands report the rule.",
10743
11476
  run: (stack) => lintUnscopedDeclaredIndexes(Array.isArray(stack.objects) ? stack.objects : []).map((f) => ({
10744
11477
  severity: f.severity === "suggestion" ? "info" : f.severity,
10745
11478
  rule: f.rule,
10746
- where: f.path,
11479
+ where: f.where,
10747
11480
  path: f.path,
10748
11481
  message: f.message,
10749
11482
  hint: f.fix ?? ""
@@ -10759,12 +11492,12 @@ var AUTHORING_RULES = [
10759
11492
  commands: ["validate", "build"],
10760
11493
  source: "packages/lint/src/data-model-rules.ts",
10761
11494
  surfaces: CLI_ONLY,
10762
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
11495
+ surfaceReason: RUNTIME_OBJECT_ADVISORY_VOLUME,
10763
11496
  scopeReason: "`os lint` already reports this rule through `lintDataModel`, which calls it directly as R10 of its best-practice sweep \u2014 registering it for `lint` as well would report every finding twice. This is coverage recorded, not coverage missing: all three commands report the rule.",
10764
11497
  run: (stack) => lintUniqueDeclarations(Array.isArray(stack.objects) ? stack.objects : []).map((f) => ({
10765
11498
  severity: f.severity === "suggestion" ? "info" : f.severity,
10766
11499
  rule: f.rule,
10767
- where: f.path,
11500
+ where: f.where,
10768
11501
  path: f.path,
10769
11502
  message: f.message,
10770
11503
  hint: f.fix ?? ""
@@ -10781,12 +11514,12 @@ var AUTHORING_RULES = [
10781
11514
  commands: ["validate", "build"],
10782
11515
  source: "packages/lint/src/data-model-rules.ts",
10783
11516
  surfaces: CLI_ONLY,
10784
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
11517
+ surfaceReason: RUNTIME_OBJECT_ADVISORY_VOLUME,
10785
11518
  scopeReason: "`os lint` already reports this rule through `lintDataModel`, which calls it directly alongside R10/R11 in its best-practice sweep \u2014 registering it for `lint` as well would report every finding twice. This is coverage recorded, not coverage missing: all three commands report the rule.",
10786
11519
  run: (stack) => lintLegacyOrganizationComposites(Array.isArray(stack.objects) ? stack.objects : []).map((f) => ({
10787
11520
  severity: f.severity === "suggestion" ? "info" : f.severity,
10788
11521
  rule: f.rule,
10789
- where: f.path,
11522
+ where: f.where,
10790
11523
  path: f.path,
10791
11524
  message: f.message,
10792
11525
  hint: f.fix ?? ""
@@ -10914,16 +11647,26 @@ var AUTHORING_RULES = [
10914
11647
  surfaceReason: RUNTIME_NEEDS_FULL_SNAPSHOT,
10915
11648
  run: (stack) => validateOrgAxisRedLines(stack)
10916
11649
  },
10917
- // #4698 — the "declared but never read" gate, for the one surface where the
10918
- // predicate is EXACT rather than inferred. A sharing rule's `condition` has a
10919
- // single runtime consumer (`bootstrapDeclaredSharingRules`) whose only use of
10920
- // the key is `compileCelToFilter(condition, { variables: {} })`; a condition
10921
- // that does not lower means the rule is SKIPPED at boot, so the grant is
10922
- // declared and does not exist. The lint calls that same compiler, from the
10923
- // same package, with the same options the verdict cannot drift from the
10924
- // consumer's. Gating for the ADR-0078 reason `SharingRuleSchema`'s own
10925
- // docblock states: the whole authorable surface is enforced, and this was the
10926
- // one field where that sentence was not yet true.
11650
+ // #4698 / #9698 — the "declared but never read" gate, for the two fields of a
11651
+ // sharing rule where the predicate is EXACT rather than inferred.
11652
+ //
11653
+ // - `condition` has a single runtime consumer
11654
+ // (`bootstrapDeclaredSharingRules`) whose only use of the key is
11655
+ // `compileCelToFilter(condition, { variables: {} })`; a condition that
11656
+ // does not lower means the rule is SKIPPED at boot.
11657
+ // - `object` decides whether the grant is refused outright: reconcile hands
11658
+ // each row to `SharingService.grant`, whose ADR-0111 D7 pre-flight THROWS
11659
+ // `SHARING_NOT_ENABLED` when the anchor's effective sharing model is
11660
+ // `public` or it is a `controlled_by_parent` detail. Both are decidable
11661
+ // from authored metadata; the other arms of that verdict (`owner_id`, the
11662
+ // bypass set, federated anchors) are not, and are excluded by name.
11663
+ //
11664
+ // Either way the grant is declared and does not exist. The lint calls the
11665
+ // same compiler and mirrors the same verdict function, from the same inputs
11666
+ // — the verdict cannot drift from the consumers'. Gating for the ADR-0078
11667
+ // reason `SharingRuleSchema`'s own docblock states: the whole authorable
11668
+ // surface is enforced, and these were the fields where that sentence was not
11669
+ // yet true.
10927
11670
  {
10928
11671
  name: "validateSharingRuleEnforceability",
10929
11672
  tier: "gating",
@@ -10931,7 +11674,7 @@ var AUTHORING_RULES = [
10931
11674
  commands: ALL,
10932
11675
  source: "packages/lint/src/validate-sharing-rule-enforceability.ts",
10933
11676
  surfaces: CLI_ONLY,
10934
- surfaceReason: "P2 (#4463): a sharing rule is not a `flow`, and P1 gates `flow` alone. The rule itself is snapshot-safe \u2014 it reads ONLY `stack.sharingRules[].condition` and needs no other collection \u2014 so widening it here is a `runtimeTypes: ['sharing_rule']` edit once the gate accepts that type, not new wiring. Recorded as pending rather than done, because a rule that has never run at a door should not claim it.",
11677
+ surfaceReason: "P2 (#4463): a sharing rule is not a `flow`, and P1 gates `flow` alone. This entry used to add that the rule reads ONLY `stack.sharingRules[].condition` and needs no other collection, so crossing was a lone `runtimeTypes` edit. #9698 FALSIFIED that: the anchor arm resolves `sharingRules[].object` against `stack.objects` to read the anchor's OWD, so the rule is now cross-collection. `objects` IS carried by the per-write snapshot (`CONTEXT_STACK_KEYS`, #8309), so the remaining gap is unchanged in SHAPE \u2014 the gate must accept a `sharing_rule` type and the snapshot must carry `sharingRules`, which it does not \u2014 but it is now TWO collections, not one. Crossing with `sharingRules` uncarried would enforce this id for zero of its inputs while the entry claimed the door (#7220). Recorded as pending rather than done, because a rule that has never run at a door should not claim it.",
10935
11678
  run: (stack) => validateSharingRuleEnforceability(stack)
10936
11679
  },
10937
11680
  // #4983 — the sibling surface of the rule above, and ADR-0056 D4's gate,
@@ -10970,8 +11713,15 @@ var AUTHORING_RULES = [
10970
11713
  input: "parsed",
10971
11714
  commands: ALL,
10972
11715
  source: "packages/lint/src/validate-rule-compilability.ts",
10973
- surfaces: CLI_ONLY,
10974
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
11716
+ // Runtime publish gate (#4716): the rule loads ajv LAZILY, only when the
11717
+ // judged snapshot actually carries a `json_schema` validation — so an
11718
+ // ordinary object write (no `json_schema` anywhere) still loads no
11719
+ // compiler, which `runtime-lazy-deps.test.ts` pins in both directions.
11720
+ // The load it does take (~64 ms cold once, ~15 ms warm per publish
11721
+ // carrying such a rule) is the measured, adjudicated price of refusing a
11722
+ // validation rule that would otherwise ship compiled-by-nothing.
11723
+ surfaces: CLI_AND_RUNTIME,
11724
+ runtimeTypes: ["object"],
10975
11725
  run: (stack) => validateRuleCompilability(stack)
10976
11726
  },
10977
11727
  // #5178 — the residual half of #5029, which registering `ajv-formats` does
@@ -10992,8 +11742,12 @@ var AUTHORING_RULES = [
10992
11742
  input: "parsed",
10993
11743
  commands: ALL,
10994
11744
  source: "packages/lint/src/validate-rule-schema-formats.ts",
10995
- surfaces: CLI_ONLY,
10996
- surfaceReason: RUNTIME_OBJECT_WRITES_P2,
11745
+ // Runtime publish gate (#4716): crosses with its compile sibling above —
11746
+ // the two judgements over one artifact stay on one side of the wall
11747
+ // (#7220's family discipline). Same lazy-ajv contract: the registered
11748
+ // format set is only enumerated once a schema actually names a format.
11749
+ surfaces: CLI_AND_RUNTIME,
11750
+ runtimeTypes: ["object"],
10997
11751
  run: (stack) => validateRuleSchemaFormats(stack)
10998
11752
  }
10999
11753
  ];
@@ -11053,7 +11807,20 @@ var TYPE_TO_STACK_KEY = {
11053
11807
  permission: "permissions",
11054
11808
  book: "books"
11055
11809
  };
11056
- var CONTEXT_STACK_KEYS = ["objects", "permissions", "books"];
11810
+ function narrowObjectsToPackageClosure(objects, scope) {
11811
+ if (!scope || typeof scope.packageId !== "string" || scope.packageId === "") return objects;
11812
+ const reachable = /* @__PURE__ */ new Set([scope.packageId, ...scope.dependencies]);
11813
+ return objects.filter((entry) => {
11814
+ if (!entry || typeof entry !== "object") return true;
11815
+ const owner = entry[PACKAGE_PROVENANCE_KEY];
11816
+ if (typeof owner !== "string" || owner === "" || owner === OVERLAY_PROVENANCE_SENTINEL) return true;
11817
+ if (reachable.has(owner)) return true;
11818
+ return isSystemObject(entry);
11819
+ });
11820
+ }
11821
+ var PACKAGE_PROVENANCE_KEY = "_packageId";
11822
+ var OVERLAY_PROVENANCE_SENTINEL = "sys_metadata";
11823
+ var CONTEXT_STACK_KEYS = ["objects", "permissions", "books", "datasets"];
11057
11824
  function runtimeAuthoringRulesFor(type) {
11058
11825
  return AUTHORING_RULES.filter(
11059
11826
  (r) => r.surfaces.includes("runtime-publish") && (r.runtimeTypes ?? []).includes(type)
@@ -11079,7 +11846,8 @@ function buildRuntimeWriteSnapshots(args) {
11079
11846
  const itemName = typeof item.name === "string" ? item.name : void 0;
11080
11847
  const baseline = {};
11081
11848
  for (const key of CONTEXT_STACK_KEYS) {
11082
- const collection = args.context?.[key] ?? [];
11849
+ const raw = args.context?.[key] ?? [];
11850
+ const collection = key === "objects" ? narrowObjectsToPackageClosure(raw, args.packageScope) : raw;
11083
11851
  baseline[key] = key === stackKey ? collection.filter((o) => !itemName || o?.name !== itemName) : collection;
11084
11852
  }
11085
11853
  const candidate = {
@@ -11088,6 +11856,20 @@ function buildRuntimeWriteSnapshots(args) {
11088
11856
  };
11089
11857
  return { baseline, candidate };
11090
11858
  }
11859
+ var NAME_KEYED_STACK_KEYS = ["objects", "permissions", "books"];
11860
+ var PATH_SAFE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
11861
+ var TOP_LEVEL_INDEX = /^(objects|permissions|books)\[(\d+)\](.*)$/;
11862
+ function nameKeyFindingPath(path, candidate) {
11863
+ const m = TOP_LEVEL_INDEX.exec(path);
11864
+ if (!m) return path;
11865
+ const [, stackKey, index, rest] = m;
11866
+ if (!NAME_KEYED_STACK_KEYS.includes(stackKey)) return path;
11867
+ const collection = candidate[stackKey];
11868
+ const entry = collection?.[Number(index)];
11869
+ const name = entry && typeof entry === "object" ? entry.name : void 0;
11870
+ if (typeof name !== "string" || !PATH_SAFE_NAME.test(name)) return path;
11871
+ return `${stackKey}.${name}${rest}`;
11872
+ }
11091
11873
  function runRules(rules, stack, ctx) {
11092
11874
  const findings = [];
11093
11875
  for (const rule of rules) {
@@ -11113,12 +11895,16 @@ function runRuntimeAuthoringRules(args) {
11113
11895
  const snapshots = buildRuntimeWriteSnapshots({
11114
11896
  type: args.type,
11115
11897
  item: args.item,
11116
- ...args.context !== void 0 ? { context: args.context } : {}
11898
+ ...args.context !== void 0 ? { context: args.context } : {},
11899
+ ...args.packageScope !== void 0 ? { packageScope: args.packageScope } : {}
11117
11900
  });
11118
11901
  if (!snapshots) return empty;
11119
- const ctx = { sduiManifest: args.sduiManifest };
11902
+ const ctx = { sduiManifest: args.sduiManifest, runtimeWriteType: args.type };
11120
11903
  const before = new Set(runRules(rules, snapshots.baseline, ctx).map(fingerprint));
11121
- const added = runRules(rules, snapshots.candidate, ctx).filter((f) => !before.has(fingerprint(f)));
11904
+ const added = runRules(rules, snapshots.candidate, ctx).filter((f) => !before.has(fingerprint(f))).map((f) => {
11905
+ const path = nameKeyFindingPath(f.path, snapshots.candidate);
11906
+ return path === f.path ? f : { ...f, path };
11907
+ });
11122
11908
  return {
11123
11909
  errors: added.filter((f) => f.severity === "error"),
11124
11910
  advisories: added.filter((f) => f.severity !== "error"),
@@ -11127,10 +11913,12 @@ function runRuntimeAuthoringRules(args) {
11127
11913
  }
11128
11914
  // Annotate the CommonJS export names for ESM import in node:
11129
11915
  0 && (module.exports = {
11916
+ ACTION_BODY_SOURCE_UNPARSEABLE,
11130
11917
  ACTION_BODY_WRITE_EXCLUSIONS,
11131
11918
  ACTION_BODY_WRITE_PATTERNS,
11132
11919
  ACTION_BODY_WRITE_PATTERN_IDS,
11133
11920
  ACTION_BODY_WRITE_UNKNOWN_FIELD,
11921
+ ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR,
11134
11922
  ACTION_NAME_UNDEFINED,
11135
11923
  ACTION_NO_PLACEMENT,
11136
11924
  ACTION_RECORD_WRITE_DISCARDED,
@@ -11176,6 +11964,7 @@ function runRuntimeAuthoringRules(args) {
11176
11964
  FIELD_GROUP_UNDECLARED,
11177
11965
  FILTER_EMPTY_COMBINATOR,
11178
11966
  FILTER_EMPTY_NODE,
11967
+ FILTER_PRESET_COMPARAND,
11179
11968
  FILTER_TOKEN_UNKNOWN,
11180
11969
  FLOW_APPROVAL_REVISE_DEAD_END,
11181
11970
  FLOW_APPROVAL_REVISE_DISABLED,
@@ -11193,6 +11982,7 @@ function runRuntimeAuthoringRules(args) {
11193
11982
  FLOW_MULTIPLE_DEFAULT_EDGES,
11194
11983
  FLOW_MULTI_WRITE_UNFILTERED,
11195
11984
  FLOW_NODE_WRITE_UNKNOWN_FIELD,
11985
+ FLOW_NODE_WRITE_UNPROVISIONED_ANCHOR,
11196
11986
  FLOW_PHANTOM_AGGREGATION,
11197
11987
  FLOW_RUNAS_UNSCOPED,
11198
11988
  FLOW_TEMPLATE_FIELD_UNPROVISIONED,
@@ -11210,10 +12000,12 @@ function runRuntimeAuthoringRules(args) {
11210
12000
  FLOW_WRITE_NODE_TYPES_DEFERRED,
11211
12001
  FORM_COLSPAN_ABSOLUTE,
11212
12002
  FORM_FIELD_UNKNOWN,
12003
+ HOOK_BODY_SOURCE_UNPARSEABLE,
11213
12004
  HOOK_BODY_WRITE_EXCLUSIONS,
11214
12005
  HOOK_BODY_WRITE_PATTERNS,
11215
12006
  HOOK_BODY_WRITE_PATTERN_IDS,
11216
12007
  HOOK_BODY_WRITE_UNKNOWN_FIELD,
12008
+ HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR,
11217
12009
  LIST_VIEW_FILTERS_IN_VIEWS_MODE,
11218
12010
  LIVENESS_DEAD_PROPERTY,
11219
12011
  LIVENESS_EXPERIMENTAL_PROPERTY,
@@ -11232,6 +12024,7 @@ function runRuntimeAuthoringRules(args) {
11232
12024
  PAGE_FIELD_UNKNOWN,
11233
12025
  PAGE_FIELD_UNPROVISIONED,
11234
12026
  PAGE_SOURCE_CLASSNAME,
12027
+ PARSE_FAILURE_HINT,
11235
12028
  PREDICATE_PATH_UNRESOLVED,
11236
12029
  PREDICATE_PATH_UNROOTED,
11237
12030
  PREDICATE_RHS_PATH_SHAPED,
@@ -11242,6 +12035,7 @@ function runRuntimeAuthoringRules(args) {
11242
12035
  REACT_CHART_DRILLDOWN_INVALID,
11243
12036
  REACT_CHART_FIELD_UNKNOWN,
11244
12037
  REACT_CHART_FIELD_UNPROVISIONED,
12038
+ REACT_PAGE_SOURCE_UNPARSEABLE,
11245
12039
  REFERENCE_INTEGRITY_RULES,
11246
12040
  RLS_PREDICATE_OVER_BUDGET,
11247
12041
  RLS_PREDICATE_UNENFORCEABLE,
@@ -11249,6 +12043,7 @@ function runRuntimeAuthoringRules(args) {
11249
12043
  RUNTIME_AJV_OPTIONS,
11250
12044
  SEAL_MARKERS,
11251
12045
  SEARCHABLE_FIELD_UNKNOWN,
12046
+ SEARCHABLE_FIELD_UNPROVISIONED,
11252
12047
  SEARCHABLE_FIELD_UNSEARCHABLE,
11253
12048
  SECURITY_ANCHOR_HIGH_PRIVILEGE,
11254
12049
  SECURITY_BOOK_AUDIENCE_UNKNOWN_SET,
@@ -11267,9 +12062,15 @@ function runRuntimeAuthoringRules(args) {
11267
12062
  SEED_VALUE_OUTSIDE_STATE_MACHINE,
11268
12063
  SEMANTIC_ROLE_FIELD_UNKNOWN,
11269
12064
  SEMANTIC_ROLE_FIELD_UNPROVISIONED,
12065
+ SHARING_RULE_OBJECT_CONTROLLED_BY_PARENT,
12066
+ SHARING_RULE_OBJECT_NOT_SHAREABLE,
11270
12067
  SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
11271
12068
  SHARING_RULE_UNLOWERABLE_CONDITION,
12069
+ SORT_FIELD_UNKNOWN,
12070
+ SORT_FIELD_UNPROVISIONED,
12071
+ SORT_FIELD_UNSORTABLE,
11272
12072
  STARTUP_OPEN_VOCABULARY_VERDICT,
12073
+ STARTUP_SOURCE_UNPARSEABLE,
11273
12074
  STARTUP_VERDICT_ASSERTIVE_WORDING,
11274
12075
  STARTUP_VERDICT_HINT,
11275
12076
  STYLE_CLASSNAME_TAILWIND,
@@ -11305,6 +12106,8 @@ function runRuntimeAuthoringRules(args) {
11305
12106
  authoringRulesFor,
11306
12107
  buildAccessMatrix,
11307
12108
  buildRuntimeWriteSnapshots,
12109
+ checkSortDeclaration,
12110
+ describeParseFailure,
11308
12111
  diffAccessMatrix,
11309
12112
  extractHookBodyWriteSet,
11310
12113
  extractHookBodyWrites,
@@ -11319,6 +12122,7 @@ function runRuntimeAuthoringRules(args) {
11319
12122
  lintUniqueDeclarations,
11320
12123
  lintUnscopedDeclaredIndexes,
11321
12124
  lintViewRefs,
12125
+ narrowObjectsToPackageClosure,
11322
12126
  nearestRegisteredFormat,
11323
12127
  nullGuardMessage,
11324
12128
  runAuthoringRules,
@@ -11357,6 +12161,7 @@ function runRuntimeAuthoringRules(args) {
11357
12161
  validatePageFieldBindings,
11358
12162
  validatePageSourceStyling,
11359
12163
  validatePredicatePathRefs,
12164
+ validatePresetComparands,
11360
12165
  validateReactPageProps,
11361
12166
  validateReactPages,
11362
12167
  validateReadonlyFlowWrites,
@@ -11373,6 +12178,7 @@ function runRuntimeAuthoringRules(args) {
11373
12178
  validateSeedStateMachine,
11374
12179
  validateSemanticRoles,
11375
12180
  validateSharingRuleEnforceability,
12181
+ validateSortableFields,
11376
12182
  validateStackExpressions,
11377
12183
  validateTranslatableSections,
11378
12184
  validateTranslationReferences,