@objectstack/lint 17.1.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,6 +20,7 @@ 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,
@@ -106,6 +107,7 @@ __export(index_exports, {
106
107
  FLOW_WRITE_NODE_TYPES_DEFERRED: () => FLOW_WRITE_NODE_TYPES_DEFERRED,
107
108
  FORM_COLSPAN_ABSOLUTE: () => FORM_COLSPAN_ABSOLUTE,
108
109
  FORM_FIELD_UNKNOWN: () => FORM_FIELD_UNKNOWN,
110
+ HOOK_BODY_SOURCE_UNPARSEABLE: () => HOOK_BODY_SOURCE_UNPARSEABLE,
109
111
  HOOK_BODY_WRITE_EXCLUSIONS: () => HOOK_BODY_WRITE_EXCLUSIONS,
110
112
  HOOK_BODY_WRITE_PATTERNS: () => HOOK_BODY_WRITE_PATTERNS,
111
113
  HOOK_BODY_WRITE_PATTERN_IDS: () => HOOK_BODY_WRITE_PATTERN_IDS,
@@ -129,6 +131,7 @@ __export(index_exports, {
129
131
  PAGE_FIELD_UNKNOWN: () => PAGE_FIELD_UNKNOWN,
130
132
  PAGE_FIELD_UNPROVISIONED: () => PAGE_FIELD_UNPROVISIONED,
131
133
  PAGE_SOURCE_CLASSNAME: () => PAGE_SOURCE_CLASSNAME,
134
+ PARSE_FAILURE_HINT: () => PARSE_FAILURE_HINT,
132
135
  PREDICATE_PATH_UNRESOLVED: () => PREDICATE_PATH_UNRESOLVED,
133
136
  PREDICATE_PATH_UNROOTED: () => PREDICATE_PATH_UNROOTED,
134
137
  PREDICATE_RHS_PATH_SHAPED: () => PREDICATE_RHS_PATH_SHAPED,
@@ -139,6 +142,7 @@ __export(index_exports, {
139
142
  REACT_CHART_DRILLDOWN_INVALID: () => REACT_CHART_DRILLDOWN_INVALID,
140
143
  REACT_CHART_FIELD_UNKNOWN: () => REACT_CHART_FIELD_UNKNOWN,
141
144
  REACT_CHART_FIELD_UNPROVISIONED: () => REACT_CHART_FIELD_UNPROVISIONED,
145
+ REACT_PAGE_SOURCE_UNPARSEABLE: () => REACT_PAGE_SOURCE_UNPARSEABLE,
142
146
  REFERENCE_INTEGRITY_RULES: () => REFERENCE_INTEGRITY_RULES,
143
147
  RLS_PREDICATE_OVER_BUDGET: () => RLS_PREDICATE_OVER_BUDGET,
144
148
  RLS_PREDICATE_UNENFORCEABLE: () => RLS_PREDICATE_UNENFORCEABLE,
@@ -170,8 +174,10 @@ __export(index_exports, {
170
174
  SHARING_RULE_RUNTIME_VARIABLE_CONDITION: () => SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
171
175
  SHARING_RULE_UNLOWERABLE_CONDITION: () => SHARING_RULE_UNLOWERABLE_CONDITION,
172
176
  SORT_FIELD_UNKNOWN: () => SORT_FIELD_UNKNOWN,
177
+ SORT_FIELD_UNPROVISIONED: () => SORT_FIELD_UNPROVISIONED,
173
178
  SORT_FIELD_UNSORTABLE: () => SORT_FIELD_UNSORTABLE,
174
179
  STARTUP_OPEN_VOCABULARY_VERDICT: () => STARTUP_OPEN_VOCABULARY_VERDICT,
180
+ STARTUP_SOURCE_UNPARSEABLE: () => STARTUP_SOURCE_UNPARSEABLE,
175
181
  STARTUP_VERDICT_ASSERTIVE_WORDING: () => STARTUP_VERDICT_ASSERTIVE_WORDING,
176
182
  STARTUP_VERDICT_HINT: () => STARTUP_VERDICT_HINT,
177
183
  STYLE_CLASSNAME_TAILWIND: () => STYLE_CLASSNAME_TAILWIND,
@@ -208,6 +214,7 @@ __export(index_exports, {
208
214
  buildAccessMatrix: () => buildAccessMatrix,
209
215
  buildRuntimeWriteSnapshots: () => buildRuntimeWriteSnapshots,
210
216
  checkSortDeclaration: () => checkSortDeclaration,
217
+ describeParseFailure: () => describeParseFailure,
211
218
  diffAccessMatrix: () => diffAccessMatrix,
212
219
  extractHookBodyWriteSet: () => extractHookBodyWriteSet,
213
220
  extractHookBodyWrites: () => extractHookBodyWrites,
@@ -1303,6 +1310,38 @@ function validateStackExpressions(stack) {
1303
1310
 
1304
1311
  // src/lint-startup-registry-verdict.ts
1305
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
1306
1345
  var import_meta = {};
1307
1346
  var cachedTs = null;
1308
1347
  function loadTypeScript() {
@@ -1319,6 +1358,7 @@ function loadTypeScript() {
1319
1358
  }
1320
1359
  var STARTUP_OPEN_VOCABULARY_VERDICT = "startup-open-vocabulary-verdict";
1321
1360
  var STARTUP_VERDICT_ASSERTIVE_WORDING = "startup-verdict-assertive-wording";
1361
+ var STARTUP_SOURCE_UNPARSEABLE = "startup-source-unparseable";
1322
1362
  var OPEN_VOCABULARY_PROBES = /* @__PURE__ */ new Map([
1323
1363
  [
1324
1364
  "getRegisteredNodeTypes",
@@ -1547,13 +1587,22 @@ function findStartupRegistryVerdicts(source, options = {}) {
1547
1587
  if (!mentionsAny) return [];
1548
1588
  const t = loadTypeScript();
1549
1589
  const fileLabel = options.file ?? "source";
1550
- let sf;
1551
- try {
1552
- sf = t.createSourceFile(fileLabel, source, t.ScriptTarget.Latest, true, t.ScriptKind.TS);
1553
- } catch {
1554
- return [];
1555
- }
1590
+ const { sourceFile: sf, failure } = createSourceFileChecked(t, fileLabel, source, {
1591
+ target: t.ScriptTarget.Latest,
1592
+ setParentNodes: true,
1593
+ scriptKind: t.ScriptKind.TS
1594
+ });
1556
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
+ }
1557
1606
  const moduleBindings = moduleLevelMutableBindings(t, sf);
1558
1607
  const functionBodies = indexFunctionBodies(t, sf);
1559
1608
  const lineOf = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
@@ -3079,6 +3128,26 @@ function validateSearchableFields(stack) {
3079
3128
  if (!isRec4(view)) continue;
3080
3129
  const viewLabel2 = strName2(view.name) ?? strName2(view.objectName) ?? `#${vi}`;
3081
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
+ }
3082
3151
  if (isRec4(view.list)) {
3083
3152
  check(
3084
3153
  view.list.searchableFields,
@@ -3526,6 +3595,7 @@ function filterAttrValue(tsc, sf, attr) {
3526
3595
  };
3527
3596
  return perPosition(init.expression);
3528
3597
  }
3598
+ var REACT_PAGE_SOURCE_UNPARSEABLE = "react-page-source-unparseable";
3529
3599
  var REACT_CHART_FIELD_UNKNOWN = "react-chart-field-unknown";
3530
3600
  var REACT_CHART_FIELD_UNPROVISIONED = "react-chart-field-unprovisioned";
3531
3601
  var REACT_CHART_AGGREGATE_INVALID = "react-chart-aggregate-invalid";
@@ -3854,11 +3924,20 @@ function validateReactPageProps(stack) {
3854
3924
  if (typeof source !== "string" || source.trim() === "") continue;
3855
3925
  const name = String(page.name ?? `#${p}`);
3856
3926
  const tsc = loadTypeScript2();
3857
- let sf;
3858
- try {
3859
- sf = tsc.createSourceFile("page.tsx", source, tsc.ScriptTarget.Latest, true, tsc.ScriptKind.TSX);
3860
- } catch {
3861
- 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
+ });
3862
3941
  }
3863
3942
  const locals = localComponentNames(tsc, sf);
3864
3943
  const visit = (node) => {
@@ -6741,6 +6820,7 @@ function validateNavObjectServability(stack) {
6741
6820
  var import_data11 = require("@objectstack/spec/data");
6742
6821
  var SORT_FIELD_UNKNOWN = "sort-field-unknown";
6743
6822
  var SORT_FIELD_UNSORTABLE = "sort-field-unsortable";
6823
+ var SORT_FIELD_UNPROVISIONED = "sort-field-unprovisioned";
6744
6824
  function isRec16(v) {
6745
6825
  return !!v && typeof v === "object" && !Array.isArray(v);
6746
6826
  }
@@ -6803,7 +6883,7 @@ function distance3(a, b) {
6803
6883
  }
6804
6884
  return prev[n];
6805
6885
  }
6806
- function checkSortDeclaration(declared, objectName, fieldsByObject, where, path, subject) {
6886
+ function checkSortDeclaration(declared, objectName, fieldsByObject, where, path, subject, unprovisionedAnchors) {
6807
6887
  const findings = [];
6808
6888
  if (declared === void 0 || declared === null) return findings;
6809
6889
  if (!objectName) return findings;
@@ -6811,10 +6891,23 @@ function checkSortDeclaration(declared, objectName, fieldsByObject, where, path,
6811
6891
  const target = fieldsByObject.get(objectName);
6812
6892
  if (!target) return findings;
6813
6893
  const known = target.names;
6894
+ const anchors = unprovisionedAnchors?.get(objectName);
6814
6895
  for (const key of readSortKeys(declared)) {
6815
6896
  const name = key.field;
6816
6897
  const head = name.split(".")[0];
6817
- if (SYSTEM_FIELDS.has(head)) continue;
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
+ }
6818
6911
  if (!known.has(head)) {
6819
6912
  const dotted = name.includes(".");
6820
6913
  findings.push({
@@ -6847,9 +6940,18 @@ function validateSortableFields(stack) {
6847
6940
  if (!isRec16(stack)) return findings;
6848
6941
  const objects = Array.isArray(stack.objects) ? stack.objects : isRec16(stack.objects) ? Object.entries(stack.objects).map(([name, def]) => ({ name, ...def })) : [];
6849
6942
  const fieldsByObject = indexObjectSearchTargets(stack);
6943
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
6850
6944
  const check = (declared, objectName, where, path, subject) => {
6851
6945
  findings.push(
6852
- ...checkSortDeclaration(declared, objectName, fieldsByObject, where, path, subject)
6946
+ ...checkSortDeclaration(
6947
+ declared,
6948
+ objectName,
6949
+ fieldsByObject,
6950
+ where,
6951
+ path,
6952
+ subject,
6953
+ unprovisionedAnchors
6954
+ )
6853
6955
  );
6854
6956
  };
6855
6957
  for (let oi = 0; oi < objects.length; oi++) {
@@ -6878,6 +6980,24 @@ function validateSortableFields(stack) {
6878
6980
  if (!isRec16(view)) continue;
6879
6981
  const viewLabel2 = strName11(view.name) ?? strName11(view.objectName) ?? `#${vi}`;
6880
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
+ }
6881
7001
  if (isRec16(view.list)) {
6882
7002
  check(
6883
7003
  view.list.sort,
@@ -8787,6 +8907,7 @@ function loadTypeScript3() {
8787
8907
  return cachedTs3;
8788
8908
  }
8789
8909
  var HOOK_BODY_WRITE_UNKNOWN_FIELD = "hook-body-write-unknown-field";
8910
+ var HOOK_BODY_SOURCE_UNPARSEABLE = "hook-body-source-unparseable";
8790
8911
  var HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR = "hook-body-write-unprovisioned-anchor";
8791
8912
  var HOOK_BODY_WRITE_PATTERNS = [
8792
8913
  {
@@ -8900,15 +9021,18 @@ function extractHookBodyWriteSet(source) {
8900
9021
  return { writes: [], ctxRecordEscapes: false };
8901
9022
  }
8902
9023
  const tsc = loadTypeScript3();
8903
- const sf = tsc.createSourceFile(
9024
+ const { sourceFile: sf, failure: parseFailure } = createSourceFileChecked(
9025
+ tsc,
8904
9026
  "hook-body.ts",
8905
9027
  `async function __body(ctx) {
8906
9028
  ${source}
8907
9029
  }`,
8908
- tsc.ScriptTarget.Latest,
8909
- /* setParentNodes */
8910
- false,
8911
- tsc.ScriptKind.TS
9030
+ {
9031
+ target: tsc.ScriptTarget.Latest,
9032
+ setParentNodes: false,
9033
+ scriptKind: tsc.ScriptKind.TS,
9034
+ synthesizedLinesBefore: 1
9035
+ }
8912
9036
  );
8913
9037
  const writes = [];
8914
9038
  const recordRefs = [];
@@ -9005,7 +9129,8 @@ ${source}
9005
9129
  visit(sf);
9006
9130
  return {
9007
9131
  writes,
9008
- ctxRecordEscapes: recordRefs.some((ref) => !consumedRecordRefs.has(ref))
9132
+ ctxRecordEscapes: recordRefs.some((ref) => !consumedRecordRefs.has(ref)),
9133
+ ...parseFailure ? { parseFailure } : {}
9009
9134
  };
9010
9135
  }
9011
9136
  function validateHookBodyWrites(stack) {
@@ -9019,11 +9144,22 @@ function validateHookBodyWrites(stack) {
9019
9144
  if (!isRec23(body) || body.language !== "js") return;
9020
9145
  const source = body.source;
9021
9146
  if (typeof source !== "string" || source.trim() === "") return;
9022
- 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));
9023
9160
  if (writes.length === 0) return;
9024
9161
  objectFields ?? (objectFields = indexObjectFields2(stack));
9025
9162
  anchors ?? (anchors = indexUnprovisionedAnchors(stack));
9026
- const hookName = typeof hook.name === "string" && hook.name ? hook.name : `#${hookIndex}`;
9027
9163
  const targets = (Array.isArray(hook.object) ? hook.object : [hook.object]).filter(
9028
9164
  (o) => typeof o === "string" && o.trim() !== ""
9029
9165
  );
@@ -9108,6 +9244,7 @@ function fixHint(field, declared) {
9108
9244
  var import_shared2 = require("@objectstack/spec/shared");
9109
9245
  var ACTION_BODY_WRITE_UNKNOWN_FIELD = "action-body-write-unknown-field";
9110
9246
  var ACTION_RECORD_WRITE_DISCARDED = "action-record-write-discarded";
9247
+ var ACTION_BODY_SOURCE_UNPARSEABLE = "action-body-source-unparseable";
9111
9248
  var ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR = "action-body-write-unprovisioned-anchor";
9112
9249
  var ACTION_BODY_WRITE_PATTERN_IDS = ["api-crud-literal"];
9113
9250
  var ACTION_RECORD_WRITE_PATTERN_IDS = ["record-property-assign"];
@@ -9175,11 +9312,21 @@ function validateActionBodyWrites(stack) {
9175
9312
  let anchors = null;
9176
9313
  for (const site of sites) {
9177
9314
  if (!/\bapi\b/.test(site.source) && !/\brecord\b/.test(site.source)) continue;
9178
- const { writes: allWrites, ctxRecordEscapes } = extractHookBodyWriteSet(site.source);
9315
+ const { writes: allWrites, ctxRecordEscapes, parseFailure } = extractHookBodyWriteSet(site.source);
9179
9316
  const writes = allWrites.filter((w) => APPLICABLE_IDS.has(w.patternId));
9180
9317
  const recordWrites = allWrites.filter((w) => RECORD_WRITE_IDS.has(w.patternId));
9181
- if (writes.length === 0 && recordWrites.length === 0) continue;
9182
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;
9183
9330
  if (recordWrites.length > 0 && !ctxRecordEscapes) {
9184
9331
  const reportedFields = /* @__PURE__ */ new Set();
9185
9332
  for (const w of recordWrites) {
@@ -9320,16 +9467,26 @@ function fixHint3(field, declared) {
9320
9467
  }
9321
9468
 
9322
9469
  // src/reference-integrity-suite.ts
9470
+ var DEFAULT_MEMBER_RUNTIME_TYPES = ["flow"];
9323
9471
  var REFERENCE_INTEGRITY_RULES = [
9324
9472
  { name: "validateObjectReferences", run: validateObjectReferences },
9325
- { 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 },
9326
9483
  // [#9257] The same reading, one axis over: a list view's `sort` is a field
9327
9484
  // name written in metadata, resolved against the object's declared fields. It
9328
9485
  // gates (`error`) because the runtime does not tolerate a bad one at all —
9329
9486
  // `assertSortFieldsExist` (#6994) and `assertOrderByIsMaterializable` (#7095)
9330
9487
  // both answer `400 INVALID_SORT` — and a view's sort is its FIRST fetch, so
9331
9488
  // the refusal is the whole view, on every load, traced to nothing.
9332
- { name: "validateSortableFields", run: validateSortableFields },
9489
+ { name: "validateSortableFields", runtimeTypes: ["flow", "view"], run: validateSortableFields },
9333
9490
  { name: "validateActionNameRefs", run: validateActionNameRefs },
9334
9491
  { name: "validatePageFieldBindings", run: validatePageFieldBindings },
9335
9492
  { name: "validateChartBindings", run: validateChartBindings },
@@ -9435,9 +9592,11 @@ var REFERENCE_INTEGRITY_RULES = [
9435
9592
  // — only a page that is actually `kind:'react'` loads the compiler.
9436
9593
  { name: "validateReactPageProps", run: validateReactPageProps }
9437
9594
  ];
9438
- function validateReferenceIntegrity(stack) {
9595
+ function validateReferenceIntegrity(stack, options) {
9439
9596
  const findings = [];
9597
+ const writeType = options?.runtimeWriteType;
9440
9598
  for (const rule of REFERENCE_INTEGRITY_RULES) {
9599
+ if (writeType !== void 0 && !(rule.runtimeTypes ?? DEFAULT_MEMBER_RUNTIME_TYPES).includes(writeType)) continue;
9441
9600
  findings.push(...rule.run(stack));
9442
9601
  }
9443
9602
  return findings;
@@ -10826,9 +10985,31 @@ var AUTHORING_RULES = [
10826
10985
  // collection in the snapshot and return nothing, and the two that would
10827
10986
  // load `typescript` need a hook/action/react body the snapshot never
10828
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.
10829
11010
  surfaces: CLI_AND_RUNTIME,
10830
- runtimeTypes: ["flow"],
10831
- run: (stack) => validateReferenceIntegrity(stack)
11011
+ runtimeTypes: ["flow", "view"],
11012
+ run: (stack, ctx) => validateReferenceIntegrity(stack, ctx)
10832
11013
  },
10833
11014
  // ADR-0078 / #5068 — the SDUI component-props gate. `PageComponent.properties`
10834
11015
  // is `z.record(z.string(), z.unknown())` and ADR-0089 D3a strictness does not
@@ -11675,6 +11856,20 @@ function buildRuntimeWriteSnapshots(args) {
11675
11856
  };
11676
11857
  return { baseline, candidate };
11677
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
+ }
11678
11873
  function runRules(rules, stack, ctx) {
11679
11874
  const findings = [];
11680
11875
  for (const rule of rules) {
@@ -11704,9 +11899,12 @@ function runRuntimeAuthoringRules(args) {
11704
11899
  ...args.packageScope !== void 0 ? { packageScope: args.packageScope } : {}
11705
11900
  });
11706
11901
  if (!snapshots) return empty;
11707
- const ctx = { sduiManifest: args.sduiManifest };
11902
+ const ctx = { sduiManifest: args.sduiManifest, runtimeWriteType: args.type };
11708
11903
  const before = new Set(runRules(rules, snapshots.baseline, ctx).map(fingerprint));
11709
- 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
+ });
11710
11908
  return {
11711
11909
  errors: added.filter((f) => f.severity === "error"),
11712
11910
  advisories: added.filter((f) => f.severity !== "error"),
@@ -11715,6 +11913,7 @@ function runRuntimeAuthoringRules(args) {
11715
11913
  }
11716
11914
  // Annotate the CommonJS export names for ESM import in node:
11717
11915
  0 && (module.exports = {
11916
+ ACTION_BODY_SOURCE_UNPARSEABLE,
11718
11917
  ACTION_BODY_WRITE_EXCLUSIONS,
11719
11918
  ACTION_BODY_WRITE_PATTERNS,
11720
11919
  ACTION_BODY_WRITE_PATTERN_IDS,
@@ -11801,6 +12000,7 @@ function runRuntimeAuthoringRules(args) {
11801
12000
  FLOW_WRITE_NODE_TYPES_DEFERRED,
11802
12001
  FORM_COLSPAN_ABSOLUTE,
11803
12002
  FORM_FIELD_UNKNOWN,
12003
+ HOOK_BODY_SOURCE_UNPARSEABLE,
11804
12004
  HOOK_BODY_WRITE_EXCLUSIONS,
11805
12005
  HOOK_BODY_WRITE_PATTERNS,
11806
12006
  HOOK_BODY_WRITE_PATTERN_IDS,
@@ -11824,6 +12024,7 @@ function runRuntimeAuthoringRules(args) {
11824
12024
  PAGE_FIELD_UNKNOWN,
11825
12025
  PAGE_FIELD_UNPROVISIONED,
11826
12026
  PAGE_SOURCE_CLASSNAME,
12027
+ PARSE_FAILURE_HINT,
11827
12028
  PREDICATE_PATH_UNRESOLVED,
11828
12029
  PREDICATE_PATH_UNROOTED,
11829
12030
  PREDICATE_RHS_PATH_SHAPED,
@@ -11834,6 +12035,7 @@ function runRuntimeAuthoringRules(args) {
11834
12035
  REACT_CHART_DRILLDOWN_INVALID,
11835
12036
  REACT_CHART_FIELD_UNKNOWN,
11836
12037
  REACT_CHART_FIELD_UNPROVISIONED,
12038
+ REACT_PAGE_SOURCE_UNPARSEABLE,
11837
12039
  REFERENCE_INTEGRITY_RULES,
11838
12040
  RLS_PREDICATE_OVER_BUDGET,
11839
12041
  RLS_PREDICATE_UNENFORCEABLE,
@@ -11865,8 +12067,10 @@ function runRuntimeAuthoringRules(args) {
11865
12067
  SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
11866
12068
  SHARING_RULE_UNLOWERABLE_CONDITION,
11867
12069
  SORT_FIELD_UNKNOWN,
12070
+ SORT_FIELD_UNPROVISIONED,
11868
12071
  SORT_FIELD_UNSORTABLE,
11869
12072
  STARTUP_OPEN_VOCABULARY_VERDICT,
12073
+ STARTUP_SOURCE_UNPARSEABLE,
11870
12074
  STARTUP_VERDICT_ASSERTIVE_WORDING,
11871
12075
  STARTUP_VERDICT_HINT,
11872
12076
  STYLE_CLASSNAME_TAILWIND,
@@ -11903,6 +12107,7 @@ function runRuntimeAuthoringRules(args) {
11903
12107
  buildAccessMatrix,
11904
12108
  buildRuntimeWriteSnapshots,
11905
12109
  checkSortDeclaration,
12110
+ describeParseFailure,
11906
12111
  diffAccessMatrix,
11907
12112
  extractHookBodyWriteSet,
11908
12113
  extractHookBodyWrites,