@objectstack/lint 10.2.0 → 11.0.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.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/validate-widget-bindings.ts","../src/validate-expressions.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { isIncoherentAggregate } from '@objectstack/spec/data';\n\n/**\n * Build-time dashboard widget binding diagnostics (issues #1719, #1721).\n *\n * Runs at `objectstack validate`/`compile`/`build` AFTER the stack has been\n * schema-parsed, so every widget's `dataset` reference can be linked to its\n * `defineDataset` and each entry in `dimensions`/`values` resolved to a\n * declared dimension/measure. This is the semantic/cross-reference phase —\n * the rules here cannot run during plain Zod parsing of the raw widget\n * literal (the dataset may even live in another package of the stack).\n *\n * Reference-integrity rules (#1721) — severity `error`, the page is broken:\n *\n * - `widget-dataset-unknown` — `dataset` does not resolve to a declared\n * `Dataset`.\n * - `widget-dimension-unknown` — a `dimensions[]` entry is not a dimension\n * name on the bound dataset.\n * - `widget-measure-unknown` — a `values[]` entry is not a measure name on\n * the bound dataset.\n * - `chart-field-unknown` — a `chartConfig` binding names a field the query\n * result will not contain: `xAxis.field` must be one of the widget's\n * dimensions (or a dataset dimension), and each `yAxis[].field` /\n * `series[].name` must be one of the widget's selected measures\n * (`values`). Post-cutover (ADR-0021) the result rows are keyed by\n * measure NAME (e.g. `sum_amount`), not the base column (`amount`) — a\n * stale base-column reference renders the axis but an empty series.\n *\n * Advisory rules — severity `warning`, build stays green:\n *\n * - `chart-config-missing` — a chart-type widget (bar/line/pie/…) has no\n * `chartConfig`, so the renderer cannot tell which measure to plot.\n * - `table-count-only` (#1719) — a `table`/`pivot` widget whose selected\n * measures are ALL `aggregate: 'count'` and which declares no\n * `dimensions` asks the analytics service for a single summary row. That\n * is the shape a `metric` widget wants — for a table it almost always\n * means the author wanted a per-record listing, which is not an\n * analytics dataset at all (model it as an object-bound ListView,\n * ADR-0017). Evaluated on the WIDGET's binding, not the dataset.\n * - `measure-aggregate-incoherent` — a dataset measure aggregates its field\n * in a way that produces a meaningless number: today, SUM (or\n * `count_distinct`) of a `percent`/rate field, whose total routinely\n * exceeds 100%. Rates must AVG. Checked once per dataset (independent of\n * any widget) when the bound object's field types are known.\n *\n * Warnings can be deliberately suppressed per widget via\n * `suppressWarnings: ['<rule-id>']`; errors cannot — they describe a\n * binding the analytics service cannot satisfy.\n */\n\nexport const WIDGET_DATASET_UNKNOWN = 'widget-dataset-unknown';\nexport const WIDGET_DIMENSION_UNKNOWN = 'widget-dimension-unknown';\nexport const WIDGET_MEASURE_UNKNOWN = 'widget-measure-unknown';\nexport const CHART_FIELD_UNKNOWN = 'chart-field-unknown';\nexport const CHART_CONFIG_MISSING = 'chart-config-missing';\nexport const TABLE_COUNT_ONLY = 'table-count-only';\nexport const MEASURE_AGGREGATE_INCOHERENT = 'measure-aggregate-incoherent';\n\nexport type WidgetBindingSeverity = 'error' | 'warning';\n\nexport interface WidgetBindingFinding {\n /** `error` = unresolvable binding (broken page); `warning` = advisory. */\n severity: WidgetBindingSeverity;\n /** Diagnostic rule id (registry entry), e.g. `widget-measure-unknown`. */\n rule: string;\n /** Human-readable location, e.g. `dashboard \"x\" › widget \"y\"`. */\n where: string;\n /** Config path, e.g. `dashboards[0].widgets[3]`. */\n path: string;\n /** What is wrong. */\n message: string;\n /** How to fix (or deliberately suppress) it. */\n hint: string;\n}\n\ntype AnyRec = Record<string, unknown>;\n\n/** Coerce a collection (array or name-keyed map) to an array. */\nfunction asArray(v: unknown): AnyRec[] {\n if (Array.isArray(v)) return v as AnyRec[];\n if (v && typeof v === 'object') {\n return Object.entries(v as AnyRec).map(([name, def]) => ({ name, ...(def as AnyRec) }));\n }\n return [];\n}\n\nfunction asStrings(v: unknown): string[] {\n return Array.isArray(v) ? v.filter((s): s is string => typeof s === 'string') : [];\n}\n\n/**\n * Chart families whose renderer needs a `chartConfig` measure mapping.\n * Single-value types (metric/kpi/gauge/…) plot their lone value and tabular\n * types (table/pivot) render every column, so they are exempt.\n */\nconst CHART_TYPES = new Set([\n 'bar', 'horizontal-bar', 'column',\n 'line', 'area',\n 'pie', 'donut', 'funnel',\n 'scatter', 'treemap', 'sankey', 'radar',\n]);\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length, n = b.length;\n let prev = Array.from({ length: n + 1 }, (_, j) => j);\n for (let i = 1; i <= m; i++) {\n const cur = [i];\n for (let j = 1; j <= n; j++) {\n cur[j] = Math.min(\n prev[j] + 1,\n cur[j - 1] + 1,\n prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),\n );\n }\n prev = cur;\n }\n return prev[n];\n}\n\n/**\n * Nearest declared name for a typo'd/stale reference, or undefined when\n * nothing is close. Containment is checked first because the cutover's\n * canonical drift is base column → prefixed measure name (`amount` →\n * `sum_amount`), which is far in edit distance but obvious to a human.\n */\nfunction didYouMean(input: string, candidates: Iterable<string>): string | undefined {\n let best: string | undefined;\n let bestScore = Infinity;\n for (const c of candidates) {\n let score: number;\n if (input.length >= 3 && (c.includes(input) || input.includes(c))) {\n score = Math.abs(c.length - input.length);\n } else {\n const d = levenshtein(input, c);\n if (d > Math.max(2, Math.floor(input.length / 3))) continue;\n score = 100 + d;\n }\n if (score < bestScore) { bestScore = score; best = c; }\n }\n return best;\n}\n\nfunction suggest(input: string, candidates: Iterable<string>): string {\n const s = didYouMean(input, candidates);\n return s ? ` Did you mean \"${s}\"?` : '';\n}\n\nfunction list(names: Iterable<string>): string {\n const arr = [...names];\n return arr.length > 0 ? arr.join(', ') : '(none)';\n}\n\n/**\n * Validate every dashboard widget's dataset binding. Returns the list of\n * findings (empty = clean). Caller decides how to surface them: `error`\n * findings describe bindings the analytics service cannot satisfy and\n * should fail validate/build; `warning` findings are advisory and must\n * never fail the build on their own.\n */\nexport function validateWidgetBindings(stack: AnyRec): WidgetBindingFinding[] {\n const findings: WidgetBindingFinding[] = [];\n\n const datasets = new Map<string, AnyRec>();\n for (const ds of asArray(stack.datasets)) {\n if (typeof ds.name === 'string') datasets.set(ds.name, ds);\n }\n\n // ── (0) dataset measures aggregate their field coherently ──\n // A measure that SUMs a percentage/rate field produces a meaningless total\n // (it can exceed 100%); rates must AVG. This is a dataset-level defect (it\n // does not depend on any widget), so it is checked once over every dataset\n // whose object's field types are known. Advisory — the page still renders.\n const objectFieldTypes = new Map<string, Map<string, string>>();\n for (const o of asArray(stack.objects)) {\n if (typeof o.name !== 'string') continue;\n const fm = new Map<string, string>();\n for (const f of asArray(o.fields)) {\n if (typeof f.name === 'string' && typeof f.type === 'string') fm.set(f.name, f.type);\n }\n objectFieldTypes.set(o.name, fm);\n }\n const datasetList = asArray(stack.datasets);\n for (let i = 0; i < datasetList.length; i++) {\n const ds = datasetList[i];\n const fieldTypes = typeof ds.object === 'string' ? objectFieldTypes.get(ds.object) : undefined;\n if (!fieldTypes) continue; // cannot judge without the object's field types\n const dsMeasures = asArray(ds.measures);\n for (let k = 0; k < dsMeasures.length; k++) {\n const m = dsMeasures[k];\n const field = typeof m.field === 'string' ? m.field : undefined;\n const aggregate = typeof m.aggregate === 'string' ? m.aggregate : undefined;\n if (!field || !aggregate) continue; // count(*) and underivable measures are fine\n const ftype = fieldTypes.get(field);\n if (ftype && isIncoherentAggregate(aggregate, ftype)) {\n findings.push({\n severity: 'warning',\n rule: MEASURE_AGGREGATE_INCOHERENT,\n where: `dataset \"${typeof ds.name === 'string' ? ds.name : `(dataset ${i})`}\" › measure \"${typeof m.name === 'string' ? m.name : `(measure ${k})`}\"`,\n path: `datasets[${i}].measures[${k}]`,\n message:\n `measure \"${m.name}\" applies ${aggregate} to ${ftype} field \"${field}\" — ` +\n `summed percentages are meaningless (they can exceed 100%).`,\n hint:\n `Use aggregate \"avg\" for percentage/rate fields (or \"count\" of records). ` +\n `If a running total is genuinely intended, suppress with: ` +\n `suppressWarnings: ['${MEASURE_AGGREGATE_INCOHERENT}'] on the measure.`,\n });\n }\n }\n }\n\n const dashboards = asArray(stack.dashboards);\n for (let i = 0; i < dashboards.length; i++) {\n const dash = dashboards[i];\n const dashName = typeof dash.name === 'string' ? dash.name : `(dashboard ${i})`;\n const widgets = Array.isArray(dash.widgets) ? (dash.widgets as AnyRec[]) : [];\n\n for (let j = 0; j < widgets.length; j++) {\n const w = widgets[j];\n const widgetId = typeof w.id === 'string' ? w.id : `(widget ${j})`;\n const where = `dashboard \"${dashName}\" › widget \"${widgetId}\"`;\n const path = `dashboards[${i}].widgets[${j}]`;\n const suppressed = (rule: string): boolean =>\n Array.isArray(w.suppressWarnings) && w.suppressWarnings.includes(rule);\n const push = (f: Omit<WidgetBindingFinding, 'where' | 'path'>): void => {\n if (f.severity === 'warning' && suppressed(f.rule)) return;\n findings.push({ ...f, where, path });\n };\n\n // ── (a) dataset reference resolves ──\n const dsName = typeof w.dataset === 'string' ? w.dataset : undefined;\n const dataset = dsName ? datasets.get(dsName) : undefined;\n if (dsName && !dataset) {\n push({\n severity: 'error',\n rule: WIDGET_DATASET_UNKNOWN,\n message: `dataset \"${dsName}\" does not resolve to a declared dataset.`,\n hint:\n `Declared datasets: ${list(datasets.keys())}.${suggest(dsName, datasets.keys())} ` +\n `Define the dataset with defineDataset() or fix the reference (ADR-0021).`,\n });\n }\n // Without a resolved dataset there is nothing to check names against.\n if (!dataset) continue;\n\n const dimensionNames = new Set<string>();\n for (const d of asArray(dataset.dimensions)) {\n if (typeof d.name === 'string') dimensionNames.add(d.name);\n }\n const measures = new Map<string, AnyRec>();\n for (const m of asArray(dataset.measures)) {\n if (typeof m.name === 'string') measures.set(m.name, m);\n }\n\n // ── (b) every dimensions[] entry is a dataset dimension ──\n const dims = asStrings(w.dimensions);\n for (let k = 0; k < dims.length; k++) {\n if (dimensionNames.has(dims[k])) continue;\n push({\n severity: 'error',\n rule: WIDGET_DIMENSION_UNKNOWN,\n message:\n `dimensions[${k}] \"${dims[k]}\" is not a dimension of dataset ` +\n `\"${dsName}\" (declared dimensions: ${list(dimensionNames)}).`,\n hint:\n `Widgets select dataset dimensions BY NAME.${suggest(dims[k], dimensionNames)} ` +\n `Add the dimension to the dataset or fix the reference.`,\n });\n }\n\n // ── (c) every values[] entry is a dataset measure ──\n const values = asStrings(w.values);\n for (let k = 0; k < values.length; k++) {\n if (measures.has(values[k])) continue;\n push({\n severity: 'error',\n rule: WIDGET_MEASURE_UNKNOWN,\n message:\n `values[${k}] \"${values[k]}\" is not a measure of dataset ` +\n `\"${dsName}\" (declared measures: ${list(measures.keys())}).`,\n hint:\n `Widgets select dataset measures BY NAME, not by base column.` +\n `${suggest(values[k], measures.keys())} ` +\n `Add the measure to the dataset or fix the reference.`,\n });\n }\n\n // ── (d) chartConfig bindings resolve against the widget's selection ──\n const chartConfig = (w.chartConfig && typeof w.chartConfig === 'object')\n ? (w.chartConfig as AnyRec)\n : undefined;\n const isChartType = typeof w.type === 'string' && CHART_TYPES.has(w.type);\n\n if (chartConfig) {\n // The query result carries the widget's selected dimensions and\n // measures; resolve every chartConfig field against that shape.\n const selectedValues = new Set(values.filter((v) => measures.has(v)));\n\n const xAxis = (chartConfig.xAxis && typeof chartConfig.xAxis === 'object')\n ? (chartConfig.xAxis as AnyRec)\n : undefined;\n // A field naming an entry of the widget's own (already-validated)\n // selection is not re-reported here — rules (b)/(c) own that error.\n if (xAxis && typeof xAxis.field === 'string'\n && !dimensionNames.has(xAxis.field) && !dims.includes(xAxis.field)) {\n push({\n severity: 'error',\n rule: CHART_FIELD_UNKNOWN,\n message:\n `chartConfig.xAxis.field \"${xAxis.field}\" does not resolve to a ` +\n `dimension of dataset \"${dsName}\" (declared dimensions: ${list(dimensionNames)}).`,\n hint: `Point xAxis.field at a dataset dimension name.${suggest(xAxis.field, dimensionNames)}`,\n });\n }\n\n const measureField = (label: string, field: string): void => {\n if (values.includes(field)) return; // resolvable, or already errored via rule (c)\n const declaredButUnselected = measures.has(field);\n push({\n severity: 'error',\n rule: CHART_FIELD_UNKNOWN,\n message: declaredButUnselected\n ? `chartConfig.${label} \"${field}\" is a measure of dataset \"${dsName}\" ` +\n `but is not selected in the widget's values (${list(values)}), so the ` +\n `query result will not contain it.`\n : `chartConfig.${label} \"${field}\" does not resolve to a measure of ` +\n `dataset \"${dsName}\" (declared measures: ${list(measures.keys())}).`,\n hint: declaredButUnselected\n ? `Add \"${field}\" to the widget's values, or bind the chart to a selected measure.`\n : `Post-cutover data is keyed by the dataset's measure NAME, not the ` +\n `base column.${suggest(field, selectedValues.size > 0 ? selectedValues : measures.keys())}`,\n });\n };\n\n const yAxes = Array.isArray(chartConfig.yAxis) ? (chartConfig.yAxis as AnyRec[]) : [];\n for (let k = 0; k < yAxes.length; k++) {\n const field = yAxes[k]?.field;\n if (typeof field === 'string') measureField(`yAxis[${k}].field`, field);\n }\n const series = Array.isArray(chartConfig.series) ? (chartConfig.series as AnyRec[]) : [];\n for (let k = 0; k < series.length; k++) {\n const name = series[k]?.name;\n if (typeof name === 'string') measureField(`series[${k}].name`, name);\n }\n } else if (isChartType) {\n push({\n severity: 'warning',\n rule: CHART_CONFIG_MISSING,\n message:\n `chart-type widget ('${w.type}') has no chartConfig — the renderer ` +\n `cannot determine which measure to plot, so the series renders empty.`,\n hint:\n `Add chartConfig with xAxis.field set to a dimension (${list(dims)}) and ` +\n `yAxis[].field set to a measure name (${list(values)}). If the default ` +\n `rendering is intentional, suppress with: suppressWarnings: ['${CHART_CONFIG_MISSING}']`,\n });\n }\n\n // ── (e) table/pivot bound to a count-only, dimensionless selection ──\n if (w.type !== 'table' && w.type !== 'pivot') continue;\n // Grouped by at least one dimension → genuinely aggregated rows.\n if (dims.length > 0) continue;\n if (values.length === 0) continue;\n const resolved = values.map((v) => measures.get(v));\n // An unresolvable measure name already errored above — don't guess here.\n if (resolved.some((m) => !m)) continue;\n\n // Derived measures combine other measures; treat them as non-count even\n // when their (ignored) `aggregate` says otherwise.\n const countOnly = resolved.every((m) => m!.aggregate === 'count' && !m!.derived);\n if (!countOnly) continue;\n\n push({\n severity: 'warning',\n rule: TABLE_COUNT_ONLY,\n message:\n `a '${w.type}' widget bound to dataset \"${dsName}\" selects only count ` +\n `measure(s) (${values.join(', ')}) and no dimensions, so it renders a ` +\n `single summary row — not a per-record list.`,\n hint:\n `A flat record listing is not an analytics dataset. Model it as an ` +\n `object-bound ListView (ADR-0017) surfaced through app navigation, and ` +\n `use a 'metric' widget here if you only need the count. If a single-row ` +\n `table is intentional, add an explicit dimension or suppress with: ` +\n `suppressWarnings: ['${TABLE_COUNT_ONLY}']`,\n });\n }\n }\n\n return findings;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Build-time expression validation (ADR-0032 §Decision 1a + 1b).\n *\n * Runs at `objectstack compile`, where the whole normalized stack is in hand —\n * so flow conditions can be checked against the *resolved* object schema\n * (field existence) in addition to CEL syntax. Uses the one shared validator\n * from `@objectstack/formula`, so the verdict matches `registerFlow` and the\n * agent `validate_expression` tool exactly.\n *\n * Scope (v1): flow predicates (start/decision `config.condition` + edge\n * `condition`), object validation-rule / formula predicates, and UI action\n * `visible` / `disabled` predicates. Each error is located (flow/object/action\n * + node/edge/field) with a corrective message.\n */\n\nimport { validateExpression } from '@objectstack/formula';\n\nexport interface ExprIssue {\n where: string;\n message: string;\n source: string;\n /**\n * `error` fails the build (e.g. a bare ref in a record-scoped formula). `warning`\n * is advisory and never fails it (e.g. a possible field typo in a flattened flow\n * condition, which might be a flow variable). Absent ⇒ treat as `error`.\n */\n severity?: 'error' | 'warning';\n}\n\ntype AnyRec = Record<string, unknown>;\n\n/** Coerce an `objects` collection (array or name-keyed map) to an array. */\nfunction asArray(v: unknown): AnyRec[] {\n if (Array.isArray(v)) return v as AnyRec[];\n if (v && typeof v === 'object') {\n return Object.entries(v as AnyRec).map(([name, def]) => ({ name, ...(def as AnyRec) }));\n }\n return [];\n}\n\n/** object name → set of its field names, for schema-aware field checks. */\nfunction buildFieldIndex(objects: AnyRec[]): Map<string, string[]> {\n const idx = new Map<string, string[]>();\n for (const obj of objects) {\n const name = typeof obj.name === 'string' ? obj.name : undefined;\n if (!name) continue;\n const fields = obj.fields;\n let names: string[] = [];\n if (Array.isArray(fields)) names = fields.map(f => (f as AnyRec).name).filter((n): n is string => typeof n === 'string');\n else if (fields && typeof fields === 'object') names = Object.keys(fields as AnyRec);\n idx.set(name, names);\n }\n return idx;\n}\n\n/**\n * Validate every predicate in the stack. Returns the list of issues (empty =\n * clean). Caller decides how to surface / whether to fail the build.\n */\nexport function validateStackExpressions(stack: AnyRec): ExprIssue[] {\n const issues: ExprIssue[] = [];\n const objects = asArray(stack.objects);\n const fieldIndex = buildFieldIndex(objects);\n\n const check = (\n where: string,\n raw: unknown,\n objectName?: string,\n scope: 'record' | 'flattened' = 'flattened',\n ): void => {\n if (raw == null) return;\n const fields = objectName ? fieldIndex.get(objectName) : undefined;\n const res = validateExpression('predicate', raw as string | { dialect?: string; source?: string },\n objectName ? { objectName, fields, scope } : { scope });\n for (const e of res.errors) issues.push({ where, message: e.message, source: e.source, severity: 'error' });\n for (const w of res.warnings) issues.push({ where, message: w.message, source: w.source, severity: 'warning' });\n };\n\n // ── Flows ──────────────────────────────────────────────────────────\n for (const flow of asArray(stack.flows)) {\n const flowName = typeof flow.name === 'string' ? flow.name : '(unnamed flow)';\n const nodes = Array.isArray(flow.nodes) ? (flow.nodes as AnyRec[]) : [];\n const edges = Array.isArray(flow.edges) ? (flow.edges as AnyRec[]) : [];\n // The record-change target object — `record.*` refs resolve against it.\n const startNode = nodes.find(n => n.type === 'start');\n const startCfg = (startNode?.config ?? {}) as AnyRec;\n const objectName = typeof startCfg.objectName === 'string' ? startCfg.objectName : undefined;\n\n for (const node of nodes) {\n const cfg = (node.config ?? {}) as AnyRec;\n check(`flow '${flowName}' · node '${node.id}' (${node.type}) condition`, cfg.condition, objectName);\n // #1870 — a `script` node must declare a callable target (`actionType` or\n // `function`). A node with neither is a silent no-op that otherwise passes\n // build. (Function *existence* isn't checkable here — functions are code,\n // not serialized into the artifact — so this is a structural check; the\n // runtime verifies the named function is actually registered.)\n if (node.type === 'script') {\n // `function` is canonical; `functionName` is an accepted alias.\n const fn =\n (typeof cfg.function === 'string' ? cfg.function.trim() : '') ||\n (typeof cfg.functionName === 'string' ? cfg.functionName.trim() : '');\n const action = typeof cfg.actionType === 'string' ? cfg.actionType.trim() : '';\n // Inline `config.script` (a JS body) is also a declared form — the\n // built-in runtime doesn't execute it (warned at run time), but the node\n // is not the empty no-op this check targets, so don't flag it.\n const inline = typeof cfg.script === 'string' ? cfg.script.trim() : '';\n if (!fn && !action && !inline) {\n issues.push({\n where: `flow '${flowName}' · node '${node.id}' (script) callable`,\n message:\n `script node declares neither \\`actionType\\` nor \\`function\\` — it would do nothing at runtime. ` +\n `Name a built-in action (e.g. \\`actionType: 'email'\\`) or a registered function ` +\n `(\\`function: 'my_fn'\\`, registered via \\`defineStack({ functions })\\`).`,\n source: JSON.stringify({ id: node.id, type: node.type, config: cfg }),\n });\n } else if (action === 'invoke_function' && !fn) {\n // `actionType: 'invoke_function'` is a marker that names no callable on\n // its own — the function name must be in `function`/`functionName`.\n issues.push({\n where: `flow '${flowName}' · node '${node.id}' (script) callable`,\n message:\n `script node uses \\`actionType: 'invoke_function'\\` but no \\`function\\` (or \\`functionName\\`) — ` +\n `it names no callable. Set \\`function: 'my_fn'\\` and register it via \\`defineStack({ functions })\\`.`,\n source: JSON.stringify({ id: node.id, type: node.type, config: cfg }),\n });\n }\n }\n }\n for (const edge of edges) {\n check(`flow '${flowName}' · edge '${edge.id}' (${edge.source}→${edge.target}) condition`, edge.condition, objectName);\n }\n }\n\n // ── Object validation-rule + formula predicates ────────────────────\n for (const obj of objects) {\n const objectName = typeof obj.name === 'string' ? obj.name : undefined;\n const validations = obj.validations ?? obj.validationRules;\n for (const rule of asArray(validations)) {\n const where = `object '${objectName}' · validation '${(rule.name as string) ?? '?'}'`;\n // Common predicate keys across rule shapes. Validation predicates are\n // `record`-scoped — no field flattening — so bare refs are flagged (#1928).\n check(where, rule.expression ?? rule.predicate ?? rule.condition ?? rule.formula, objectName, 'record');\n // `conditional` rules carry a nested `when` predicate (record-scoped).\n check(`${where} when`, (rule as AnyRec).when, objectName, 'record');\n }\n // Field-level formulas (computed fields) reference the same object.\n const fields = obj.fields;\n const fieldList = Array.isArray(fields)\n ? (fields as AnyRec[])\n : (fields && typeof fields === 'object' ? Object.values(fields as AnyRec) as AnyRec[] : []);\n\n // ── ADR-0062 D7 — reject `field.columnName` on external objects ──────\n // `field.columnName` (localField → physicalColumn) is the managed-object\n // mechanism; it is NOT applied by the driver's query pipeline for federated\n // objects, where `external.columnMap` (remoteColumn → localField) is the\n // authoritative — and inverse — mapping. Allowing both would be a silent\n // dual-source ambiguity, so reject `columnName` on any object that declares\n // an `external` binding (a federated object). Managed objects are untouched.\n if (obj.external != null) {\n const fieldEntries: Array<[string, AnyRec]> = Array.isArray(fields)\n ? (fields as AnyRec[]).map((f) => [((f as AnyRec)?.name as string) ?? '?', f as AnyRec])\n : (fields && typeof fields === 'object'\n ? (Object.entries(fields as AnyRec) as Array<[string, AnyRec]>)\n : []);\n for (const [fname, fdef] of fieldEntries) {\n if (fdef && typeof fdef === 'object' && (fdef as AnyRec).columnName != null) {\n issues.push({\n where: `object '${objectName}' · field '${fname}'`,\n message:\n `external object '${objectName}': field '${fname}' sets columnName='${String((fdef as AnyRec).columnName)}', ` +\n `which is not supported on federated objects (ADR-0062 D7). The driver's query pipeline ignores ` +\n `field.columnName for external objects; map remote columns via the datasource's external.columnMap instead.`,\n source: `columnName='${String((fdef as AnyRec).columnName)}'`,\n severity: 'error',\n });\n }\n }\n }\n\n for (const f of fieldList) {\n // Field-level conditional rules are server-enforced (rule-validator) and\n // record-scoped — a bare ref silently fails the rule (required/readonly\n // not enforced = data-integrity hole). #1928 class, same as actions.\n if (f && typeof f === 'object') {\n const fname = (f.name as string) ?? '?';\n for (const key of ['requiredWhen', 'readonlyWhen', 'conditionalRequired', 'visibleWhen'] as const) {\n check(`object '${objectName}' · field '${fname}' ${key}`, (f as AnyRec)[key], objectName, 'record');\n }\n }\n if (f && typeof f === 'object' && f.formula) {\n // formulas are `value` role (any return type), still CEL. They are\n // `record`-scoped — `record.<field>`, never bare — so flag bare refs (#1928).\n const res = validateExpression('value', f.formula as string | { dialect?: string; source?: string },\n objectName ? { objectName, fields: fieldIndex.get(objectName), scope: 'record' } : { scope: 'record' });\n const fieldWhere = `object '${objectName}' · field '${(f.name as string) ?? '?'}' formula`;\n for (const e of res.errors) issues.push({ where: fieldWhere, message: e.message, source: e.source, severity: 'error' });\n for (const w of res.warnings) issues.push({ where: fieldWhere, message: w.message, source: w.source, severity: 'warning' });\n }\n }\n }\n\n // ── Action `visible` / `disabled` predicates ───────────────────────\n // Record-scoped, same as validation rules: a record-header / row action's\n // `visible` is evaluated by ActionEngine against `{ record, recordId,\n // objectName, user, … }` with fail-closed semantics, so a BARE field ref\n // (`done` instead of `record.done`) throws and the action is silently hidden\n // on every record (the trap behind the #2183 \"Mark Done never hides\" hunt).\n // Flagging it here turns that into a build error with a corrective message.\n // `disabled` may be a boolean (skip) or a predicate (check).\n const seenActions = new Set<string>();\n const checkAction = (where: string, action: AnyRec, objectName?: string): void => {\n const obj = objectName\n ?? (typeof action.objectName === 'string' ? action.objectName : undefined)\n ?? (typeof action.object === 'string' ? action.object : undefined);\n const name = typeof action.name === 'string' ? action.name : '?';\n const key = `${obj ?? ''}:${name}`;\n if (seenActions.has(key)) return; // de-dup (actions are merged onto objects AND kept top-level)\n seenActions.add(key);\n check(`${where} · action '${name}' visible`, action.visible, obj, 'record');\n if (typeof action.disabled !== 'boolean') {\n check(`${where} · action '${name}' disabled`, action.disabled, obj, 'record');\n }\n };\n for (const action of asArray(stack.actions)) {\n checkAction('stack', action);\n }\n for (const obj of objects) {\n const objectName = typeof obj.name === 'string' ? obj.name : undefined;\n for (const action of asArray(obj.actions)) {\n checkAction(`object '${objectName}'`, action, objectName);\n }\n }\n\n // ── Sharing-rule predicates (security-critical, record-scoped) ─────\n // A criteria sharing rule's `condition` decides which rows a principal sees.\n // It is evaluated against the record, so a bare ref silently changes access.\n for (const rule of asArray(stack.sharingRules)) {\n const ruleObj = typeof rule.object === 'string' ? rule.object : undefined;\n const where = `sharingRule '${(rule.name as string) ?? '?'}'${ruleObj ? ` (${ruleObj})` : ''} condition`;\n check(where, rule.condition ?? rule.criteria ?? rule.predicate, ruleObj, 'record');\n }\n\n // ── Hook `condition` predicates (record-scoped gate) ───────────────\n // A lifecycle hook's `condition` skips the handler when false; it is\n // evaluated against the record, so a bare ref silently makes the hook\n // run on every record (or never) instead of the intended subset.\n for (const hook of asArray(stack.hooks)) {\n const hookObj = typeof hook.object === 'string' ? hook.object : undefined; // array targets → no single field set\n check(`hook '${(hook.name as string) ?? '?'}'${hookObj ? ` (${hookObj})` : ''} condition`, hook.condition, hookObj, 'record');\n }\n\n return issues;\n}\n"],"mappings":"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","names":["asArray"]}
1
+ {"version":3,"sources":["../src/validate-widget-bindings.ts","../src/validate-expressions.ts","../src/validate-responsive-styles.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { isIncoherentAggregate } from '@objectstack/spec/data';\n\n/**\n * Build-time dashboard widget binding diagnostics (issues #1719, #1721).\n *\n * Runs at `objectstack validate`/`compile`/`build` AFTER the stack has been\n * schema-parsed, so every widget's `dataset` reference can be linked to its\n * `defineDataset` and each entry in `dimensions`/`values` resolved to a\n * declared dimension/measure. This is the semantic/cross-reference phase —\n * the rules here cannot run during plain Zod parsing of the raw widget\n * literal (the dataset may even live in another package of the stack).\n *\n * Reference-integrity rules (#1721) — severity `error`, the page is broken:\n *\n * - `widget-dataset-unknown` — `dataset` does not resolve to a declared\n * `Dataset`.\n * - `widget-dimension-unknown` — a `dimensions[]` entry is not a dimension\n * name on the bound dataset.\n * - `widget-measure-unknown` — a `values[]` entry is not a measure name on\n * the bound dataset.\n * - `chart-field-unknown` — a `chartConfig` binding names a field the query\n * result will not contain: `xAxis.field` must be one of the widget's\n * dimensions (or a dataset dimension), and each `yAxis[].field` /\n * `series[].name` must be one of the widget's selected measures\n * (`values`). Post-cutover (ADR-0021) the result rows are keyed by\n * measure NAME (e.g. `sum_amount`), not the base column (`amount`) — a\n * stale base-column reference renders the axis but an empty series.\n *\n * Advisory rules — severity `warning`, build stays green:\n *\n * - `chart-config-missing` — a chart-type widget (bar/line/pie/…) has no\n * `chartConfig`, so the renderer cannot tell which measure to plot.\n * - `table-count-only` (#1719) — a `table`/`pivot` widget whose selected\n * measures are ALL `aggregate: 'count'` and which declares no\n * `dimensions` asks the analytics service for a single summary row. That\n * is the shape a `metric` widget wants — for a table it almost always\n * means the author wanted a per-record listing, which is not an\n * analytics dataset at all (model it as an object-bound ListView,\n * ADR-0017). Evaluated on the WIDGET's binding, not the dataset.\n * - `measure-aggregate-incoherent` — a dataset measure aggregates its field\n * in a way that produces a meaningless number: today, SUM (or\n * `count_distinct`) of a `percent`/rate field, whose total routinely\n * exceeds 100%. Rates must AVG. Checked once per dataset (independent of\n * any widget) when the bound object's field types are known.\n *\n * Warnings can be deliberately suppressed per widget via\n * `suppressWarnings: ['<rule-id>']`; errors cannot — they describe a\n * binding the analytics service cannot satisfy.\n */\n\nexport const WIDGET_DATASET_UNKNOWN = 'widget-dataset-unknown';\nexport const WIDGET_DIMENSION_UNKNOWN = 'widget-dimension-unknown';\nexport const WIDGET_MEASURE_UNKNOWN = 'widget-measure-unknown';\nexport const CHART_FIELD_UNKNOWN = 'chart-field-unknown';\nexport const CHART_CONFIG_MISSING = 'chart-config-missing';\nexport const TABLE_COUNT_ONLY = 'table-count-only';\nexport const MEASURE_AGGREGATE_INCOHERENT = 'measure-aggregate-incoherent';\n\nexport type WidgetBindingSeverity = 'error' | 'warning';\n\nexport interface WidgetBindingFinding {\n /** `error` = unresolvable binding (broken page); `warning` = advisory. */\n severity: WidgetBindingSeverity;\n /** Diagnostic rule id (registry entry), e.g. `widget-measure-unknown`. */\n rule: string;\n /** Human-readable location, e.g. `dashboard \"x\" › widget \"y\"`. */\n where: string;\n /** Config path, e.g. `dashboards[0].widgets[3]`. */\n path: string;\n /** What is wrong. */\n message: string;\n /** How to fix (or deliberately suppress) it. */\n hint: string;\n}\n\ntype AnyRec = Record<string, unknown>;\n\n/** Coerce a collection (array or name-keyed map) to an array. */\nfunction asArray(v: unknown): AnyRec[] {\n if (Array.isArray(v)) return v as AnyRec[];\n if (v && typeof v === 'object') {\n return Object.entries(v as AnyRec).map(([name, def]) => ({ name, ...(def as AnyRec) }));\n }\n return [];\n}\n\nfunction asStrings(v: unknown): string[] {\n return Array.isArray(v) ? v.filter((s): s is string => typeof s === 'string') : [];\n}\n\n/**\n * Chart families whose renderer needs a `chartConfig` measure mapping.\n * Single-value types (metric/kpi/gauge/…) plot their lone value and tabular\n * types (table/pivot) render every column, so they are exempt.\n */\nconst CHART_TYPES = new Set([\n 'bar', 'horizontal-bar', 'column',\n 'line', 'area',\n 'pie', 'donut', 'funnel',\n 'scatter', 'treemap', 'sankey', 'radar',\n]);\n\nfunction levenshtein(a: string, b: string): number {\n const m = a.length, n = b.length;\n let prev = Array.from({ length: n + 1 }, (_, j) => j);\n for (let i = 1; i <= m; i++) {\n const cur = [i];\n for (let j = 1; j <= n; j++) {\n cur[j] = Math.min(\n prev[j] + 1,\n cur[j - 1] + 1,\n prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),\n );\n }\n prev = cur;\n }\n return prev[n];\n}\n\n/**\n * Nearest declared name for a typo'd/stale reference, or undefined when\n * nothing is close. Containment is checked first because the cutover's\n * canonical drift is base column → prefixed measure name (`amount` →\n * `sum_amount`), which is far in edit distance but obvious to a human.\n */\nfunction didYouMean(input: string, candidates: Iterable<string>): string | undefined {\n let best: string | undefined;\n let bestScore = Infinity;\n for (const c of candidates) {\n let score: number;\n if (input.length >= 3 && (c.includes(input) || input.includes(c))) {\n score = Math.abs(c.length - input.length);\n } else {\n const d = levenshtein(input, c);\n if (d > Math.max(2, Math.floor(input.length / 3))) continue;\n score = 100 + d;\n }\n if (score < bestScore) { bestScore = score; best = c; }\n }\n return best;\n}\n\nfunction suggest(input: string, candidates: Iterable<string>): string {\n const s = didYouMean(input, candidates);\n return s ? ` Did you mean \"${s}\"?` : '';\n}\n\nfunction list(names: Iterable<string>): string {\n const arr = [...names];\n return arr.length > 0 ? arr.join(', ') : '(none)';\n}\n\n/**\n * Validate every dashboard widget's dataset binding. Returns the list of\n * findings (empty = clean). Caller decides how to surface them: `error`\n * findings describe bindings the analytics service cannot satisfy and\n * should fail validate/build; `warning` findings are advisory and must\n * never fail the build on their own.\n */\nexport function validateWidgetBindings(stack: AnyRec): WidgetBindingFinding[] {\n const findings: WidgetBindingFinding[] = [];\n\n const datasets = new Map<string, AnyRec>();\n for (const ds of asArray(stack.datasets)) {\n if (typeof ds.name === 'string') datasets.set(ds.name, ds);\n }\n\n // ── (0) dataset measures aggregate their field coherently ──\n // A measure that SUMs a percentage/rate field produces a meaningless total\n // (it can exceed 100%); rates must AVG. This is a dataset-level defect (it\n // does not depend on any widget), so it is checked once over every dataset\n // whose object's field types are known. Advisory — the page still renders.\n const objectFieldTypes = new Map<string, Map<string, string>>();\n for (const o of asArray(stack.objects)) {\n if (typeof o.name !== 'string') continue;\n const fm = new Map<string, string>();\n for (const f of asArray(o.fields)) {\n if (typeof f.name === 'string' && typeof f.type === 'string') fm.set(f.name, f.type);\n }\n objectFieldTypes.set(o.name, fm);\n }\n const datasetList = asArray(stack.datasets);\n for (let i = 0; i < datasetList.length; i++) {\n const ds = datasetList[i];\n const fieldTypes = typeof ds.object === 'string' ? objectFieldTypes.get(ds.object) : undefined;\n if (!fieldTypes) continue; // cannot judge without the object's field types\n const dsMeasures = asArray(ds.measures);\n for (let k = 0; k < dsMeasures.length; k++) {\n const m = dsMeasures[k];\n const field = typeof m.field === 'string' ? m.field : undefined;\n const aggregate = typeof m.aggregate === 'string' ? m.aggregate : undefined;\n if (!field || !aggregate) continue; // count(*) and underivable measures are fine\n const ftype = fieldTypes.get(field);\n if (ftype && isIncoherentAggregate(aggregate, ftype)) {\n findings.push({\n severity: 'warning',\n rule: MEASURE_AGGREGATE_INCOHERENT,\n where: `dataset \"${typeof ds.name === 'string' ? ds.name : `(dataset ${i})`}\" › measure \"${typeof m.name === 'string' ? m.name : `(measure ${k})`}\"`,\n path: `datasets[${i}].measures[${k}]`,\n message:\n `measure \"${m.name}\" applies ${aggregate} to ${ftype} field \"${field}\" — ` +\n `summed percentages are meaningless (they can exceed 100%).`,\n hint:\n `Use aggregate \"avg\" for percentage/rate fields (or \"count\" of records). ` +\n `If a running total is genuinely intended, suppress with: ` +\n `suppressWarnings: ['${MEASURE_AGGREGATE_INCOHERENT}'] on the measure.`,\n });\n }\n }\n }\n\n const dashboards = asArray(stack.dashboards);\n for (let i = 0; i < dashboards.length; i++) {\n const dash = dashboards[i];\n const dashName = typeof dash.name === 'string' ? dash.name : `(dashboard ${i})`;\n const widgets = Array.isArray(dash.widgets) ? (dash.widgets as AnyRec[]) : [];\n\n for (let j = 0; j < widgets.length; j++) {\n const w = widgets[j];\n const widgetId = typeof w.id === 'string' ? w.id : `(widget ${j})`;\n const where = `dashboard \"${dashName}\" › widget \"${widgetId}\"`;\n const path = `dashboards[${i}].widgets[${j}]`;\n const suppressed = (rule: string): boolean =>\n Array.isArray(w.suppressWarnings) && w.suppressWarnings.includes(rule);\n const push = (f: Omit<WidgetBindingFinding, 'where' | 'path'>): void => {\n if (f.severity === 'warning' && suppressed(f.rule)) return;\n findings.push({ ...f, where, path });\n };\n\n // ── (a) dataset reference resolves ──\n const dsName = typeof w.dataset === 'string' ? w.dataset : undefined;\n const dataset = dsName ? datasets.get(dsName) : undefined;\n if (dsName && !dataset) {\n push({\n severity: 'error',\n rule: WIDGET_DATASET_UNKNOWN,\n message: `dataset \"${dsName}\" does not resolve to a declared dataset.`,\n hint:\n `Declared datasets: ${list(datasets.keys())}.${suggest(dsName, datasets.keys())} ` +\n `Define the dataset with defineDataset() or fix the reference (ADR-0021).`,\n });\n }\n // Without a resolved dataset there is nothing to check names against.\n if (!dataset) continue;\n\n const dimensionNames = new Set<string>();\n for (const d of asArray(dataset.dimensions)) {\n if (typeof d.name === 'string') dimensionNames.add(d.name);\n }\n const measures = new Map<string, AnyRec>();\n for (const m of asArray(dataset.measures)) {\n if (typeof m.name === 'string') measures.set(m.name, m);\n }\n\n // ── (b) every dimensions[] entry is a dataset dimension ──\n const dims = asStrings(w.dimensions);\n for (let k = 0; k < dims.length; k++) {\n if (dimensionNames.has(dims[k])) continue;\n push({\n severity: 'error',\n rule: WIDGET_DIMENSION_UNKNOWN,\n message:\n `dimensions[${k}] \"${dims[k]}\" is not a dimension of dataset ` +\n `\"${dsName}\" (declared dimensions: ${list(dimensionNames)}).`,\n hint:\n `Widgets select dataset dimensions BY NAME.${suggest(dims[k], dimensionNames)} ` +\n `Add the dimension to the dataset or fix the reference.`,\n });\n }\n\n // ── (c) every values[] entry is a dataset measure ──\n const values = asStrings(w.values);\n for (let k = 0; k < values.length; k++) {\n if (measures.has(values[k])) continue;\n push({\n severity: 'error',\n rule: WIDGET_MEASURE_UNKNOWN,\n message:\n `values[${k}] \"${values[k]}\" is not a measure of dataset ` +\n `\"${dsName}\" (declared measures: ${list(measures.keys())}).`,\n hint:\n `Widgets select dataset measures BY NAME, not by base column.` +\n `${suggest(values[k], measures.keys())} ` +\n `Add the measure to the dataset or fix the reference.`,\n });\n }\n\n // ── (d) chartConfig bindings resolve against the widget's selection ──\n const chartConfig = (w.chartConfig && typeof w.chartConfig === 'object')\n ? (w.chartConfig as AnyRec)\n : undefined;\n const isChartType = typeof w.type === 'string' && CHART_TYPES.has(w.type);\n\n if (chartConfig) {\n // The query result carries the widget's selected dimensions and\n // measures; resolve every chartConfig field against that shape.\n const selectedValues = new Set(values.filter((v) => measures.has(v)));\n\n const xAxis = (chartConfig.xAxis && typeof chartConfig.xAxis === 'object')\n ? (chartConfig.xAxis as AnyRec)\n : undefined;\n // A field naming an entry of the widget's own (already-validated)\n // selection is not re-reported here — rules (b)/(c) own that error.\n if (xAxis && typeof xAxis.field === 'string'\n && !dimensionNames.has(xAxis.field) && !dims.includes(xAxis.field)) {\n push({\n severity: 'error',\n rule: CHART_FIELD_UNKNOWN,\n message:\n `chartConfig.xAxis.field \"${xAxis.field}\" does not resolve to a ` +\n `dimension of dataset \"${dsName}\" (declared dimensions: ${list(dimensionNames)}).`,\n hint: `Point xAxis.field at a dataset dimension name.${suggest(xAxis.field, dimensionNames)}`,\n });\n }\n\n const measureField = (label: string, field: string): void => {\n if (values.includes(field)) return; // resolvable, or already errored via rule (c)\n const declaredButUnselected = measures.has(field);\n push({\n severity: 'error',\n rule: CHART_FIELD_UNKNOWN,\n message: declaredButUnselected\n ? `chartConfig.${label} \"${field}\" is a measure of dataset \"${dsName}\" ` +\n `but is not selected in the widget's values (${list(values)}), so the ` +\n `query result will not contain it.`\n : `chartConfig.${label} \"${field}\" does not resolve to a measure of ` +\n `dataset \"${dsName}\" (declared measures: ${list(measures.keys())}).`,\n hint: declaredButUnselected\n ? `Add \"${field}\" to the widget's values, or bind the chart to a selected measure.`\n : `Post-cutover data is keyed by the dataset's measure NAME, not the ` +\n `base column.${suggest(field, selectedValues.size > 0 ? selectedValues : measures.keys())}`,\n });\n };\n\n const yAxes = Array.isArray(chartConfig.yAxis) ? (chartConfig.yAxis as AnyRec[]) : [];\n for (let k = 0; k < yAxes.length; k++) {\n const field = yAxes[k]?.field;\n if (typeof field === 'string') measureField(`yAxis[${k}].field`, field);\n }\n const series = Array.isArray(chartConfig.series) ? (chartConfig.series as AnyRec[]) : [];\n for (let k = 0; k < series.length; k++) {\n const name = series[k]?.name;\n if (typeof name === 'string') measureField(`series[${k}].name`, name);\n }\n } else if (isChartType) {\n push({\n severity: 'warning',\n rule: CHART_CONFIG_MISSING,\n message:\n `chart-type widget ('${w.type}') has no chartConfig — the renderer ` +\n `cannot determine which measure to plot, so the series renders empty.`,\n hint:\n `Add chartConfig with xAxis.field set to a dimension (${list(dims)}) and ` +\n `yAxis[].field set to a measure name (${list(values)}). If the default ` +\n `rendering is intentional, suppress with: suppressWarnings: ['${CHART_CONFIG_MISSING}']`,\n });\n }\n\n // ── (e) table/pivot bound to a count-only, dimensionless selection ──\n if (w.type !== 'table' && w.type !== 'pivot') continue;\n // Grouped by at least one dimension → genuinely aggregated rows.\n if (dims.length > 0) continue;\n if (values.length === 0) continue;\n const resolved = values.map((v) => measures.get(v));\n // An unresolvable measure name already errored above — don't guess here.\n if (resolved.some((m) => !m)) continue;\n\n // Derived measures combine other measures; treat them as non-count even\n // when their (ignored) `aggregate` says otherwise.\n const countOnly = resolved.every((m) => m!.aggregate === 'count' && !m!.derived);\n if (!countOnly) continue;\n\n push({\n severity: 'warning',\n rule: TABLE_COUNT_ONLY,\n message:\n `a '${w.type}' widget bound to dataset \"${dsName}\" selects only count ` +\n `measure(s) (${values.join(', ')}) and no dimensions, so it renders a ` +\n `single summary row — not a per-record list.`,\n hint:\n `A flat record listing is not an analytics dataset. Model it as an ` +\n `object-bound ListView (ADR-0017) surfaced through app navigation, and ` +\n `use a 'metric' widget here if you only need the count. If a single-row ` +\n `table is intentional, add an explicit dimension or suppress with: ` +\n `suppressWarnings: ['${TABLE_COUNT_ONLY}']`,\n });\n }\n }\n\n return findings;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Build-time expression validation (ADR-0032 §Decision 1a + 1b).\n *\n * Runs at `objectstack compile`, where the whole normalized stack is in hand —\n * so flow conditions can be checked against the *resolved* object schema\n * (field existence) in addition to CEL syntax. Uses the one shared validator\n * from `@objectstack/formula`, so the verdict matches `registerFlow` and the\n * agent `validate_expression` tool exactly.\n *\n * Scope (v1): flow predicates (start/decision `config.condition` + edge\n * `condition`), object validation-rule / formula predicates, and UI action\n * `visible` / `disabled` predicates. Each error is located (flow/object/action\n * + node/edge/field) with a corrective message.\n */\n\nimport { validateExpression } from '@objectstack/formula';\n\nexport interface ExprIssue {\n where: string;\n message: string;\n source: string;\n /**\n * `error` fails the build (e.g. a bare ref in a record-scoped formula). `warning`\n * is advisory and never fails it (e.g. a possible field typo in a flattened flow\n * condition, which might be a flow variable). Absent ⇒ treat as `error`.\n */\n severity?: 'error' | 'warning';\n}\n\ntype AnyRec = Record<string, unknown>;\n\n/** Coerce an `objects` collection (array or name-keyed map) to an array. */\nfunction asArray(v: unknown): AnyRec[] {\n if (Array.isArray(v)) return v as AnyRec[];\n if (v && typeof v === 'object') {\n return Object.entries(v as AnyRec).map(([name, def]) => ({ name, ...(def as AnyRec) }));\n }\n return [];\n}\n\n/** object name → set of its field names, for schema-aware field checks. */\nfunction buildFieldIndex(objects: AnyRec[]): Map<string, string[]> {\n const idx = new Map<string, string[]>();\n for (const obj of objects) {\n const name = typeof obj.name === 'string' ? obj.name : undefined;\n if (!name) continue;\n const fields = obj.fields;\n let names: string[] = [];\n if (Array.isArray(fields)) names = fields.map(f => (f as AnyRec).name).filter((n): n is string => typeof n === 'string');\n else if (fields && typeof fields === 'object') names = Object.keys(fields as AnyRec);\n idx.set(name, names);\n }\n return idx;\n}\n\n/**\n * Validate every predicate in the stack. Returns the list of issues (empty =\n * clean). Caller decides how to surface / whether to fail the build.\n */\nexport function validateStackExpressions(stack: AnyRec): ExprIssue[] {\n const issues: ExprIssue[] = [];\n const objects = asArray(stack.objects);\n const fieldIndex = buildFieldIndex(objects);\n\n const check = (\n where: string,\n raw: unknown,\n objectName?: string,\n scope: 'record' | 'flattened' = 'flattened',\n ): void => {\n if (raw == null) return;\n const fields = objectName ? fieldIndex.get(objectName) : undefined;\n const res = validateExpression('predicate', raw as string | { dialect?: string; source?: string },\n objectName ? { objectName, fields, scope } : { scope });\n for (const e of res.errors) issues.push({ where, message: e.message, source: e.source, severity: 'error' });\n for (const w of res.warnings) issues.push({ where, message: w.message, source: w.source, severity: 'warning' });\n };\n\n // ── Flows ──────────────────────────────────────────────────────────\n for (const flow of asArray(stack.flows)) {\n const flowName = typeof flow.name === 'string' ? flow.name : '(unnamed flow)';\n const nodes = Array.isArray(flow.nodes) ? (flow.nodes as AnyRec[]) : [];\n const edges = Array.isArray(flow.edges) ? (flow.edges as AnyRec[]) : [];\n // The record-change target object — `record.*` refs resolve against it.\n const startNode = nodes.find(n => n.type === 'start');\n const startCfg = (startNode?.config ?? {}) as AnyRec;\n const objectName = typeof startCfg.objectName === 'string' ? startCfg.objectName : undefined;\n\n for (const node of nodes) {\n const cfg = (node.config ?? {}) as AnyRec;\n check(`flow '${flowName}' · node '${node.id}' (${node.type}) condition`, cfg.condition, objectName);\n // #1870 — a `script` node must declare a callable target (`actionType` or\n // `function`). A node with neither is a silent no-op that otherwise passes\n // build. (Function *existence* isn't checkable here — functions are code,\n // not serialized into the artifact — so this is a structural check; the\n // runtime verifies the named function is actually registered.)\n if (node.type === 'script') {\n // `function` is canonical; `functionName` is an accepted alias.\n const fn =\n (typeof cfg.function === 'string' ? cfg.function.trim() : '') ||\n (typeof cfg.functionName === 'string' ? cfg.functionName.trim() : '');\n const action = typeof cfg.actionType === 'string' ? cfg.actionType.trim() : '';\n // Inline `config.script` (a JS body) is also a declared form — the\n // built-in runtime doesn't execute it (warned at run time), but the node\n // is not the empty no-op this check targets, so don't flag it.\n const inline = typeof cfg.script === 'string' ? cfg.script.trim() : '';\n if (!fn && !action && !inline) {\n issues.push({\n where: `flow '${flowName}' · node '${node.id}' (script) callable`,\n message:\n `script node declares neither \\`actionType\\` nor \\`function\\` — it would do nothing at runtime. ` +\n `Name a built-in action (e.g. \\`actionType: 'email'\\`) or a registered function ` +\n `(\\`function: 'my_fn'\\`, registered via \\`defineStack({ functions })\\`).`,\n source: JSON.stringify({ id: node.id, type: node.type, config: cfg }),\n });\n } else if (action === 'invoke_function' && !fn) {\n // `actionType: 'invoke_function'` is a marker that names no callable on\n // its own — the function name must be in `function`/`functionName`.\n issues.push({\n where: `flow '${flowName}' · node '${node.id}' (script) callable`,\n message:\n `script node uses \\`actionType: 'invoke_function'\\` but no \\`function\\` (or \\`functionName\\`) — ` +\n `it names no callable. Set \\`function: 'my_fn'\\` and register it via \\`defineStack({ functions })\\`.`,\n source: JSON.stringify({ id: node.id, type: node.type, config: cfg }),\n });\n }\n }\n }\n for (const edge of edges) {\n check(`flow '${flowName}' · edge '${edge.id}' (${edge.source}→${edge.target}) condition`, edge.condition, objectName);\n }\n }\n\n // ── Object validation-rule + formula predicates ────────────────────\n for (const obj of objects) {\n const objectName = typeof obj.name === 'string' ? obj.name : undefined;\n const validations = obj.validations ?? obj.validationRules;\n for (const rule of asArray(validations)) {\n const where = `object '${objectName}' · validation '${(rule.name as string) ?? '?'}'`;\n // Common predicate keys across rule shapes. Validation predicates are\n // `record`-scoped — no field flattening — so bare refs are flagged (#1928).\n check(where, rule.expression ?? rule.predicate ?? rule.condition ?? rule.formula, objectName, 'record');\n // `conditional` rules carry a nested `when` predicate (record-scoped).\n check(`${where} when`, (rule as AnyRec).when, objectName, 'record');\n }\n // Field-level formulas (computed fields) reference the same object.\n const fields = obj.fields;\n const fieldList = Array.isArray(fields)\n ? (fields as AnyRec[])\n : (fields && typeof fields === 'object' ? Object.values(fields as AnyRec) as AnyRec[] : []);\n\n // ── ADR-0062 D7 — reject `field.columnName` on external objects ──────\n // `field.columnName` (localField → physicalColumn) is the managed-object\n // mechanism; it is NOT applied by the driver's query pipeline for federated\n // objects, where `external.columnMap` (remoteColumn → localField) is the\n // authoritative — and inverse — mapping. Allowing both would be a silent\n // dual-source ambiguity, so reject `columnName` on any object that declares\n // an `external` binding (a federated object). Managed objects are untouched.\n if (obj.external != null) {\n const fieldEntries: Array<[string, AnyRec]> = Array.isArray(fields)\n ? (fields as AnyRec[]).map((f) => [((f as AnyRec)?.name as string) ?? '?', f as AnyRec])\n : (fields && typeof fields === 'object'\n ? (Object.entries(fields as AnyRec) as Array<[string, AnyRec]>)\n : []);\n for (const [fname, fdef] of fieldEntries) {\n if (fdef && typeof fdef === 'object' && (fdef as AnyRec).columnName != null) {\n issues.push({\n where: `object '${objectName}' · field '${fname}'`,\n message:\n `external object '${objectName}': field '${fname}' sets columnName='${String((fdef as AnyRec).columnName)}', ` +\n `which is not supported on federated objects (ADR-0062 D7). The driver's query pipeline ignores ` +\n `field.columnName for external objects; map remote columns via the datasource's external.columnMap instead.`,\n source: `columnName='${String((fdef as AnyRec).columnName)}'`,\n severity: 'error',\n });\n }\n }\n }\n\n for (const f of fieldList) {\n // Field-level conditional rules are server-enforced (rule-validator) and\n // record-scoped — a bare ref silently fails the rule (required/readonly\n // not enforced = data-integrity hole). #1928 class, same as actions.\n if (f && typeof f === 'object') {\n const fname = (f.name as string) ?? '?';\n for (const key of ['requiredWhen', 'readonlyWhen', 'conditionalRequired', 'visibleWhen'] as const) {\n check(`object '${objectName}' · field '${fname}' ${key}`, (f as AnyRec)[key], objectName, 'record');\n }\n }\n if (f && typeof f === 'object' && f.formula) {\n // formulas are `value` role (any return type), still CEL. They are\n // `record`-scoped — `record.<field>`, never bare — so flag bare refs (#1928).\n const res = validateExpression('value', f.formula as string | { dialect?: string; source?: string },\n objectName ? { objectName, fields: fieldIndex.get(objectName), scope: 'record' } : { scope: 'record' });\n const fieldWhere = `object '${objectName}' · field '${(f.name as string) ?? '?'}' formula`;\n for (const e of res.errors) issues.push({ where: fieldWhere, message: e.message, source: e.source, severity: 'error' });\n for (const w of res.warnings) issues.push({ where: fieldWhere, message: w.message, source: w.source, severity: 'warning' });\n }\n }\n }\n\n // ── Action `visible` / `disabled` predicates ───────────────────────\n // Record-scoped, same as validation rules: a record-header / row action's\n // `visible` is evaluated by ActionEngine against `{ record, recordId,\n // objectName, user, … }` with fail-closed semantics, so a BARE field ref\n // (`done` instead of `record.done`) throws and the action is silently hidden\n // on every record (the trap behind the #2183 \"Mark Done never hides\" hunt).\n // Flagging it here turns that into a build error with a corrective message.\n // `disabled` may be a boolean (skip) or a predicate (check).\n const seenActions = new Set<string>();\n const checkAction = (where: string, action: AnyRec, objectName?: string): void => {\n const obj = objectName\n ?? (typeof action.objectName === 'string' ? action.objectName : undefined)\n ?? (typeof action.object === 'string' ? action.object : undefined);\n const name = typeof action.name === 'string' ? action.name : '?';\n const key = `${obj ?? ''}:${name}`;\n if (seenActions.has(key)) return; // de-dup (actions are merged onto objects AND kept top-level)\n seenActions.add(key);\n check(`${where} · action '${name}' visible`, action.visible, obj, 'record');\n if (typeof action.disabled !== 'boolean') {\n check(`${where} · action '${name}' disabled`, action.disabled, obj, 'record');\n }\n };\n for (const action of asArray(stack.actions)) {\n checkAction('stack', action);\n }\n for (const obj of objects) {\n const objectName = typeof obj.name === 'string' ? obj.name : undefined;\n for (const action of asArray(obj.actions)) {\n checkAction(`object '${objectName}'`, action, objectName);\n }\n }\n\n // ── Sharing-rule predicates (security-critical, record-scoped) ─────\n // A criteria sharing rule's `condition` decides which rows a principal sees.\n // It is evaluated against the record, so a bare ref silently changes access.\n for (const rule of asArray(stack.sharingRules)) {\n const ruleObj = typeof rule.object === 'string' ? rule.object : undefined;\n const where = `sharingRule '${(rule.name as string) ?? '?'}'${ruleObj ? ` (${ruleObj})` : ''} condition`;\n check(where, rule.condition ?? rule.criteria ?? rule.predicate, ruleObj, 'record');\n }\n\n // ── Hook `condition` predicates (record-scoped gate) ───────────────\n // A lifecycle hook's `condition` skips the handler when false; it is\n // evaluated against the record, so a bare ref silently makes the hook\n // run on every record (or never) instead of the intended subset.\n for (const hook of asArray(stack.hooks)) {\n const hookObj = typeof hook.object === 'string' ? hook.object : undefined; // array targets → no single field set\n check(`hook '${(hook.name as string) ?? '?'}'${hookObj ? ` (${hookObj})` : ''} condition`, hook.condition, hookObj, 'record');\n }\n\n return issues;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n//\n// Build-time diagnostics for the SDUI scoped-styling model (ADR-0065).\n//\n// A pure `(stack) => Finding[]` rule (ADR-0019): the same bar holds for\n// hand-authored and AI-generated pages, run from `os validate`/`compile` and\n// reusable by AI authoring. It catches the deterministic ways a `responsiveStyles`\n// block silently fails or drifts — the class of mistake an AI author is most\n// likely to make — *before* render, with actionable hints it can self-correct on.\n//\n// What it does NOT catch: whether the result looks good. Visual/semantic quality\n// (contrast, balance, \"is it ugly\") is only catchable by rendering + a VLM gate,\n// which is a separate, render-time concern (ADR-0065 §Decision-5).\n\nexport type StyleSeverity = 'error' | 'warning';\n\nexport interface StyleFinding {\n severity: StyleSeverity;\n rule: string;\n /** Human-readable location, e.g. `page \"pricing\" › node \"plan_solo\"`. */\n where: string;\n /** Config path, e.g. `pages[0].regions[0].components[1]`. */\n path: string;\n message: string;\n hint: string;\n}\n\n// Rule ids (registry entries).\nexport const STYLE_NODE_MISSING_ID = 'style-node-missing-id';\nexport const STYLE_CLASSNAME_TAILWIND = 'style-classname-tailwind';\nexport const STYLE_RESPONSIVE_NO_BASE = 'style-responsive-no-base';\nexport const STYLE_UNKNOWN_CSS_PROPERTY = 'style-unknown-css-property';\nexport const STYLE_UNKNOWN_TOKEN = 'style-unknown-token';\n\ntype AnyRec = Record<string, unknown>;\n\nconst BREAKPOINTS = ['large', 'medium', 'small', 'xsmall'] as const;\n\n/** SDUI design-token palette (ADR-0065) + base theme tokens, referenced as\n * `var(--name)`. Authors should resolve values against these. Kept in sync with\n * `apps/console/src/index.css` / `@object-ui/components` `:root`. */\nconst KNOWN_TOKENS = new Set<string>([\n // SDUI tokens\n 'space-1', 'space-2', 'space-3', 'space-4', 'space-5', 'space-6', 'space-8', 'space-10', 'space-12',\n 'radius', 'radius-sm', 'radius-md', 'radius-lg', 'radius-xl',\n 'shadow-sm', 'shadow-md', 'shadow-lg',\n 'surface', 'surface-sunken', 'text-strong', 'text-muted', 'brand', 'brand-foreground', 'hairline',\n // Base theme tokens (shadcn) — usually wrapped as hsl(var(--x)).\n 'background', 'foreground', 'card', 'card-foreground', 'popover', 'popover-foreground',\n 'primary', 'primary-foreground', 'secondary', 'secondary-foreground',\n 'muted', 'muted-foreground', 'accent', 'accent-foreground',\n 'destructive', 'destructive-foreground', 'border', 'input', 'ring',\n 'success', 'success-foreground', 'warning', 'warning-foreground',\n 'chart-1', 'chart-2', 'chart-3', 'chart-4', 'chart-5',\n]);\n\n/** Common CSS properties (camelCase) an SDUI block realistically sets. Generous\n * on purpose: an unknown property is only a *warning* (typo catcher), never a\n * blocker. Custom properties (`--x`) are always allowed. */\nconst KNOWN_CSS_PROPERTIES = new Set<string>([\n 'display', 'position', 'top', 'right', 'bottom', 'left', 'inset', 'zIndex', 'overflow', 'overflowX', 'overflowY', 'visibility', 'boxSizing', 'float', 'clear',\n 'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight', 'aspectRatio',\n 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'marginInline', 'marginBlock',\n 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'paddingInline', 'paddingBlock',\n 'flex', 'flexDirection', 'flexWrap', 'flexGrow', 'flexShrink', 'flexBasis', 'alignItems', 'alignContent', 'alignSelf', 'justifyContent', 'justifyItems', 'justifySelf', 'gap', 'rowGap', 'columnGap', 'order', 'placeItems', 'placeContent',\n 'grid', 'gridTemplate', 'gridTemplateColumns', 'gridTemplateRows', 'gridTemplateAreas', 'gridColumn', 'gridRow', 'gridArea', 'gridAutoFlow', 'gridAutoColumns', 'gridAutoRows',\n 'color', 'backgroundColor', 'background', 'backgroundImage', 'backgroundSize', 'backgroundPosition', 'backgroundRepeat', 'backgroundClip', 'opacity', 'mixBlendMode',\n 'fontSize', 'fontWeight', 'fontFamily', 'fontStyle', 'lineHeight', 'letterSpacing', 'textAlign', 'textTransform', 'textDecoration', 'textOverflow', 'whiteSpace', 'wordBreak', 'overflowWrap', 'fontVariantNumeric', 'verticalAlign', 'textShadow',\n 'border', 'borderTop', 'borderRight', 'borderBottom', 'borderLeft', 'borderColor', 'borderWidth', 'borderStyle', 'borderRadius', 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius', 'outline', 'outlineOffset',\n 'boxShadow', 'transform', 'transformOrigin', 'transition', 'transitionProperty', 'transitionDuration', 'transitionTimingFunction', 'transitionDelay', 'animation', 'filter', 'backdropFilter', 'willChange',\n 'cursor', 'pointerEvents', 'userSelect', 'objectFit', 'objectPosition', 'content',\n]);\n\nconst VAR_RE = /var\\(\\s*--([a-zA-Z0-9-]+)\\s*[,)]/g;\n\n// High-precision Tailwind-utility detection. A `className` in page metadata is\n// \"Tailwind-looking\" if any token is a responsive/state variant, an arbitrary\n// `[…]` value, a known utility stem followed by a Tailwind *value* (number /\n// fraction / size keyword), or a bare layout utility. Tuned to NOT trip on\n// ordinary custom class names (e.g. `my-custom-scope`, `os-s-plan_solo`).\nconst TW_VARIANT = /^(sm|md|lg|xl|2xl|hover|focus|active|disabled|dark|group-hover|peer-[a-z]+|first|last|odd|even):/;\nconst TW_STEM_VALUE = /^-?(p|m|px|py|pt|pb|pl|pr|mx|my|mt|mb|ml|mr|gap|gap-x|gap-y|space-x|space-y|w|h|min-w|max-w|min-h|max-h|size|text|leading|tracking|bg|border|rounded|shadow|ring|opacity|inset|top|bottom|left|right|z|order|col|row|grid-cols|grid-rows|basis)-(\\d+(\\.\\d+)?|\\d+\\/\\d+|px|full|auto|none|screen|min|max|fit|xs|sm|md|lg|xl|2xl|3xl|4xl|5xl|6xl)$/;\nconst TW_BARE = /^(flex|grid|block|inline|inline-block|inline-flex|hidden|contents|table|flow-root|grow|shrink|truncate|italic|underline|uppercase|lowercase|capitalize|antialiased|absolute|relative|fixed|sticky|static|isolate|flex-col|flex-row|flex-wrap|flex-nowrap|items-center|items-start|items-end|items-stretch|justify-center|justify-between|justify-around|justify-start|justify-end|text-center|text-left|text-right|font-bold|font-semibold|font-medium|font-normal|tabular-nums)$/;\n\nfunction looksLikeTailwind(className: string): boolean {\n return className.split(/\\s+/).some((tok) => {\n if (!tok) return false;\n if (TW_VARIANT.test(tok)) return true;\n if (/\\[[^\\]]+\\]/.test(tok)) return true; // arbitrary value, e.g. p-[13px]\n if (TW_STEM_VALUE.test(tok)) return true;\n if (TW_BARE.test(tok)) return true;\n return false;\n });\n}\n\nfunction asArray(v: unknown): AnyRec[] {\n if (Array.isArray(v)) return v as AnyRec[];\n if (v && typeof v === 'object') {\n return Object.entries(v as AnyRec).map(([name, def]) => ({ name, ...(def as AnyRec) }));\n }\n return [];\n}\n\n/** Child nodes can hang off `children`, `properties.children`, `body`, or\n * `properties.body` depending on block type — collect them all. */\nfunction childrenOf(node: AnyRec): AnyRec[] {\n const props = (node.properties as AnyRec) ?? {};\n const out: AnyRec[] = [];\n for (const c of [node.children, props.children, node.body, props.body]) {\n if (Array.isArray(c)) out.push(...(c.filter((x) => x && typeof x === 'object') as AnyRec[]));\n }\n return out;\n}\n\nfunction checkNode(node: AnyRec, pageName: string, path: string, findings: StyleFinding[]): void {\n const id = typeof node.id === 'string' ? node.id : undefined;\n const type = typeof node.type === 'string' ? node.type : 'node';\n const where = `page \"${pageName}\" › ${id ? `node \"${id}\"` : `<${type}>`}`;\n const rs = node.responsiveStyles as AnyRec | undefined;\n const hasRs = !!rs && typeof rs === 'object' && BREAKPOINTS.some((b) => rs[b]);\n\n // (1) responsiveStyles needs an id to scope to — else the CSS is dropped.\n if (hasRs && !id) {\n findings.push({\n severity: 'error', rule: STYLE_NODE_MISSING_ID, where, path,\n message: `Node has responsiveStyles but no \\`id\\`; scoped CSS cannot be generated and the styles are silently dropped.`,\n hint: `Add a stable \\`id\\` to this node.`,\n });\n }\n\n // (2) responsive breakpoint without a `large` base → unstyled at desktop.\n if (hasRs && !rs!.large && BREAKPOINTS.slice(1).some((b) => rs![b])) {\n findings.push({\n severity: 'warning', rule: STYLE_RESPONSIVE_NO_BASE, where, path,\n message: `responsiveStyles sets a smaller breakpoint but no \\`large\\` base; the node is unstyled at desktop width.`,\n hint: `Put the unconditional/base styles under \\`responsiveStyles.large\\` (desktop-first).`,\n });\n }\n\n // (3) className that looks like Tailwind → won't render from metadata.\n if (typeof node.className === 'string' && node.className.trim() && looksLikeTailwind(node.className)) {\n findings.push({\n severity: 'warning', rule: STYLE_CLASSNAME_TAILWIND, where, path,\n message: `\\`className\\` contains Tailwind-looking utilities (\"${node.className.trim().slice(0, 60)}\"); these are not compiled from metadata and will silently do nothing.`,\n hint: `Style this node with \\`responsiveStyles\\` + design tokens instead of \\`className\\` (ADR-0065).`,\n });\n }\n\n // (4)+(5) unknown CSS property / unknown token inside each breakpoint map.\n if (rs && typeof rs === 'object') {\n for (const bp of BREAKPOINTS) {\n const map = rs[bp] as AnyRec | undefined;\n if (!map || typeof map !== 'object') continue;\n for (const [prop, value] of Object.entries(map)) {\n if (!prop.startsWith('--') && !KNOWN_CSS_PROPERTIES.has(prop)) {\n findings.push({\n severity: 'warning', rule: STYLE_UNKNOWN_CSS_PROPERTY, where, path: `${path}.responsiveStyles.${bp}`,\n message: `Unknown CSS property \"${prop}\" (typo?); if unintended it will not apply.`,\n hint: `Use a camelCase CSS property name (e.g. \\`flexDirection\\`, \\`backgroundColor\\`).`,\n });\n }\n if (typeof value === 'string') {\n let m: RegExpExecArray | null;\n VAR_RE.lastIndex = 0;\n while ((m = VAR_RE.exec(value))) {\n const token = m[1];\n if (!KNOWN_TOKENS.has(token) && !token.startsWith('tw-')) {\n findings.push({\n severity: 'warning', rule: STYLE_UNKNOWN_TOKEN, where, path: `${path}.responsiveStyles.${bp}.${prop}`,\n message: `References unknown design token \\`var(--${token})\\` (typo?); it will not resolve.`,\n hint: `Use a token from the ADR-0065 palette (e.g. \\`var(--space-6)\\`, \\`var(--surface)\\`, \\`hsl(var(--primary))\\`).`,\n });\n }\n }\n }\n }\n }\n }\n\n // Recurse.\n const kids = childrenOf(node);\n for (let i = 0; i < kids.length; i++) {\n checkNode(kids[i], pageName, `${path}.children[${i}]`, findings);\n }\n}\n\n/**\n * Validate every page's component tree for SDUI styling correctness (ADR-0065).\n * Returns findings (empty = clean). `error` findings describe styles that are\n * silently dropped and should fail validate/build; `warning` findings are\n * advisory (typos, drift, footguns).\n */\nexport function validateResponsiveStyles(stack: AnyRec): StyleFinding[] {\n const findings: StyleFinding[] = [];\n const pages = asArray(stack.pages);\n for (let p = 0; p < pages.length; p++) {\n const page = pages[p];\n const pageName = typeof page.name === 'string' ? page.name : `pages[${p}]`;\n const regions = asArray(page.regions);\n for (let r = 0; r < regions.length; r++) {\n const components = asArray(regions[r].components);\n for (let c = 0; c < components.length; c++) {\n checkNode(components[c], pageName, `pages[${p}].regions[${r}].components[${c}]`, findings);\n }\n }\n }\n return findings;\n}\n"],"mappings":"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","names":["asArray","asArray"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/lint",
3
- "version": "10.2.0",
3
+ "version": "11.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Static, build-time validation for an ObjectStack metadata graph — dashboard widget bindings, CEL/predicate expressions, and more. Pure (stack) => Issue[] functions shared by the CLI's `os validate` and any other consumer (e.g. AI authoring). Depends on @objectstack/spec; never on a runtime.",
6
6
  "type": "module",
@@ -14,8 +14,8 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@objectstack/spec": "10.2.0",
18
- "@objectstack/formula": "10.2.0"
17
+ "@objectstack/spec": "11.0.0",
18
+ "@objectstack/formula": "11.0.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^26.0.0",
package/LICENSE.apache DELETED
@@ -1,202 +0,0 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- http://www.apache.org/licenses/
4
-
5
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
-
7
- 1. Definitions.
8
-
9
- "License" shall mean the terms and conditions for use, reproduction,
10
- and distribution as defined by Sections 1 through 9 of this document.
11
-
12
- "Licensor" shall mean the copyright owner or entity authorized by
13
- the copyright owner that is granting the License.
14
-
15
- "Legal Entity" shall mean the union of the acting entity and all
16
- other entities that control, are controlled by, or are under common
17
- control with that entity. For the purposes of this definition,
18
- "control" means (i) the power, direct or indirect, to cause the
19
- direction or management of such entity, whether by contract or
20
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
- outstanding shares, or (iii) beneficial ownership of such entity.
22
-
23
- "You" (or "Your") shall mean an individual or Legal Entity
24
- exercising permissions granted by this License.
25
-
26
- "Source" form shall mean the preferred form for making modifications,
27
- including but not limited to software source code, documentation
28
- source, and configuration files.
29
-
30
- "Object" form shall mean any form resulting from mechanical
31
- transformation or translation of a Source form, including but
32
- not limited to compiled object code, generated documentation,
33
- and conversions to other media types.
34
-
35
- "Work" shall mean the work of authorship, whether in Source or
36
- Object form, made available under the License, as indicated by a
37
- copyright notice that is included in or attached to the work
38
- (an example is provided in the Appendix below).
39
-
40
- "Derivative Works" shall mean any work, whether in Source or Object
41
- form, that is based on (or derived from) the Work and for which the
42
- editorial revisions, annotations, elaborations, or other modifications
43
- represent, as a whole, an original work of authorship. For the purposes
44
- of this License, Derivative Works shall not include works that remain
45
- separable from, or merely link (or bind by name) to the interfaces of,
46
- the Work and Derivative Works thereof.
47
-
48
- "Contribution" shall mean any work of authorship, including
49
- the original version of the Work and any modifications or additions
50
- to that Work or Derivative Works thereof, that is intentionally
51
- submitted to Licensor for inclusion in the Work by the copyright owner
52
- or by an individual or Legal Entity authorized to submit on behalf of
53
- the copyright owner. For the purposes of this definition, "submitted"
54
- means any form of electronic, verbal, or written communication sent
55
- to the Licensor or its representatives, including but not limited to
56
- communication on electronic mailing lists, source code control systems,
57
- and issue tracking systems that are managed by, or on behalf of, the
58
- Licensor for the purpose of discussing and improving the Work, but
59
- excluding communication that is conspicuously marked or otherwise
60
- designated in writing by the copyright owner as "Not a Contribution."
61
-
62
- "Contributor" shall mean Licensor and any individual or Legal Entity
63
- on behalf of whom a Contribution has been received by Licensor and
64
- subsequently incorporated within the Work.
65
-
66
- 2. Grant of Copyright License. Subject to the terms and conditions of
67
- this License, each Contributor hereby grants to You a perpetual,
68
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
- copyright license to reproduce, prepare Derivative Works of,
70
- publicly display, publicly perform, sublicense, and distribute the
71
- Work and such Derivative Works in Source or Object form.
72
-
73
- 3. Grant of Patent License. Subject to the terms and conditions of
74
- this License, each Contributor hereby grants to You a perpetual,
75
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
- (except as stated in this section) patent license to make, have made,
77
- use, offer to sell, sell, import, and otherwise transfer the Work,
78
- where such license applies only to those patent claims licensable
79
- by such Contributor that are necessarily infringed by their
80
- Contribution(s) alone or by combination of their Contribution(s)
81
- with the Work to which such Contribution(s) was submitted. If You
82
- institute patent litigation against any entity (including a
83
- cross-claim or counterclaim in a lawsuit) alleging that the Work
84
- or a Contribution incorporated within the Work constitutes direct
85
- or contributory patent infringement, then any patent licenses
86
- granted to You under this License for that Work shall terminate
87
- as of the date such litigation is filed.
88
-
89
- 4. Redistribution. You may reproduce and distribute copies of the
90
- Work or Derivative Works thereof in any medium, with or without
91
- modifications, and in Source or Object form, provided that You
92
- meet the following conditions:
93
-
94
- (a) You must give any other recipients of the Work or
95
- Derivative Works a copy of this License; and
96
-
97
- (b) You must cause any modified files to carry prominent notices
98
- stating that You changed the files; and
99
-
100
- (c) You must retain, in the Source form of any Derivative Works
101
- that You distribute, all copyright, patent, trademark, and
102
- attribution notices from the Source form of the Work,
103
- excluding those notices that do not pertain to any part of
104
- the Derivative Works; and
105
-
106
- (d) If the Work includes a "NOTICE" text file as part of its
107
- distribution, then any Derivative Works that You distribute
108
- must include a readable copy of the attribution notices
109
- contained within such NOTICE file, excluding those notices
110
- that do not pertain to any part of the Derivative Works,
111
- in at least one of the following places: within a NOTICE
112
- text file distributed as part of the Derivative Works; within
113
- the Source form or documentation, if provided along with
114
- the Derivative Works; or, within a display generated by the
115
- Derivative Works, if and wherever such third-party notices
116
- normally appear. The contents of the NOTICE file are for
117
- informational purposes only and do not modify the License.
118
- You may add Your own attribution notices within Derivative
119
- Works that You distribute, alongside or as an addendum to
120
- the NOTICE text from the Work, provided that such additional
121
- attribution notices cannot be construed as modifying the
122
- License.
123
-
124
- You may add Your own copyright statement to Your modifications and
125
- may provide additional or different license terms and conditions
126
- for use, reproduction, or distribution of Your modifications, or
127
- for any such Derivative Works as a whole, provided Your use,
128
- reproduction, and distribution of the Work otherwise complies with
129
- the conditions stated in this License.
130
-
131
- 5. Submission of Contributions. Unless You explicitly state otherwise,
132
- any Contribution intentionally submitted for inclusion in the Work
133
- by You to the Licensor shall be under the terms and conditions of
134
- this License, without any additional terms or conditions.
135
- Notwithstanding the above, nothing herein shall supersede or modify
136
- the terms of any separate license agreement you may have executed
137
- with Licensor regarding such Contributions.
138
-
139
- 6. Trademarks. This License does not grant permission to use the trade
140
- names, trademarks, service marks, or product names of the Licensor,
141
- except as required for reasonable and customary use in describing the
142
- origin of the Work and reproducing the content of the NOTICE file.
143
-
144
- 7. Disclaimer of Warranty. Unless required by applicable law or
145
- agreed to in writing, Licensor provides the Work (and each
146
- Contributor provides its Contributions) on an "AS IS" BASIS,
147
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
- implied, including, without limitation, any warranties or conditions
149
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
- PARTICULAR PURPOSE. You are solely responsible for determining the
151
- appropriateness of using or redistributing the Work and assume any
152
- risks associated with Your exercise of permissions under this License.
153
-
154
- 8. Limitation of Liability. In no event and under no legal theory,
155
- whether in tort (including negligence), contract, or otherwise,
156
- unless required by applicable law (such as deliberate and grossly
157
- negligent acts) or agreed to in writing, shall any Contributor be
158
- liable to You for damages, including any direct, indirect, special,
159
- incidental, or consequential damages of any character arising as a
160
- result of this License or out of the use or inability to use the
161
- Work (including but not limited to damages for loss of goodwill,
162
- work stoppage, computer failure or malfunction, or any and all
163
- other commercial damages or losses), even if such Contributor
164
- has been advised of the possibility of such damages.
165
-
166
- 9. Accepting Warranty or Additional Liability. While redistributing
167
- the Work or Derivative Works thereof, You may choose to offer,
168
- and charge a fee for, acceptance of support, warranty, indemnity,
169
- or other liability obligations and/or rights consistent with this
170
- License. However, in accepting such obligations, You may act only
171
- on Your own behalf and on Your sole responsibility, not on behalf
172
- of any other Contributor, and only if You agree to indemnify,
173
- defend, and hold each Contributor harmless for any liability
174
- incurred by, or claims asserted against, such Contributor by reason
175
- of your accepting any such warranty or additional liability.
176
-
177
- END OF TERMS AND CONDITIONS
178
-
179
- APPENDIX: How to apply the Apache License to your work.
180
-
181
- To apply the Apache License to your work, attach the following
182
- boilerplate notice, with the fields enclosed by brackets "[]"
183
- replaced with your own identifying information. (Don't include
184
- the brackets!) The text should be enclosed in the appropriate
185
- comment syntax for the file format. We also recommend that a
186
- file or class name and description of purpose be included on the
187
- same "printed page" as the copyright notice for easier
188
- identification within third-party archives.
189
-
190
- Copyright 2026 ObjectStack
191
-
192
- Licensed under the Apache License, Version 2.0 (the "License");
193
- you may not use this file except in compliance with the License.
194
- You may obtain a copy of the License at
195
-
196
- http://www.apache.org/licenses/LICENSE-2.0
197
-
198
- Unless required by applicable law or agreed to in writing, software
199
- distributed under the License is distributed on an "AS IS" BASIS,
200
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
- See the License for the specific language governing permissions and
202
- limitations under the License.