@objectstack/lint 15.1.1 → 16.0.0-rc.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,14 +20,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ APPROVAL_APPROVER_NOT_MEMBERSHIP_TIER: () => APPROVAL_APPROVER_NOT_MEMBERSHIP_TIER,
24
+ APPROVAL_APPROVER_TYPE_DEPRECATED: () => APPROVAL_APPROVER_TYPE_DEPRECATED,
23
25
  APPROVAL_APPROVER_TYPE_UNKNOWN: () => APPROVAL_APPROVER_TYPE_UNKNOWN,
24
26
  APPROVAL_ESCALATION_REASSIGN_NO_TARGET: () => APPROVAL_ESCALATION_REASSIGN_NO_TARGET,
25
- APPROVAL_ROLE_NOT_MEMBERSHIP_TIER: () => APPROVAL_ROLE_NOT_MEMBERSHIP_TIER,
26
27
  CAPABILITY_REFERENCE_UNKNOWN: () => CAPABILITY_REFERENCE_UNKNOWN,
27
28
  CHART_CONFIG_MISSING: () => CHART_CONFIG_MISSING,
28
29
  CHART_FIELD_UNKNOWN: () => CHART_FIELD_UNKNOWN,
29
30
  FIELD_GROUP_EMPTY: () => FIELD_GROUP_EMPTY,
30
31
  FIELD_GROUP_UNDECLARED: () => FIELD_GROUP_UNDECLARED,
32
+ FLOW_DRAFT_STATUS_AMBIGUOUS: () => FLOW_DRAFT_STATUS_AMBIGUOUS,
33
+ FLOW_TRIGGER_UNKNOWN_OBJECT: () => FLOW_TRIGGER_UNKNOWN_OBJECT,
31
34
  FORM_COLSPAN_ABSOLUTE: () => FORM_COLSPAN_ABSOLUTE,
32
35
  FORM_FIELD_UNKNOWN: () => FORM_FIELD_UNKNOWN,
33
36
  LIST_VIEW_FILTERS_IN_VIEWS_MODE: () => LIST_VIEW_FILTERS_IN_VIEWS_MODE,
@@ -53,6 +56,7 @@ __export(index_exports, {
53
56
  TABLE_COUNT_ONLY: () => TABLE_COUNT_ONLY,
54
57
  TITLE_FORMAT_RETIRED: () => TITLE_FORMAT_RETIRED,
55
58
  TITLE_UNRESOLVABLE: () => TITLE_UNRESOLVABLE,
59
+ VIEW_CONTAINER_SHAPE: () => VIEW_CONTAINER_SHAPE,
56
60
  VISIBILITY_ALIAS_DEPRECATED: () => VISIBILITY_ALIAS_DEPRECATED,
57
61
  VISIBILITY_ROOT_MISLAYERED: () => VISIBILITY_ROOT_MISLAYERED,
58
62
  WIDGET_DATASET_UNKNOWN: () => WIDGET_DATASET_UNKNOWN,
@@ -62,6 +66,7 @@ __export(index_exports, {
62
66
  diffAccessMatrix: () => diffAccessMatrix,
63
67
  validateApprovalApprovers: () => validateApprovalApprovers,
64
68
  validateCapabilityReferences: () => validateCapabilityReferences,
69
+ validateFlowTriggerReadiness: () => validateFlowTriggerReadiness,
65
70
  validateFormLayout: () => validateFormLayout,
66
71
  validateJsxPages: () => validateJsxPages,
67
72
  validateListViewMode: () => validateListViewMode,
@@ -73,6 +78,7 @@ __export(index_exports, {
73
78
  validateSecurityPosture: () => validateSecurityPosture,
74
79
  validateSemanticRoles: () => validateSemanticRoles,
75
80
  validateStackExpressions: () => validateStackExpressions,
81
+ validateViewContainers: () => validateViewContainers,
76
82
  validateVisibilityPredicates: () => validateVisibilityPredicates,
77
83
  validateWidgetBindings: () => validateWidgetBindings
78
84
  });
@@ -87,6 +93,18 @@ var CHART_FIELD_UNKNOWN = "chart-field-unknown";
87
93
  var CHART_CONFIG_MISSING = "chart-config-missing";
88
94
  var TABLE_COUNT_ONLY = "table-count-only";
89
95
  var MEASURE_AGGREGATE_INCOHERENT = "measure-aggregate-incoherent";
96
+ var WIDGET_LEGACY_ANALYTICS_SHAPE = "widget-legacy-analytics-shape";
97
+ var WIDGET_LEGACY_ANALYTICS_UNRENDERABLE = "widget-legacy-analytics-unrenderable";
98
+ var LEGACY_ANALYTICS_KEYS = [
99
+ "categoryField",
100
+ "valueField",
101
+ "xAxisField",
102
+ "yAxisFields",
103
+ "aggregate",
104
+ "aggregation",
105
+ "rowField",
106
+ "columnField"
107
+ ];
90
108
  function asArray(v) {
91
109
  if (Array.isArray(v)) return v;
92
110
  if (v && typeof v === "object") {
@@ -208,6 +226,29 @@ function validateWidgetBindings(stack) {
208
226
  if (f.severity === "warning" && suppressed(f.rule)) return;
209
227
  findings.push({ ...f, where, path });
210
228
  };
229
+ const legacyUsed = LEGACY_ANALYTICS_KEYS.filter((k) => w[k] !== void 0);
230
+ if (legacyUsed.length > 0) {
231
+ const optionsData = typeof w.options === "object" && w.options !== null && w.options.data !== void 0;
232
+ const hasDataSource = w.dataset !== void 0 || w.object !== void 0 || w.data !== void 0 || optionsData;
233
+ const keyList = legacyUsed.map((k) => `\`${k}\``).join(", ");
234
+ const plural = legacyUsed.length > 1;
235
+ const datasetHint = `Bind a semantic dataset and select fields BY NAME: \`dataset: '<name>', dimensions: [...], values: [...]\`. Dataset-bound widgets render through DatasetWidget (pivot rows/cols come from \`dimensions\`, cell values from \`values\`).`;
236
+ if (!hasDataSource) {
237
+ push({
238
+ severity: "error",
239
+ rule: WIDGET_LEGACY_ANALYTICS_UNRENDERABLE,
240
+ message: `sets legacy analytics key${plural ? "s" : ""} ${keyList} (removed by the ADR-0021 single-form cutover) and binds no data source (no \`dataset\`, \`object\`, or inline \`data\`) \u2014 it renders nothing.`,
241
+ hint: `${datasetHint} The renderer ignores the legacy keys, so without a data source this widget has no data at all.`
242
+ });
243
+ } else {
244
+ push({
245
+ severity: "warning",
246
+ rule: WIDGET_LEGACY_ANALYTICS_SHAPE,
247
+ message: `sets legacy analytics key${plural ? "s" : ""} ${keyList} that the ADR-0021 single-form cutover removed \u2014 the dashboard renderer ignores ${plural ? "them" : "it"}.`,
248
+ hint: `${datasetHint} These inline keys are a no-op. Suppress with suppressWarnings: ['${WIDGET_LEGACY_ANALYTICS_SHAPE}'] if intentional.`
249
+ });
250
+ }
251
+ }
211
252
  const dsName = typeof w.dataset === "string" ? w.dataset : void 0;
212
253
  const dataset = dsName ? datasets.get(dsName) : void 0;
213
254
  if (dsName && !dataset) {
@@ -328,17 +369,42 @@ function buildFieldIndex(objects) {
328
369
  }
329
370
  return idx;
330
371
  }
372
+ function buildFieldTypeIndex(objects) {
373
+ const idx = /* @__PURE__ */ new Map();
374
+ for (const obj of objects) {
375
+ const name = typeof obj.name === "string" ? obj.name : void 0;
376
+ if (!name) continue;
377
+ const fields = obj.fields;
378
+ const types = {};
379
+ if (Array.isArray(fields)) {
380
+ for (const f of fields) {
381
+ const fn = f?.name;
382
+ const ft = f?.type;
383
+ if (typeof fn === "string" && typeof ft === "string") types[fn] = ft;
384
+ }
385
+ } else if (fields && typeof fields === "object") {
386
+ for (const [fn, def] of Object.entries(fields)) {
387
+ const ft = def?.type;
388
+ if (typeof ft === "string") types[fn] = ft;
389
+ }
390
+ }
391
+ idx.set(name, types);
392
+ }
393
+ return idx;
394
+ }
331
395
  function validateStackExpressions(stack) {
332
396
  const issues = [];
333
397
  const objects = asArray2(stack.objects);
334
398
  const fieldIndex = buildFieldIndex(objects);
399
+ const fieldTypeIndex = buildFieldTypeIndex(objects);
335
400
  const check = (where, raw, objectName, scope = "flattened") => {
336
401
  if (raw == null) return;
337
402
  const fields = objectName ? fieldIndex.get(objectName) : void 0;
403
+ const fieldTypes = objectName ? fieldTypeIndex.get(objectName) : void 0;
338
404
  const res = (0, import_formula.validateExpression)(
339
405
  "predicate",
340
406
  raw,
341
- objectName ? { objectName, fields, scope } : { scope }
407
+ objectName ? { objectName, fields, fieldTypes, scope } : { scope }
342
408
  );
343
409
  for (const e of res.errors) issues.push({ where, message: e.message, source: e.source, severity: "error" });
344
410
  for (const w of res.warnings) issues.push({ where, message: w.message, source: w.source, severity: "warning" });
@@ -386,19 +452,6 @@ function validateStackExpressions(stack) {
386
452
  }
387
453
  const fields = obj.fields;
388
454
  const fieldList = Array.isArray(fields) ? fields : fields && typeof fields === "object" ? Object.values(fields) : [];
389
- if (obj.external != null) {
390
- const fieldEntries = Array.isArray(fields) ? fields.map((f) => [f?.name ?? "?", f]) : fields && typeof fields === "object" ? Object.entries(fields) : [];
391
- for (const [fname, fdef] of fieldEntries) {
392
- if (fdef && typeof fdef === "object" && fdef.columnName != null) {
393
- issues.push({
394
- where: `object '${objectName}' \xB7 field '${fname}'`,
395
- message: `external object '${objectName}': field '${fname}' sets columnName='${String(fdef.columnName)}', which is not supported on federated objects (ADR-0062 D7). The driver's query pipeline ignores field.columnName for external objects; map remote columns via the datasource's external.columnMap instead.`,
396
- source: `columnName='${String(fdef.columnName)}'`,
397
- severity: "error"
398
- });
399
- }
400
- }
401
- }
402
455
  for (const f of fieldList) {
403
456
  if (f && typeof f === "object") {
404
457
  const fname = f.name ?? "?";
@@ -410,7 +463,7 @@ function validateStackExpressions(stack) {
410
463
  const res = (0, import_formula.validateExpression)(
411
464
  "value",
412
465
  f.formula,
413
- objectName ? { objectName, fields: fieldIndex.get(objectName), scope: "record" } : { scope: "record" }
466
+ objectName ? { objectName, fields: fieldIndex.get(objectName), fieldTypes: fieldTypeIndex.get(objectName), scope: "record" } : { scope: "record" }
414
467
  );
415
468
  const fieldWhere = `object '${objectName}' \xB7 field '${f.name ?? "?"}' formula`;
416
469
  for (const e of res.errors) issues.push({ where: fieldWhere, message: e.message, source: e.source, severity: "error" });
@@ -517,6 +570,117 @@ function validateListViewMode(stack) {
517
570
  return out;
518
571
  }
519
572
 
573
+ // src/validate-flow-trigger-readiness.ts
574
+ var FLOW_TRIGGER_UNKNOWN_OBJECT = "flow-trigger-unknown-object";
575
+ var FLOW_DRAFT_STATUS_AMBIGUOUS = "flow-draft-status-ambiguous";
576
+ function asArray4(v) {
577
+ if (Array.isArray(v)) return v;
578
+ if (v && typeof v === "object") {
579
+ return Object.entries(v).map(([name, def]) => ({
580
+ name,
581
+ ...def
582
+ }));
583
+ }
584
+ return [];
585
+ }
586
+ function startNodeOf(flow) {
587
+ const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
588
+ const index = nodes.findIndex((n) => n?.type === "start");
589
+ return index >= 0 ? { node: nodes[index], index } : void 0;
590
+ }
591
+ function validateFlowTriggerReadiness(stack) {
592
+ const findings = [];
593
+ const flows = asArray4(stack.flows);
594
+ if (flows.length === 0) return findings;
595
+ const objectNames = new Set(
596
+ asArray4(stack.objects).map((o) => typeof o.name === "string" ? o.name : void 0).filter((n) => !!n)
597
+ );
598
+ flows.forEach((flow, flowIndex) => {
599
+ const flowName = typeof flow.name === "string" ? flow.name : `#${flowIndex}`;
600
+ const start = startNodeOf(flow);
601
+ const config = start?.node.config ?? {};
602
+ const triggerType = typeof config.triggerType === "string" ? config.triggerType : void 0;
603
+ const isRecordTriggered = !!triggerType && triggerType.startsWith("record-");
604
+ const isTimeRelative = config.timeRelative != null && typeof config.timeRelative === "object";
605
+ const isAutoTriggered = isRecordTriggered || triggerType === "api" || config.schedule != null || isTimeRelative || flow.type === "schedule" || flow.type === "api";
606
+ if (isRecordTriggered && start) {
607
+ const objectName = typeof config.objectName === "string" ? config.objectName : void 0;
608
+ if (objectName && !objectNames.has(objectName) && !objectName.startsWith("sys_")) {
609
+ findings.push({
610
+ severity: "warning",
611
+ rule: FLOW_TRIGGER_UNKNOWN_OBJECT,
612
+ where: `flow "${flowName}" \u203A start node`,
613
+ path: `flows[${flowIndex}].nodes[${start.index}].config.objectName`,
614
+ message: `targets object '${objectName}', which this stack does not define \u2014 if the name is wrong, the flow will never fire (and the runtime stays silent about it).`,
615
+ hint: `Object names match exactly. Check config.objectName against the object's registered name (e.g. 'app_candidate', not 'candidate'). If the object comes from another installed package, this warning can be ignored.`
616
+ });
617
+ }
618
+ }
619
+ if (isTimeRelative && start) {
620
+ const tr = config.timeRelative;
621
+ const objectName = typeof tr.object === "string" ? tr.object : void 0;
622
+ if (objectName && !objectNames.has(objectName) && !objectName.startsWith("sys_")) {
623
+ findings.push({
624
+ severity: "warning",
625
+ rule: FLOW_TRIGGER_UNKNOWN_OBJECT,
626
+ where: `flow "${flowName}" \u203A start node`,
627
+ path: `flows[${flowIndex}].nodes[${start.index}].config.timeRelative.object`,
628
+ message: `sweeps object '${objectName}', which this stack does not define \u2014 if the name is wrong, the sweep will match nothing (and the runtime stays quiet about it).`,
629
+ hint: `Object names match exactly. Check config.timeRelative.object against the object's registered name. If the object comes from another installed package, this warning can be ignored.`
630
+ });
631
+ }
632
+ }
633
+ if (isAutoTriggered && (flow.status == null || flow.status === "draft")) {
634
+ findings.push({
635
+ severity: "warning",
636
+ rule: FLOW_DRAFT_STATUS_AMBIGUOUS,
637
+ where: `flow "${flowName}"`,
638
+ path: `flows[${flowIndex}].status`,
639
+ message: `has status 'draft' (the default when none is authored). Draft flows DO still fire their triggers (only 'obsolete'/'invalid' disable), so the intent is ambiguous.`,
640
+ hint: `Declare status: 'active' to arm it deliberately, or status: 'obsolete' to disable it.`
641
+ });
642
+ }
643
+ });
644
+ return findings;
645
+ }
646
+
647
+ // src/validate-view-containers.ts
648
+ var VIEW_CONTAINER_SHAPE = "view-container-shape";
649
+ var CONTAINER_SLOT_KEYS = ["list", "form", "listViews", "formViews"];
650
+ function asEntries(v) {
651
+ if (Array.isArray(v)) return v.map((value, i) => ({ key: `[${i}]`, value }));
652
+ if (v && typeof v === "object") {
653
+ return Object.entries(v).map(([name, value]) => ({ key: `.${name}`, value }));
654
+ }
655
+ return [];
656
+ }
657
+ function containerViewCount(rec) {
658
+ const named = (slot) => slot && typeof slot === "object" && !Array.isArray(slot) ? Object.keys(slot).length : 0;
659
+ return (rec.list ? 1 : 0) + (rec.form ? 1 : 0) + named(rec.listViews) + named(rec.formViews);
660
+ }
661
+ function validateViewContainers(stack) {
662
+ const out = [];
663
+ if (!stack || typeof stack !== "object") return out;
664
+ for (const { key, value } of asEntries(stack.views)) {
665
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
666
+ const rec = value;
667
+ if (rec.viewKind != null) continue;
668
+ if (containerViewCount(rec) > 0) continue;
669
+ const label = typeof rec.name === "string" ? ` ("${rec.name}")` : "";
670
+ const hasContainerSlot = CONTAINER_SLOT_KEYS.some((k) => k in rec);
671
+ const looksFlat = !hasContainerSlot && ["type", "columns", "data", "filter", "sort"].some((k) => k in rec);
672
+ out.push({
673
+ severity: "error",
674
+ rule: VIEW_CONTAINER_SHAPE,
675
+ where: `views${key}${label}`,
676
+ path: `views${key}`,
677
+ message: looksFlat ? "Flat list-view object is not a view container: `ViewSchema` strips its keys, so it parses to an EMPTY container \u2014 zero views register and the Console renders no view for it." : "View container defines no views \u2014 all of `list` / `form` / `listViews` / `formViews` are absent or empty, so nothing registers.",
678
+ hint: "Wrap every view in a defineView container: defineView({ list: { type, data, columns, ... }, listViews: { ... }, formViews: { ... } }). See examples/app-showcase/src/ui/views/task.view.ts."
679
+ });
680
+ }
681
+ return out;
682
+ }
683
+
520
684
  // src/validate-responsive-styles.ts
521
685
  var STYLE_NODE_MISSING_ID = "style-node-missing-id";
522
686
  var STYLE_CLASSNAME_TAILWIND = "style-classname-tailwind";
@@ -720,7 +884,7 @@ function looksLikeTailwind(className) {
720
884
  return false;
721
885
  });
722
886
  }
723
- function asArray4(v) {
887
+ function asArray5(v) {
724
888
  if (Array.isArray(v)) return v;
725
889
  if (v && typeof v === "object") {
726
890
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -813,13 +977,13 @@ function checkNode(node, pageName, path, findings) {
813
977
  }
814
978
  function validateResponsiveStyles(stack) {
815
979
  const findings = [];
816
- const pages = asArray4(stack.pages);
980
+ const pages = asArray5(stack.pages);
817
981
  for (let p = 0; p < pages.length; p++) {
818
982
  const page = pages[p];
819
983
  const pageName = typeof page.name === "string" ? page.name : `pages[${p}]`;
820
- const regions = asArray4(page.regions);
984
+ const regions = asArray5(page.regions);
821
985
  for (let r = 0; r < regions.length; r++) {
822
- const components = asArray4(regions[r].components);
986
+ const components = asArray5(regions[r].components);
823
987
  for (let c = 0; c < components.length; c++) {
824
988
  checkNode(components[c], pageName, `pages[${p}].regions[${r}].components[${c}]`, findings);
825
989
  }
@@ -830,10 +994,10 @@ function validateResponsiveStyles(stack) {
830
994
 
831
995
  // src/validate-jsx-pages.ts
832
996
  var import_sdui_parser = require("@objectstack/sdui-parser");
833
- var asArray5 = (v) => Array.isArray(v) ? v : [];
997
+ var asArray6 = (v) => Array.isArray(v) ? v : [];
834
998
  function validateJsxPages(stack, opts = {}) {
835
999
  const findings = [];
836
- const pages = asArray5(stack.pages);
1000
+ const pages = asArray6(stack.pages);
837
1001
  for (let p = 0; p < pages.length; p++) {
838
1002
  const page = pages[p];
839
1003
  if (!page || page.kind !== "html" && page.kind !== "jsx") continue;
@@ -881,10 +1045,10 @@ function loadSucraseTransform() {
881
1045
  }
882
1046
  return cachedTransform;
883
1047
  }
884
- var asArray6 = (v) => Array.isArray(v) ? v : [];
1048
+ var asArray7 = (v) => Array.isArray(v) ? v : [];
885
1049
  function validateReactPages(stack) {
886
1050
  const findings = [];
887
- const pages = asArray6(stack.pages);
1051
+ const pages = asArray7(stack.pages);
888
1052
  for (let p = 0; p < pages.length; p++) {
889
1053
  const page = pages[p];
890
1054
  if (!page || page.kind !== "react") continue;
@@ -936,7 +1100,7 @@ function loadTypeScript() {
936
1100
  }
937
1101
  return cachedTs;
938
1102
  }
939
- var asArray7 = (v) => Array.isArray(v) ? v : [];
1103
+ var asArray8 = (v) => Array.isArray(v) ? v : [];
940
1104
  var BLOCKS = new Map(
941
1105
  import_ui.REACT_BLOCKS.map((b) => [
942
1106
  b.tag,
@@ -975,7 +1139,7 @@ function nearestKnown(prop, known) {
975
1139
  }
976
1140
  function validateReactPageProps(stack) {
977
1141
  const findings = [];
978
- const pages = asArray7(stack.pages);
1142
+ const pages = asArray8(stack.pages);
979
1143
  for (let p = 0; p < pages.length; p++) {
980
1144
  const page = pages[p];
981
1145
  if (!page || page.kind !== "react") continue;
@@ -1043,11 +1207,11 @@ function validateReactPageProps(stack) {
1043
1207
 
1044
1208
  // src/validate-page-source-styling.ts
1045
1209
  var PAGE_SOURCE_CLASSNAME = "page-source-className-tailwind";
1046
- var asArray8 = (v) => Array.isArray(v) ? v : [];
1210
+ var asArray9 = (v) => Array.isArray(v) ? v : [];
1047
1211
  var CLASSNAME_ATTR = /\bclassName\s*=\s*["'{]/g;
1048
1212
  function validatePageSourceStyling(stack) {
1049
1213
  const findings = [];
1050
- const pages = asArray8(stack.pages);
1214
+ const pages = asArray9(stack.pages);
1051
1215
  for (let p = 0; p < pages.length; p++) {
1052
1216
  const page = pages[p];
1053
1217
  if (!page) continue;
@@ -1076,7 +1240,7 @@ function validatePageSourceStyling(stack) {
1076
1240
  var import_data2 = require("@objectstack/spec/data");
1077
1241
  var TITLE_FORMAT_RETIRED = "title-format-retired";
1078
1242
  var TITLE_UNRESOLVABLE = "title-unresolvable";
1079
- function asArray9(v) {
1243
+ function asArray10(v) {
1080
1244
  if (Array.isArray(v)) return v;
1081
1245
  if (v && typeof v === "object") {
1082
1246
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1085,7 +1249,7 @@ function asArray9(v) {
1085
1249
  }
1086
1250
  function validateRecordTitle(stack) {
1087
1251
  const findings = [];
1088
- const objects = asArray9(stack.objects);
1252
+ const objects = asArray10(stack.objects);
1089
1253
  for (let i = 0; i < objects.length; i++) {
1090
1254
  const obj = objects[i];
1091
1255
  const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
@@ -1121,7 +1285,7 @@ var FIELD_GROUP_UNDECLARED = "field-group-undeclared";
1121
1285
  var FIELD_GROUP_EMPTY = "field-group-empty";
1122
1286
  var FIELD_GROUP_SHADOWED = "field-group-shadowed";
1123
1287
  var SEMANTIC_ROLE_FIELD_UNKNOWN = "semantic-role-field-unknown";
1124
- function asArray10(v) {
1288
+ function asArray11(v) {
1125
1289
  if (Array.isArray(v)) return v;
1126
1290
  if (v && typeof v === "object") {
1127
1291
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1130,7 +1294,7 @@ function asArray10(v) {
1130
1294
  }
1131
1295
  function validateSemanticRoles(stack) {
1132
1296
  const findings = [];
1133
- const objects = asArray10(stack.objects);
1297
+ const objects = asArray11(stack.objects);
1134
1298
  for (let i = 0; i < objects.length; i++) {
1135
1299
  const obj = objects[i];
1136
1300
  if (!obj || typeof obj !== "object") continue;
@@ -1225,7 +1389,7 @@ function validateSemanticRoles(stack) {
1225
1389
  // src/validate-form-layout.ts
1226
1390
  var FORM_FIELD_UNKNOWN = "form-field-unknown";
1227
1391
  var FORM_COLSPAN_ABSOLUTE = "absolute-colspan-discouraged";
1228
- function asArray11(v) {
1392
+ function asArray12(v) {
1229
1393
  if (Array.isArray(v)) return v;
1230
1394
  if (v && typeof v === "object") {
1231
1395
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1250,13 +1414,13 @@ function boundObject(view) {
1250
1414
  function validateFormLayout(stack) {
1251
1415
  const findings = [];
1252
1416
  const objectFields = /* @__PURE__ */ new Map();
1253
- for (const obj of asArray11(stack.objects)) {
1417
+ for (const obj of asArray12(stack.objects)) {
1254
1418
  const name = typeof obj.name === "string" ? obj.name : void 0;
1255
1419
  if (!name) continue;
1256
1420
  const fields = obj.fields && typeof obj.fields === "object" && !Array.isArray(obj.fields) ? Object.keys(obj.fields) : [];
1257
1421
  objectFields.set(name, new Set(fields));
1258
1422
  }
1259
- const views = asArray11(stack.views);
1423
+ const views = asArray12(stack.views);
1260
1424
  for (let i = 0; i < views.length; i++) {
1261
1425
  const view = views[i];
1262
1426
  if (!view || typeof view !== "object") continue;
@@ -1306,7 +1470,7 @@ var VISIBILITY_ALIAS_DEPRECATED = "visibility-alias-deprecated";
1306
1470
  var VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
1307
1471
  var CANONICAL = "visibleWhen";
1308
1472
  var ALIASES = ["visibleOn", "visibility"];
1309
- function asArray12(v) {
1473
+ function asArray13(v) {
1310
1474
  if (Array.isArray(v)) return v;
1311
1475
  if (v && typeof v === "object") {
1312
1476
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1368,7 +1532,7 @@ function isFieldObject(entry) {
1368
1532
  function validateVisibilityPredicates(stack, opts = {}) {
1369
1533
  const layer = opts.layer ?? "runtime";
1370
1534
  const findings = [];
1371
- const views = asArray12(stack.views);
1535
+ const views = asArray13(stack.views);
1372
1536
  for (let i = 0; i < views.length; i++) {
1373
1537
  const view = views[i];
1374
1538
  if (!view || typeof view !== "object") continue;
@@ -1391,7 +1555,7 @@ function validateVisibilityPredicates(stack, opts = {}) {
1391
1555
  }
1392
1556
  }
1393
1557
  }
1394
- const pages = asArray12(stack.pages);
1558
+ const pages = asArray13(stack.pages);
1395
1559
  for (let i = 0; i < pages.length; i++) {
1396
1560
  const page = pages[i];
1397
1561
  if (!page || typeof page !== "object") continue;
@@ -1415,7 +1579,7 @@ function validateVisibilityPredicates(stack, opts = {}) {
1415
1579
  // src/validate-capability-references.ts
1416
1580
  var import_security = require("@objectstack/spec/security");
1417
1581
  var CAPABILITY_REFERENCE_UNKNOWN = "capability-reference-unknown";
1418
- function asArray13(v) {
1582
+ function asArray14(v) {
1419
1583
  if (Array.isArray(v)) return v;
1420
1584
  if (v && typeof v === "object") {
1421
1585
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1440,13 +1604,13 @@ function validateCapabilityReferences(stack) {
1440
1604
  const findings = [];
1441
1605
  if (!stack || typeof stack !== "object") return findings;
1442
1606
  const known = new Set(import_security.PLATFORM_CAPABILITY_NAMES);
1443
- for (const cap of asArray13(stack.capabilities)) {
1607
+ for (const cap of asArray14(stack.capabilities)) {
1444
1608
  if (typeof cap.name === "string" && cap.name.length > 0) known.add(cap.name);
1445
1609
  }
1446
- for (const ps of asArray13(stack.permissions)) {
1610
+ for (const ps of asArray14(stack.permissions)) {
1447
1611
  for (const cap of asCapArray(ps.systemPermissions)) known.add(cap);
1448
1612
  }
1449
- for (const seed of asArray13(stack.data)) {
1613
+ for (const seed of asArray14(stack.data)) {
1450
1614
  if (seed.object !== "sys_capability") continue;
1451
1615
  for (const rec of Array.isArray(seed.records) ? seed.records : []) {
1452
1616
  const name = rec?.name;
@@ -1465,7 +1629,7 @@ function validateCapabilityReferences(stack) {
1465
1629
  hint
1466
1630
  });
1467
1631
  };
1468
- const objects = asArray13(stack.objects);
1632
+ const objects = asArray14(stack.objects);
1469
1633
  for (let i = 0; i < objects.length; i++) {
1470
1634
  const obj = objects[i];
1471
1635
  if (!obj || typeof obj !== "object") continue;
@@ -1474,27 +1638,27 @@ function validateCapabilityReferences(stack) {
1474
1638
  for (const { cap, key } of flattenObjectRequired(obj.requiredPermissions)) {
1475
1639
  flag(cap, `object "${objName}"`, `${objPath}.requiredPermissions${key ? `.${key}` : ""}`);
1476
1640
  }
1477
- const fields = asArray13(obj.fields);
1641
+ const fields = asArray14(obj.fields);
1478
1642
  for (const f of fields) {
1479
1643
  const fname = typeof f.name === "string" ? f.name : "(field)";
1480
1644
  for (const cap of asCapArray(f.requiredPermissions)) {
1481
1645
  flag(cap, `field "${objName}.${fname}"`, `${objPath}.fields.${fname}.requiredPermissions`);
1482
1646
  }
1483
1647
  }
1484
- for (const [ai, action] of asArray13(obj.actions).entries()) {
1648
+ for (const [ai, action] of asArray14(obj.actions).entries()) {
1485
1649
  const aName = typeof action.name === "string" ? action.name : `(action ${ai})`;
1486
1650
  for (const cap of asCapArray(action.requiredPermissions)) {
1487
1651
  flag(cap, `action "${objName}.${aName}"`, `${objPath}.actions[${ai}].requiredPermissions`);
1488
1652
  }
1489
1653
  }
1490
1654
  }
1491
- for (const [i, action] of asArray13(stack.actions).entries()) {
1655
+ for (const [i, action] of asArray14(stack.actions).entries()) {
1492
1656
  const aName = typeof action.name === "string" ? action.name : `(action ${i})`;
1493
1657
  for (const cap of asCapArray(action.requiredPermissions)) {
1494
1658
  flag(cap, `action "${aName}"`, `actions[${i}].requiredPermissions`);
1495
1659
  }
1496
1660
  }
1497
- const apps = asArray13(stack.apps);
1661
+ const apps = asArray14(stack.apps);
1498
1662
  for (let i = 0; i < apps.length; i++) {
1499
1663
  const app = apps[i];
1500
1664
  if (!app || typeof app !== "object") continue;
@@ -1522,7 +1686,8 @@ function validateCapabilityReferences(stack) {
1522
1686
 
1523
1687
  // src/validate-approval-approvers.ts
1524
1688
  var import_automation = require("@objectstack/spec/automation");
1525
- var APPROVAL_ROLE_NOT_MEMBERSHIP_TIER = "approval-role-not-membership-tier";
1689
+ var APPROVAL_APPROVER_NOT_MEMBERSHIP_TIER = "approval-approver-not-membership-tier";
1690
+ var APPROVAL_APPROVER_TYPE_DEPRECATED = "approval-approver-type-deprecated";
1526
1691
  var APPROVAL_APPROVER_TYPE_UNKNOWN = "approval-approver-type-unknown";
1527
1692
  var APPROVAL_ESCALATION_REASSIGN_NO_TARGET = "approval-escalation-reassign-no-target";
1528
1693
  var MEMBERSHIP_TIERS = /* @__PURE__ */ new Set(["owner", "admin", "member", "guest"]);
@@ -1530,7 +1695,7 @@ var TYPE_FIX = {
1530
1695
  business_unit: "department",
1531
1696
  bu: "department"
1532
1697
  };
1533
- function asArray14(v) {
1698
+ function asArray15(v) {
1534
1699
  if (Array.isArray(v)) return v;
1535
1700
  if (v && typeof v === "object") {
1536
1701
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1540,7 +1705,7 @@ function asArray14(v) {
1540
1705
  function validateApprovalApprovers(stack) {
1541
1706
  const findings = [];
1542
1707
  if (!stack || typeof stack !== "object") return findings;
1543
- const flows = asArray14(stack.flows);
1708
+ const flows = asArray15(stack.flows);
1544
1709
  const validTypes = new Set(import_automation.ApproverType.options);
1545
1710
  for (let fi = 0; fi < flows.length; fi++) {
1546
1711
  const flow = flows[fi];
@@ -1572,14 +1737,25 @@ function validateApprovalApprovers(stack) {
1572
1737
  });
1573
1738
  continue;
1574
1739
  }
1575
- if (type === "role" && value && !MEMBERSHIP_TIERS.has(value.toLowerCase())) {
1740
+ const canonical = (0, import_automation.canonicalApproverType)(type);
1741
+ if (canonical === "org_membership_level" && value && !MEMBERSHIP_TIERS.has(value.toLowerCase())) {
1576
1742
  findings.push({
1577
1743
  severity: "warning",
1578
- rule: APPROVAL_ROLE_NOT_MEMBERSHIP_TIER,
1744
+ rule: APPROVAL_APPROVER_NOT_MEMBERSHIP_TIER,
1579
1745
  where,
1580
1746
  path: `${path}.value`,
1581
- message: `approver { type: 'role', value: '${value}' } resolves against the better-auth org-membership tier (sys_member.role: owner/admin/member) \u2014 '${value}' is not a membership tier, so this approver matches nobody and the request stalls.`,
1582
- hint: `If '${value}' is an org position, author { type: 'position', value: '${value}' } (resolved via sys_user_position, ADR-0090 D3). Keep type 'role' only for membership tiers (owner/admin/member).`
1747
+ message: `approver { type: '${type}', value: '${value}' } resolves against the better-auth org-membership tier (sys_member.role: owner/admin/member) \u2014 '${value}' is not a membership tier, so this approver matches nobody and the request stalls.`,
1748
+ hint: `If '${value}' is an org position, author { type: 'position', value: '${value}' } (resolved via sys_user_position, ADR-0090 D3). Keep type 'org_membership_level' only for membership tiers (owner/admin/member).`
1749
+ });
1750
+ } else if (type in import_automation.DEPRECATED_APPROVER_TYPES) {
1751
+ const fix = (0, import_automation.canonicalApproverType)(type);
1752
+ findings.push({
1753
+ severity: "warning",
1754
+ rule: APPROVAL_APPROVER_TYPE_DEPRECATED,
1755
+ where,
1756
+ path: `${path}.type`,
1757
+ message: `approver type '${type}' is the deprecated spelling of '${fix}' (ADR-0090 D3) and is removed in the next major.`,
1758
+ hint: `Author { type: '${fix}', value: '${value}' }. It resolves identically today.`
1583
1759
  });
1584
1760
  }
1585
1761
  }
@@ -1628,7 +1804,7 @@ var OWD_WIDTH = {
1628
1804
  public_read: 1,
1629
1805
  public_read_write: 2
1630
1806
  };
1631
- function asArray15(v) {
1807
+ function asArray16(v) {
1632
1808
  if (Array.isArray(v)) return v;
1633
1809
  if (v && typeof v === "object") {
1634
1810
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1654,7 +1830,7 @@ function refOf(def) {
1654
1830
  return typeof r === "string" && r ? r : void 0;
1655
1831
  }
1656
1832
  function firstMasterDetailField(obj) {
1657
- for (const f of asArray15(obj.fields)) {
1833
+ for (const f of asArray16(obj.fields)) {
1658
1834
  if (f.type === "master_detail") {
1659
1835
  return { name: String(f.name ?? "?"), parent: refOf(f) };
1660
1836
  }
@@ -1667,8 +1843,8 @@ function grantsObjectAccess(p) {
1667
1843
  function validateSecurityPosture(stack, opts) {
1668
1844
  const findings = [];
1669
1845
  if (!stack || typeof stack !== "object") return findings;
1670
- const objects = asArray15(stack.objects);
1671
- const permissionSets = asArray15(stack.permissions);
1846
+ const objects = asArray16(stack.objects);
1847
+ const permissionSets = asArray16(stack.permissions);
1672
1848
  for (let i = 0; i < objects.length; i++) {
1673
1849
  const obj = objects[i];
1674
1850
  if (!obj || typeof obj !== "object") continue;
@@ -1797,10 +1973,10 @@ function validateSecurityPosture(stack, opts) {
1797
1973
  if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
1798
1974
  const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
1799
1975
  flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
1800
- for (const f of asArray15(obj.fields)) {
1976
+ for (const f of asArray16(obj.fields)) {
1801
1977
  flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
1802
1978
  }
1803
- for (const [ai, action] of asArray15(obj.actions).entries()) {
1979
+ for (const [ai, action] of asArray16(obj.actions).entries()) {
1804
1980
  flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
1805
1981
  }
1806
1982
  }
@@ -1809,19 +1985,19 @@ function validateSecurityPosture(stack, opts) {
1809
1985
  if (!ps || typeof ps !== "object") continue;
1810
1986
  flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
1811
1987
  }
1812
- for (const [i, pos] of asArray15(stack.positions).entries()) {
1988
+ for (const [i, pos] of asArray16(stack.positions).entries()) {
1813
1989
  flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
1814
1990
  }
1815
- for (const [i, app] of asArray15(stack.apps).entries()) {
1991
+ for (const [i, app] of asArray16(stack.apps).entries()) {
1816
1992
  flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
1817
1993
  }
1818
- for (const [i, book] of asArray15(stack.books).entries()) {
1994
+ for (const [i, book] of asArray16(stack.books).entries()) {
1819
1995
  flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
1820
1996
  }
1821
1997
  const stackSetNames = new Set(
1822
1998
  permissionSets.map((ps) => typeof ps.name === "string" ? ps.name : void 0).filter((n) => !!n)
1823
1999
  );
1824
- for (const [i, book] of asArray15(stack.books).entries()) {
2000
+ for (const [i, book] of asArray16(stack.books).entries()) {
1825
2001
  const audience = book.audience;
1826
2002
  if (!audience || typeof audience !== "object") continue;
1827
2003
  const setName = audience.permissionSet;
@@ -1899,7 +2075,7 @@ function validateSecurityPosture(stack, opts) {
1899
2075
  }
1900
2076
  const GRANT_SEED_OBJECTS = /* @__PURE__ */ new Set(["sys_user_position", "sys_user_permission_set"]);
1901
2077
  const nowMs = opts?.nowMs ?? Date.now();
1902
- for (const [i, seed] of asArray15(stack.data).entries()) {
2078
+ for (const [i, seed] of asArray16(stack.data).entries()) {
1903
2079
  const seedObject = typeof seed.object === "string" ? seed.object : "";
1904
2080
  if (!GRANT_SEED_OBJECTS.has(seedObject)) continue;
1905
2081
  const records = Array.isArray(seed.records) ? seed.records : [];
@@ -1940,7 +2116,7 @@ function validateSecurityPosture(stack, opts) {
1940
2116
  }
1941
2117
 
1942
2118
  // src/build-access-matrix.ts
1943
- function asArray16(v) {
2119
+ function asArray17(v) {
1944
2120
  if (Array.isArray(v)) return v;
1945
2121
  if (v && typeof v === "object") {
1946
2122
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1951,13 +2127,13 @@ function buildAccessMatrix(stack) {
1951
2127
  const entries = [];
1952
2128
  if (!stack || typeof stack !== "object") return { version: 1, entries };
1953
2129
  const owdByObject = /* @__PURE__ */ new Map();
1954
- for (const obj of asArray16(stack.objects)) {
2130
+ for (const obj of asArray17(stack.objects)) {
1955
2131
  const name = typeof obj.name === "string" ? obj.name : "";
1956
2132
  if (!name) continue;
1957
2133
  const owd = obj.sharingModel ?? obj.security?.sharingModel;
1958
2134
  if (typeof owd === "string") owdByObject.set(name, owd);
1959
2135
  }
1960
- for (const ps of asArray16(stack.permissions)) {
2136
+ for (const ps of asArray17(stack.permissions)) {
1961
2137
  const psName = typeof ps.name === "string" ? ps.name : "";
1962
2138
  if (!psName) continue;
1963
2139
  const objects = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
@@ -2029,14 +2205,17 @@ function diffAccessMatrix(before, after) {
2029
2205
  }
2030
2206
  // Annotate the CommonJS export names for ESM import in node:
2031
2207
  0 && (module.exports = {
2208
+ APPROVAL_APPROVER_NOT_MEMBERSHIP_TIER,
2209
+ APPROVAL_APPROVER_TYPE_DEPRECATED,
2032
2210
  APPROVAL_APPROVER_TYPE_UNKNOWN,
2033
2211
  APPROVAL_ESCALATION_REASSIGN_NO_TARGET,
2034
- APPROVAL_ROLE_NOT_MEMBERSHIP_TIER,
2035
2212
  CAPABILITY_REFERENCE_UNKNOWN,
2036
2213
  CHART_CONFIG_MISSING,
2037
2214
  CHART_FIELD_UNKNOWN,
2038
2215
  FIELD_GROUP_EMPTY,
2039
2216
  FIELD_GROUP_UNDECLARED,
2217
+ FLOW_DRAFT_STATUS_AMBIGUOUS,
2218
+ FLOW_TRIGGER_UNKNOWN_OBJECT,
2040
2219
  FORM_COLSPAN_ABSOLUTE,
2041
2220
  FORM_FIELD_UNKNOWN,
2042
2221
  LIST_VIEW_FILTERS_IN_VIEWS_MODE,
@@ -2062,6 +2241,7 @@ function diffAccessMatrix(before, after) {
2062
2241
  TABLE_COUNT_ONLY,
2063
2242
  TITLE_FORMAT_RETIRED,
2064
2243
  TITLE_UNRESOLVABLE,
2244
+ VIEW_CONTAINER_SHAPE,
2065
2245
  VISIBILITY_ALIAS_DEPRECATED,
2066
2246
  VISIBILITY_ROOT_MISLAYERED,
2067
2247
  WIDGET_DATASET_UNKNOWN,
@@ -2071,6 +2251,7 @@ function diffAccessMatrix(before, after) {
2071
2251
  diffAccessMatrix,
2072
2252
  validateApprovalApprovers,
2073
2253
  validateCapabilityReferences,
2254
+ validateFlowTriggerReadiness,
2074
2255
  validateFormLayout,
2075
2256
  validateJsxPages,
2076
2257
  validateListViewMode,
@@ -2082,6 +2263,7 @@ function diffAccessMatrix(before, after) {
2082
2263
  validateSecurityPosture,
2083
2264
  validateSemanticRoles,
2084
2265
  validateStackExpressions,
2266
+ validateViewContainers,
2085
2267
  validateVisibilityPredicates,
2086
2268
  validateWidgetBindings
2087
2269
  });