@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.js CHANGED
@@ -1016,6 +1016,38 @@ function validateStackExpressions(stack) {
1016
1016
 
1017
1017
  // src/lint-startup-registry-verdict.ts
1018
1018
  import { createRequire } from "module";
1019
+
1020
+ // src/checked-parse.ts
1021
+ function createSourceFileChecked(tsc, fileName, source, options) {
1022
+ const sourceFile = tsc.createSourceFile(
1023
+ fileName,
1024
+ source,
1025
+ options.target,
1026
+ options.setParentNodes,
1027
+ options.scriptKind
1028
+ );
1029
+ const diagnostics = sourceFile.parseDiagnostics;
1030
+ if (!diagnostics || diagnostics.length === 0) return { sourceFile };
1031
+ const first = diagnostics[0];
1032
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(first.start ?? 0);
1033
+ const offset = options.synthesizedLinesBefore ?? 0;
1034
+ return {
1035
+ sourceFile,
1036
+ failure: {
1037
+ message: tsc.flattenDiagnosticMessageText(first.messageText, " "),
1038
+ line: Math.max(1, line + 1 - offset),
1039
+ column: character + 1,
1040
+ count: diagnostics.length
1041
+ }
1042
+ };
1043
+ }
1044
+ function describeParseFailure(failure) {
1045
+ const more = failure.count > 1 ? `; ${failure.count} syntax errors in total` : "";
1046
+ return `line ${failure.line}, column ${failure.column}: ${failure.message}${more}`;
1047
+ }
1048
+ 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.";
1049
+
1050
+ // src/lint-startup-registry-verdict.ts
1019
1051
  var cachedTs = null;
1020
1052
  function loadTypeScript() {
1021
1053
  if (cachedTs) return cachedTs;
@@ -1031,6 +1063,7 @@ function loadTypeScript() {
1031
1063
  }
1032
1064
  var STARTUP_OPEN_VOCABULARY_VERDICT = "startup-open-vocabulary-verdict";
1033
1065
  var STARTUP_VERDICT_ASSERTIVE_WORDING = "startup-verdict-assertive-wording";
1066
+ var STARTUP_SOURCE_UNPARSEABLE = "startup-source-unparseable";
1034
1067
  var OPEN_VOCABULARY_PROBES = /* @__PURE__ */ new Map([
1035
1068
  [
1036
1069
  "getRegisteredNodeTypes",
@@ -1259,13 +1292,22 @@ function findStartupRegistryVerdicts(source, options = {}) {
1259
1292
  if (!mentionsAny) return [];
1260
1293
  const t = loadTypeScript();
1261
1294
  const fileLabel = options.file ?? "source";
1262
- let sf;
1263
- try {
1264
- sf = t.createSourceFile(fileLabel, source, t.ScriptTarget.Latest, true, t.ScriptKind.TS);
1265
- } catch {
1266
- return [];
1267
- }
1295
+ const { sourceFile: sf, failure } = createSourceFileChecked(t, fileLabel, source, {
1296
+ target: t.ScriptTarget.Latest,
1297
+ setParentNodes: true,
1298
+ scriptKind: t.ScriptKind.TS
1299
+ });
1268
1300
  const findings = [];
1301
+ if (failure) {
1302
+ findings.push({
1303
+ severity: "warning",
1304
+ rule: STARTUP_SOURCE_UNPARSEABLE,
1305
+ where: fileLabel,
1306
+ path: `${fileLabel}:${failure.line}`,
1307
+ 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.`,
1308
+ hint: PARSE_FAILURE_HINT
1309
+ });
1310
+ }
1269
1311
  const moduleBindings = moduleLevelMutableBindings(t, sf);
1270
1312
  const functionBodies = indexFunctionBodies(t, sf);
1271
1313
  const lineOf = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
@@ -2811,6 +2853,26 @@ function validateSearchableFields(stack) {
2811
2853
  if (!isRec4(view)) continue;
2812
2854
  const viewLabel2 = strName2(view.name) ?? strName2(view.objectName) ?? `#${vi}`;
2813
2855
  const viewObject = strName2(view.objectName) ?? strName2(view.object);
2856
+ if (view.viewKind === "list" && !isRec4(view.config)) {
2857
+ check(
2858
+ view.searchableFields,
2859
+ listViewObject(view) ?? viewObject,
2860
+ `view "${viewLabel2}" (flattened list overlay)`,
2861
+ `views[${vi}].searchableFields`,
2862
+ "list-view searchableFields",
2863
+ "narrowing"
2864
+ );
2865
+ }
2866
+ if (view.viewKind === "list" && isRec4(view.config)) {
2867
+ check(
2868
+ view.config.searchableFields,
2869
+ listViewObject(view.config) ?? viewObject,
2870
+ `view "${viewLabel2}" (ViewItem record)`,
2871
+ `views[${vi}].config.searchableFields`,
2872
+ "list-view searchableFields",
2873
+ "narrowing"
2874
+ );
2875
+ }
2814
2876
  if (isRec4(view.list)) {
2815
2877
  check(
2816
2878
  view.list.searchableFields,
@@ -3257,6 +3319,7 @@ function filterAttrValue(tsc, sf, attr) {
3257
3319
  };
3258
3320
  return perPosition(init.expression);
3259
3321
  }
3322
+ var REACT_PAGE_SOURCE_UNPARSEABLE = "react-page-source-unparseable";
3260
3323
  var REACT_CHART_FIELD_UNKNOWN = "react-chart-field-unknown";
3261
3324
  var REACT_CHART_FIELD_UNPROVISIONED = "react-chart-field-unprovisioned";
3262
3325
  var REACT_CHART_AGGREGATE_INVALID = "react-chart-aggregate-invalid";
@@ -3585,11 +3648,20 @@ function validateReactPageProps(stack) {
3585
3648
  if (typeof source !== "string" || source.trim() === "") continue;
3586
3649
  const name = String(page.name ?? `#${p}`);
3587
3650
  const tsc = loadTypeScript2();
3588
- let sf;
3589
- try {
3590
- sf = tsc.createSourceFile("page.tsx", source, tsc.ScriptTarget.Latest, true, tsc.ScriptKind.TSX);
3591
- } catch {
3592
- continue;
3651
+ const { sourceFile: sf, failure } = createSourceFileChecked(tsc, "page.tsx", source, {
3652
+ target: tsc.ScriptTarget.Latest,
3653
+ setParentNodes: true,
3654
+ scriptKind: tsc.ScriptKind.TSX
3655
+ });
3656
+ if (failure) {
3657
+ findings.push({
3658
+ severity: "warning",
3659
+ rule: REACT_PAGE_SOURCE_UNPARSEABLE,
3660
+ where: `page "${name}"`,
3661
+ path: `pages[${p}].source`,
3662
+ 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.`,
3663
+ hint: PARSE_FAILURE_HINT
3664
+ });
3593
3665
  }
3594
3666
  const locals = localComponentNames(tsc, sf);
3595
3667
  const visit = (node) => {
@@ -6499,6 +6571,7 @@ function validateNavObjectServability(stack) {
6499
6571
  import { isVirtualSearchField as isVirtualSearchField2 } from "@objectstack/spec/data";
6500
6572
  var SORT_FIELD_UNKNOWN = "sort-field-unknown";
6501
6573
  var SORT_FIELD_UNSORTABLE = "sort-field-unsortable";
6574
+ var SORT_FIELD_UNPROVISIONED = "sort-field-unprovisioned";
6502
6575
  function isRec16(v) {
6503
6576
  return !!v && typeof v === "object" && !Array.isArray(v);
6504
6577
  }
@@ -6561,7 +6634,7 @@ function distance3(a, b) {
6561
6634
  }
6562
6635
  return prev[n];
6563
6636
  }
6564
- function checkSortDeclaration(declared, objectName, fieldsByObject, where, path, subject) {
6637
+ function checkSortDeclaration(declared, objectName, fieldsByObject, where, path, subject, unprovisionedAnchors) {
6565
6638
  const findings = [];
6566
6639
  if (declared === void 0 || declared === null) return findings;
6567
6640
  if (!objectName) return findings;
@@ -6569,10 +6642,23 @@ function checkSortDeclaration(declared, objectName, fieldsByObject, where, path,
6569
6642
  const target = fieldsByObject.get(objectName);
6570
6643
  if (!target) return findings;
6571
6644
  const known = target.names;
6645
+ const anchors = unprovisionedAnchors?.get(objectName);
6572
6646
  for (const key of readSortKeys(declared)) {
6573
6647
  const name = key.field;
6574
6648
  const head = name.split(".")[0];
6575
- if (SYSTEM_FIELDS.has(head)) continue;
6649
+ if (SYSTEM_FIELDS.has(head)) {
6650
+ if (!name.includes(".") && anchors?.has(head)) {
6651
+ findings.push({
6652
+ severity: "warning",
6653
+ rule: SORT_FIELD_UNPROVISIONED,
6654
+ where,
6655
+ path: `${path}${key.at}`,
6656
+ 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.`,
6657
+ hint: unprovisionedAnchorHint(objectName, name)
6658
+ });
6659
+ }
6660
+ continue;
6661
+ }
6576
6662
  if (!known.has(head)) {
6577
6663
  const dotted = name.includes(".");
6578
6664
  findings.push({
@@ -6605,9 +6691,18 @@ function validateSortableFields(stack) {
6605
6691
  if (!isRec16(stack)) return findings;
6606
6692
  const objects = Array.isArray(stack.objects) ? stack.objects : isRec16(stack.objects) ? Object.entries(stack.objects).map(([name, def]) => ({ name, ...def })) : [];
6607
6693
  const fieldsByObject = indexObjectSearchTargets(stack);
6694
+ const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
6608
6695
  const check = (declared, objectName, where, path, subject) => {
6609
6696
  findings.push(
6610
- ...checkSortDeclaration(declared, objectName, fieldsByObject, where, path, subject)
6697
+ ...checkSortDeclaration(
6698
+ declared,
6699
+ objectName,
6700
+ fieldsByObject,
6701
+ where,
6702
+ path,
6703
+ subject,
6704
+ unprovisionedAnchors
6705
+ )
6611
6706
  );
6612
6707
  };
6613
6708
  for (let oi = 0; oi < objects.length; oi++) {
@@ -6636,6 +6731,24 @@ function validateSortableFields(stack) {
6636
6731
  if (!isRec16(view)) continue;
6637
6732
  const viewLabel2 = strName11(view.name) ?? strName11(view.objectName) ?? `#${vi}`;
6638
6733
  const viewObject = strName11(view.objectName) ?? strName11(view.object);
6734
+ if (view.viewKind === "list" && !isRec16(view.config)) {
6735
+ check(
6736
+ view.sort,
6737
+ listViewObject2(view) ?? viewObject,
6738
+ `view "${viewLabel2}" (flattened list overlay)`,
6739
+ `views[${vi}].sort`,
6740
+ "list-view sort"
6741
+ );
6742
+ }
6743
+ if (view.viewKind === "list" && isRec16(view.config)) {
6744
+ check(
6745
+ view.config.sort,
6746
+ listViewObject2(view.config) ?? viewObject,
6747
+ `view "${viewLabel2}" (ViewItem record)`,
6748
+ `views[${vi}].config.sort`,
6749
+ "list-view sort"
6750
+ );
6751
+ }
6639
6752
  if (isRec16(view.list)) {
6640
6753
  check(
6641
6754
  view.list.sort,
@@ -8543,6 +8656,7 @@ function loadTypeScript3() {
8543
8656
  return cachedTs3;
8544
8657
  }
8545
8658
  var HOOK_BODY_WRITE_UNKNOWN_FIELD = "hook-body-write-unknown-field";
8659
+ var HOOK_BODY_SOURCE_UNPARSEABLE = "hook-body-source-unparseable";
8546
8660
  var HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR = "hook-body-write-unprovisioned-anchor";
8547
8661
  var HOOK_BODY_WRITE_PATTERNS = [
8548
8662
  {
@@ -8656,15 +8770,18 @@ function extractHookBodyWriteSet(source) {
8656
8770
  return { writes: [], ctxRecordEscapes: false };
8657
8771
  }
8658
8772
  const tsc = loadTypeScript3();
8659
- const sf = tsc.createSourceFile(
8773
+ const { sourceFile: sf, failure: parseFailure } = createSourceFileChecked(
8774
+ tsc,
8660
8775
  "hook-body.ts",
8661
8776
  `async function __body(ctx) {
8662
8777
  ${source}
8663
8778
  }`,
8664
- tsc.ScriptTarget.Latest,
8665
- /* setParentNodes */
8666
- false,
8667
- tsc.ScriptKind.TS
8779
+ {
8780
+ target: tsc.ScriptTarget.Latest,
8781
+ setParentNodes: false,
8782
+ scriptKind: tsc.ScriptKind.TS,
8783
+ synthesizedLinesBefore: 1
8784
+ }
8668
8785
  );
8669
8786
  const writes = [];
8670
8787
  const recordRefs = [];
@@ -8761,7 +8878,8 @@ ${source}
8761
8878
  visit(sf);
8762
8879
  return {
8763
8880
  writes,
8764
- ctxRecordEscapes: recordRefs.some((ref) => !consumedRecordRefs.has(ref))
8881
+ ctxRecordEscapes: recordRefs.some((ref) => !consumedRecordRefs.has(ref)),
8882
+ ...parseFailure ? { parseFailure } : {}
8765
8883
  };
8766
8884
  }
8767
8885
  function validateHookBodyWrites(stack) {
@@ -8775,11 +8893,22 @@ function validateHookBodyWrites(stack) {
8775
8893
  if (!isRec23(body) || body.language !== "js") return;
8776
8894
  const source = body.source;
8777
8895
  if (typeof source !== "string" || source.trim() === "") return;
8778
- const writes = extractHookBodyWrites(source).filter((w) => HOOK_APPLICABLE_IDS.has(w.patternId));
8896
+ const extracted = extractHookBodyWriteSet(source);
8897
+ const hookName = typeof hook.name === "string" && hook.name ? hook.name : `#${hookIndex}`;
8898
+ if (extracted.parseFailure) {
8899
+ findings.push({
8900
+ severity: "warning",
8901
+ rule: HOOK_BODY_SOURCE_UNPARSEABLE,
8902
+ where: `hook "${hookName}" \u203A body`,
8903
+ path: `hooks[${hookIndex}].body.source`,
8904
+ 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.`,
8905
+ hint: PARSE_FAILURE_HINT
8906
+ });
8907
+ }
8908
+ const writes = extracted.writes.filter((w) => HOOK_APPLICABLE_IDS.has(w.patternId));
8779
8909
  if (writes.length === 0) return;
8780
8910
  objectFields ?? (objectFields = indexObjectFields2(stack));
8781
8911
  anchors ?? (anchors = indexUnprovisionedAnchors(stack));
8782
- const hookName = typeof hook.name === "string" && hook.name ? hook.name : `#${hookIndex}`;
8783
8912
  const targets = (Array.isArray(hook.object) ? hook.object : [hook.object]).filter(
8784
8913
  (o) => typeof o === "string" && o.trim() !== ""
8785
8914
  );
@@ -8864,6 +8993,7 @@ function fixHint(field, declared) {
8864
8993
  import { findClosestMatches as findClosestMatches3, formatSuggestion as formatSuggestion3 } from "@objectstack/spec/shared";
8865
8994
  var ACTION_BODY_WRITE_UNKNOWN_FIELD = "action-body-write-unknown-field";
8866
8995
  var ACTION_RECORD_WRITE_DISCARDED = "action-record-write-discarded";
8996
+ var ACTION_BODY_SOURCE_UNPARSEABLE = "action-body-source-unparseable";
8867
8997
  var ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR = "action-body-write-unprovisioned-anchor";
8868
8998
  var ACTION_BODY_WRITE_PATTERN_IDS = ["api-crud-literal"];
8869
8999
  var ACTION_RECORD_WRITE_PATTERN_IDS = ["record-property-assign"];
@@ -8931,11 +9061,21 @@ function validateActionBodyWrites(stack) {
8931
9061
  let anchors = null;
8932
9062
  for (const site of sites) {
8933
9063
  if (!/\bapi\b/.test(site.source) && !/\brecord\b/.test(site.source)) continue;
8934
- const { writes: allWrites, ctxRecordEscapes } = extractHookBodyWriteSet(site.source);
9064
+ const { writes: allWrites, ctxRecordEscapes, parseFailure } = extractHookBodyWriteSet(site.source);
8935
9065
  const writes = allWrites.filter((w) => APPLICABLE_IDS.has(w.patternId));
8936
9066
  const recordWrites = allWrites.filter((w) => RECORD_WRITE_IDS.has(w.patternId));
8937
- if (writes.length === 0 && recordWrites.length === 0) continue;
8938
9067
  const where = `action "${site.name}" \u203A body`;
9068
+ if (parseFailure) {
9069
+ findings.push({
9070
+ severity: "warning",
9071
+ rule: ACTION_BODY_SOURCE_UNPARSEABLE,
9072
+ where,
9073
+ path: site.path,
9074
+ 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.`,
9075
+ hint: PARSE_FAILURE_HINT
9076
+ });
9077
+ }
9078
+ if (writes.length === 0 && recordWrites.length === 0) continue;
8939
9079
  if (recordWrites.length > 0 && !ctxRecordEscapes) {
8940
9080
  const reportedFields = /* @__PURE__ */ new Set();
8941
9081
  for (const w of recordWrites) {
@@ -9076,16 +9216,26 @@ function fixHint3(field, declared) {
9076
9216
  }
9077
9217
 
9078
9218
  // src/reference-integrity-suite.ts
9219
+ var DEFAULT_MEMBER_RUNTIME_TYPES = ["flow"];
9079
9220
  var REFERENCE_INTEGRITY_RULES = [
9080
9221
  { name: "validateObjectReferences", run: validateObjectReferences },
9081
- { name: "validateSearchableFields", run: validateSearchableFields },
9222
+ // [#9313] `runtimeTypes` gains `view` on this member and its sort sibling:
9223
+ // both judge a LIST VIEW's field references, and a standalone list view is
9224
+ // written through `PUT /api/v1/meta/view` — the only door a Studio tenant or
9225
+ // an MCP/AI author has. Their walks read the flattened overlay shape that
9226
+ // door carries (see each rule's `views[]` self rung), and they resolve only
9227
+ // against `stack.objects`, which the per-write snapshot DOES carry — so the
9228
+ // crossing has no missing-collection false-positive channel. Measured over
9229
+ // the shipped view corpus before crossing (0 refusals; population in the
9230
+ // #9313 PR).
9231
+ { name: "validateSearchableFields", runtimeTypes: ["flow", "view"], run: validateSearchableFields },
9082
9232
  // [#9257] The same reading, one axis over: a list view's `sort` is a field
9083
9233
  // name written in metadata, resolved against the object's declared fields. It
9084
9234
  // gates (`error`) because the runtime does not tolerate a bad one at all —
9085
9235
  // `assertSortFieldsExist` (#6994) and `assertOrderByIsMaterializable` (#7095)
9086
9236
  // both answer `400 INVALID_SORT` — and a view's sort is its FIRST fetch, so
9087
9237
  // the refusal is the whole view, on every load, traced to nothing.
9088
- { name: "validateSortableFields", run: validateSortableFields },
9238
+ { name: "validateSortableFields", runtimeTypes: ["flow", "view"], run: validateSortableFields },
9089
9239
  { name: "validateActionNameRefs", run: validateActionNameRefs },
9090
9240
  { name: "validatePageFieldBindings", run: validatePageFieldBindings },
9091
9241
  { name: "validateChartBindings", run: validateChartBindings },
@@ -9191,9 +9341,11 @@ var REFERENCE_INTEGRITY_RULES = [
9191
9341
  // — only a page that is actually `kind:'react'` loads the compiler.
9192
9342
  { name: "validateReactPageProps", run: validateReactPageProps }
9193
9343
  ];
9194
- function validateReferenceIntegrity(stack) {
9344
+ function validateReferenceIntegrity(stack, options) {
9195
9345
  const findings = [];
9346
+ const writeType = options?.runtimeWriteType;
9196
9347
  for (const rule of REFERENCE_INTEGRITY_RULES) {
9348
+ if (writeType !== void 0 && !(rule.runtimeTypes ?? DEFAULT_MEMBER_RUNTIME_TYPES).includes(writeType)) continue;
9197
9349
  findings.push(...rule.run(stack));
9198
9350
  }
9199
9351
  return findings;
@@ -10585,9 +10737,31 @@ var AUTHORING_RULES = [
10585
10737
  // collection in the snapshot and return nothing, and the two that would
10586
10738
  // load `typescript` need a hook/action/react body the snapshot never
10587
10739
  // carries — which is what `runtime-lazy-deps.test.ts` pins.
10740
+ //
10741
+ // [#9313] `view` joins, and the granularity decision #4463 P2 reserved is
10742
+ // taken HERE, in writing: the entry-level `runtimeTypes` says which WRITES
10743
+ // dispatch the suite, and the suite's own per-member `runtimeTypes`
10744
+ // (`ReferenceIntegrityRule`, default `['flow']`) says which MEMBERS judge
10745
+ // that snapshot — the gate passes the written type through
10746
+ // `ctx.runtimeWriteType`. A `view` write therefore reaches exactly the two
10747
+ // members that judge a list view's field references and resolve only
10748
+ // against the `objects` collection the snapshot carries
10749
+ // (`validateSearchableFields`, `validateSortableFields`). The whole suite
10750
+ // is NOT the right granularity for this door, measured not assumed, and
10751
+ // the crossing fails differently per body shape — both ways wrong:
10752
+ // `validateActionNameRefs` (error-tier) resolves `views[].list` /
10753
+ // `views[].listViews.*` action names against `stack.actions`, a collection
10754
+ // no per-write snapshot carries, so on a CONTAINER view write it would
10755
+ // refuse every stack-level action the body names (measured:
10756
+ // `action-name-undefined` on the snapshot shape, clean on the full stack)
10757
+ // — RUNTIME_NEEDS_FULL_SNAPSHOT's exact sentence, on the hottest write
10758
+ // type the gate has. On a FLATTENED overlay it has no rung at all, so the
10759
+ // crossing would be a silent no-op that reads as coverage — the very shape
10760
+ // #9313 was filed about. Flow snapshots are unchanged: every member keeps
10761
+ // the default `flow` declaration.
10588
10762
  surfaces: CLI_AND_RUNTIME,
10589
- runtimeTypes: ["flow"],
10590
- run: (stack) => validateReferenceIntegrity(stack)
10763
+ runtimeTypes: ["flow", "view"],
10764
+ run: (stack, ctx) => validateReferenceIntegrity(stack, ctx)
10591
10765
  },
10592
10766
  // ADR-0078 / #5068 — the SDUI component-props gate. `PageComponent.properties`
10593
10767
  // is `z.record(z.string(), z.unknown())` and ADR-0089 D3a strictness does not
@@ -11434,6 +11608,20 @@ function buildRuntimeWriteSnapshots(args) {
11434
11608
  };
11435
11609
  return { baseline, candidate };
11436
11610
  }
11611
+ var NAME_KEYED_STACK_KEYS = ["objects", "permissions", "books"];
11612
+ var PATH_SAFE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
11613
+ var TOP_LEVEL_INDEX = /^(objects|permissions|books)\[(\d+)\](.*)$/;
11614
+ function nameKeyFindingPath(path, candidate) {
11615
+ const m = TOP_LEVEL_INDEX.exec(path);
11616
+ if (!m) return path;
11617
+ const [, stackKey, index, rest] = m;
11618
+ if (!NAME_KEYED_STACK_KEYS.includes(stackKey)) return path;
11619
+ const collection = candidate[stackKey];
11620
+ const entry = collection?.[Number(index)];
11621
+ const name = entry && typeof entry === "object" ? entry.name : void 0;
11622
+ if (typeof name !== "string" || !PATH_SAFE_NAME.test(name)) return path;
11623
+ return `${stackKey}.${name}${rest}`;
11624
+ }
11437
11625
  function runRules(rules, stack, ctx) {
11438
11626
  const findings = [];
11439
11627
  for (const rule of rules) {
@@ -11463,9 +11651,12 @@ function runRuntimeAuthoringRules(args) {
11463
11651
  ...args.packageScope !== void 0 ? { packageScope: args.packageScope } : {}
11464
11652
  });
11465
11653
  if (!snapshots) return empty;
11466
- const ctx = { sduiManifest: args.sduiManifest };
11654
+ const ctx = { sduiManifest: args.sduiManifest, runtimeWriteType: args.type };
11467
11655
  const before = new Set(runRules(rules, snapshots.baseline, ctx).map(fingerprint));
11468
- const added = runRules(rules, snapshots.candidate, ctx).filter((f) => !before.has(fingerprint(f)));
11656
+ const added = runRules(rules, snapshots.candidate, ctx).filter((f) => !before.has(fingerprint(f))).map((f) => {
11657
+ const path = nameKeyFindingPath(f.path, snapshots.candidate);
11658
+ return path === f.path ? f : { ...f, path };
11659
+ });
11469
11660
  return {
11470
11661
  errors: added.filter((f) => f.severity === "error"),
11471
11662
  advisories: added.filter((f) => f.severity !== "error"),
@@ -11473,6 +11664,7 @@ function runRuntimeAuthoringRules(args) {
11473
11664
  };
11474
11665
  }
11475
11666
  export {
11667
+ ACTION_BODY_SOURCE_UNPARSEABLE,
11476
11668
  ACTION_BODY_WRITE_EXCLUSIONS,
11477
11669
  ACTION_BODY_WRITE_PATTERNS,
11478
11670
  ACTION_BODY_WRITE_PATTERN_IDS,
@@ -11559,6 +11751,7 @@ export {
11559
11751
  FLOW_WRITE_NODE_TYPES_DEFERRED,
11560
11752
  FORM_COLSPAN_ABSOLUTE,
11561
11753
  FORM_FIELD_UNKNOWN,
11754
+ HOOK_BODY_SOURCE_UNPARSEABLE,
11562
11755
  HOOK_BODY_WRITE_EXCLUSIONS,
11563
11756
  HOOK_BODY_WRITE_PATTERNS,
11564
11757
  HOOK_BODY_WRITE_PATTERN_IDS,
@@ -11582,6 +11775,7 @@ export {
11582
11775
  PAGE_FIELD_UNKNOWN,
11583
11776
  PAGE_FIELD_UNPROVISIONED,
11584
11777
  PAGE_SOURCE_CLASSNAME,
11778
+ PARSE_FAILURE_HINT,
11585
11779
  PREDICATE_PATH_UNRESOLVED,
11586
11780
  PREDICATE_PATH_UNROOTED,
11587
11781
  PREDICATE_RHS_PATH_SHAPED,
@@ -11592,6 +11786,7 @@ export {
11592
11786
  REACT_CHART_DRILLDOWN_INVALID,
11593
11787
  REACT_CHART_FIELD_UNKNOWN,
11594
11788
  REACT_CHART_FIELD_UNPROVISIONED,
11789
+ REACT_PAGE_SOURCE_UNPARSEABLE,
11595
11790
  REFERENCE_INTEGRITY_RULES,
11596
11791
  RLS_PREDICATE_OVER_BUDGET,
11597
11792
  RLS_PREDICATE_UNENFORCEABLE,
@@ -11623,8 +11818,10 @@ export {
11623
11818
  SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
11624
11819
  SHARING_RULE_UNLOWERABLE_CONDITION,
11625
11820
  SORT_FIELD_UNKNOWN,
11821
+ SORT_FIELD_UNPROVISIONED,
11626
11822
  SORT_FIELD_UNSORTABLE,
11627
11823
  STARTUP_OPEN_VOCABULARY_VERDICT,
11824
+ STARTUP_SOURCE_UNPARSEABLE,
11628
11825
  STARTUP_VERDICT_ASSERTIVE_WORDING,
11629
11826
  STARTUP_VERDICT_HINT,
11630
11827
  STYLE_CLASSNAME_TAILWIND,
@@ -11661,6 +11858,7 @@ export {
11661
11858
  buildAccessMatrix,
11662
11859
  buildRuntimeWriteSnapshots,
11663
11860
  checkSortDeclaration,
11861
+ describeParseFailure,
11664
11862
  diffAccessMatrix,
11665
11863
  extractHookBodyWriteSet,
11666
11864
  extractHookBodyWrites,