@objectstack/lint 17.1.0 → 17.3.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/CHANGELOG.md +2523 -0
- package/dist/index.cjs +4857 -2572
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1592 -218
- package/dist/index.d.ts +1592 -218
- package/dist/index.js +8518 -6292
- package/dist/index.js.map +1 -1
- package/dist/{runtime-s3X9D9hm.d.cts → runtime-B8F5-Cbg.d.cts} +44 -1
- package/dist/{runtime-s3X9D9hm.d.ts → runtime-B8F5-Cbg.d.ts} +44 -1
- package/dist/runtime.cjs +3173 -1185
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +3154 -1161
- package/dist/runtime.js.map +1 -1
- package/package.json +26 -13
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
1
2
|
import { Manifest } from '@objectstack/sdui-parser';
|
|
2
3
|
import { SearchFieldMeta } from '@objectstack/spec/data';
|
|
3
4
|
import { Options } from 'ajv';
|
|
4
5
|
import { AccessMatrixParsed } from '@objectstack/spec/security';
|
|
5
|
-
export { A as AUTHORING_COMMANDS, a as AUTHORING_RULES, b as AUTHORING_SURFACES, c as AuthoringCommand, d as AuthoringFinding, e as AuthoringRule, f as AuthoringRuleContext, g as AuthoringRuleInputTier, h as AuthoringRuleRun, i as AuthoringRuleTier, j as AuthoringSeverity, k as AuthoringSurface, E as EXPRESSION_INVALID, R as RuntimeGateResult, l as RuntimePackageScope, m as RuntimeStackContext, n as authoringRulesFor, o as buildRuntimeWriteSnapshots, p as narrowObjectsToPackageClosure, r as runAuthoringRules, q as runRuntimeAuthoringRules, s as runtimeAuthoringRulesFor, t as runtimeGatedTypes, u as splitBySeverity, v as stackKeyForType } from './runtime-
|
|
6
|
+
export { A as AUTHORING_COMMANDS, a as AUTHORING_RULES, b as AUTHORING_SURFACES, c as AuthoringCommand, d as AuthoringFinding, e as AuthoringRule, f as AuthoringRuleContext, g as AuthoringRuleInputTier, h as AuthoringRuleRun, i as AuthoringRuleTier, j as AuthoringSeverity, k as AuthoringSurface, E as EXPRESSION_INVALID, R as RuntimeGateResult, l as RuntimePackageScope, m as RuntimeStackContext, n as authoringRulesFor, o as buildRuntimeWriteSnapshots, p as narrowObjectsToPackageClosure, r as runAuthoringRules, q as runRuntimeAuthoringRules, s as runtimeAuthoringRulesFor, t as runtimeGatedTypes, u as splitBySeverity, v as stackKeyForType } from './runtime-B8F5-Cbg.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Build-time dashboard widget binding diagnostics (issues #1719, #1721).
|
|
@@ -39,13 +40,26 @@ export { A as AUTHORING_COMMANDS, a as AUTHORING_RULES, b as AUTHORING_SURFACES,
|
|
|
39
40
|
* - `dashboard-filter-field-unknown` (#3365) — a dashboard-level filter
|
|
40
41
|
* (`dateRange` or a `globalFilters[]` entry) is wired into EVERY widget's
|
|
41
42
|
* analytics query (#2501), but its EFFECTIVE field (after any `filterBindings`
|
|
42
|
-
* re-target) does not
|
|
43
|
+
* re-target) does not resolve on a bound widget's dataset object. The widget's
|
|
43
44
|
* query then references a non-existent column and crashes at render time
|
|
44
45
|
* (`no such column …`) — a build-decidable invariant that previously escaped
|
|
45
46
|
* the static gate and failed only when a user opened the dashboard. A widget
|
|
46
47
|
* opts out with `filterBindings: { <name>: false }` or re-targets to a real
|
|
47
48
|
* field. This is the same field-existence invariant ADR-0032 enforces for
|
|
48
49
|
* CEL formula / sharing-rule references, applied to dashboard filter fields.
|
|
50
|
+
* Resolution is {@link resolveFieldPath}'s (#14275), so a DOTTED effective
|
|
51
|
+
* field (`account.signed_at`) is walked hop by hop rather than skipped, and a
|
|
52
|
+
* bare name is judged against the object's OWN injected columns rather than
|
|
53
|
+
* the object-independent `SYSTEM_FIELDS` union — see "What #14275 closed"
|
|
54
|
+
* below for why each half was a hole rather than a nicety.
|
|
55
|
+
* - `dashboard-filter-field-not-included` (#14275) — the effective field
|
|
56
|
+
* RESOLVES, but its relationship prefix is not declared in the bound
|
|
57
|
+
* dataset's `include`, so ADR-0021 compiles no join for it and the column is
|
|
58
|
+
* out of the broadcast query's reach. The `include` half of the same two
|
|
59
|
+
* clauses `widget-filter-field-not-included` applies to the widget's own
|
|
60
|
+
* `filter`, at the position one level up — a separate id because the fix is a
|
|
61
|
+
* different edit (declare the join, versus point the filter somewhere real)
|
|
62
|
+
* and because the family keeps one id per class.
|
|
49
63
|
*
|
|
50
64
|
* Advisory rules — severity `warning`, build stays green:
|
|
51
65
|
*
|
|
@@ -78,6 +92,91 @@ export { A as AUTHORING_COMMANDS, a as AUTHORING_RULES, b as AUTHORING_SURFACES,
|
|
|
78
92
|
* of the same invariant, warned rather than errored because this pass cannot
|
|
79
93
|
* see the remote schema (#8116's severity reasoning).
|
|
80
94
|
*
|
|
95
|
+
* ── The widget's OWN two references (#14148) ─────────────────────────────────
|
|
96
|
+
*
|
|
97
|
+
* - `widget-filter-field-unknown` — a KEY of the widget's own `filter` resolves
|
|
98
|
+
* to no column on the bound dataset's object graph.
|
|
99
|
+
* - `widget-filter-field-not-included` — that key RESOLVES, but its relationship
|
|
100
|
+
* prefix is not declared in the dataset's `include`, so ADR-0021 compiles no
|
|
101
|
+
* join for it and the column is out of the query's reach.
|
|
102
|
+
* - `widget-sortby-unselected` — `options.sortBy` names neither a `dimensions[]`
|
|
103
|
+
* nor a `values[]` entry of this widget, so the ordering the author wrote
|
|
104
|
+
* cannot be applied to a result that will not contain that column.
|
|
105
|
+
*
|
|
106
|
+
* All three are `error`, and the reason is the reporting card's, quoted because
|
|
107
|
+
* it is the sharpest statement of it in the family: the dashboard it was
|
|
108
|
+
* measured on leads with a "not moving" tile — open work untouched >14 days —
|
|
109
|
+
* and *"an empty tile is indistinguishable from a healthy team: a missing
|
|
110
|
+
* number reads as zero, and zero is the answer the manager is hoping for."*
|
|
111
|
+
* The failure is not merely silent, it is silent in the direction the reader
|
|
112
|
+
* WANTS to believe, which is why it gates rather than advises.
|
|
113
|
+
*
|
|
114
|
+
* ### Why these two were the surviving holes
|
|
115
|
+
*
|
|
116
|
+
* On the very same node, the TOKEN was checked and the COLUMN was not:
|
|
117
|
+
* `filter-token-unknown` (#3574) fires path-precise at
|
|
118
|
+
* `…widgets[4].filter.due_date.$lte`, so the traversal already walked the
|
|
119
|
+
* filter tree and already knew the widget's dataset. And the identical
|
|
120
|
+
* resolution already existed one key over — `dashboard-filter-field-unknown`
|
|
121
|
+
* resolves a DASHBOARD-level filter's field against each widget's dataset base
|
|
122
|
+
* object. `widgets[].filter` is the same field-existence invariant on the
|
|
123
|
+
* filter an author is MORE likely to write by hand, and it was simply never fed
|
|
124
|
+
* through it. `options.sortBy` is the declared-≠-enforced half:
|
|
125
|
+
* `DashboardWidgetOptionsSchema.sortBy` states its own contract in prose —
|
|
126
|
+
* *"must be one this widget actually selects"* — and nothing enforced it.
|
|
127
|
+
*
|
|
128
|
+
* ### The `include` clause, and why a dotted path is RESOLVED here
|
|
129
|
+
*
|
|
130
|
+
* A widget's `filter` is ANDed into the dataset query as `runtimeFilter`
|
|
131
|
+
* (`DashboardWidgetSchema.filter`; `dataset-executor.ts` `combineFilters(
|
|
132
|
+
* compiled.filter, selection.runtimeFilter)`), and that compiled query carries
|
|
133
|
+
* ONLY the joins the dataset's `include` declared (`dataset-compiler.ts`: joins
|
|
134
|
+
* are derived from `include`, and `assertDeclared` refuses an undeclared
|
|
135
|
+
* relationship path). So a dotted key here is judged on the same two clauses
|
|
136
|
+
* `validate-dataset-references.ts` applies one level down — existence, then
|
|
137
|
+
* joinability — rather than skipped. The runtime is NOT a backstop for it
|
|
138
|
+
* either — `assertDeclared` runs over `dimensions` and `measures` only, never
|
|
139
|
+
* over `runtimeFilter` — so nothing between the author and the empty tile asks
|
|
140
|
+
* this question.
|
|
141
|
+
*
|
|
142
|
+
* ### What #14275 closed, one position up
|
|
143
|
+
*
|
|
144
|
+
* `dashboard-filter-field-unknown` (a1) shipped with the two limitations the
|
|
145
|
+
* paragraph above used to record as deliberate, and #14275 closed both by
|
|
146
|
+
* migrating that branch onto this same seam:
|
|
147
|
+
*
|
|
148
|
+
* 1. it SKIPPED every dotted effective field (`if (field.includes('.'))
|
|
149
|
+
* continue;`), which was accurate when nothing in this package could walk
|
|
150
|
+
* hops and false once {@link resolveFieldPath} landed. The consequence was
|
|
151
|
+
* the sharper one: a dashboard filter is broadcast to EVERY widget on the
|
|
152
|
+
* board, so a single unjudged `filterBindings: { dateRange:
|
|
153
|
+
* 'account.signed_at' }` degraded the whole dashboard rather than one tile
|
|
154
|
+
* — unjudged whether or not `account` existed, whether or not `signed_at`
|
|
155
|
+
* existed on it, and whether or not `account` was in the dataset's
|
|
156
|
+
* `include`;
|
|
157
|
+
* 2. it resolved bare names against the object-independent `SYSTEM_FIELDS`
|
|
158
|
+
* union, which answers "could this be a system column ANYWHERE". The two
|
|
159
|
+
* differ exactly where it matters: on `ownership: 'none'` the platform
|
|
160
|
+
* injects no `owner_id`, so the union answered a real defect as
|
|
161
|
+
* resolvable. Both positions now take skip 3 per object, through
|
|
162
|
+
* {@link resolveFieldPath}'s use of `injectedColumnsFor`.
|
|
163
|
+
*
|
|
164
|
+
* The #8340 provenance question rides on the migrated branch unchanged, and is
|
|
165
|
+
* now GATED by the verdict's `injected` marker rather than by union membership:
|
|
166
|
+
* a leaf that resolved because it is injected is the only leaf whose anchor can
|
|
167
|
+
* be unprovisioned (an author-DECLARED column of the same name is the author's,
|
|
168
|
+
* #7859), and the marker states that fact where the flat union could not. It
|
|
169
|
+
* also travels: the anchor is looked up on the object the LEAF landed on, so a
|
|
170
|
+
* dotted filter path ending on an ADR-0015 `external` object is answered too.
|
|
171
|
+
*
|
|
172
|
+
* Resolution is {@link resolveFieldPath}'s and its `unknowable` verdicts are
|
|
173
|
+
* never reported (ADR-0072 D1), so the three skips every field-existence rule
|
|
174
|
+
* in this package takes apply unchanged: an object this stack does not define,
|
|
175
|
+
* an object with no readable field map (ADR-0015 `external`), and a
|
|
176
|
+
* registry-injected system column — the last resolved PER OBJECT rather than
|
|
177
|
+
* through the flat `SYSTEM_FIELDS` union, which is what lets a reference to
|
|
178
|
+
* `owner_id` on an `ownership: 'none'` object stay a real finding.
|
|
179
|
+
*
|
|
81
180
|
* Warnings can be deliberately suppressed per widget via
|
|
82
181
|
* `suppressWarnings: ['<rule-id>']`; errors cannot — they describe a
|
|
83
182
|
* binding the analytics service cannot satisfy.
|
|
@@ -93,6 +192,17 @@ declare const WIDGET_LEGACY_ANALYTICS_SHAPE = "widget-legacy-analytics-shape";
|
|
|
93
192
|
declare const WIDGET_LEGACY_ANALYTICS_UNRENDERABLE = "widget-legacy-analytics-unrenderable";
|
|
94
193
|
declare const DASHBOARD_FILTER_FIELD_UNKNOWN = "dashboard-filter-field-unknown";
|
|
95
194
|
declare const DASHBOARD_FILTER_FIELD_UNPROVISIONED = "dashboard-filter-field-unprovisioned";
|
|
195
|
+
/**
|
|
196
|
+
* [#14275] A dashboard filter's effective field resolves, but its relationship
|
|
197
|
+
* prefix is not declared in the bound dataset's `include`.
|
|
198
|
+
*/
|
|
199
|
+
declare const DASHBOARD_FILTER_FIELD_NOT_INCLUDED = "dashboard-filter-field-not-included";
|
|
200
|
+
/** [#14148] A key of the widget's OWN `filter` that resolves to no column. */
|
|
201
|
+
declare const WIDGET_FILTER_FIELD_UNKNOWN = "widget-filter-field-unknown";
|
|
202
|
+
/** [#14148] A widget filter key whose relationship prefix is not in `include`. */
|
|
203
|
+
declare const WIDGET_FILTER_FIELD_NOT_INCLUDED = "widget-filter-field-not-included";
|
|
204
|
+
/** [#14148] `options.sortBy` names nothing this widget selects. */
|
|
205
|
+
declare const WIDGET_SORTBY_UNSELECTED = "widget-sortby-unselected";
|
|
96
206
|
type WidgetBindingSeverity = 'error' | 'warning';
|
|
97
207
|
interface WidgetBindingFinding {
|
|
98
208
|
/** `error` = unresolvable binding (broken page); `warning` = advisory. */
|
|
@@ -108,7 +218,7 @@ interface WidgetBindingFinding {
|
|
|
108
218
|
/** How to fix (or deliberately suppress) it. */
|
|
109
219
|
hint: string;
|
|
110
220
|
}
|
|
111
|
-
type AnyRec$
|
|
221
|
+
type AnyRec$L = Record<string, unknown>;
|
|
112
222
|
/**
|
|
113
223
|
* Validate every dashboard widget's dataset binding. Returns the list of
|
|
114
224
|
* findings (empty = clean). Caller decides how to surface them: `error`
|
|
@@ -116,7 +226,7 @@ type AnyRec$G = Record<string, unknown>;
|
|
|
116
226
|
* should fail validate/build; `warning` findings are advisory and must
|
|
117
227
|
* never fail the build on their own.
|
|
118
228
|
*/
|
|
119
|
-
declare function validateWidgetBindings(stack: AnyRec$
|
|
229
|
+
declare function validateWidgetBindings(stack: AnyRec$L): WidgetBindingFinding[];
|
|
120
230
|
|
|
121
231
|
interface ExprIssue {
|
|
122
232
|
where: string;
|
|
@@ -129,12 +239,293 @@ interface ExprIssue {
|
|
|
129
239
|
*/
|
|
130
240
|
severity?: 'error' | 'warning';
|
|
131
241
|
}
|
|
132
|
-
type AnyRec$
|
|
242
|
+
type AnyRec$K = Record<string, unknown>;
|
|
243
|
+
/**
|
|
244
|
+
* A FIELD-level conditional rule (`visibleWhen` / `readonlyWhen` /
|
|
245
|
+
* `requiredWhen`) that reaches for a namespace root the field level does not
|
|
246
|
+
* bind. The field level binds THREE roots and nothing else — `record`,
|
|
247
|
+
* `previous`, and `parent` on a master-detail line item — measured at three
|
|
248
|
+
* independent ends: the server (`rule-validator.ts` binds
|
|
249
|
+
* `{ record, previous, extra: { parent } }` for `readonlyWhen` and
|
|
250
|
+
* `{ record, previous, ...parentScope }` for `requiredWhen`), the client
|
|
251
|
+
* (`evalFieldPredicate` binds `record` + `previous` + a caller `scope` that
|
|
252
|
+
* is only ever `{ parent }` across objectui's five field-level call sites),
|
|
253
|
+
* and the authoring surface (objectui's `FIELD_RULE_ROOTS`, whose comment
|
|
254
|
+
* says "nothing else").
|
|
255
|
+
*
|
|
256
|
+
* ## Why this is a rule of its own rather than a missing root
|
|
257
|
+
*
|
|
258
|
+
* Until #6290 the same rejection fell out of `@objectstack/formula`'s
|
|
259
|
+
* `SCOPE_ROOTS` not listing `current_user` — a global baseline, doing a
|
|
260
|
+
* per-surface job by accident. Two things were wrong with that:
|
|
261
|
+
*
|
|
262
|
+
* 1. The rest of the package said the opposite. `introspectScope` hands
|
|
263
|
+
* `current_user` to authors as a legal root and `checkRoleCatalog`'s four
|
|
264
|
+
* position-membership regexes all lead with it, because ADR-0068 D1 makes
|
|
265
|
+
* it THE canonical spelling and `buildScope` really does mount it. One
|
|
266
|
+
* package, two accounts of one root.
|
|
267
|
+
* 2. The diagnostic was the generic bare-field one, so it prescribed
|
|
268
|
+
* "Write `record.current_user`" — a shape that binds on NO layer. An
|
|
269
|
+
* author who followed it wrote something strictly worse than what they
|
|
270
|
+
* started with, and the failure stayed silent.
|
|
271
|
+
*
|
|
272
|
+
* So the baseline now declares the root (a reference through it never faults
|
|
273
|
+
* platform-wide) and the surface that genuinely cannot bind it says so here,
|
|
274
|
+
* in its own words, with the prescriptions that actually exist.
|
|
275
|
+
*
|
|
276
|
+
* ## Why the membership test is an ALLOWLIST (#6713)
|
|
277
|
+
*
|
|
278
|
+
* Until #6713 this rule matched a hand-written DENYLIST — one root in #6584,
|
|
279
|
+
* three after #6585 (`current_user` / `user` / `ctx`). The denylist was never
|
|
280
|
+
* the truth: the three anchors above pin a three-item ALLOWLIST, and
|
|
281
|
+
* everything outside it is equally unbound, faults identically, and — this is
|
|
282
|
+
* the part that made the hole invisible — is equally SILENT. A root in
|
|
283
|
+
* `SCOPE_ROOTS` resolves in the strict env, so the bare-reference check one
|
|
284
|
+
* line up never fires on it either. #6713 measured 21 roots living in that
|
|
285
|
+
* gap (`input`, `output`, `os`, `vars`, `variables`, `automation`, `context`,
|
|
286
|
+
* `args`, `item`, `env`, `step`, `result`, `trigger`, `event`, `payload`,
|
|
287
|
+
* `data`, `params`, `config`, `settings`, `features`, `current`), two of them
|
|
288
|
+
* highly credible author typos rather than theoretical members:
|
|
289
|
+
*
|
|
290
|
+
* - `os.user.id` — ADR-0068 D1's FOURTH user spelling. #6585 took three and
|
|
291
|
+
* left this one, so the same semantic error stayed silent under one of the
|
|
292
|
+
* four names the platform itself mounts the user object under.
|
|
293
|
+
* - `data.status == 'x'` — `data` is the LEGAL root of this very
|
|
294
|
+
* `visibleWhen` key on a METADATA form (`view.zod.ts`: "Root: `record` …
|
|
295
|
+
* in runtime forms, or `data` in metadata forms"). Two form kinds, one key
|
|
296
|
+
* name, different roots — and the repo's own `*.form.ts` files are full of
|
|
297
|
+
* the `data` spelling for an author to copy.
|
|
298
|
+
*
|
|
299
|
+
* A denylist cannot track `SCOPE_ROOTS`: every root added there is unreported
|
|
300
|
+
* here until somebody remembers to copy it across (`current_user` itself
|
|
301
|
+
* arrived in #6290 and needed #6584 to be noticed). The allowlist inverts the
|
|
302
|
+
* maintenance burden onto the three roots that are pinned by three anchors and
|
|
303
|
+
* change only when the evaluators do.
|
|
304
|
+
*
|
|
305
|
+
* The membership test is {@link FIELD_RULE_JUDGED_ROOTS} minus the allowlist.
|
|
306
|
+
* Its bulk is `SCOPE_ROOTS`, taken from `@objectstack/formula` rather than
|
|
307
|
+
* restated here (#6713 published it for this consumer) — one list, one
|
|
308
|
+
* definition, no drift — plus the ambient roots #13935 measured outside that
|
|
309
|
+
* baseline; see "Why the judged vocabulary is WIDER than `SCOPE_ROOTS`" below.
|
|
310
|
+
* It deliberately is NOT `firstUndeclaredReference`, the declaredness oracle
|
|
311
|
+
* the sibling visibility rule uses, and the difference is a measured false
|
|
312
|
+
* positive rather than a preference: the strict env also declares CEL's TYPE
|
|
313
|
+
* names, so `type(record.x) == string` reports `string` as a root that
|
|
314
|
+
* "resolves". Judging by declaredness would reject that legitimate predicate;
|
|
315
|
+
* judging by membership does not. Everything the oracle owns and this list
|
|
316
|
+
* does not — bare field references, comprehension-macro variables — keeps
|
|
317
|
+
* falling to the bare-reference check, which has the right prescription for
|
|
318
|
+
* it. The two partitions are disjoint and there is no gap between them.
|
|
319
|
+
*
|
|
320
|
+
* ⚠️ That disjointness used to hold for FREE and no longer does. While every
|
|
321
|
+
* judged root was a `SCOPE_ROOTS` member it was declared in the strict env, so
|
|
322
|
+
* the bare-reference check could not fire on it whatever this rule decided.
|
|
323
|
+
* An AMBIENT root is undeclared there, so both checks see it — the walk
|
|
324
|
+
* suppresses the bare-reference verdict for a root this rule has claimed
|
|
325
|
+
* (`claimedRoot` on the `check` closure) to keep the invariant true by
|
|
326
|
+
* construction instead of by coincidence.
|
|
327
|
+
*
|
|
328
|
+
* ## Why it is an error and not a warning
|
|
329
|
+
*
|
|
330
|
+
* Every fault direction available here is silent, and two of the three are
|
|
331
|
+
* the opposite of what the author declared (see the per-slot table below):
|
|
332
|
+
* a `visibleWhen` written to HIDE leaves the field visible to everyone, a
|
|
333
|
+
* `readonlyWhen` written to unlock-under-a-condition locks the field on every
|
|
334
|
+
* write, and a `requiredWhen` simply never fires. None of the three produces
|
|
335
|
+
* a runtime error an author can find; the only signal that exists is this one
|
|
336
|
+
* (#6146).
|
|
337
|
+
*
|
|
338
|
+
* Verdict scope is the field level only. Per-option `visibleWhen` is checked
|
|
339
|
+
* by the loop in the field walk and deliberately NOT passed through here:
|
|
340
|
+
* options resolve against the host's predicate scope, which binds
|
|
341
|
+
* `current_user` (ADR-0068 / objectui#2284) — that surface is where such a
|
|
342
|
+
* predicate belongs, which is why it is also the first prescription below.
|
|
343
|
+
*
|
|
344
|
+
* ## The user roots, and why `ctx` is judged whole-root (#6585)
|
|
345
|
+
*
|
|
346
|
+
* ADR-0068 D1 makes `user` and `ctx.user` ALIASES of `current_user` — one
|
|
347
|
+
* `EvalUser` object under every spelling (`buildScope` in
|
|
348
|
+
* `formula/stdlib.ts` hangs the same reference on `current_user` / `user` /
|
|
349
|
+
* `ctx.user` / `os.user`). Matching only the canonical spelling meant the
|
|
350
|
+
* identical semantic error got a diagnostic under `current_user` and total
|
|
351
|
+
* silence under the aliases — which spelling the author picked decided
|
|
352
|
+
* whether they got the diagnostic, the exact fork AI authors cannot
|
|
353
|
+
* self-check. All four roots now share one verdict and one prescription;
|
|
354
|
+
* the message names the spelling found, nothing else varies. (`os` joined the
|
|
355
|
+
* set in #6713 — it is the fourth name `buildScope` mounts the same object
|
|
356
|
+
* under, and the allowlist would have rejected it anyway; what the user tier
|
|
357
|
+
* decides is only WHICH prescription it gets, and the user one is right for
|
|
358
|
+
* `os.user`. `os.org` / `os.env` land in the same tier because the option
|
|
359
|
+
* surface named by the first prescription binds the whole `os` namespace, not
|
|
360
|
+
* only its `user` member.)
|
|
361
|
+
*
|
|
362
|
+
* `ctx` is judged as a WHOLE root, not only in `ctx.user` form, because at
|
|
363
|
+
* this surface that is simply what is true: `buildScope` creates the `ctx`
|
|
364
|
+
* root ONLY when the evaluation carries a user (`scope.ctx = { user }`
|
|
365
|
+
* inside `if (ctx.user !== undefined)`), and no field-level site passes one
|
|
366
|
+
* — the server binds `record`+`previous`(+`parent`) (`rule-validator.ts`
|
|
367
|
+
* `readonlyWhenBindings` / the `requiredWhen` block) and the client's
|
|
368
|
+
* `evalFieldPredicate` binds `record`+`previous`+caller `scope` (only ever
|
|
369
|
+
* `{ parent }`). So `ctx.locale` faults exactly like `ctx.user.id` here.
|
|
370
|
+
* `ctx` IS ActionEngine's predicate root elsewhere — the platform's real
|
|
371
|
+
* `ctx.user` predicates all sit on action `visible` (`sys-user.object.ts`,
|
|
372
|
+
* `sys-invitation.object.ts`), a surface this helper never reads — and
|
|
373
|
+
* measured usage of field-level `*When` with ANY user root is zero across
|
|
374
|
+
* examples/, packages/ and objectui (#6585's sweep).
|
|
375
|
+
*
|
|
376
|
+
* The rejected alternative was matching `ctx` only in its `ctx.user` form.
|
|
377
|
+
* `collectCelRootIdentifiers` reports ROOTS and drops member names by
|
|
378
|
+
* design, so that reading needs a source-level spelling match — and a
|
|
379
|
+
* spelling match is precisely the defect this rule exists to remove: it
|
|
380
|
+
* would re-open the same fork one level down (`ctx["user"].id` silent,
|
|
381
|
+
* `ctx.user.id` rejected), while leaving a real fail-open fault (`ctx.locale`)
|
|
382
|
+
* unreported for the sake of a narrower rule NAME.
|
|
383
|
+
*
|
|
384
|
+
* ## The message tiers on TWO orthogonal axes
|
|
385
|
+
*
|
|
386
|
+
* The causal half tiers by SLOT (#6716); the prescription half tiers by ROOT
|
|
387
|
+
* (#6713). They are independent — `data` on a `readonlyWhen` needs the
|
|
388
|
+
* metadata-form prescription AND the locked-field consequence — so one
|
|
389
|
+
* skeleton carries both rather than a message per combination.
|
|
390
|
+
*
|
|
391
|
+
* ### Axis 1 — the consequence, by SLOT (#6716, all three cells measured)
|
|
392
|
+
*
|
|
393
|
+
* Until #6713/#6716 all three slots shared ONE sentence: "the predicate
|
|
394
|
+
* faults and falls back to VISIBLE, leaving the field the test was meant to
|
|
395
|
+
* hide showing for everyone". That is precise for exactly one of them. The
|
|
396
|
+
* measurement, at both ends of each slot:
|
|
397
|
+
*
|
|
398
|
+
* - **`visibleWhen` — client-only, and since objectui#6010 it fails in TWO
|
|
399
|
+
* ways rather than one.** The server half is unchanged and still exactly
|
|
400
|
+
* right: it never evaluates a FIELD-level `visibleWhen` at all —
|
|
401
|
+
* `rule-validator.ts`'s `ConditionalFieldDef` has no such member, and
|
|
402
|
+
* `hasFieldRules` gates on `requiredWhen || readonlyWhen ||
|
|
403
|
+
* fieldHasOptionVisibility` — the `visibleWhen` it does evaluate is the
|
|
404
|
+
* per-OPTION one. The RENDERER half moved. `plugin-form`'s
|
|
405
|
+
* `sectionFields.ts` (`fromObjectSchema`) copies an object field's ADR-0036
|
|
406
|
+
* `visibleWhen` / `readonlyWhen` / `requiredWhen` straight onto the runtime
|
|
407
|
+
* form field, and the SDUI form renderer resolves those through
|
|
408
|
+
* `resolveFieldRuleState` **with `predicateScope` bound**. So under a host
|
|
409
|
+
* that publishes a scope the predicate does not fault: it RESOLVES, the
|
|
410
|
+
* control is hidden in that one form, and the server still returns the value
|
|
411
|
+
* to every other reader — a silent enforcement gap. Only where no host
|
|
412
|
+
* publishes a scope (the public `/f/:slug` route, every non-form reader)
|
|
413
|
+
* does the old sentence still describe what happens: unbound root, fault,
|
|
414
|
+
* `fallback: true`, field visible to everyone.
|
|
415
|
+
*
|
|
416
|
+
* ⛔ This is not a reason to relax {@link FIELD_RULE_BOUND_ROOTS}. The
|
|
417
|
+
* verdict is MORE justified than when it was written, not less: trading a
|
|
418
|
+
* loud lint error for a gap nobody can see is the one outcome worse than
|
|
419
|
+
* the error. Only the causal clause was re-measured.
|
|
420
|
+
* - **`readonlyWhen` — the two ends fault in OPPOSITE directions, and the
|
|
421
|
+
* server wins.** Server: `isReadonlyWhenLocked` matches the fault with
|
|
422
|
+
* `unknownVariableOf` and returns `true` — "the declared lock is not
|
|
423
|
+
* waived because it could not be evaluated" (#4889's carve-out, whose
|
|
424
|
+
* trigger is precisely the unbound-ROOT case, not the undeclared-key one)
|
|
425
|
+
* — and `stripReadonlyWhenFields` then DELETES the field from the incoming
|
|
426
|
+
* payload and lets the rest of the write through. Client:
|
|
427
|
+
* `resolveFieldRuleState` passes `fallback: false`, so the form renders the
|
|
428
|
+
* field editable. The standing "server enforces, client is courtesy" rule
|
|
429
|
+
* — ADR-0124 D1 — resolves the disagreement: the author edits
|
|
430
|
+
* the field, the save reports
|
|
431
|
+
* success, and the value silently never lands. The old sentence told this
|
|
432
|
+
* author the field would be VISIBLE TO EVERYONE — the opposite failure, and
|
|
433
|
+
* the opposite troubleshooting direction.
|
|
434
|
+
* - **`requiredWhen` — fail-OPEN at both ends, and never about visibility.**
|
|
435
|
+
* Server: the `requiredWhen` block logs `unknownVariableOf`'s name and
|
|
436
|
+
* `continue`s — #4977 deliberately did not copy #4889's carve-out, so the
|
|
437
|
+
* required-check is skipped for that write. Client: `fallback: false`, so
|
|
438
|
+
* the form does not mark the field required either. Both ends agree and
|
|
439
|
+
* both do nothing: the requirement is never enforced anywhere, and a record
|
|
440
|
+
* saves with the field empty. "Falls back to VISIBLE" was not merely
|
|
441
|
+
* imprecise here, it named the wrong property of the field.
|
|
442
|
+
*
|
|
443
|
+
* `conditionalRequired` also reaches this helper (the field walk still passes
|
|
444
|
+
* it). It is a `retiredKey` in `FieldSchema` — the strict schema rejects it by
|
|
445
|
+
* name — so the branch is inert on the parsed compile path, and it gets a
|
|
446
|
+
* slot-agnostic clause rather than a fabricated fourth measurement.
|
|
447
|
+
*
|
|
448
|
+
* ### Axis 2 — the prescription, by ROOT (#6713)
|
|
449
|
+
*
|
|
450
|
+
* The pre-#6713 prescriptions are user-oriented (move to the option level,
|
|
451
|
+
* declare permission-set FLS) because the pre-#6713 denylist held only user
|
|
452
|
+
* roots. Handed to an author who wrote `data.type == 'select'`, "move it to
|
|
453
|
+
* the option's `visibleWhen`" answers a question nobody asked. Three tiers:
|
|
454
|
+
*
|
|
455
|
+
* - **user roots** (`current_user` / `user` / `ctx` / `os`) keep the existing
|
|
456
|
+
* two user-specific prescriptions plus the `record` rewrite. What changed is
|
|
457
|
+
* the GROUNDING of the first one, not the prescription: it used to read
|
|
458
|
+
* "per-option is the one `*When` surface that binds `current_user`", and
|
|
459
|
+
* since objectui#6010 a form VIEW's field predicate binds those roots too —
|
|
460
|
+
* client-side only, because nothing on the write path evaluates a form-view
|
|
461
|
+
* field `visibleWhen`. So per-option is recommended for the reason that
|
|
462
|
+
* survives the change: the rule validator enforces it on write. The message
|
|
463
|
+
* now says that, and names the view surface only to refuse it as a
|
|
464
|
+
* destination for a server-enforced object rule (trading a loud lint error
|
|
465
|
+
* for a silent enforcement gap is the one outcome worse than the error);
|
|
466
|
+
* - **`data`** gets the metadata-form-vs-runtime-form explanation, because
|
|
467
|
+
* that is what the mistake IS — the same key name, the other form kind's
|
|
468
|
+
* root;
|
|
469
|
+
* - **`app` and the other AMBIENT roots** get the renderer-mounted
|
|
470
|
+
* explanation and an explicit refusal of `record.<root>` (#13935, below);
|
|
471
|
+
* - **everything else** gets the general rewrite, phrased without claiming
|
|
472
|
+
* which other surface the author copied it from.
|
|
473
|
+
*
|
|
474
|
+
* ## Why the judged vocabulary is WIDER than `SCOPE_ROOTS` (#13935)
|
|
475
|
+
*
|
|
476
|
+
* `SCOPE_ROOTS` was the membership test until #13935, and it is the wrong
|
|
477
|
+
* question by one word: it answers "is this root declared PLATFORM-WIDE",
|
|
478
|
+
* while the rule needs "is this root bound at SOME evaluation site". The two
|
|
479
|
+
* agreed for 27 roots and then disagreed for `app` — bound by objectui's
|
|
480
|
+
* `ExpressionProvider` on the very surface an author migrates a rule DOWN
|
|
481
|
+
* from, absent from the baseline. Falling outside the membership test sent it
|
|
482
|
+
* to the bare-reference check, whose prescription is "Write `record.app`" —
|
|
483
|
+
* and following that earns ``unknown field `app` `` from the field-existence
|
|
484
|
+
* pass one line up. A first diagnostic that is actively false about where the
|
|
485
|
+
* root binds, and a wasted correction cycle.
|
|
486
|
+
*
|
|
487
|
+
* `SCOPE_ROOTS`' own docblock made this measurable rather than a matter of
|
|
488
|
+
* taste: its `current_user` entry claims to be "the last one this list was
|
|
489
|
+
* missing (#6290)". `app` is that sentence's second counterexample — the same
|
|
490
|
+
* mechanism (#6713's point: a hand-maintained list doing a per-surface job
|
|
491
|
+
* drifts), a second sighting, not an analogy to the first.
|
|
492
|
+
*
|
|
493
|
+
* ⛔ The repair deliberately does NOT add `app` to `SCOPE_ROOTS`. That list is
|
|
494
|
+
* the published strict-lint accept baseline in `@objectstack/formula`, so
|
|
495
|
+
* adding a root there stops EVERY surface that judges bare identifiers from
|
|
496
|
+
* faulting it — a widened public accept set, to fix one surface's diagnostic.
|
|
497
|
+
* The judged vocabulary is assembled HERE, where the per-surface question is
|
|
498
|
+
* asked, and `SCOPE_ROOTS` is left as the proper subset it already is.
|
|
499
|
+
*/
|
|
500
|
+
/**
|
|
501
|
+
* The roots a field-level `*When` predicate binds. Everything else in
|
|
502
|
+
* {@link FIELD_RULE_JUDGED_ROOTS} is rejected — see the allowlist section above.
|
|
503
|
+
*/
|
|
504
|
+
declare const FIELD_RULE_BOUND_ROOTS: readonly ["record", "previous", "parent"];
|
|
505
|
+
/**
|
|
506
|
+
* The decision half of the rule documented above, as ONE exported function so
|
|
507
|
+
* every surface that judges a field-level `*When` stands on the same verdict
|
|
508
|
+
* AND the same message (Prime Directive #12). Two consumers today: the
|
|
509
|
+
* metadata walk in {@link validateStackExpressions} below, and the docs-corpus
|
|
510
|
+
* gate `scripts/check-doc-formula-expressions.mjs`, which judges the same three
|
|
511
|
+
* slots where a fenced example's enclosing structure identifies the field layer
|
|
512
|
+
* (#11407). Before that gate existed this lived as a closure inside the walk —
|
|
513
|
+
* fine while there was one caller, and exactly how a second caller comes to own
|
|
514
|
+
* a DIALECT of the rule instead of the rule.
|
|
515
|
+
*
|
|
516
|
+
* `null` = nothing to report: the source does not parse (the syntax pass owns
|
|
517
|
+
* that, and reporting it twice under a worse name is the #4889 mistake), or
|
|
518
|
+
* every root it reads is one the field level binds.
|
|
519
|
+
*/
|
|
520
|
+
declare function fieldRuleRootIssue(slot: string, source: string): {
|
|
521
|
+
root: string;
|
|
522
|
+
message: string;
|
|
523
|
+
} | null;
|
|
133
524
|
/**
|
|
134
525
|
* Validate every predicate in the stack. Returns the list of issues (empty =
|
|
135
526
|
* clean). Caller decides how to surface / whether to fail the build.
|
|
136
527
|
*/
|
|
137
|
-
declare function validateStackExpressions(stack: AnyRec$
|
|
528
|
+
declare function validateStackExpressions(stack: AnyRec$K): ExprIssue[];
|
|
138
529
|
|
|
139
530
|
/**
|
|
140
531
|
* The corrective sentence, lifted **verbatim** from `unevaluableRuleError` in
|
|
@@ -225,6 +616,16 @@ declare const STARTUP_OPEN_VOCABULARY_VERDICT = "startup-open-vocabulary-verdict
|
|
|
225
616
|
* {@link STARTUP_OPEN_VOCABULARY_VERDICT} already fired.
|
|
226
617
|
*/
|
|
227
618
|
declare const STARTUP_VERDICT_ASSERTIVE_WORDING = "startup-verdict-assertive-wording";
|
|
619
|
+
/**
|
|
620
|
+
* The source could not be parsed, so this rule's verdict about it covers only
|
|
621
|
+
* what error recovery left standing (#10653).
|
|
622
|
+
*
|
|
623
|
+
* This rule's own subject matter, turned on the rule: a terminal conclusion
|
|
624
|
+
* drawn about a world that had not finished forming. "No findings" about a
|
|
625
|
+
* source the parser could not read is exactly that, and it is what this module
|
|
626
|
+
* used to return — silently, with a `catch` above it that never ran.
|
|
627
|
+
*/
|
|
628
|
+
declare const STARTUP_SOURCE_UNPARSEABLE = "startup-source-unparseable";
|
|
228
629
|
/**
|
|
229
630
|
* Accessors that read a capability vocabulary a plugin can still extend.
|
|
230
631
|
*
|
|
@@ -298,8 +699,10 @@ interface StartupRegistryVerdictOptions {
|
|
|
298
699
|
* Find startup open-vocabulary verdicts in one TypeScript/JavaScript source.
|
|
299
700
|
*
|
|
300
701
|
* Pure: parses, never executes, never type-checks, touches no filesystem. An
|
|
301
|
-
* unparseable source
|
|
302
|
-
* source someone else owns,
|
|
702
|
+
* unparseable source is REPORTED ({@link STARTUP_SOURCE_UNPARSEABLE}) rather
|
|
703
|
+
* than thrown on — this advises on source someone else owns, so refusing to
|
|
704
|
+
* parse is not a verdict about them, but going silent about it was a verdict
|
|
705
|
+
* too, and the wrong one (#10653).
|
|
303
706
|
*/
|
|
304
707
|
declare function findStartupRegistryVerdicts(source: string, options?: StartupRegistryVerdictOptions): StartupRegistryVerdictFinding[];
|
|
305
708
|
|
|
@@ -315,7 +718,7 @@ interface ListViewModeFinding {
|
|
|
315
718
|
hint: string;
|
|
316
719
|
}
|
|
317
720
|
declare const LIST_VIEW_FILTERS_IN_VIEWS_MODE = "list-view-filters-in-views-mode";
|
|
318
|
-
type AnyRec$
|
|
721
|
+
type AnyRec$J = Record<string, unknown>;
|
|
319
722
|
/**
|
|
320
723
|
* Flag ADR-0047 "views" mode violations on an object's built-in named views or a
|
|
321
724
|
* `defineView` default `list` / named `listViews`: `quickFilters`, or a `tabs`
|
|
@@ -325,7 +728,7 @@ type AnyRec$E = Record<string, unknown>;
|
|
|
325
728
|
*
|
|
326
729
|
* Feed the PRE-parse stack (normalizeStackInput output) — see file header.
|
|
327
730
|
*/
|
|
328
|
-
declare function validateListViewMode(stack: AnyRec$
|
|
731
|
+
declare function validateListViewMode(stack: AnyRec$J): ListViewModeFinding[];
|
|
329
732
|
|
|
330
733
|
type FunctionalCompletenessSeverity = 'error' | 'warning';
|
|
331
734
|
interface FunctionalCompletenessFinding {
|
|
@@ -440,7 +843,7 @@ declare const FLOW_TIME_RELATIVE_DESCRIPTOR_UNROUTABLE = "flow-time-relative-des
|
|
|
440
843
|
* call sites that each skip it.
|
|
441
844
|
*/
|
|
442
845
|
declare const FLOW_TRIGGER_UNROUTABLE = "flow-trigger-unroutable";
|
|
443
|
-
type AnyRec$
|
|
846
|
+
type AnyRec$I = Record<string, unknown>;
|
|
444
847
|
/**
|
|
445
848
|
* Validate auto-launched flow trigger wiring against the stack definition.
|
|
446
849
|
*
|
|
@@ -449,7 +852,7 @@ type AnyRec$D = Record<string, unknown>;
|
|
|
449
852
|
* owns the `timeRelative` descriptor's contract, which is the point: the rule
|
|
450
853
|
* ASKS that schema rather than restating it.
|
|
451
854
|
*/
|
|
452
|
-
declare function validateFlowTriggerReadiness(stack: AnyRec$
|
|
855
|
+
declare function validateFlowTriggerReadiness(stack: AnyRec$I): FlowTriggerReadinessFinding[];
|
|
453
856
|
|
|
454
857
|
type FlowTemplatePathSeverity = 'error' | 'warning';
|
|
455
858
|
interface FlowTemplatePathFinding {
|
|
@@ -465,12 +868,12 @@ interface FlowTemplatePathFinding {
|
|
|
465
868
|
declare const FLOW_TEMPLATE_UNKNOWN_FIELD = "flow-template-unknown-field";
|
|
466
869
|
declare const FLOW_TEMPLATE_LOOKUP_TRAVERSAL = "flow-template-lookup-traversal";
|
|
467
870
|
declare const FLOW_TEMPLATE_FIELD_UNPROVISIONED = "flow-template-field-unprovisioned";
|
|
468
|
-
type AnyRec$
|
|
871
|
+
type AnyRec$H = Record<string, unknown>;
|
|
469
872
|
/**
|
|
470
873
|
* Validate `{record.<path>}` template references across every record-change
|
|
471
874
|
* flow. Pure and dependency-free; safe on pre- or post-parse stacks.
|
|
472
875
|
*/
|
|
473
|
-
declare function validateFlowTemplatePaths(stack: AnyRec$
|
|
876
|
+
declare function validateFlowTemplatePaths(stack: AnyRec$H): FlowTemplatePathFinding[];
|
|
474
877
|
|
|
475
878
|
type ReadonlyFlowWriteSeverity = 'error' | 'warning';
|
|
476
879
|
interface ReadonlyFlowWriteFinding {
|
|
@@ -485,12 +888,275 @@ interface ReadonlyFlowWriteFinding {
|
|
|
485
888
|
}
|
|
486
889
|
declare const FLOW_UPDATE_READONLY_FIELD = "flow-update-readonly-field";
|
|
487
890
|
declare const FLOW_UPDATE_READONLY_WHEN_FIELD = "flow-update-readonly-when-field";
|
|
488
|
-
type AnyRec$
|
|
891
|
+
type AnyRec$G = Record<string, unknown>;
|
|
489
892
|
/**
|
|
490
893
|
* Validate flow `update_record` writes against target-object readonly
|
|
491
894
|
* declarations. Pure and dependency-free; safe on pre- or post-parse stacks.
|
|
492
895
|
*/
|
|
493
|
-
declare function validateReadonlyFlowWrites(stack: AnyRec$
|
|
896
|
+
declare function validateReadonlyFlowWrites(stack: AnyRec$G): ReadonlyFlowWriteFinding[];
|
|
897
|
+
|
|
898
|
+
/** What a caller needs to tell an author WHICH source went unread, and where. */
|
|
899
|
+
interface SourceParseFailure {
|
|
900
|
+
/** The first parse diagnostic, in the compiler's own wording, flattened to one line. */
|
|
901
|
+
message: string;
|
|
902
|
+
/** 1-based line, in the AUTHORED source's coordinates (see `synthesizedLinesBefore`). */
|
|
903
|
+
line: number;
|
|
904
|
+
/** 1-based column. */
|
|
905
|
+
column: number;
|
|
906
|
+
/** How many parse diagnostics in total — `message` is the first of `count`. */
|
|
907
|
+
count: number;
|
|
908
|
+
}
|
|
909
|
+
/** A parse plus the verdict on whether it succeeded. `failure` absent ⇒ it parsed. */
|
|
910
|
+
interface CheckedParse {
|
|
911
|
+
/**
|
|
912
|
+
* The tree, ALWAYS returned — including when `failure` is set. Error recovery
|
|
913
|
+
* produces a partial tree, and a caller that already reports findings from it
|
|
914
|
+
* keeps doing so: the fix here is the missing SIGNAL, not the removal of
|
|
915
|
+
* whatever the recovered tree could still be read for.
|
|
916
|
+
*/
|
|
917
|
+
sourceFile: ts.SourceFile;
|
|
918
|
+
/** Set when the parser reported at least one syntax diagnostic. */
|
|
919
|
+
failure?: SourceParseFailure;
|
|
920
|
+
}
|
|
921
|
+
interface CheckedParseOptions {
|
|
922
|
+
target: ts.ScriptTarget;
|
|
923
|
+
setParentNodes: boolean;
|
|
924
|
+
scriptKind: ts.ScriptKind;
|
|
925
|
+
/**
|
|
926
|
+
* Lines the CALLER synthesised ahead of the authored source, subtracted from
|
|
927
|
+
* the reported position so it lands in the author's coordinates.
|
|
928
|
+
*
|
|
929
|
+
* `validate-hook-body-writes.ts` parses an L2 hook body wrapped in
|
|
930
|
+
* `async function __body(ctx) {\n…\n}` — the shape the runtime compiles it
|
|
931
|
+
* into — so its diagnostics are one line low. The reported line is clamped to
|
|
932
|
+
* at least 1, so a diagnostic that lands on a synthesised line is attributed
|
|
933
|
+
* to the nearest AUTHORED line and never to a line the author did not write.
|
|
934
|
+
*/
|
|
935
|
+
synthesizedLinesBefore?: number;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* The one wording every caller's message embeds, so three findings about the
|
|
939
|
+
* same defect do not describe it three ways.
|
|
940
|
+
*/
|
|
941
|
+
declare function describeParseFailure(failure: SourceParseFailure): string;
|
|
942
|
+
/**
|
|
943
|
+
* The hint every caller's finding carries. It says what the finding IS — a
|
|
944
|
+
* statement about what the checker could read, not a second syntax verdict —
|
|
945
|
+
* because a source that does not parse is not scored, and an author who reads
|
|
946
|
+
* "no problems found" about it would be reading a green line that lied.
|
|
947
|
+
*/
|
|
948
|
+
declare const PARSE_FAILURE_HINT: string;
|
|
949
|
+
|
|
950
|
+
type HookBodyWriteSeverity = 'warning';
|
|
951
|
+
interface HookBodyWriteFinding {
|
|
952
|
+
/** v1 is advisory-only by contract — the type says so. */
|
|
953
|
+
severity: HookBodyWriteSeverity;
|
|
954
|
+
rule: string;
|
|
955
|
+
/** Human-readable location, e.g. `hook "normalize_lead" › body`. */
|
|
956
|
+
where: string;
|
|
957
|
+
/** Config path, e.g. `hooks[0].body.source`. */
|
|
958
|
+
path: string;
|
|
959
|
+
message: string;
|
|
960
|
+
hint: string;
|
|
961
|
+
}
|
|
962
|
+
declare const HOOK_BODY_WRITE_UNKNOWN_FIELD = "hook-body-write-unknown-field";
|
|
963
|
+
/**
|
|
964
|
+
* [#10653] The body did not parse, so its write set is whatever error recovery
|
|
965
|
+
* left readable.
|
|
966
|
+
*
|
|
967
|
+
* Reported rather than skipped for the reason this rule exists at all: a
|
|
968
|
+
* mistake must be visible where it is MADE. An unparseable body reached the
|
|
969
|
+
* extractor, produced fewer matches, and came back as a hook with nothing to
|
|
970
|
+
* report — the same silence the undeclared write itself has at run time, this
|
|
971
|
+
* time wearing the checker's badge. `warning` because the whole rule is
|
|
972
|
+
* advisory and never gates (the severity type admits nothing else).
|
|
973
|
+
*/
|
|
974
|
+
declare const HOOK_BODY_SOURCE_UNPARSEABLE = "hook-body-source-unparseable";
|
|
975
|
+
/**
|
|
976
|
+
* [#8663] The write-axis twin of `flow-template-field-unprovisioned` (#8340):
|
|
977
|
+
* the body writes a field {@link IMPLICIT_FIELDS} exempts, but on THIS target
|
|
978
|
+
* the platform registered that anchor without provisioning storage for it.
|
|
979
|
+
*
|
|
980
|
+
* A separate id at `warning` severity rather than a reclassification of
|
|
981
|
+
* {@link HOOK_BODY_WRITE_UNKNOWN_FIELD}, matching #8340's precedent exactly:
|
|
982
|
+
* the existence verdict is unchanged (the name IS addressable), and what is
|
|
983
|
+
* added is a second, independently suppressible finding on the path where the
|
|
984
|
+
* existence check stays silent.
|
|
985
|
+
*/
|
|
986
|
+
declare const HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR = "hook-body-write-unprovisioned-anchor";
|
|
987
|
+
/** One syntactic write shape the extractor recognizes. */
|
|
988
|
+
interface HookBodyWritePattern {
|
|
989
|
+
/** Stable pattern id, carried on every extracted write. */
|
|
990
|
+
readonly id: string;
|
|
991
|
+
/** Author-facing syntax summary (for docs/diagnostics, not matching). */
|
|
992
|
+
readonly syntax: string;
|
|
993
|
+
/** Reconciliation fixture: extracting `source` must yield exactly `writes`. */
|
|
994
|
+
readonly example: {
|
|
995
|
+
readonly source: string;
|
|
996
|
+
readonly writes: ReadonlyArray<{
|
|
997
|
+
field: string;
|
|
998
|
+
object?: string;
|
|
999
|
+
}>;
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
declare const HOOK_BODY_WRITE_PATTERNS: readonly HookBodyWritePattern[];
|
|
1003
|
+
/** A ledger pattern a given rule does NOT consume, and why. */
|
|
1004
|
+
interface BodyWritePatternExclusion {
|
|
1005
|
+
/** The {@link HOOK_BODY_WRITE_PATTERNS} entry id being excluded. */
|
|
1006
|
+
readonly id: string;
|
|
1007
|
+
/** Why the shape does not mean the same thing on this rule's surface. */
|
|
1008
|
+
readonly reason: string;
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* The ledger shapes THIS rule consumes.
|
|
1012
|
+
*
|
|
1013
|
+
* Declared rather than implied: before the ledger carried a shape the hook
|
|
1014
|
+
* surface does not have, every write with no `object` was necessarily a
|
|
1015
|
+
* `ctx.input` write, and the rule could branch on that alone. It no longer can
|
|
1016
|
+
* — a `record-property-assign` write also carries no object, and would have
|
|
1017
|
+
* been reported as "the hook writes 'stage' to its input", which is false.
|
|
1018
|
+
* Each consumer declaring its own subset is what stops the next added shape
|
|
1019
|
+
* from silently landing in a branch that was never written for it.
|
|
1020
|
+
*/
|
|
1021
|
+
declare const HOOK_BODY_WRITE_PATTERN_IDS: readonly string[];
|
|
1022
|
+
/** Ledger shapes this rule leaves alone, each with its reason. */
|
|
1023
|
+
declare const HOOK_BODY_WRITE_EXCLUSIONS: readonly BodyWritePatternExclusion[];
|
|
1024
|
+
type AnyRec$F = Record<string, unknown>;
|
|
1025
|
+
/** One statically-extracted field write found in an L2 body. */
|
|
1026
|
+
interface ExtractedHookBodyWrite {
|
|
1027
|
+
/** Which {@link HOOK_BODY_WRITE_PATTERNS} entry matched. */
|
|
1028
|
+
patternId: string;
|
|
1029
|
+
/** Target object name; `undefined` = the hook's own target object(s). */
|
|
1030
|
+
object?: string;
|
|
1031
|
+
/** The `ctx.api` method for diagnostics (`insert`/`create`/`update`/`updateById`). */
|
|
1032
|
+
method?: string;
|
|
1033
|
+
field: string;
|
|
1034
|
+
}
|
|
1035
|
+
/** Everything one parse of an L2 body yields. */
|
|
1036
|
+
interface ExtractedHookBodyWriteSet {
|
|
1037
|
+
/** Every literal write the {@link HOOK_BODY_WRITE_PATTERNS} ledger declares. */
|
|
1038
|
+
writes: ExtractedHookBodyWrite[];
|
|
1039
|
+
/**
|
|
1040
|
+
* `ctx.record` is handed to something as a VALUE somewhere in the body — an
|
|
1041
|
+
* argument, an assignment RHS, a spread, a return — rather than only having
|
|
1042
|
+
* its properties read and written, or being truthiness/type tested.
|
|
1043
|
+
*
|
|
1044
|
+
* The action rule needs this to tell a dead snapshot write from a live one:
|
|
1045
|
+
* `ctx.record.stage = 'won'; await ctx.api.object('d').update(ctx.record)`
|
|
1046
|
+
* builds a payload and persists it, so the assignment is not a no-op. When
|
|
1047
|
+
* this is true, no record write in the body can be judged, and none is
|
|
1048
|
+
* reported. (One-level aliasing — `const r = ctx.record` — reads as an
|
|
1049
|
+
* escape too, which is the safe direction: it suppresses findings.)
|
|
1050
|
+
*/
|
|
1051
|
+
ctxRecordEscapes: boolean;
|
|
1052
|
+
/**
|
|
1053
|
+
* [#10653] Set when the body did not parse, so `writes` is whatever error
|
|
1054
|
+
* recovery left readable rather than the body's actual write set.
|
|
1055
|
+
*
|
|
1056
|
+
* Absent means one of two things, and they are not the same: the body parsed,
|
|
1057
|
+
* or the cheap pre-filter above rejected it before any parse. The filter is a
|
|
1058
|
+
* raw-text scan for `ctx` / `Object`, and a body containing neither cannot
|
|
1059
|
+
* match any pattern however it parses — so a skipped parse claims nothing and
|
|
1060
|
+
* hides nothing.
|
|
1061
|
+
*
|
|
1062
|
+
* ## Whose fault an unparseable body is — asked, not assumed
|
|
1063
|
+
*
|
|
1064
|
+
* The body is parsed inside a synthesised wrapper (`async function __body(ctx)
|
|
1065
|
+
* { … }`) because that is the shape the runtime compiles it into
|
|
1066
|
+
* (`new AsyncFunction('ctx', source)`). So a parse failure here could in
|
|
1067
|
+
* principle be the WRAPPER's fault rather than the author's, and blaming the
|
|
1068
|
+
* author for the checker's own bug is the failure this whole change is about.
|
|
1069
|
+
* It cannot be: the wrapper is a constant, and `validate-hook-body-writes.
|
|
1070
|
+
* test.ts` pins that it parses clean around an empty body and around every
|
|
1071
|
+
* example in the pattern ledger. Any diagnostic therefore comes from the
|
|
1072
|
+
* body — and its position is reported in the BODY's own coordinates (the
|
|
1073
|
+
* wrapper's line is subtracted, and the result is clamped so it can never
|
|
1074
|
+
* point at a line the author did not write).
|
|
1075
|
+
*/
|
|
1076
|
+
parseFailure?: SourceParseFailure;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Extract every literal field write the pattern ledger declares from an L2
|
|
1080
|
+
* body's source. Parse-only (the source is never executed), error-tolerant
|
|
1081
|
+
* (a body with syntax errors simply yields fewer matches), and lazy: the
|
|
1082
|
+
* TypeScript compiler is not loaded when no pattern can possibly match.
|
|
1083
|
+
*
|
|
1084
|
+
* Thin projection of {@link extractHookBodyWriteSet} — use that one when the
|
|
1085
|
+
* `ctx.record` liveness signal matters, so the body is parsed once, not twice.
|
|
1086
|
+
*/
|
|
1087
|
+
declare function extractHookBodyWrites(source: string): ExtractedHookBodyWrite[];
|
|
1088
|
+
/** {@link extractHookBodyWrites} plus the `ctx.record` liveness signal, one parse. */
|
|
1089
|
+
declare function extractHookBodyWriteSet(source: string): ExtractedHookBodyWriteSet;
|
|
1090
|
+
/**
|
|
1091
|
+
* Validate L2 hook-body writes against target-object field declarations.
|
|
1092
|
+
* Pure `(stack) => Finding[]` (ADR-0019); safe on pre- or post-parse stacks.
|
|
1093
|
+
*/
|
|
1094
|
+
declare function validateHookBodyWrites(stack: AnyRec$F): HookBodyWriteFinding[];
|
|
1095
|
+
|
|
1096
|
+
type ReadonlyHookWriteSeverity = 'error' | 'warning';
|
|
1097
|
+
interface ReadonlyHookWriteFinding {
|
|
1098
|
+
severity: ReadonlyHookWriteSeverity;
|
|
1099
|
+
rule: string;
|
|
1100
|
+
/** Human-readable location, e.g. `hook "touch_account" > body`. */
|
|
1101
|
+
where: string;
|
|
1102
|
+
/** Config path, e.g. `hooks[0].body.source`. */
|
|
1103
|
+
path: string;
|
|
1104
|
+
message: string;
|
|
1105
|
+
hint: string;
|
|
1106
|
+
}
|
|
1107
|
+
declare const HOOK_API_UPDATE_READONLY_FIELD = "hook-api-update-readonly-field";
|
|
1108
|
+
declare const HOOK_API_UPDATE_READONLY_WHEN_FIELD = "hook-api-update-readonly-when-field";
|
|
1109
|
+
/**
|
|
1110
|
+
* The `HOOK_BODY_WRITE_PATTERNS` shapes THIS rule consumes.
|
|
1111
|
+
*
|
|
1112
|
+
* Declared as data rather than implied by a branch, for the reason the sibling
|
|
1113
|
+
* rules declare theirs: a write with no `object` is not a single thing (both
|
|
1114
|
+
* `ctx.input` and `ctx.record` shapes carry none), so a future ledger addition
|
|
1115
|
+
* must not be able to land silently in a branch never written for it.
|
|
1116
|
+
*/
|
|
1117
|
+
declare const READONLY_HOOK_WRITE_PATTERN_IDS: readonly string[];
|
|
1118
|
+
/** Ledger shapes this rule leaves alone, each with its reason. */
|
|
1119
|
+
declare const READONLY_HOOK_WRITE_EXCLUSIONS: readonly BodyWritePatternExclusion[];
|
|
1120
|
+
type AnyRec$E = Record<string, unknown>;
|
|
1121
|
+
/**
|
|
1122
|
+
* Validate L2 hook-body `ctx.api` writes against target-object readonly
|
|
1123
|
+
* declarations. Pure `(stack) => Finding[]` (ADR-0019); safe on pre- or
|
|
1124
|
+
* post-parse stacks.
|
|
1125
|
+
*/
|
|
1126
|
+
declare function validateReadonlyHookWrites(stack: AnyRec$E): ReadonlyHookWriteFinding[];
|
|
1127
|
+
|
|
1128
|
+
type ReadonlyActionWriteSeverity = 'warning';
|
|
1129
|
+
interface ReadonlyActionWriteFinding {
|
|
1130
|
+
/** Advisory by contract - the conditional strip is per record state. */
|
|
1131
|
+
severity: ReadonlyActionWriteSeverity;
|
|
1132
|
+
rule: string;
|
|
1133
|
+
/** Human-readable location, e.g. `action "freeze_invoice" > body`. */
|
|
1134
|
+
where: string;
|
|
1135
|
+
/** Config path, e.g. `actions[0].body.source`. */
|
|
1136
|
+
path: string;
|
|
1137
|
+
message: string;
|
|
1138
|
+
hint: string;
|
|
1139
|
+
}
|
|
1140
|
+
/** Rule id (registry entry). */
|
|
1141
|
+
declare const ACTION_API_UPDATE_READONLY_WHEN_FIELD = "action-api-update-readonly-when-field";
|
|
1142
|
+
/**
|
|
1143
|
+
* The `HOOK_BODY_WRITE_PATTERNS` shapes THIS rule consumes.
|
|
1144
|
+
*
|
|
1145
|
+
* Declared as data rather than implied by a branch, for the reason the sibling
|
|
1146
|
+
* rules declare theirs: a write with no `object` is not a single thing (both
|
|
1147
|
+
* `ctx.input` and `ctx.record` shapes carry none), so a future ledger addition
|
|
1148
|
+
* must not be able to land silently in a branch never written for it.
|
|
1149
|
+
*/
|
|
1150
|
+
declare const READONLY_ACTION_WRITE_PATTERN_IDS: readonly string[];
|
|
1151
|
+
/** Ledger shapes this rule leaves alone, each with its reason. */
|
|
1152
|
+
declare const READONLY_ACTION_WRITE_EXCLUSIONS: readonly BodyWritePatternExclusion[];
|
|
1153
|
+
type AnyRec$D = Record<string, unknown>;
|
|
1154
|
+
/**
|
|
1155
|
+
* Validate L2 action-body `ctx.api` writes against target-object `readonlyWhen`
|
|
1156
|
+
* declarations. Pure `(stack) => Finding[]` (ADR-0019); safe on pre- or
|
|
1157
|
+
* post-parse stacks.
|
|
1158
|
+
*/
|
|
1159
|
+
declare function validateReadonlyActionWrites(stack: AnyRec$D): ReadonlyActionWriteFinding[];
|
|
494
1160
|
|
|
495
1161
|
type ViewContainerSeverity = 'error' | 'warning';
|
|
496
1162
|
interface ViewContainerFinding {
|
|
@@ -527,14 +1193,14 @@ declare const STYLE_CLASSNAME_TAILWIND = "style-classname-tailwind";
|
|
|
527
1193
|
declare const STYLE_RESPONSIVE_NO_BASE = "style-responsive-no-base";
|
|
528
1194
|
declare const STYLE_UNKNOWN_CSS_PROPERTY = "style-unknown-css-property";
|
|
529
1195
|
declare const STYLE_UNKNOWN_TOKEN = "style-unknown-token";
|
|
530
|
-
type AnyRec$
|
|
1196
|
+
type AnyRec$C = Record<string, unknown>;
|
|
531
1197
|
/**
|
|
532
1198
|
* Validate every page's component tree for SDUI styling correctness (ADR-0065).
|
|
533
1199
|
* Returns findings (empty = clean). `error` findings describe styles that are
|
|
534
1200
|
* silently dropped and should fail validate/build; `warning` findings are
|
|
535
1201
|
* advisory (typos, drift, footguns).
|
|
536
1202
|
*/
|
|
537
|
-
declare function validateResponsiveStyles(stack: AnyRec$
|
|
1203
|
+
declare function validateResponsiveStyles(stack: AnyRec$C): StyleFinding[];
|
|
538
1204
|
|
|
539
1205
|
type JsxPageSeverity = 'error' | 'warning';
|
|
540
1206
|
interface JsxPageFinding {
|
|
@@ -547,8 +1213,8 @@ interface JsxPageFinding {
|
|
|
547
1213
|
message: string;
|
|
548
1214
|
hint: string;
|
|
549
1215
|
}
|
|
550
|
-
type AnyRec$
|
|
551
|
-
declare function validateJsxPages(stack: AnyRec$
|
|
1216
|
+
type AnyRec$B = Record<string, unknown>;
|
|
1217
|
+
declare function validateJsxPages(stack: AnyRec$B, opts?: {
|
|
552
1218
|
manifest?: Manifest;
|
|
553
1219
|
}): JsxPageFinding[];
|
|
554
1220
|
|
|
@@ -561,8 +1227,8 @@ interface ReactPageFinding {
|
|
|
561
1227
|
message: string;
|
|
562
1228
|
hint: string;
|
|
563
1229
|
}
|
|
564
|
-
type AnyRec$
|
|
565
|
-
declare function validateReactPages(stack: AnyRec$
|
|
1230
|
+
type AnyRec$A = Record<string, unknown>;
|
|
1231
|
+
declare function validateReactPages(stack: AnyRec$A): ReactPageFinding[];
|
|
566
1232
|
|
|
567
1233
|
type ReactPropSeverity = 'error' | 'warning';
|
|
568
1234
|
interface ReactPropFinding {
|
|
@@ -573,14 +1239,39 @@ interface ReactPropFinding {
|
|
|
573
1239
|
message: string;
|
|
574
1240
|
hint: string;
|
|
575
1241
|
}
|
|
576
|
-
type AnyRec$
|
|
1242
|
+
type AnyRec$z = Record<string, unknown>;
|
|
1243
|
+
/**
|
|
1244
|
+
* The source could not be parsed, so the prop checks below read a partially
|
|
1245
|
+
* recovered tree (#10653).
|
|
1246
|
+
*
|
|
1247
|
+
* Severity is `warning`, not `error`, and the reason is measured rather than
|
|
1248
|
+
* cautious. The syntax VERDICT on a react page belongs to `validate-react-
|
|
1249
|
+
* pages.ts`, which transpiles the same source through **Sucrase** — the parser
|
|
1250
|
+
* family that actually compiles a react page — and errors when it refuses. This
|
|
1251
|
+
* rule speaks for a different parser, and the two acceptance sets are not the
|
|
1252
|
+
* same set: measured on 2026-08-21 (TypeScript 6.0.3 / sucrase 3.35.x), a react
|
|
1253
|
+
* source containing `0755`, `'\012'`, `0b2` or `1__0` parses CLEAN through
|
|
1254
|
+
* Sucrase while TypeScript reports a parse diagnostic, and `with (o) {}` goes
|
|
1255
|
+
* the other way. Erroring here would newly fail builds the platform's own
|
|
1256
|
+
* transpiler accepts; going silent is the defect this rule closes. A warning
|
|
1257
|
+
* says the true thing: these checks did not get to run.
|
|
1258
|
+
*/
|
|
1259
|
+
declare const REACT_PAGE_SOURCE_UNPARSEABLE = "react-page-source-unparseable";
|
|
1260
|
+
/**
|
|
1261
|
+
* [#11284] A prop written in a deprecated react-tier spelling (maintainer
|
|
1262
|
+
* ruling 2026-08-23: the react tier converges on the metadata-tier
|
|
1263
|
+
* vocabulary, deprecate-first). Warning, never error: the old spelling keeps
|
|
1264
|
+
* working for the whole deprecation window — this is the loud half of
|
|
1265
|
+
* "alias + loud deprecation", same shape as `approval-approver-type-deprecated`.
|
|
1266
|
+
*/
|
|
1267
|
+
declare const REACT_PROP_DEPRECATED = "react-prop-deprecated";
|
|
577
1268
|
declare const REACT_CHART_FIELD_UNKNOWN = "react-chart-field-unknown";
|
|
578
1269
|
declare const REACT_CHART_FIELD_UNPROVISIONED = "react-chart-field-unprovisioned";
|
|
579
1270
|
declare const REACT_CHART_AGGREGATE_INVALID = "react-chart-aggregate-invalid";
|
|
580
1271
|
declare const REACT_CHART_AXIS_UNKNOWN = "react-chart-axis-unknown";
|
|
581
1272
|
declare const REACT_CHART_DRILLDOWN_INVALID = "react-chart-drilldown-invalid";
|
|
582
1273
|
declare const REACT_BLOCK_NEEDS_RECORD_CONTEXT = "react-block-needs-record-context";
|
|
583
|
-
declare function validateReactPageProps(stack: AnyRec$
|
|
1274
|
+
declare function validateReactPageProps(stack: AnyRec$z): ReactPropFinding[];
|
|
584
1275
|
|
|
585
1276
|
type SourceStyleSeverity = 'error' | 'warning';
|
|
586
1277
|
interface SourceStyleFinding {
|
|
@@ -592,8 +1283,8 @@ interface SourceStyleFinding {
|
|
|
592
1283
|
hint: string;
|
|
593
1284
|
}
|
|
594
1285
|
declare const PAGE_SOURCE_CLASSNAME = "page-source-className-tailwind";
|
|
595
|
-
type AnyRec$
|
|
596
|
-
declare function validatePageSourceStyling(stack: AnyRec$
|
|
1286
|
+
type AnyRec$y = Record<string, unknown>;
|
|
1287
|
+
declare function validatePageSourceStyling(stack: AnyRec$y): SourceStyleFinding[];
|
|
597
1288
|
|
|
598
1289
|
/**
|
|
599
1290
|
* Build-time record-title diagnostics (ADR-0079).
|
|
@@ -637,14 +1328,14 @@ interface RecordTitleFinding {
|
|
|
637
1328
|
/** How to fix it. */
|
|
638
1329
|
hint: string;
|
|
639
1330
|
}
|
|
640
|
-
type AnyRec$
|
|
1331
|
+
type AnyRec$x = Record<string, unknown>;
|
|
641
1332
|
/**
|
|
642
1333
|
* Validate every object's record-title declaration. Returns the list of
|
|
643
1334
|
* findings (empty = clean). Both rules are advisory (`warning`): the caller
|
|
644
1335
|
* must never fail the build on them alone — auto-provision + the `Record #<id>`
|
|
645
1336
|
* floor guarantee a resolvable title at runtime.
|
|
646
1337
|
*/
|
|
647
|
-
declare function validateRecordTitle(stack: AnyRec$
|
|
1338
|
+
declare function validateRecordTitle(stack: AnyRec$x): RecordTitleFinding[];
|
|
648
1339
|
|
|
649
1340
|
declare const FIELD_GROUP_UNDECLARED = "field-group-undeclared";
|
|
650
1341
|
declare const FIELD_GROUP_EMPTY = "field-group-empty";
|
|
@@ -666,16 +1357,23 @@ interface SemanticRoleFinding {
|
|
|
666
1357
|
/** How to fix it. */
|
|
667
1358
|
hint: string;
|
|
668
1359
|
}
|
|
669
|
-
type AnyRec$
|
|
1360
|
+
type AnyRec$w = Record<string, unknown>;
|
|
670
1361
|
/**
|
|
671
1362
|
* Validate every object's semantic-role pointers. Returns the list of
|
|
672
1363
|
* findings (empty = clean). Advisory only — the caller must never fail the
|
|
673
1364
|
* build on these alone.
|
|
674
1365
|
*/
|
|
675
|
-
declare function validateSemanticRoles(stack: AnyRec$
|
|
1366
|
+
declare function validateSemanticRoles(stack: AnyRec$w): SemanticRoleFinding[];
|
|
676
1367
|
|
|
677
1368
|
declare const FORM_FIELD_UNKNOWN = "form-field-unknown";
|
|
678
1369
|
declare const FORM_COLSPAN_ABSOLUTE = "absolute-colspan-discouraged";
|
|
1370
|
+
/**
|
|
1371
|
+
* [#13855] A section's `group` names a field group the bound object does not
|
|
1372
|
+
* declare. The reference form inherits the section's whole membership from that
|
|
1373
|
+
* group, so a dangling key leaves the section with no members and it does not
|
|
1374
|
+
* render at all. Advisory like both rules above — see `object-field-groups.ts`.
|
|
1375
|
+
*/
|
|
1376
|
+
declare const FORM_SECTION_GROUP_UNKNOWN = "form-section-group-unknown";
|
|
679
1377
|
type FormLayoutSeverity = 'error' | 'warning';
|
|
680
1378
|
interface FormLayoutFinding {
|
|
681
1379
|
/** Always `warning` today — both rules are advisory (see module note). */
|
|
@@ -691,12 +1389,12 @@ interface FormLayoutFinding {
|
|
|
691
1389
|
/** How to fix it. */
|
|
692
1390
|
hint: string;
|
|
693
1391
|
}
|
|
694
|
-
type AnyRec$
|
|
1392
|
+
type AnyRec$v = Record<string, unknown>;
|
|
695
1393
|
/**
|
|
696
1394
|
* Validate authored form-view layout. Returns findings (empty = clean).
|
|
697
1395
|
* Advisory only — the caller must never fail the build on these alone.
|
|
698
1396
|
*/
|
|
699
|
-
declare function validateFormLayout(stack: AnyRec$
|
|
1397
|
+
declare function validateFormLayout(stack: AnyRec$v): FormLayoutFinding[];
|
|
700
1398
|
|
|
701
1399
|
declare const VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
|
|
702
1400
|
declare const VISIBILITY_BARE_IDENTIFIER = "visibility-bare-identifier";
|
|
@@ -709,6 +1407,15 @@ declare const VISIBILITY_PREDICATE_SYNTAX = "visibility-predicate-syntax";
|
|
|
709
1407
|
* consequence is identical, the FIX is not, and `--json` consumers key on the id.
|
|
710
1408
|
*/
|
|
711
1409
|
declare const VISIBILITY_PREDICATE_OVER_BUDGET = "visibility-predicate-over-budget";
|
|
1410
|
+
/**
|
|
1411
|
+
* A predicate calling a function the CEL environment does not register —
|
|
1412
|
+
* #13594, the scoped supersession of this file's parse-only ruling (see the
|
|
1413
|
+
* module note's §Function existence). A separate id from
|
|
1414
|
+
* {@link VISIBILITY_PREDICATE_SYNTAX} for the same reason `over-budget` is one:
|
|
1415
|
+
* the source parses perfectly, so the dialect prescription cannot succeed on it
|
|
1416
|
+
* and `--json` consumers key on the id.
|
|
1417
|
+
*/
|
|
1418
|
+
declare const VISIBILITY_PREDICATE_UNKNOWN_FUNCTION = "visibility-predicate-unknown-function";
|
|
712
1419
|
type VisibilitySeverity = 'error' | 'warning';
|
|
713
1420
|
/**
|
|
714
1421
|
* Which binding environment the linted surface belongs to (ADR-0089 §Context):
|
|
@@ -736,9 +1443,9 @@ interface VisibilityOptions {
|
|
|
736
1443
|
interface VisibilityFinding {
|
|
737
1444
|
/**
|
|
738
1445
|
* `warning` for the ADR-0089 D3b advisory (`visibility-root-mislayered`);
|
|
739
|
-
* `error` for the
|
|
740
|
-
* `visibility-predicate-over-budget
|
|
741
|
-
* module note).
|
|
1446
|
+
* `error` for the four rules that gate — `visibility-predicate-syntax`,
|
|
1447
|
+
* `visibility-predicate-over-budget`, `visibility-predicate-unknown-function`
|
|
1448
|
+
* and `visibility-bare-identifier` (see module note).
|
|
742
1449
|
*/
|
|
743
1450
|
severity: VisibilitySeverity;
|
|
744
1451
|
/** Diagnostic rule id, e.g. `visibility-root-mislayered`. */
|
|
@@ -752,7 +1459,7 @@ interface VisibilityFinding {
|
|
|
752
1459
|
/** How to fix it. */
|
|
753
1460
|
hint: string;
|
|
754
1461
|
}
|
|
755
|
-
type AnyRec$
|
|
1462
|
+
type AnyRec$u = Record<string, unknown>;
|
|
756
1463
|
/**
|
|
757
1464
|
* Validate conditional-visibility predicates across authored views and pages.
|
|
758
1465
|
*
|
|
@@ -765,8 +1472,10 @@ type AnyRec$s = Record<string, unknown>;
|
|
|
765
1472
|
*
|
|
766
1473
|
* Returns findings (empty = clean). `visibility-root-mislayered` is advisory
|
|
767
1474
|
* (`warning`); `visibility-predicate-syntax` (#6253),
|
|
768
|
-
* `visibility-predicate-over-budget` (#7217)
|
|
769
|
-
* (#
|
|
1475
|
+
* `visibility-predicate-over-budget` (#7217),
|
|
1476
|
+
* `visibility-predicate-unknown-function` (#13594) and
|
|
1477
|
+
* `visibility-bare-identifier` (#6128) are `error` and the caller is expected to
|
|
1478
|
+
* fail the build on them.
|
|
770
1479
|
*
|
|
771
1480
|
* The binding-root check is layer-directional (ADR-0089 D3). A form view that
|
|
772
1481
|
* declares `data: { provider: 'schema', schemaId }` is judged at `metadata` on
|
|
@@ -775,9 +1484,11 @@ type AnyRec$s = Record<string, unknown>;
|
|
|
775
1484
|
* predicate is flagged), or leave it at the `'runtime'` default for `*.view.ts` /
|
|
776
1485
|
* `*.page.ts` surfaces (so a `data.`-rooted predicate is flagged). The syntax and
|
|
777
1486
|
* bare-identifier checks are layer-agnostic — but the ROOT their hints prescribe
|
|
778
|
-
* is not, which is the second half of what #7815 fixes.
|
|
1487
|
+
* is not, which is the second half of what #7815 fixes. The unknown-function
|
|
1488
|
+
* check (#13594) is layer-agnostic in both halves: it prescribes a NAME, and the
|
|
1489
|
+
* environment registers the same names on every layer.
|
|
779
1490
|
*/
|
|
780
|
-
declare function validateVisibilityPredicates(stack: AnyRec$
|
|
1491
|
+
declare function validateVisibilityPredicates(stack: AnyRec$u, opts?: VisibilityOptions): VisibilityFinding[];
|
|
781
1492
|
|
|
782
1493
|
declare const PREDICATE_PATH_UNRESOLVED = "predicate-path-unresolved";
|
|
783
1494
|
declare const PREDICATE_PATH_UNROOTED = "predicate-path-unrooted";
|
|
@@ -820,7 +1531,7 @@ interface PredicatePathOptions {
|
|
|
820
1531
|
*/
|
|
821
1532
|
resolveSchema?: (schemaId: string) => unknown;
|
|
822
1533
|
}
|
|
823
|
-
type AnyRec$
|
|
1534
|
+
type AnyRec$t = Record<string, unknown>;
|
|
824
1535
|
/**
|
|
825
1536
|
* Refuse a metadata-form predicate that names a path the target schema does not
|
|
826
1537
|
* declare (#7010).
|
|
@@ -849,7 +1560,7 @@ type AnyRec$r = Record<string, unknown>;
|
|
|
849
1560
|
*
|
|
850
1561
|
* Returns findings (empty = clean).
|
|
851
1562
|
*/
|
|
852
|
-
declare function validatePredicatePathRefs(stack: AnyRec$
|
|
1563
|
+
declare function validatePredicatePathRefs(stack: AnyRec$t, opts?: PredicatePathOptions): PredicatePathFinding[];
|
|
853
1564
|
|
|
854
1565
|
declare const CAPABILITY_REFERENCE_UNKNOWN = "capability-reference-unknown";
|
|
855
1566
|
type CapabilityRefSeverity = 'error' | 'warning';
|
|
@@ -867,12 +1578,12 @@ interface CapabilityRefFinding {
|
|
|
867
1578
|
/** How to fix it. */
|
|
868
1579
|
hint: string;
|
|
869
1580
|
}
|
|
870
|
-
type AnyRec$
|
|
1581
|
+
type AnyRec$s = Record<string, unknown>;
|
|
871
1582
|
/**
|
|
872
1583
|
* Validate every capability reference in a stack. Returns findings (empty =
|
|
873
1584
|
* clean). Advisory only — callers must not fail the build on these alone.
|
|
874
1585
|
*/
|
|
875
|
-
declare function validateCapabilityReferences(stack: AnyRec$
|
|
1586
|
+
declare function validateCapabilityReferences(stack: AnyRec$s): CapabilityRefFinding[];
|
|
876
1587
|
|
|
877
1588
|
declare const APPROVAL_APPROVER_NOT_MEMBERSHIP_TIER = "approval-approver-not-membership-tier";
|
|
878
1589
|
declare const APPROVAL_APPROVER_TYPE_DEPRECATED = "approval-approver-type-deprecated";
|
|
@@ -898,12 +1609,12 @@ interface ApprovalApproverFinding {
|
|
|
898
1609
|
/** How to fix it. */
|
|
899
1610
|
hint: string;
|
|
900
1611
|
}
|
|
901
|
-
type AnyRec$
|
|
1612
|
+
type AnyRec$r = Record<string, unknown>;
|
|
902
1613
|
/**
|
|
903
1614
|
* Validate the approvers of every Approval node in the stack's flows.
|
|
904
1615
|
* Returns findings (empty = clean).
|
|
905
1616
|
*/
|
|
906
|
-
declare function validateApprovalApprovers(stack: AnyRec$
|
|
1617
|
+
declare function validateApprovalApprovers(stack: AnyRec$r): ApprovalApproverFinding[];
|
|
907
1618
|
|
|
908
1619
|
type SeedReplaySafetySeverity = 'error' | 'warning';
|
|
909
1620
|
interface SeedReplaySafetyFinding {
|
|
@@ -917,7 +1628,7 @@ interface SeedReplaySafetyFinding {
|
|
|
917
1628
|
hint: string;
|
|
918
1629
|
}
|
|
919
1630
|
declare const SEED_INSERT_MODE_DUPLICATES_ON_REPLAY = "seed-insert-mode-duplicates-on-replay";
|
|
920
|
-
type AnyRec$
|
|
1631
|
+
type AnyRec$q = Record<string, unknown>;
|
|
921
1632
|
/**
|
|
922
1633
|
* Flag every seed dataset declared with `mode: 'insert'` — the one non-idempotent
|
|
923
1634
|
* mode, which duplicates its rows on every replay boot (framework#3434). Returns
|
|
@@ -927,7 +1638,7 @@ type AnyRec$o = Record<string, unknown>;
|
|
|
927
1638
|
* Reads `stack.data` (the `SeedSchema[]` fixtures). Safe on any shape — a stack
|
|
928
1639
|
* with no `data` array yields no findings.
|
|
929
1640
|
*/
|
|
930
|
-
declare function validateSeedReplaySafety(stack: AnyRec$
|
|
1641
|
+
declare function validateSeedReplaySafety(stack: AnyRec$q): SeedReplaySafetyFinding[];
|
|
931
1642
|
|
|
932
1643
|
type SeedStateMachineSeverity = 'warning';
|
|
933
1644
|
interface SeedStateMachineFinding {
|
|
@@ -941,7 +1652,7 @@ interface SeedStateMachineFinding {
|
|
|
941
1652
|
hint: string;
|
|
942
1653
|
}
|
|
943
1654
|
declare const SEED_VALUE_OUTSIDE_STATE_MACHINE = "seed-value-outside-state-machine";
|
|
944
|
-
type AnyRec$
|
|
1655
|
+
type AnyRec$p = Record<string, unknown>;
|
|
945
1656
|
/**
|
|
946
1657
|
* Flag every seed record whose `state_machine`-governed field carries a value
|
|
947
1658
|
* the machine does not declare (framework#3433 follow-up). Returns the findings
|
|
@@ -954,7 +1665,7 @@ type AnyRec$n = Record<string, unknown>;
|
|
|
954
1665
|
* unresolved `cel` Expression envelope, a number) is skipped: it cannot be
|
|
955
1666
|
* statically compared to the declared-state set.
|
|
956
1667
|
*/
|
|
957
|
-
declare function validateSeedStateMachine(stack: AnyRec$
|
|
1668
|
+
declare function validateSeedStateMachine(stack: AnyRec$p): SeedStateMachineFinding[];
|
|
958
1669
|
|
|
959
1670
|
declare const SECURITY_OWD_UNSET = "security-owd-unset";
|
|
960
1671
|
declare const SECURITY_OWD_ALIAS = "security-owd-alias";
|
|
@@ -969,6 +1680,7 @@ declare const SECURITY_FLS_UNQUALIFIED_KEY = "security-fls-unqualified-key";
|
|
|
969
1680
|
declare const SECURITY_GRANT_EXPIRED_AT_AUTHORING = "security-grant-expired-at-authoring";
|
|
970
1681
|
declare const SECURITY_DELEGATION_MISSING_REASON = "security-delegation-missing-reason";
|
|
971
1682
|
declare const SECURITY_CBP_NO_RELATION = "security-controlled-by-parent-no-relation";
|
|
1683
|
+
declare const SECURITY_CBP_AMBIGUOUS_RELATION = "security-controlled-by-parent-ambiguous-relation";
|
|
972
1684
|
type SecuritySeverity = 'error' | 'warning' | 'info';
|
|
973
1685
|
interface SecurityFinding {
|
|
974
1686
|
severity: SecuritySeverity;
|
|
@@ -983,7 +1695,7 @@ interface SecurityFinding {
|
|
|
983
1695
|
/** How to fix it. */
|
|
984
1696
|
hint: string;
|
|
985
1697
|
}
|
|
986
|
-
type AnyRec$
|
|
1698
|
+
type AnyRec$o = Record<string, unknown>;
|
|
987
1699
|
/**
|
|
988
1700
|
* Validate the security posture of a stack. Returns findings (empty = clean).
|
|
989
1701
|
* `error` findings gate the build in `os compile`; `info` is advisory.
|
|
@@ -991,7 +1703,7 @@ type AnyRec$m = Record<string, unknown>;
|
|
|
991
1703
|
* `opts.nowMs` injects the clock for the ADR-0091 authoring-time expiry rule
|
|
992
1704
|
* (tests); production callers omit it.
|
|
993
1705
|
*/
|
|
994
|
-
declare function validateSecurityPosture(stack: AnyRec$
|
|
1706
|
+
declare function validateSecurityPosture(stack: AnyRec$o, opts?: {
|
|
995
1707
|
nowMs?: number;
|
|
996
1708
|
}): SecurityFinding[];
|
|
997
1709
|
/**
|
|
@@ -1022,7 +1734,7 @@ declare function validateSecurityPosture(stack: AnyRec$m, opts?: {
|
|
|
1022
1734
|
* carries `positions`/`apps` and both types are gated — at which point it
|
|
1023
1735
|
* crosses whole, in one edit, as its own entry.
|
|
1024
1736
|
*/
|
|
1025
|
-
declare function validateSecurityRoleWord(stack: AnyRec$
|
|
1737
|
+
declare function validateSecurityRoleWord(stack: AnyRec$o): SecurityFinding[];
|
|
1026
1738
|
|
|
1027
1739
|
/**
|
|
1028
1740
|
* [ADR-0105 D6] The two organization-axis red lines, enforced at authoring time.
|
|
@@ -1288,13 +2000,13 @@ interface DashboardActionRefFinding {
|
|
|
1288
2000
|
/** How to fix it. */
|
|
1289
2001
|
hint: string;
|
|
1290
2002
|
}
|
|
1291
|
-
type AnyRec$
|
|
2003
|
+
type AnyRec$n = Record<string, unknown>;
|
|
1292
2004
|
/**
|
|
1293
2005
|
* Validate every dashboard header action reference in a stack. Returns
|
|
1294
2006
|
* findings (empty = clean). `script`/`modal` dead targets are errors; `url`
|
|
1295
2007
|
* unresolved routes are warnings.
|
|
1296
2008
|
*/
|
|
1297
|
-
declare function validateDashboardActionRefs(stack: AnyRec$
|
|
2009
|
+
declare function validateDashboardActionRefs(stack: AnyRec$n): DashboardActionRefFinding[];
|
|
1298
2010
|
|
|
1299
2011
|
/**
|
|
1300
2012
|
* Build-time filter-placeholder diagnostics (issue #3574).
|
|
@@ -1503,12 +2215,12 @@ interface ObjectRefFinding {
|
|
|
1503
2215
|
/** How to fix it. */
|
|
1504
2216
|
hint: string;
|
|
1505
2217
|
}
|
|
1506
|
-
type AnyRec$
|
|
2218
|
+
type AnyRec$m = Record<string, unknown>;
|
|
1507
2219
|
/**
|
|
1508
2220
|
* Validate every object-name reference on the surfaces listed in the module
|
|
1509
2221
|
* header. Returns findings (empty = clean).
|
|
1510
2222
|
*/
|
|
1511
|
-
declare function validateObjectReferences(stack: AnyRec$
|
|
2223
|
+
declare function validateObjectReferences(stack: AnyRec$m): ObjectRefFinding[];
|
|
1512
2224
|
|
|
1513
2225
|
type ReferenceIntegritySeverity = 'error' | 'warning';
|
|
1514
2226
|
/**
|
|
@@ -1532,21 +2244,75 @@ interface ReferenceIntegrityFinding {
|
|
|
1532
2244
|
/** One member of the suite. `name` is the exported function's name — the id a wiring test can assert on. */
|
|
1533
2245
|
interface ReferenceIntegrityRule {
|
|
1534
2246
|
name: string;
|
|
2247
|
+
/**
|
|
2248
|
+
* [#9313] The runtime-publish per-write snapshot types this member judges.
|
|
2249
|
+
*
|
|
2250
|
+
* The suite is ONE entry in `AUTHORING_RULES`, and that entry's
|
|
2251
|
+
* `runtimeTypes` says which WRITES dispatch the suite at the runtime publish
|
|
2252
|
+
* gate. This field is the finer axis the entry cannot express: which MEMBERS
|
|
2253
|
+
* are safe to judge that per-write snapshot. The two axes differ because the
|
|
2254
|
+
* snapshot is partial by design (`RuntimeStackContext` carries objects /
|
|
2255
|
+
* permissions / books / datasets and nothing else): a member that resolves
|
|
2256
|
+
* against a collection the snapshot does not carry would not go quiet — it
|
|
2257
|
+
* would report every reference into that collection as dead. Measured on the
|
|
2258
|
+
* `view` widening: `validateActionNameRefs` resolves the action names in
|
|
2259
|
+
* `views[].list` / `views[].listViews.*` against `stack.actions`, which no
|
|
2260
|
+
* per-write snapshot carries, so crossing it with the suite would refuse a
|
|
2261
|
+
* legitimate CONTAINER view write for every stack-level action it names —
|
|
2262
|
+
* a false 422 on the only door a Studio tenant has (on a FLATTENED overlay
|
|
2263
|
+
* it has no rung, so the same crossing would be a silent no-op instead;
|
|
2264
|
+
* measured both ways, `runtime-gate.view-writes.test.ts`).
|
|
2265
|
+
*
|
|
2266
|
+
* ABSENT = `['flow']`, the surface the whole suite has run on since #4463 P1.
|
|
2267
|
+
* The default is deliberately the frozen historical surface, never "all":
|
|
2268
|
+
* widening a member onto another type is an explicit declaration here plus
|
|
2269
|
+
* its own false-positive measurement (#4716's budget), exactly the
|
|
2270
|
+
* discipline `runtimeTypes` gives registry entries. CLI commands ignore this
|
|
2271
|
+
* field entirely — all members always run there (see
|
|
2272
|
+
* {@link validateReferenceIntegrity}).
|
|
2273
|
+
*/
|
|
2274
|
+
runtimeTypes?: readonly string[];
|
|
1535
2275
|
run: (stack: Record<string, unknown>) => ReferenceIntegrityFinding[];
|
|
1536
2276
|
}
|
|
1537
2277
|
/**
|
|
1538
2278
|
* Every reference-integrity rule, in the order their findings are reported.
|
|
1539
2279
|
*
|
|
1540
2280
|
* ADDING A RULE: append it here and it runs on `validate`, `lint` and
|
|
1541
|
-
* `compile` at once. Do not re-wire the commands.
|
|
2281
|
+
* `compile` at once. Do not re-wire the commands. It joins the runtime
|
|
2282
|
+
* publish gate on the DEFAULT member surface (`flow` snapshots only, #9313) —
|
|
2283
|
+
* widening it to another write type is a `runtimeTypes` declaration on the
|
|
2284
|
+
* member plus that type's own false-positive measurement, never automatic.
|
|
1542
2285
|
*/
|
|
1543
2286
|
declare const REFERENCE_INTEGRITY_RULES: readonly ReferenceIntegrityRule[];
|
|
2287
|
+
/**
|
|
2288
|
+
* Options for {@link validateReferenceIntegrity}.
|
|
2289
|
+
*
|
|
2290
|
+
* Declared as the suite's own type rather than importing
|
|
2291
|
+
* `AuthoringRuleContext` from `authoring-rules.ts` — the suite predates the
|
|
2292
|
+
* registry and the registry imports the suite, so the dependency must keep
|
|
2293
|
+
* pointing that way. The registry's context is assignable to this shape by
|
|
2294
|
+
* construction (`runtimeWriteType` spells the same key on both).
|
|
2295
|
+
*/
|
|
2296
|
+
interface ReferenceIntegrityRunOptions {
|
|
2297
|
+
/**
|
|
2298
|
+
* [#9313] The singular metadata type of the per-write snapshot being judged,
|
|
2299
|
+
* when the caller is the runtime publish gate. Set by `runtime-gate.ts` for
|
|
2300
|
+
* every gated write; ABSENT on the three CLI commands and every whole-stack
|
|
2301
|
+
* caller, which run all members unconditionally.
|
|
2302
|
+
*/
|
|
2303
|
+
runtimeWriteType?: string;
|
|
2304
|
+
}
|
|
1544
2305
|
/**
|
|
1545
2306
|
* Run every reference-integrity rule over a stack. Returns the concatenated
|
|
1546
2307
|
* findings (empty = clean). Pure: no I/O, safe on both the schema-parsed stack
|
|
1547
2308
|
* and the raw/normalized config the `lint` path carries.
|
|
2309
|
+
*
|
|
2310
|
+
* [#9313] When `options.runtimeWriteType` is set — the runtime publish gate
|
|
2311
|
+
* judging one write's snapshot — only the members declaring that type run
|
|
2312
|
+
* (see {@link ReferenceIntegrityRule.runtimeTypes}). Whole-stack callers pass
|
|
2313
|
+
* no options and keep the full suite, byte-identically.
|
|
1548
2314
|
*/
|
|
1549
|
-
declare function validateReferenceIntegrity(stack: Record<string, unknown
|
|
2315
|
+
declare function validateReferenceIntegrity(stack: Record<string, unknown>, options?: ReferenceIntegrityRunOptions): ReferenceIntegrityFinding[];
|
|
1550
2316
|
|
|
1551
2317
|
/**
|
|
1552
2318
|
* [ADR-0072 — reference resolvability] App-navigation targets that are not
|
|
@@ -1612,6 +2378,90 @@ type NavTargetRefFinding = ReferenceIntegrityFinding;
|
|
|
1612
2378
|
declare const NAV_TARGET_UNRESOLVED = "nav-target-unresolved";
|
|
1613
2379
|
declare function validateNavTargetRefs(stack: unknown): NavTargetRefFinding[];
|
|
1614
2380
|
|
|
2381
|
+
/**
|
|
2382
|
+
* [#13216 — reference resolvability] A `type: 'page'` list view's `pageName`
|
|
2383
|
+
* must name a page the stack actually declares.
|
|
2384
|
+
*
|
|
2385
|
+
* ## What the schema already settles, and what it cannot
|
|
2386
|
+
*
|
|
2387
|
+
* `ListViewSchema`'s `checkListViewPageMount` refuses a `page` view with no
|
|
2388
|
+
* `pageName`, refuses `pageName` on any other view type, and refuses a
|
|
2389
|
+
* `pageName` outside `SnakeCaseIdentifierSchema`'s grammar — so by the time a
|
|
2390
|
+
* body reaches here the key is present, well-formed, and on the one view type
|
|
2391
|
+
* that reads it. What a per-body parse cannot ask is whether the page EXISTS,
|
|
2392
|
+
* because the answer lives in a sibling collection the body never sees. That is
|
|
2393
|
+
* this rule's whole question, and it is the same question
|
|
2394
|
+
* `validate-nav-target-refs` asks about the identical reference one surface
|
|
2395
|
+
* over — `{ type: 'page', pageName }` on an app navigation item.
|
|
2396
|
+
*
|
|
2397
|
+
* ## Severity: warning, for its twin's reason
|
|
2398
|
+
*
|
|
2399
|
+
* `validate-nav-target-refs` explains why the honest ceiling for a page
|
|
2400
|
+
* reference is advisory: unlike objects, pages have no curated
|
|
2401
|
+
* cross-package registry, so "unresolved here" cannot be told apart from
|
|
2402
|
+
* "provided by a package this stack cannot see". Nothing about mounting the
|
|
2403
|
+
* same page on a view changes that, so the verdict matches its twin rather
|
|
2404
|
+
* than inventing a second severity for one reference kind. `defineStack`'s
|
|
2405
|
+
* `validateCrossReferences` still hard-fails first whenever the stack DOES
|
|
2406
|
+
* declare pages — this rule is what speaks when that check has switched itself
|
|
2407
|
+
* off (`pageNames.size > 0`), which is exactly the state a stack is in when
|
|
2408
|
+
* the target was never written.
|
|
2409
|
+
*
|
|
2410
|
+
* ## Why it runs at the runtime publish gate too (`runtimeTypes: view`)
|
|
2411
|
+
*
|
|
2412
|
+
* The mount this rule guards is reachable in exactly the way #13100 measured:
|
|
2413
|
+
* an agent publishes a page through the metadata API, then writes a view that
|
|
2414
|
+
* mounts it. Both writes go through `PUT /api/v1/meta/view` — no CLI is
|
|
2415
|
+
* involved anywhere on that path, so a build-time-only rule would never speak
|
|
2416
|
+
* to the author who needs it. Crossing the member onto `view` snapshots is
|
|
2417
|
+
* therefore the point, not a bonus.
|
|
2418
|
+
*
|
|
2419
|
+
* It is safe to cross ONLY because the per-write snapshot now carries `pages`
|
|
2420
|
+
* (`RuntimeStackContext.pages`, added with this rule). Without that collection
|
|
2421
|
+
* the member would not go quiet — it would report EVERY page mount as dead,
|
|
2422
|
+
* which is the missing-collection false-positive channel
|
|
2423
|
+
* `ReferenceIntegrityRule.runtimeTypes` exists to keep closed, and the reason
|
|
2424
|
+
* `validateNavTargetRefs` is NOT crossed: nothing carries a snapshot's `apps`.
|
|
2425
|
+
*
|
|
2426
|
+
* ## The rungs, and why the list matches the sort rule's
|
|
2427
|
+
*
|
|
2428
|
+
* A `pageName` can be authored on every shape that carries a list view, and the
|
|
2429
|
+
* #9313 lesson is that declaring `runtimeTypes` is necessary and NOT sufficient
|
|
2430
|
+
* — the WALK has to reach the flattened top-level shape the write door actually
|
|
2431
|
+
* carries, or the crossing is a silent no-op that reads as coverage. So the
|
|
2432
|
+
* rungs are `validate-sortable-fields`' rungs, one key over:
|
|
2433
|
+
*
|
|
2434
|
+
* - `objects[].listViews.<key>` — built-in named list views;
|
|
2435
|
+
* - `views[]` itself on a FLATTENED LIST OVERLAY (`viewKind: 'list'`, no
|
|
2436
|
+
* nested `config`) — the shape the runtime gate snapshots as `views: [item]`;
|
|
2437
|
+
* - `views[].config` on a standalone ViewItem RECORD (`viewKind: 'list'` with
|
|
2438
|
+
* a record-shaped `config`) — the shape a Studio-saved view round-trips as;
|
|
2439
|
+
* - `views[].list` — a `defineView` aggregate's default list;
|
|
2440
|
+
* - `views[].listViews.<key>` — its named list views.
|
|
2441
|
+
*
|
|
2442
|
+
* ## Not covered, deliberately
|
|
2443
|
+
*
|
|
2444
|
+
* An interpolated target (`${…}`) resolves at render time and is skipped — the
|
|
2445
|
+
* conservative exemption `validate-nav-target-refs` and
|
|
2446
|
+
* `validate-object-references` both use (ADR-0072 D1). It cannot currently
|
|
2447
|
+
* arise (the schema's snake_case grammar refuses `$` and `{`), and is kept so
|
|
2448
|
+
* this rule does not become the thing that has to change if the grammar ever
|
|
2449
|
+
* widens.
|
|
2450
|
+
*
|
|
2451
|
+
* The view's `type` is NOT re-checked here. A body reaching a CLI command has
|
|
2452
|
+
* been parsed; a body reaching `os lint` may not have been, and in that state
|
|
2453
|
+
* reading `pageName` wherever it is written is the more useful answer — a
|
|
2454
|
+
* `pageName` on a `grid` view is a schema refusal, not this rule's business,
|
|
2455
|
+
* and reporting the dead reference underneath it as well helps rather than
|
|
2456
|
+
* misleads.
|
|
2457
|
+
*/
|
|
2458
|
+
|
|
2459
|
+
type ViewPageRefSeverity = 'error' | 'warning';
|
|
2460
|
+
type ViewPageRefFinding = ReferenceIntegrityFinding;
|
|
2461
|
+
/** Emitted when a `type: 'page'` view mounts a page the stack cannot resolve. */
|
|
2462
|
+
declare const VIEW_PAGE_UNRESOLVED = "view-page-unresolved";
|
|
2463
|
+
declare function validateViewPageRefs(stack: unknown): ViewPageRefFinding[];
|
|
2464
|
+
|
|
1615
2465
|
type NavObjectServabilityFinding = ReferenceIntegrityFinding;
|
|
1616
2466
|
/** Emitted when a nav entry targets an object whose `enable` block cannot serve a list. */
|
|
1617
2467
|
declare const NAV_OBJECT_UNSERVABLE = "nav-object-unservable";
|
|
@@ -1767,7 +2617,7 @@ interface SearchableFieldFinding {
|
|
|
1767
2617
|
* runtime would refuse are flagged (#4830).
|
|
1768
2618
|
*/
|
|
1769
2619
|
type SearchableFieldRole = 'canonical' | 'narrowing';
|
|
1770
|
-
type AnyRec$
|
|
2620
|
+
type AnyRec$l = Record<string, unknown>;
|
|
1771
2621
|
/**
|
|
1772
2622
|
* The slice of an object the search checks resolve against: the authored
|
|
1773
2623
|
* field map (existence + the `type`/`hidden` meta search resolution reads),
|
|
@@ -1787,21 +2637,31 @@ interface ObjectSearchTarget {
|
|
|
1787
2637
|
}
|
|
1788
2638
|
/**
|
|
1789
2639
|
* Validate every `searchableFields` declaration in the stack — the object's own
|
|
1790
|
-
* (the canonical set, ADR-0061) and the list views that narrow it
|
|
1791
|
-
*
|
|
2640
|
+
* (the canonical set, ADR-0061) and the list views that narrow it, including
|
|
2641
|
+
* the two standalone `views[]` shapes the `PUT /api/v1/meta/view` door
|
|
2642
|
+
* carries and the runtime publish gate snapshots: the flattened list overlay
|
|
2643
|
+
* (#9313, top-level set) and the ViewItem record (#10001,
|
|
2644
|
+
* `config.searchableFields` one level down). Returns findings (empty = clean).
|
|
1792
2645
|
*
|
|
1793
2646
|
* The react page surface (`<ListView searchableFields={…}>`) is deliberately
|
|
1794
2647
|
* NOT walked here: its declaration lives inside JSX source, and
|
|
1795
2648
|
* `validate-react-page-props` — the gate that already parses that source —
|
|
1796
2649
|
* runs the same `checkSearchableFieldList` core on it (#4329).
|
|
1797
2650
|
*/
|
|
1798
|
-
declare function validateSearchableFields(stack: AnyRec$
|
|
2651
|
+
declare function validateSearchableFields(stack: AnyRec$l): SearchableFieldFinding[];
|
|
1799
2652
|
|
|
1800
2653
|
declare const SORT_FIELD_UNKNOWN = "sort-field-unknown";
|
|
1801
2654
|
declare const SORT_FIELD_UNSORTABLE = "sort-field-unsortable";
|
|
2655
|
+
/** [#10474] The provenance verdict — a WARNING, unlike the two above. */
|
|
2656
|
+
declare const SORT_FIELD_UNPROVISIONED = "sort-field-unprovisioned";
|
|
1802
2657
|
type SortableFieldSeverity = 'error' | 'warning';
|
|
1803
2658
|
interface SortableFieldFinding {
|
|
1804
|
-
/**
|
|
2659
|
+
/**
|
|
2660
|
+
* `error` for the two verdicts the runtime REFUSES (`400 INVALID_SORT` at
|
|
2661
|
+
* request time); `warning` for the #10474 provenance verdict, which no
|
|
2662
|
+
* runtime door refuses and which this pass cannot prove against a remote
|
|
2663
|
+
* schema it cannot see.
|
|
2664
|
+
*/
|
|
1805
2665
|
severity: SortableFieldSeverity;
|
|
1806
2666
|
/** Diagnostic rule id. */
|
|
1807
2667
|
rule: string;
|
|
@@ -1814,7 +2674,7 @@ interface SortableFieldFinding {
|
|
|
1814
2674
|
/** How to fix it. */
|
|
1815
2675
|
hint: string;
|
|
1816
2676
|
}
|
|
1817
|
-
type AnyRec$
|
|
2677
|
+
type AnyRec$k = Record<string, unknown>;
|
|
1818
2678
|
/**
|
|
1819
2679
|
* Check ONE authored `sort` declaration against the object it is bound to —
|
|
1820
2680
|
* the shared core behind every list-view surface that declares an ordering.
|
|
@@ -1826,7 +2686,7 @@ type AnyRec$i = Record<string, unknown>;
|
|
|
1826
2686
|
* created to end. `subject` names the declaration for the message; the parsed
|
|
1827
2687
|
* key's position is appended to `path` so the author can go straight to it.
|
|
1828
2688
|
*/
|
|
1829
|
-
declare function checkSortDeclaration(declared: unknown, objectName: string | undefined, fieldsByObject: ReadonlyMap<string, ObjectSearchTarget | null>, where: string, path: string, subject: string): SortableFieldFinding[];
|
|
2689
|
+
declare function checkSortDeclaration(declared: unknown, objectName: string | undefined, fieldsByObject: ReadonlyMap<string, ObjectSearchTarget | null>, where: string, path: string, subject: string, unprovisionedAnchors?: ReadonlyMap<string, ReadonlySet<string>>): SortableFieldFinding[];
|
|
1830
2690
|
/**
|
|
1831
2691
|
* Validate every list-view `sort` declaration in the stack — the object's
|
|
1832
2692
|
* built-in named list views and the `defineView` aggregates that declare one.
|
|
@@ -1837,32 +2697,64 @@ declare function checkSortDeclaration(declared: unknown, objectName: string | un
|
|
|
1837
2697
|
* over the bound object's own columns. See the module note for the four
|
|
1838
2698
|
* sort-carrying surfaces that were checked and deliberately left out.
|
|
1839
2699
|
*/
|
|
1840
|
-
declare function validateSortableFields(stack: AnyRec$
|
|
2700
|
+
declare function validateSortableFields(stack: AnyRec$k): SortableFieldFinding[];
|
|
1841
2701
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2702
|
+
/** A list-view field reference that resolves to no field on the bound object. */
|
|
2703
|
+
declare const LIST_VIEW_FIELD_UNKNOWN = "list-view-field-unknown";
|
|
2704
|
+
/**
|
|
2705
|
+
* [#14282] A list-view field reference written as a DOTTED path at a position
|
|
2706
|
+
* whose name reaches a query door — where the door refuses it by name. See the
|
|
2707
|
+
* second-class section on this module for the scope and the measurements.
|
|
2708
|
+
*/
|
|
2709
|
+
declare const LIST_VIEW_FIELD_DOTTED = "list-view-field-dotted";
|
|
2710
|
+
type ListViewFieldRefSeverity = 'error' | 'warning';
|
|
2711
|
+
interface ListViewFieldRefFinding {
|
|
2712
|
+
/** See the two-tier note on this module. */
|
|
2713
|
+
severity: ListViewFieldRefSeverity;
|
|
1847
2714
|
/** Diagnostic rule id. */
|
|
1848
2715
|
rule: string;
|
|
1849
|
-
/** Human-readable location, e.g. `view "
|
|
2716
|
+
/** Human-readable location, e.g. `view "task" › list › kanban`. */
|
|
1850
2717
|
where: string;
|
|
1851
|
-
/** Config path, e.g. `views[0].list.
|
|
2718
|
+
/** Config path, e.g. `views[0].list.kanban.groupByField`. */
|
|
1852
2719
|
path: string;
|
|
1853
2720
|
/** What is wrong. */
|
|
1854
2721
|
message: string;
|
|
1855
2722
|
/** How to fix it. */
|
|
1856
2723
|
hint: string;
|
|
1857
2724
|
}
|
|
1858
|
-
type AnyRec$
|
|
2725
|
+
type AnyRec$j = Record<string, unknown>;
|
|
1859
2726
|
/**
|
|
1860
|
-
* Validate every
|
|
1861
|
-
* (empty = clean).
|
|
2727
|
+
* Validate every list view's field references against the object graph.
|
|
2728
|
+
* Returns findings (empty = clean). Pure `(stack) => Finding[]`; no I/O, and
|
|
2729
|
+
* safe on both the schema-parsed stack and the raw config the `lint` path
|
|
2730
|
+
* carries.
|
|
1862
2731
|
*/
|
|
1863
|
-
declare function
|
|
2732
|
+
declare function validateListViewFieldRefs(stack: AnyRec$j): ListViewFieldRefFinding[];
|
|
1864
2733
|
|
|
1865
|
-
|
|
2734
|
+
declare const ACTION_NAME_UNDEFINED = "action-name-undefined";
|
|
2735
|
+
type ActionNameRefSeverity = 'error' | 'warning';
|
|
2736
|
+
interface ActionNameRefFinding {
|
|
2737
|
+
/** Always `error` — a name-bound action that resolves nowhere is a dead button. */
|
|
2738
|
+
severity: ActionNameRefSeverity;
|
|
2739
|
+
/** Diagnostic rule id. */
|
|
2740
|
+
rule: string;
|
|
2741
|
+
/** Human-readable location, e.g. `view "crm_lead" · list "all" · bulkActions`. */
|
|
2742
|
+
where: string;
|
|
2743
|
+
/** Config path, e.g. `views[0].list.bulkActions[1]`. */
|
|
2744
|
+
path: string;
|
|
2745
|
+
/** What is wrong. */
|
|
2746
|
+
message: string;
|
|
2747
|
+
/** How to fix it. */
|
|
2748
|
+
hint: string;
|
|
2749
|
+
}
|
|
2750
|
+
type AnyRec$i = Record<string, unknown>;
|
|
2751
|
+
/**
|
|
2752
|
+
* Validate every name-bound action reference in a stack. Returns findings
|
|
2753
|
+
* (empty = clean).
|
|
2754
|
+
*/
|
|
2755
|
+
declare function validateActionNameRefs(stack: AnyRec$i): ActionNameRefFinding[];
|
|
2756
|
+
|
|
2757
|
+
/**
|
|
1866
2758
|
* [ADR-0078 Phase 3 — Tier-A `action-locations`] An action nobody placed.
|
|
1867
2759
|
*
|
|
1868
2760
|
* `locations` is an action's placement declaration. An action that omits it —
|
|
@@ -1927,12 +2819,12 @@ interface ActionLocationsFinding {
|
|
|
1927
2819
|
/** How to fix it. */
|
|
1928
2820
|
hint: string;
|
|
1929
2821
|
}
|
|
1930
|
-
type AnyRec$
|
|
2822
|
+
type AnyRec$h = Record<string, unknown>;
|
|
1931
2823
|
/**
|
|
1932
2824
|
* Flag every action that declares no placement and that no view places by
|
|
1933
2825
|
* name. Returns findings (empty = clean).
|
|
1934
2826
|
*/
|
|
1935
|
-
declare function validateActionLocations(stack: AnyRec$
|
|
2827
|
+
declare function validateActionLocations(stack: AnyRec$h): ActionLocationsFinding[];
|
|
1936
2828
|
|
|
1937
2829
|
/**
|
|
1938
2830
|
* Shared page-component traversal for the lint rules that inspect
|
|
@@ -1958,11 +2850,11 @@ declare function validateActionLocations(stack: AnyRec$g): ActionLocationsFindin
|
|
|
1958
2850
|
* author never wrote, so those pages are skipped here and covered by
|
|
1959
2851
|
* `validate-jsx-pages` / `validate-react-page-props` instead.
|
|
1960
2852
|
*/
|
|
1961
|
-
type AnyRec$
|
|
2853
|
+
type AnyRec$g = Record<string, unknown>;
|
|
1962
2854
|
/** A visited component plus everything needed to locate and bind it. */
|
|
1963
2855
|
interface WalkedComponent {
|
|
1964
2856
|
/** The component record itself. */
|
|
1965
|
-
component: AnyRec$
|
|
2857
|
+
component: AnyRec$g;
|
|
1966
2858
|
/** Config path, e.g. `pages[0].regions[1].components[2]`. */
|
|
1967
2859
|
path: string;
|
|
1968
2860
|
/**
|
|
@@ -1973,14 +2865,92 @@ interface WalkedComponent {
|
|
|
1973
2865
|
objectName?: string;
|
|
1974
2866
|
}
|
|
1975
2867
|
/** Is this page authored as `source` (so its `regions` must not be linted)? */
|
|
1976
|
-
declare function isSourceAuthoredPage(page: AnyRec$
|
|
2868
|
+
declare function isSourceAuthoredPage(page: AnyRec$g): boolean;
|
|
1977
2869
|
/**
|
|
1978
2870
|
* Walk every component on a page, depth-first, yielding each with its config
|
|
1979
2871
|
* path and resolved object binding. Source-authored pages yield nothing.
|
|
1980
2872
|
*
|
|
1981
2873
|
* `pagePath` is the caller's path prefix for the page (e.g. `pages[3]`).
|
|
2874
|
+
*
|
|
2875
|
+
* The descent is cycle-safe. Every composition slot below is `z.array(z.unknown())`
|
|
2876
|
+
* authored data, so a component that contains itself — directly, or through a
|
|
2877
|
+
* chain of containers — is LEGAL input, and an unguarded walk recurses until the
|
|
2878
|
+
* stack dies. That crash is not scoped to one rule: this is the one shared
|
|
2879
|
+
* traversal under every page-shaped lint rule and the CLI's i18n object-sections
|
|
2880
|
+
* pass, so it takes all of them down in the same process.
|
|
2881
|
+
*
|
|
2882
|
+
* The guard is an ANCESTOR set, not a visited set, and the difference is
|
|
2883
|
+
* load-bearing rather than stylistic — it is the same predicate
|
|
2884
|
+
* `translatePage` (`packages/spec/src/system/i18n-resolver.ts`) settled on. A
|
|
2885
|
+
* component object reused twice as a SIBLING is two legitimate placements at two
|
|
2886
|
+
* distinct config paths, and every rule built on this walk must see both; a
|
|
2887
|
+
* visited set would yield the first and silently drop the second, converting a
|
|
2888
|
+
* crash into missing lint coverage. Only a node that is its own ancestor is a
|
|
2889
|
+
* cycle.
|
|
2890
|
+
*
|
|
2891
|
+
* A cycle stops the descent SILENTLY — the repeated node is not yielded a second
|
|
2892
|
+
* time and no finding is produced. The guard is a safety property of the walk,
|
|
2893
|
+
* not a verdict about the document: deciding that a self-referential page is
|
|
2894
|
+
* itself an authoring error would be new reject behaviour on authored input, and
|
|
2895
|
+
* that is a contract call, not this walk's to make.
|
|
2896
|
+
*
|
|
2897
|
+
* Deliberately NO depth cap, which is a different instrument (`translatePage`
|
|
2898
|
+
* carries both). A cap bounds a legal-but-absurd document; on a resolver it
|
|
2899
|
+
* leaves copy untranslated, but on a LINT walk it would drop real components
|
|
2900
|
+
* from the walk output and every rule would go quiet about them — a silent loss
|
|
2901
|
+
* of coverage that looks exactly like a clean page. With the cycle guard the
|
|
2902
|
+
* descent is bounded by the document's own finite nesting, so the cap would only
|
|
2903
|
+
* ever fire on acyclic input, which is precisely the input it must not truncate.
|
|
2904
|
+
*/
|
|
2905
|
+
declare function walkPageComponents(page: AnyRec$g, pagePath: string): WalkedComponent[];
|
|
2906
|
+
|
|
2907
|
+
/**
|
|
2908
|
+
* object name → its DECLARED field-group keys.
|
|
2909
|
+
*
|
|
2910
|
+
* Declared, not derived: `deriveFieldGroupLayout` drops a group no visible field
|
|
2911
|
+
* joins, and a reference to a declared-but-empty group is a different (and
|
|
2912
|
+
* data-dependent) finding from a reference to a group that was never declared.
|
|
2913
|
+
* This index answers only the second question — the one with a closed oracle.
|
|
2914
|
+
*
|
|
2915
|
+
* `fieldGroups` is an ARRAY on `ObjectSchema`, and `asArray` additionally
|
|
2916
|
+
* resolves the name-keyed map shape that raw (non-`defineStack`) metadata can
|
|
2917
|
+
* carry, the same tolerance every other index in this package extends.
|
|
2918
|
+
*/
|
|
2919
|
+
declare function indexObjectFieldGroups(stack: unknown): Map<string, Set<string>>;
|
|
2920
|
+
/** One `section.group` reference, with the path that located it. */
|
|
2921
|
+
interface SectionGroupRef {
|
|
2922
|
+
/** The referenced field-group key, as authored. */
|
|
2923
|
+
key: string;
|
|
2924
|
+
/** Config path of the `group` key itself. */
|
|
2925
|
+
path: string;
|
|
2926
|
+
}
|
|
2927
|
+
/**
|
|
2928
|
+
* Pull `group` references out of a `sections`-shaped value.
|
|
2929
|
+
*
|
|
2930
|
+
* `sep` joins the index onto `basePath` the way the calling surface addresses
|
|
2931
|
+
* itself — `.` for metadata config paths, and the react surface's ` › ` if it
|
|
2932
|
+
* ever grows a section-bearing block.
|
|
2933
|
+
*/
|
|
2934
|
+
declare function sectionGroupRefs(sections: unknown, basePath: string, sep?: string): SectionGroupRef[];
|
|
2935
|
+
/** The finding shape both host rules already declare, structurally. */
|
|
2936
|
+
interface SectionGroupFinding {
|
|
2937
|
+
severity: 'warning';
|
|
2938
|
+
rule: string;
|
|
2939
|
+
where: string;
|
|
2940
|
+
path: string;
|
|
2941
|
+
message: string;
|
|
2942
|
+
hint: string;
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Check one batch of group references against `objectName`'s declared groups.
|
|
2946
|
+
*
|
|
2947
|
+
* Bails out when the object is not defined in this stack — it may come from
|
|
2948
|
+
* another installed package, and a group cannot be judged on a schema we cannot
|
|
2949
|
+
* see. That is the same skip {@link checkFieldRefs} takes, and for the same
|
|
2950
|
+
* reason: silence about an unknowable object beats a finding the author cannot
|
|
2951
|
+
* act on.
|
|
1982
2952
|
*/
|
|
1983
|
-
declare function
|
|
2953
|
+
declare function checkSectionGroupRefs(refs: readonly SectionGroupRef[], objectName: string | undefined, objectFieldGroups: ReadonlyMap<string, Set<string>>, where: string, rule: string): SectionGroupFinding[];
|
|
1984
2954
|
|
|
1985
2955
|
/**
|
|
1986
2956
|
* [ADR-0078 — completeness] Field-reference integrity for page components
|
|
@@ -2047,6 +3017,14 @@ declare const PAGE_FIELD_UNKNOWN = "page-field-unknown";
|
|
|
2047
3017
|
* #8116's severity reasoning, unchanged.
|
|
2048
3018
|
*/
|
|
2049
3019
|
declare const PAGE_FIELD_UNPROVISIONED = "page-field-unprovisioned";
|
|
3020
|
+
/**
|
|
3021
|
+
* [#13855] A section's `group` names a field group the bound object does not
|
|
3022
|
+
* declare. The reference form inherits the section's whole membership from that
|
|
3023
|
+
* group, so a dangling key leaves the section with no members and it does not
|
|
3024
|
+
* render at all. Always `warning` — see `object-field-groups.ts` for why this
|
|
3025
|
+
* sits with the family's advisory findings rather than gating.
|
|
3026
|
+
*/
|
|
3027
|
+
declare const PAGE_SECTION_GROUP_UNKNOWN = "page-section-group-unknown";
|
|
2050
3028
|
type PageFieldSeverity = 'error' | 'warning';
|
|
2051
3029
|
interface PageFieldFinding {
|
|
2052
3030
|
/**
|
|
@@ -2068,7 +3046,59 @@ interface PageFieldFinding {
|
|
|
2068
3046
|
hint: string;
|
|
2069
3047
|
}
|
|
2070
3048
|
|
|
2071
|
-
declare function validatePageFieldBindings(stack: AnyRec$
|
|
3049
|
+
declare function validatePageFieldBindings(stack: AnyRec$g): PageFieldFinding[];
|
|
3050
|
+
|
|
3051
|
+
declare const PAGE_VISUALIZATION_WITHOUT_BINDING = "page/visualization-without-binding";
|
|
3052
|
+
type PageVisualizationSeverity = 'error' | 'warning';
|
|
3053
|
+
interface PageVisualizationFinding {
|
|
3054
|
+
/**
|
|
3055
|
+
* `error` when the entry leads `allowedVisualizations` — it becomes the
|
|
3056
|
+
* forced `viewType` and the runtime reaches its renderer's refusal screen.
|
|
3057
|
+
* `warning` otherwise — the type is filtered out of the switcher silently.
|
|
3058
|
+
*/
|
|
3059
|
+
severity: PageVisualizationSeverity;
|
|
3060
|
+
/** Diagnostic rule id. */
|
|
3061
|
+
rule: string;
|
|
3062
|
+
/** Human-readable location, e.g. `page "showcase_task_calendar" · interfaceConfig.appearance`. */
|
|
3063
|
+
where: string;
|
|
3064
|
+
/** Config path, e.g. `pages[0].interfaceConfig.appearance.allowedVisualizations[1]`. */
|
|
3065
|
+
path: string;
|
|
3066
|
+
/** What is wrong. */
|
|
3067
|
+
message: string;
|
|
3068
|
+
/** How to fix it. */
|
|
3069
|
+
hint: string;
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* One row of the renderer's derivation, transcribed.
|
|
3073
|
+
*
|
|
3074
|
+
* `types` is the TYPE predicate (checked first, in the object's declaration
|
|
3075
|
+
* order); `namePattern` is the NAME fallback's regex SOURCE, or `null` where
|
|
3076
|
+
* the renderer has no name fallback for that visualization. `viewBlocks` are
|
|
3077
|
+
* the keys on the referenced view that supply the binding directly — read
|
|
3078
|
+
* ahead of any derivation, exactly as `view.<viz> ?? derived` does.
|
|
3079
|
+
*/
|
|
3080
|
+
interface VisualizationPredicate {
|
|
3081
|
+
visualization: string;
|
|
3082
|
+
/** The binding key the renderer would produce (what the message names). */
|
|
3083
|
+
binding: string;
|
|
3084
|
+
types: readonly string[];
|
|
3085
|
+
namePattern: string | null;
|
|
3086
|
+
viewBlocks: readonly string[];
|
|
3087
|
+
}
|
|
3088
|
+
/**
|
|
3089
|
+
* The mirrored table — objectui `f0f774b0`,
|
|
3090
|
+
* `packages/app-shell/src/views/InterfaceListPage.tsx`:
|
|
3091
|
+
* `SELECT_TYPES`/`DATE_TYPES`/`IMAGE_TYPES`/`LOCATION_TYPES` at `:149-151`
|
|
3092
|
+
* and `:203`, `defaultKanbanFromObject` `:153`, `defaultDateField` `:163`,
|
|
3093
|
+
* `defaultCalendarFromObject` `:170`, `defaultGalleryFromObject` `:175`,
|
|
3094
|
+
* `defaultGanttFromObject` `:185`, `defaultMapFromObject` `:253`.
|
|
3095
|
+
*
|
|
3096
|
+
* ⛔ Pinned verbatim by the fixture test. Editing a row here without editing
|
|
3097
|
+
* that pin is the drift this table's whole purpose is to make loud.
|
|
3098
|
+
*/
|
|
3099
|
+
declare const OBJECTUI_DERIVATION_PREDICATES: readonly VisualizationPredicate[];
|
|
3100
|
+
type AnyRec$f = Record<string, unknown>;
|
|
3101
|
+
declare function validatePageVisualizationBindings(stack: AnyRec$f): PageVisualizationFinding[];
|
|
2072
3102
|
|
|
2073
3103
|
/** A key authored in `properties` that the type's props schema does not declare. */
|
|
2074
3104
|
declare const COMPONENT_PROPS_UNKNOWN_KEY = "component-props-unknown-key";
|
|
@@ -2094,7 +3124,24 @@ interface ComponentPropsFinding {
|
|
|
2094
3124
|
/** How to fix it. */
|
|
2095
3125
|
hint: string;
|
|
2096
3126
|
}
|
|
2097
|
-
declare function validateComponentProps(stack: AnyRec$
|
|
3127
|
+
declare function validateComponentProps(stack: AnyRec$g): ComponentPropsFinding[];
|
|
3128
|
+
|
|
3129
|
+
/** A component `type` inside a spec-reserved namespace that the vocabulary does not declare. */
|
|
3130
|
+
declare const COMPONENT_TYPE_UNKNOWN = "component-type-unknown";
|
|
3131
|
+
interface ComponentTypeFinding {
|
|
3132
|
+
severity: 'error';
|
|
3133
|
+
/** Diagnostic rule id. */
|
|
3134
|
+
rule: string;
|
|
3135
|
+
/** Human-readable location, e.g. `page "app_launcher" · global:serch`. */
|
|
3136
|
+
where: string;
|
|
3137
|
+
/** Config path, e.g. `pages[0].regions[1].components[0].type`. */
|
|
3138
|
+
path: string;
|
|
3139
|
+
/** What is wrong. */
|
|
3140
|
+
message: string;
|
|
3141
|
+
/** How to fix it. */
|
|
3142
|
+
hint: string;
|
|
3143
|
+
}
|
|
3144
|
+
declare function validateComponentTypes(stack: AnyRec$g): ComponentTypeFinding[];
|
|
2098
3145
|
|
|
2099
3146
|
/**
|
|
2100
3147
|
* [ADR-0021 — semantic layer] Chart-binding integrity for the surfaces the
|
|
@@ -2151,7 +3198,265 @@ interface ChartBindingFinding {
|
|
|
2151
3198
|
hint: string;
|
|
2152
3199
|
}
|
|
2153
3200
|
|
|
2154
|
-
declare function validateChartBindings(stack: AnyRec$
|
|
3201
|
+
declare function validateChartBindings(stack: AnyRec$g): ChartBindingFinding[];
|
|
3202
|
+
|
|
3203
|
+
/** An `include[]` entry that does not name a joinable relationship path. */
|
|
3204
|
+
declare const DATASET_INCLUDE_UNKNOWN = "dataset-include-unknown";
|
|
3205
|
+
/** A `dimensions[].field` / `measures[].field` path that resolves to no column. */
|
|
3206
|
+
declare const DATASET_FIELD_UNKNOWN = "dataset-field-unknown";
|
|
3207
|
+
/** A field path whose relationship prefix was never declared in `include`. */
|
|
3208
|
+
declare const DATASET_FIELD_NOT_INCLUDED = "dataset-field-not-included";
|
|
3209
|
+
/** A filter KEY — on the dataset or a measure — that resolves to no column. */
|
|
3210
|
+
declare const DATASET_FILTER_FIELD_UNKNOWN = "dataset-filter-field-unknown";
|
|
3211
|
+
type DatasetRefSeverity = 'error' | 'warning';
|
|
3212
|
+
interface DatasetRefFinding {
|
|
3213
|
+
/** Always `error` today — see the severity note on this module. */
|
|
3214
|
+
severity: DatasetRefSeverity;
|
|
3215
|
+
/** Diagnostic rule id. */
|
|
3216
|
+
rule: string;
|
|
3217
|
+
/** Human-readable location, e.g. `dataset "sales" › dimension "region"`. */
|
|
3218
|
+
where: string;
|
|
3219
|
+
/** Config path, e.g. `datasets[0].dimensions[2].field`. */
|
|
3220
|
+
path: string;
|
|
3221
|
+
/** What is wrong. */
|
|
3222
|
+
message: string;
|
|
3223
|
+
/** How to fix it. */
|
|
3224
|
+
hint: string;
|
|
3225
|
+
}
|
|
3226
|
+
type AnyRec$e = Record<string, unknown>;
|
|
3227
|
+
/**
|
|
3228
|
+
* Validate every ADR-0021 dataset's references against the object graph.
|
|
3229
|
+
* Returns findings (empty = clean). Pure `(stack) => Finding[]`; no I/O, and
|
|
3230
|
+
* safe on both the schema-parsed stack and the raw config the `lint` path
|
|
3231
|
+
* carries.
|
|
3232
|
+
*/
|
|
3233
|
+
declare function validateDatasetReferences(stack: AnyRec$e): DatasetRefFinding[];
|
|
3234
|
+
|
|
3235
|
+
/**
|
|
3236
|
+
* Field types that address ANOTHER object, so a `.<hop>` through one is a join
|
|
3237
|
+
* the query compiler derives rather than a column read.
|
|
3238
|
+
*
|
|
3239
|
+
* `user` and `tree` are members alongside the two obvious ones for the reason
|
|
3240
|
+
* `validate-flow-template-paths.ts` lists them: both store a foreign key and
|
|
3241
|
+
* both are traversed the same way. A dataset `include` naming one is joinable
|
|
3242
|
+
* exactly as a `lookup` is.
|
|
3243
|
+
*/
|
|
3244
|
+
declare const RELATIONSHIP_FIELD_TYPES: ReadonlySet<string>;
|
|
3245
|
+
/** The slice of one field this module reads. */
|
|
3246
|
+
interface GraphField {
|
|
3247
|
+
/** Declared `type`, when the author wrote one. */
|
|
3248
|
+
type?: string;
|
|
3249
|
+
/**
|
|
3250
|
+
* The object a relationship field addresses.
|
|
3251
|
+
*
|
|
3252
|
+
* `reference` is the ONLY spelling `FieldSchema` declares — `referenceTo` /
|
|
3253
|
+
* `relatedTo` / `target` / `targetObject` / `lookupObject` are REJECTED
|
|
3254
|
+
* aliases the strict error map maps back to it (#5017), so a field spelling
|
|
3255
|
+
* one of them does not parse at all. Re-admitting an alias here would be the
|
|
3256
|
+
* tolerant consumer Prime Directive #12 refuses, so only `reference` is read.
|
|
3257
|
+
*/
|
|
3258
|
+
reference?: string;
|
|
3259
|
+
/**
|
|
3260
|
+
* The declared `multiple: true` flag, when the author wrote one.
|
|
3261
|
+
*
|
|
3262
|
+
* Read here because the dotted-path verdict a caller may reach for
|
|
3263
|
+
* ({@link classifyDottedFilterHead} in `@objectstack/spec/data`) is a
|
|
3264
|
+
* function of BOTH `type` and `multiple`: an array-valued head is
|
|
3265
|
+
* deliberately unjudged there, since a numeric-index dotted path genuinely
|
|
3266
|
+
* reaches into it on two of three backends. A caller handed only `type`
|
|
3267
|
+
* would have to re-derive the flag from the raw stack, which is the second
|
|
3268
|
+
* copy this module exists to prevent. Additive (#14282): every existing
|
|
3269
|
+
* consumer that ignores the key keeps its verdicts byte-for-byte.
|
|
3270
|
+
*/
|
|
3271
|
+
multiple?: boolean;
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* One object's resolvable surface. `null` in the index marks an object that
|
|
3275
|
+
* declares no readable field map, keeping "declared nothing" distinguishable
|
|
3276
|
+
* from "not in this stack" — the same distinction
|
|
3277
|
+
* {@link indexObjectSearchTargets} draws for the search axis.
|
|
3278
|
+
*/
|
|
3279
|
+
interface GraphObject {
|
|
3280
|
+
/** Authored field names. */
|
|
3281
|
+
names: ReadonlySet<string>;
|
|
3282
|
+
/** name → the slice above. */
|
|
3283
|
+
fields: ReadonlyMap<string, GraphField>;
|
|
3284
|
+
/** Registry-injected columns addressable on THIS object (skip 3). */
|
|
3285
|
+
injected: ReadonlySet<string>;
|
|
3286
|
+
}
|
|
3287
|
+
/** object name → its resolvable surface, or `null` (skip 2). */
|
|
3288
|
+
type ObjectGraph = ReadonlyMap<string, GraphObject | null>;
|
|
3289
|
+
/**
|
|
3290
|
+
* Index a stack's objects once. Callers that resolve many paths build this a
|
|
3291
|
+
* single time and hand it to every {@link resolveFieldPath} call.
|
|
3292
|
+
*/
|
|
3293
|
+
declare function indexObjectGraph(stack: unknown): ObjectGraph;
|
|
3294
|
+
/** What a path resolved to, or precisely how it failed. */
|
|
3295
|
+
type FieldPathVerdict =
|
|
3296
|
+
/**
|
|
3297
|
+
* Every hop and the leaf resolved. `object` is the object the LEAF lives on.
|
|
3298
|
+
* `injected` marks a leaf resolved through skip 3 — a registry-injected
|
|
3299
|
+
* column, real at runtime, whose TYPE and relationship target are
|
|
3300
|
+
* registry-owned and invisible here. A caller asking a second question about
|
|
3301
|
+
* the leaf (is it a relationship? is it materialised?) must treat an
|
|
3302
|
+
* `injected` leaf as unanswerable rather than assume the absence of a
|
|
3303
|
+
* declared type means the absence of the property.
|
|
3304
|
+
*/
|
|
3305
|
+
{
|
|
3306
|
+
kind: 'ok';
|
|
3307
|
+
object: string;
|
|
3308
|
+
field: string;
|
|
3309
|
+
meta?: GraphField;
|
|
3310
|
+
injected?: true;
|
|
3311
|
+
}
|
|
3312
|
+
/**
|
|
3313
|
+
* The graph cannot answer — one of the three skips. Never report this: it is
|
|
3314
|
+
* the absence of a judgement, not a passing one.
|
|
3315
|
+
*/
|
|
3316
|
+
| {
|
|
3317
|
+
kind: 'unknowable';
|
|
3318
|
+
reason: 'object-not-in-stack' | 'no-field-map' | 'injected-hop';
|
|
3319
|
+
object: string;
|
|
3320
|
+
}
|
|
3321
|
+
/**
|
|
3322
|
+
* A hop names nothing on the object it was written against. `at` is the
|
|
3323
|
+
* 0-based segment index, `object` the object the hop was resolved against.
|
|
3324
|
+
*/
|
|
3325
|
+
| {
|
|
3326
|
+
kind: 'hop-unknown';
|
|
3327
|
+
at: number;
|
|
3328
|
+
segment: string;
|
|
3329
|
+
object: string;
|
|
3330
|
+
candidates: ReadonlySet<string>;
|
|
3331
|
+
}
|
|
3332
|
+
/**
|
|
3333
|
+
* A hop resolves to a real field that is NOT a relationship, so there is
|
|
3334
|
+
* nothing to traverse through — the author wrote `amount.total` where
|
|
3335
|
+
* `amount` is a number.
|
|
3336
|
+
*/
|
|
3337
|
+
| {
|
|
3338
|
+
kind: 'hop-not-relationship';
|
|
3339
|
+
at: number;
|
|
3340
|
+
segment: string;
|
|
3341
|
+
object: string;
|
|
3342
|
+
type?: string;
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* A relationship hop resolves but declares no `reference`, so the target
|
|
3346
|
+
* object is unknown and the rest of the path cannot be judged. Treated as a
|
|
3347
|
+
* SKIP by callers for the same reason as `unknowable`.
|
|
3348
|
+
*/
|
|
3349
|
+
| {
|
|
3350
|
+
kind: 'hop-untargeted';
|
|
3351
|
+
at: number;
|
|
3352
|
+
segment: string;
|
|
3353
|
+
object: string;
|
|
3354
|
+
}
|
|
3355
|
+
/** Every hop resolved; the LEAF names nothing on the object it landed on. */
|
|
3356
|
+
| {
|
|
3357
|
+
kind: 'field-unknown';
|
|
3358
|
+
object: string;
|
|
3359
|
+
field: string;
|
|
3360
|
+
candidates: ReadonlySet<string>;
|
|
3361
|
+
};
|
|
3362
|
+
/**
|
|
3363
|
+
* Resolve `path` against `objectName` in `graph`.
|
|
3364
|
+
*
|
|
3365
|
+
* A bare `field` is the one-segment case and resolves against the root object
|
|
3366
|
+
* directly. A dotted `a.b.field` walks `a`, then `b`, as to-one relationship
|
|
3367
|
+
* hops, and resolves the leaf on whatever object the last hop landed on.
|
|
3368
|
+
*
|
|
3369
|
+
* ⛔ This function answers EXISTENCE only. Whether a hop was DECLARED joinable
|
|
3370
|
+
* — an ADR-0021 dataset joins only what `Dataset.include` names — is a second
|
|
3371
|
+
* question the caller owns, because only the caller knows which declaration
|
|
3372
|
+
* governs the position. Conflating them here would make the seam unusable at
|
|
3373
|
+
* the positions that have no `include` (a list view's field path, #14107).
|
|
3374
|
+
*/
|
|
3375
|
+
declare function resolveFieldPath(graph: ObjectGraph, objectName: string | undefined, path: string): FieldPathVerdict | undefined;
|
|
3376
|
+
/**
|
|
3377
|
+
* The relationship prefixes a document declared as joinable.
|
|
3378
|
+
*
|
|
3379
|
+
* ADR-0021: *"Declaring `a.b` implicitly includes the intermediate `a`."* So
|
|
3380
|
+
* every PREFIX of every declared path is joinable, not only the paths as
|
|
3381
|
+
* written — which is why this expands rather than reading `include` verbatim.
|
|
3382
|
+
*
|
|
3383
|
+
* Here rather than in a rule because the SAME `include` governs positions two
|
|
3384
|
+
* different rules judge: a dataset's own `dimensions[].field` / `measures[].field`
|
|
3385
|
+
* / filter keys (#14105), and a dashboard widget's `filter` keys (#14148), whose
|
|
3386
|
+
* condition is ANDed into that same dataset's compiled query as `runtimeFilter`.
|
|
3387
|
+
* Two copies of the prefix expansion would let the two positions drift apart on
|
|
3388
|
+
* a clause that is one sentence of one ADR.
|
|
3389
|
+
*/
|
|
3390
|
+
declare function joinablePrefixes(include: unknown): ReadonlySet<string>;
|
|
3391
|
+
/** The two halves of a rendered verdict: the finding's message, and its detail. */
|
|
3392
|
+
interface FieldPathAccount {
|
|
3393
|
+
/** What is wrong, in prose, carrying the "did you mean" when there is one. */
|
|
3394
|
+
message: string;
|
|
3395
|
+
/** The supporting field list, for the finding's hint. */
|
|
3396
|
+
detail: string;
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* Turn a resolution verdict into the message half of an existence finding, or
|
|
3400
|
+
* `undefined` when the verdict is one no rule may report.
|
|
3401
|
+
*
|
|
3402
|
+
* Shared by every position that resolves a field PATH — a dataset dimension, a
|
|
3403
|
+
* measure, a dataset filter key (#14105), a widget filter key (#14148) — so
|
|
3404
|
+
* they cannot drift into N different accounts of the same miss. The caller
|
|
3405
|
+
* supplies `subject` (how the position is named in prose) and owns the rule id,
|
|
3406
|
+
* the severity, the path and the hint's prescription; this function holds none
|
|
3407
|
+
* of them, matching the rest of this module.
|
|
3408
|
+
*/
|
|
3409
|
+
declare function describeFieldPathVerdict(verdict: FieldPathVerdict, path: string, subject: string): FieldPathAccount | undefined;
|
|
3410
|
+
/**
|
|
3411
|
+
* True when the verdict is one no rule may report — the graph could not answer.
|
|
3412
|
+
* Callers spell the skip through this predicate rather than re-listing the
|
|
3413
|
+
* kinds, so a future verdict added to the union defaults to being reported
|
|
3414
|
+
* loudly (a missed skip is a visible false positive; a missed report is
|
|
3415
|
+
* silence, which is the failure mode this whole family exists to end).
|
|
3416
|
+
*/
|
|
3417
|
+
declare function isUnjudgeable(verdict: FieldPathVerdict | undefined): boolean;
|
|
3418
|
+
/**
|
|
3419
|
+
* Nearest declared name for a typo'd reference, or `undefined` when nothing is
|
|
3420
|
+
* close enough. The sole shared helper behind every "Did you mean?" hint in
|
|
3421
|
+
* this package (issue #14268 consolidated the four hand-copied instances that
|
|
3422
|
+
* had drifted apart into this one).
|
|
3423
|
+
*
|
|
3424
|
+
* Containment is checked first, ahead of edit distance: the ADR-0021 cutover's
|
|
3425
|
+
* canonical drift is base column → prefixed measure name (`amount` →
|
|
3426
|
+
* `sum_amount`), which is far in edit distance but obvious to a human. A
|
|
3427
|
+
* candidate that contains the target (or vice versa, when both are at least 3
|
|
3428
|
+
* characters) scores on the length delta instead and always wins over an
|
|
3429
|
+
* edit-distance match; otherwise the target must be within budget —
|
|
3430
|
+
* `max(2, floor(len/3))` — of a candidate to be offered at all.
|
|
3431
|
+
*/
|
|
3432
|
+
declare function nearestName(target: string, known: Iterable<string>): string | undefined;
|
|
3433
|
+
/** ` Did you mean "x"?`, or the empty string — the platform's message shape. */
|
|
3434
|
+
declare function suggestName(target: string, known: Iterable<string>): string;
|
|
3435
|
+
/** A readable field list for a hint, or `(none)`. */
|
|
3436
|
+
declare function listNames(names: Iterable<string>): string;
|
|
3437
|
+
|
|
3438
|
+
/** One field position inside an authored filter. */
|
|
3439
|
+
interface FilterFieldKey {
|
|
3440
|
+
/**
|
|
3441
|
+
* The field the condition filters BY, exactly as authored — a bare name
|
|
3442
|
+
* (`status`), or a dotted relationship path (`account.region`, whether
|
|
3443
|
+
* spelled that way or reached by descending a nested condition object).
|
|
3444
|
+
*/
|
|
3445
|
+
field: string;
|
|
3446
|
+
/** Config path of the position, e.g. `datasets[1].measures[1].filter.last_update_at`. */
|
|
3447
|
+
path: string;
|
|
3448
|
+
}
|
|
3449
|
+
/**
|
|
3450
|
+
* Emit every FIELD KEY inside one authored filter subtree, whatever shape it
|
|
3451
|
+
* was authored in — the key half of what `validate-preset-comparands.ts` does
|
|
3452
|
+
* for values, and the traversal `filter-token-unknown` already performs while
|
|
3453
|
+
* reasoning only about the strings it finds.
|
|
3454
|
+
*
|
|
3455
|
+
* Holds no judgement: it does not know which object the filter is bound to and
|
|
3456
|
+
* emits no findings. Resolution is {@link resolveFieldPath}'s job
|
|
3457
|
+
* (`object-graph.ts`) and the verdict is the caller's.
|
|
3458
|
+
*/
|
|
3459
|
+
declare function walkFilterFieldKeys(node: unknown, path: string, visit: (key: FilterFieldKey) => void, depth?: number): void;
|
|
2155
3460
|
|
|
2156
3461
|
type RuleCompilabilitySeverity = 'error';
|
|
2157
3462
|
interface RuleCompilabilityFinding {
|
|
@@ -2176,11 +3481,11 @@ declare const VALIDATION_RULE_SCHEMA_UNCOMPILABLE = "validation-rule-json-schema
|
|
|
2176
3481
|
* still says the same thing.
|
|
2177
3482
|
*/
|
|
2178
3483
|
declare const RUNTIME_AJV_OPTIONS: Options;
|
|
2179
|
-
type AnyRec$
|
|
3484
|
+
type AnyRec$d = Record<string, unknown>;
|
|
2180
3485
|
/** One authored validation rule, located for a finding. */
|
|
2181
3486
|
interface WalkedValidationRule {
|
|
2182
3487
|
/** The rule record itself — a top-level entry, or a `conditional` branch. */
|
|
2183
|
-
rule: AnyRec$
|
|
3488
|
+
rule: AnyRec$d;
|
|
2184
3489
|
/** The declaring object's `name`, or `(unnamed object)`. */
|
|
2185
3490
|
objectName: string;
|
|
2186
3491
|
/** The nesting-aware rule name as prose: `'outer' → 'inner'`. */
|
|
@@ -2264,12 +3569,12 @@ interface NavAccessFinding {
|
|
|
2264
3569
|
/** How to fix it. */
|
|
2265
3570
|
hint: string;
|
|
2266
3571
|
}
|
|
2267
|
-
type AnyRec$
|
|
3572
|
+
type AnyRec$c = Record<string, unknown>;
|
|
2268
3573
|
/**
|
|
2269
3574
|
* Validate that every object a stack's navigation exposes is readable by at
|
|
2270
3575
|
* least one permission set the stack declares. Returns findings (empty = clean).
|
|
2271
3576
|
*/
|
|
2272
|
-
declare function validateNavAccess(stack: AnyRec$
|
|
3577
|
+
declare function validateNavAccess(stack: AnyRec$c): NavAccessFinding[];
|
|
2273
3578
|
|
|
2274
3579
|
declare const TRANSLATION_TARGET_UNKNOWN = "translation-target-unknown";
|
|
2275
3580
|
declare const TRANSLATION_OPTION_KEY_UNKNOWN = "translation-option-key-unknown";
|
|
@@ -2288,12 +3593,12 @@ interface TranslationRefFinding {
|
|
|
2288
3593
|
/** How to fix it. */
|
|
2289
3594
|
hint: string;
|
|
2290
3595
|
}
|
|
2291
|
-
type AnyRec$
|
|
3596
|
+
type AnyRec$b = Record<string, unknown>;
|
|
2292
3597
|
/**
|
|
2293
3598
|
* Validate every reference a translation bundle makes against the metadata it
|
|
2294
3599
|
* claims to translate. Returns findings (empty = clean).
|
|
2295
3600
|
*/
|
|
2296
|
-
declare function validateTranslationReferences(stack: AnyRec$
|
|
3601
|
+
declare function validateTranslationReferences(stack: AnyRec$b): TranslationRefFinding[];
|
|
2297
3602
|
|
|
2298
3603
|
declare const TRANSLATION_SECTION_NAME_MISSING = "translation-section-name-missing";
|
|
2299
3604
|
type TranslatableSectionSeverity = 'warning';
|
|
@@ -2311,12 +3616,12 @@ interface TranslatableSectionFinding {
|
|
|
2311
3616
|
/** How to fix it. */
|
|
2312
3617
|
hint: string;
|
|
2313
3618
|
}
|
|
2314
|
-
type AnyRec$
|
|
3619
|
+
type AnyRec$a = Record<string, unknown>;
|
|
2315
3620
|
/**
|
|
2316
3621
|
* Report every form/detail section that declares a heading but no `name`, on an
|
|
2317
3622
|
* object the stack actually translates. Returns findings (empty = clean).
|
|
2318
3623
|
*/
|
|
2319
|
-
declare function validateTranslatableSections(stack: AnyRec$
|
|
3624
|
+
declare function validateTranslatableSections(stack: AnyRec$a): TranslatableSectionFinding[];
|
|
2320
3625
|
|
|
2321
3626
|
/**
|
|
2322
3627
|
* [ADR-0064 §3] Skill ↔ agent surface affinity (issue #3820).
|
|
@@ -2361,12 +3666,12 @@ interface AiSurfaceAffinityFinding {
|
|
|
2361
3666
|
/** How to fix it. */
|
|
2362
3667
|
hint: string;
|
|
2363
3668
|
}
|
|
2364
|
-
type AnyRec$
|
|
3669
|
+
type AnyRec$9 = Record<string, unknown>;
|
|
2365
3670
|
/**
|
|
2366
3671
|
* Validate every in-stack agent→skill binding against the ADR-0064 §3 surface
|
|
2367
3672
|
* affinity contract. Returns findings (empty = clean).
|
|
2368
3673
|
*/
|
|
2369
|
-
declare function validateAiSurfaceAffinity(stack: AnyRec$
|
|
3674
|
+
declare function validateAiSurfaceAffinity(stack: AnyRec$9): AiSurfaceAffinityFinding[];
|
|
2370
3675
|
|
|
2371
3676
|
declare const AI_SKILL_TOOL_UNRESOLVED = "ai-skill-tool-unresolved";
|
|
2372
3677
|
type AiToolRefSeverity = 'error' | 'warning';
|
|
@@ -2384,12 +3689,12 @@ interface AiToolRefFinding {
|
|
|
2384
3689
|
/** How to fix it. */
|
|
2385
3690
|
hint: string;
|
|
2386
3691
|
}
|
|
2387
|
-
type AnyRec$
|
|
3692
|
+
type AnyRec$8 = Record<string, unknown>;
|
|
2388
3693
|
/**
|
|
2389
3694
|
* Validate every `skill.tools[]` reference in a stack. Returns findings
|
|
2390
3695
|
* (empty = clean).
|
|
2391
3696
|
*/
|
|
2392
|
-
declare function validateAiToolReferences(stack: AnyRec$
|
|
3697
|
+
declare function validateAiToolReferences(stack: AnyRec$8): AiToolRefFinding[];
|
|
2393
3698
|
|
|
2394
3699
|
/**
|
|
2395
3700
|
* [ADR-0063 §2] `stack.agents` is a platform-internal slot (issue #3820).
|
|
@@ -2433,10 +3738,41 @@ declare function validateAiToolReferences(stack: AnyRec$9): AiToolRefFinding[];
|
|
|
2433
3738
|
* at warning tier, reusing `PLATFORM_AGENT_NAMES` rather than narrowing the
|
|
2434
3739
|
* schema to an enum (a breaking authoring change ADR-0063 already walked
|
|
2435
3740
|
* back once).
|
|
3741
|
+
*
|
|
3742
|
+
* ## Why the value limb no longer reads the same roster (issue #14461)
|
|
3743
|
+
*
|
|
3744
|
+
* `PLATFORM_AGENT_NAMES` holds FOUR names, and reusing it for the value limb
|
|
3745
|
+
* meant this gate accepted `defaultAgent: 'metadata_assistant'` — the exact
|
|
3746
|
+
* spelling `skills/objectstack-ai` tells authors is "not vocabulary". The
|
|
3747
|
+
* platform then taught it from its own only live example: `studio.app.ts`
|
|
3748
|
+
* pinned the alias. So an AI author copying the one working example in the
|
|
3749
|
+
* repo wrote the forbidden spelling and this rule waved it through — the
|
|
3750
|
+
* silent-tolerance shape ADR-0078 exists to close, committed by the gate
|
|
3751
|
+
* itself.
|
|
3752
|
+
*
|
|
3753
|
+
* The maintainer ruling on #14461 (2026-09-03) re-pins Studio to `build` and
|
|
3754
|
+
* SPLITS the two limbs' rosters, keeping both of #6041's operative decisions
|
|
3755
|
+
* intact (warning tier, no Zod enum):
|
|
3756
|
+
*
|
|
3757
|
+
* - the DECLARATION limb still reads all four names. Its question is "does
|
|
3758
|
+
* this record shadow a platform record?", and declaring `metadata_assistant`
|
|
3759
|
+
* shadows `build` through the alias exactly as declaring `build` does. That
|
|
3760
|
+
* judgement is unchanged.
|
|
3761
|
+
* - the VALUE limb reads `CANONICAL_AGENT_NAMES` only, and a legacy alias
|
|
3762
|
+
* gets its own rule id and wording ({@link DEFAULT_AGENT_LEGACY_ALIAS}).
|
|
3763
|
+
* Its question is "is this the right thing to WRITE?", and the answer for
|
|
3764
|
+
* an alias is no even though it resolves.
|
|
3765
|
+
*
|
|
3766
|
+
* The alias limb stays `warning`, not `error`, and for a sharper reason than
|
|
3767
|
+
* the roster limb: an aliased pin is not broken. It resolves, the app gets the
|
|
3768
|
+
* agent it meant, and nothing a user can see is wrong — which is precisely why
|
|
3769
|
+
* the signal has to be an authoring-time nudge rather than a build break.
|
|
2436
3770
|
*/
|
|
2437
3771
|
declare const AGENT_AUTHORING_WITHDRAWN = "agent-authoring-withdrawn";
|
|
2438
3772
|
/** `app.defaultAgent` names something outside the platform agent roster. */
|
|
2439
3773
|
declare const DEFAULT_AGENT_OUTSIDE_ROSTER = "default-agent-outside-roster";
|
|
3774
|
+
/** `app.defaultAgent` spells a platform agent by its RETIRED alias (#14461). */
|
|
3775
|
+
declare const DEFAULT_AGENT_LEGACY_ALIAS = "default-agent-legacy-alias";
|
|
2440
3776
|
type AiAgentAuthoringSeverity = 'error' | 'warning';
|
|
2441
3777
|
interface AiAgentAuthoringFinding {
|
|
2442
3778
|
/** Always `warning` — the runtime is the gate; this is the authoring-time signal. */
|
|
@@ -2452,121 +3788,12 @@ interface AiAgentAuthoringFinding {
|
|
|
2452
3788
|
/** How to fix it. */
|
|
2453
3789
|
hint: string;
|
|
2454
3790
|
}
|
|
2455
|
-
type AnyRec$
|
|
3791
|
+
type AnyRec$7 = Record<string, unknown>;
|
|
2456
3792
|
/**
|
|
2457
3793
|
* Flag every agent declared in a stack. Returns findings (empty = clean,
|
|
2458
3794
|
* which is what every app package should be).
|
|
2459
3795
|
*/
|
|
2460
|
-
declare function validateAiAgentAuthoring(stack: AnyRec$
|
|
2461
|
-
|
|
2462
|
-
type HookBodyWriteSeverity = 'warning';
|
|
2463
|
-
interface HookBodyWriteFinding {
|
|
2464
|
-
/** v1 is advisory-only by contract — the type says so. */
|
|
2465
|
-
severity: HookBodyWriteSeverity;
|
|
2466
|
-
rule: string;
|
|
2467
|
-
/** Human-readable location, e.g. `hook "normalize_lead" › body`. */
|
|
2468
|
-
where: string;
|
|
2469
|
-
/** Config path, e.g. `hooks[0].body.source`. */
|
|
2470
|
-
path: string;
|
|
2471
|
-
message: string;
|
|
2472
|
-
hint: string;
|
|
2473
|
-
}
|
|
2474
|
-
declare const HOOK_BODY_WRITE_UNKNOWN_FIELD = "hook-body-write-unknown-field";
|
|
2475
|
-
/**
|
|
2476
|
-
* [#8663] The write-axis twin of `flow-template-field-unprovisioned` (#8340):
|
|
2477
|
-
* the body writes a field {@link IMPLICIT_FIELDS} exempts, but on THIS target
|
|
2478
|
-
* the platform registered that anchor without provisioning storage for it.
|
|
2479
|
-
*
|
|
2480
|
-
* A separate id at `warning` severity rather than a reclassification of
|
|
2481
|
-
* {@link HOOK_BODY_WRITE_UNKNOWN_FIELD}, matching #8340's precedent exactly:
|
|
2482
|
-
* the existence verdict is unchanged (the name IS addressable), and what is
|
|
2483
|
-
* added is a second, independently suppressible finding on the path where the
|
|
2484
|
-
* existence check stays silent.
|
|
2485
|
-
*/
|
|
2486
|
-
declare const HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR = "hook-body-write-unprovisioned-anchor";
|
|
2487
|
-
/** One syntactic write shape the extractor recognizes. */
|
|
2488
|
-
interface HookBodyWritePattern {
|
|
2489
|
-
/** Stable pattern id, carried on every extracted write. */
|
|
2490
|
-
readonly id: string;
|
|
2491
|
-
/** Author-facing syntax summary (for docs/diagnostics, not matching). */
|
|
2492
|
-
readonly syntax: string;
|
|
2493
|
-
/** Reconciliation fixture: extracting `source` must yield exactly `writes`. */
|
|
2494
|
-
readonly example: {
|
|
2495
|
-
readonly source: string;
|
|
2496
|
-
readonly writes: ReadonlyArray<{
|
|
2497
|
-
field: string;
|
|
2498
|
-
object?: string;
|
|
2499
|
-
}>;
|
|
2500
|
-
};
|
|
2501
|
-
}
|
|
2502
|
-
declare const HOOK_BODY_WRITE_PATTERNS: readonly HookBodyWritePattern[];
|
|
2503
|
-
/** A ledger pattern a given rule does NOT consume, and why. */
|
|
2504
|
-
interface BodyWritePatternExclusion {
|
|
2505
|
-
/** The {@link HOOK_BODY_WRITE_PATTERNS} entry id being excluded. */
|
|
2506
|
-
readonly id: string;
|
|
2507
|
-
/** Why the shape does not mean the same thing on this rule's surface. */
|
|
2508
|
-
readonly reason: string;
|
|
2509
|
-
}
|
|
2510
|
-
/**
|
|
2511
|
-
* The ledger shapes THIS rule consumes.
|
|
2512
|
-
*
|
|
2513
|
-
* Declared rather than implied: before the ledger carried a shape the hook
|
|
2514
|
-
* surface does not have, every write with no `object` was necessarily a
|
|
2515
|
-
* `ctx.input` write, and the rule could branch on that alone. It no longer can
|
|
2516
|
-
* — a `record-property-assign` write also carries no object, and would have
|
|
2517
|
-
* been reported as "the hook writes 'stage' to its input", which is false.
|
|
2518
|
-
* Each consumer declaring its own subset is what stops the next added shape
|
|
2519
|
-
* from silently landing in a branch that was never written for it.
|
|
2520
|
-
*/
|
|
2521
|
-
declare const HOOK_BODY_WRITE_PATTERN_IDS: readonly string[];
|
|
2522
|
-
/** Ledger shapes this rule leaves alone, each with its reason. */
|
|
2523
|
-
declare const HOOK_BODY_WRITE_EXCLUSIONS: readonly BodyWritePatternExclusion[];
|
|
2524
|
-
type AnyRec$7 = Record<string, unknown>;
|
|
2525
|
-
/** One statically-extracted field write found in an L2 body. */
|
|
2526
|
-
interface ExtractedHookBodyWrite {
|
|
2527
|
-
/** Which {@link HOOK_BODY_WRITE_PATTERNS} entry matched. */
|
|
2528
|
-
patternId: string;
|
|
2529
|
-
/** Target object name; `undefined` = the hook's own target object(s). */
|
|
2530
|
-
object?: string;
|
|
2531
|
-
/** The `ctx.api` method for diagnostics (`insert`/`create`/`update`/`updateById`). */
|
|
2532
|
-
method?: string;
|
|
2533
|
-
field: string;
|
|
2534
|
-
}
|
|
2535
|
-
/** Everything one parse of an L2 body yields. */
|
|
2536
|
-
interface ExtractedHookBodyWriteSet {
|
|
2537
|
-
/** Every literal write the {@link HOOK_BODY_WRITE_PATTERNS} ledger declares. */
|
|
2538
|
-
writes: ExtractedHookBodyWrite[];
|
|
2539
|
-
/**
|
|
2540
|
-
* `ctx.record` is handed to something as a VALUE somewhere in the body — an
|
|
2541
|
-
* argument, an assignment RHS, a spread, a return — rather than only having
|
|
2542
|
-
* its properties read and written, or being truthiness/type tested.
|
|
2543
|
-
*
|
|
2544
|
-
* The action rule needs this to tell a dead snapshot write from a live one:
|
|
2545
|
-
* `ctx.record.stage = 'won'; await ctx.api.object('d').update(ctx.record)`
|
|
2546
|
-
* builds a payload and persists it, so the assignment is not a no-op. When
|
|
2547
|
-
* this is true, no record write in the body can be judged, and none is
|
|
2548
|
-
* reported. (One-level aliasing — `const r = ctx.record` — reads as an
|
|
2549
|
-
* escape too, which is the safe direction: it suppresses findings.)
|
|
2550
|
-
*/
|
|
2551
|
-
ctxRecordEscapes: boolean;
|
|
2552
|
-
}
|
|
2553
|
-
/**
|
|
2554
|
-
* Extract every literal field write the pattern ledger declares from an L2
|
|
2555
|
-
* body's source. Parse-only (the source is never executed), error-tolerant
|
|
2556
|
-
* (a body with syntax errors simply yields fewer matches), and lazy: the
|
|
2557
|
-
* TypeScript compiler is not loaded when no pattern can possibly match.
|
|
2558
|
-
*
|
|
2559
|
-
* Thin projection of {@link extractHookBodyWriteSet} — use that one when the
|
|
2560
|
-
* `ctx.record` liveness signal matters, so the body is parsed once, not twice.
|
|
2561
|
-
*/
|
|
2562
|
-
declare function extractHookBodyWrites(source: string): ExtractedHookBodyWrite[];
|
|
2563
|
-
/** {@link extractHookBodyWrites} plus the `ctx.record` liveness signal, one parse. */
|
|
2564
|
-
declare function extractHookBodyWriteSet(source: string): ExtractedHookBodyWriteSet;
|
|
2565
|
-
/**
|
|
2566
|
-
* Validate L2 hook-body writes against target-object field declarations.
|
|
2567
|
-
* Pure `(stack) => Finding[]` (ADR-0019); safe on pre- or post-parse stacks.
|
|
2568
|
-
*/
|
|
2569
|
-
declare function validateHookBodyWrites(stack: AnyRec$7): HookBodyWriteFinding[];
|
|
3796
|
+
declare function validateAiAgentAuthoring(stack: AnyRec$7): AiAgentAuthoringFinding[];
|
|
2570
3797
|
|
|
2571
3798
|
type ActionBodyWriteSeverity = 'warning';
|
|
2572
3799
|
interface ActionBodyWriteFinding {
|
|
@@ -2582,6 +3809,17 @@ interface ActionBodyWriteFinding {
|
|
|
2582
3809
|
}
|
|
2583
3810
|
declare const ACTION_BODY_WRITE_UNKNOWN_FIELD = "action-body-write-unknown-field";
|
|
2584
3811
|
declare const ACTION_RECORD_WRITE_DISCARDED = "action-record-write-discarded";
|
|
3812
|
+
/**
|
|
3813
|
+
* [#10653] The action-surface twin of `hook-body-source-unparseable`. Same
|
|
3814
|
+
* extractor, same synthesised wrapper, same parse — so the body that came back
|
|
3815
|
+
* silently unread on the hook surface came back silently unread here too.
|
|
3816
|
+
*
|
|
3817
|
+
* Wiring only the hook rule would have left the blind half standing at the call
|
|
3818
|
+
* site next door, which this rule's own division of labour forbids: an action
|
|
3819
|
+
* body runs through the same `HookBodySchema` and the same sandbox, so it gets
|
|
3820
|
+
* the same treatment.
|
|
3821
|
+
*/
|
|
3822
|
+
declare const ACTION_BODY_SOURCE_UNPARSEABLE = "action-body-source-unparseable";
|
|
2585
3823
|
/**
|
|
2586
3824
|
* [#8663] The action-surface twin of `hook-body-write-unprovisioned-anchor`.
|
|
2587
3825
|
* Same question, same wording, same `warning` severity — this rule and the hook
|
|
@@ -2772,6 +4010,40 @@ declare const FLOW_INERT_NODE_CONDITION = "flow-inert-node-condition";
|
|
|
2772
4010
|
* how it divides labour with the #3810 run-time guard.
|
|
2773
4011
|
*/
|
|
2774
4012
|
declare const FLOW_MULTI_WRITE_UNFILTERED = "flow-multi-write-unfiltered";
|
|
4013
|
+
/**
|
|
4014
|
+
* #13681 / #14394 — a `loop` body that runs a node which can fail, with no
|
|
4015
|
+
* `try_catch` between the loop and that node. The first failing item kills the
|
|
4016
|
+
* whole sweep: `loop-node.ts:123-135` iterates with a bare `await` and no
|
|
4017
|
+
* `try`/`catch` anywhere in the file, so the body's failure propagates out of
|
|
4018
|
+
* the container and the remaining items are never processed.
|
|
4019
|
+
*
|
|
4020
|
+
* A **warning**, per the severity policy at the top of this file: a loop whose
|
|
4021
|
+
* body is deliberately allowed to die (fail fast on the first bad row) is a
|
|
4022
|
+
* legitimate reading, and the rule cannot prove the author did not mean it. What
|
|
4023
|
+
* it can do is make the choice visible at authoring time — today it is silent,
|
|
4024
|
+
* and the measured consequence is a 5-row sweep that processes 3 rows and
|
|
4025
|
+
* reports a run that "completed" nothing unusual.
|
|
4026
|
+
*
|
|
4027
|
+
* See {@link scanUncontainedLoopBodies} for the containment judgement, and
|
|
4028
|
+
* {@link FALLIBLE_NODE_TYPES} for what counts as fallible and why.
|
|
4029
|
+
*/
|
|
4030
|
+
declare const FLOW_LOOP_BODY_UNCONTAINED = "flow-loop-body-uncontained";
|
|
4031
|
+
/**
|
|
4032
|
+
* #13681 / #14394 — the near-miss shape: a `try_catch` that declares no `catch`
|
|
4033
|
+
* region. `catch` is optional in the schema (`control-flow.zod.ts:315`) and
|
|
4034
|
+
* omitting it makes the container **fail** (`try-catch-node.ts:190`), so the
|
|
4035
|
+
* wrapped region dies exactly like an unwrapped one — measured side by side, the
|
|
4036
|
+
* no-`catch` run and the no-wrapper control produce identical output.
|
|
4037
|
+
*
|
|
4038
|
+
* This is the family's first target for the containment pair, not an extra: an
|
|
4039
|
+
* author who reaches for `try_catch` has recognised the hazard and stopped one
|
|
4040
|
+
* key short, and before this rule got **zero containment and zero diagnostics**.
|
|
4041
|
+
* A warning rather than an error under the same policy — a `retry`-only
|
|
4042
|
+
* `try_catch` (retry the region, then fail loudly) is a legitimate reading.
|
|
4043
|
+
*
|
|
4044
|
+
* See {@link scanTryCatchWithoutCatch}.
|
|
4045
|
+
*/
|
|
4046
|
+
declare const FLOW_TRY_CATCH_WITHOUT_CATCH = "flow-try-catch-without-catch";
|
|
2775
4047
|
/**
|
|
2776
4048
|
* Lint every flow for known authoring anti-patterns — its own graph AND every
|
|
2777
4049
|
* nested ADR-0031 region (#5383). Returns a (possibly empty) list of findings;
|
|
@@ -2788,13 +4060,44 @@ interface LivenessLintFinding {
|
|
|
2788
4060
|
}
|
|
2789
4061
|
declare const LIVENESS_DEAD_PROPERTY = "liveness-dead-property";
|
|
2790
4062
|
declare const LIVENESS_EXPERIMENTAL_PROPERTY = "liveness-experimental-property";
|
|
4063
|
+
declare const LIVENESS_PLANNED_PROPERTY = "liveness-planned-property";
|
|
4064
|
+
declare const LIVENESS_LIVE_ELSEWHERE_PROPERTY = "liveness-live-elsewhere-property";
|
|
2791
4065
|
type AnyRec$2 = Record<string, unknown>;
|
|
4066
|
+
/**
|
|
4067
|
+
* The property paths of `type`'s ledger that warn an author for authoring them
|
|
4068
|
+
* — i.e. exactly the set {@link lintLivenessProperties} raises a finding on for
|
|
4069
|
+
* that type, exposed as a decision procedure.
|
|
4070
|
+
*
|
|
4071
|
+
* WHY THIS IS EXPORTED RATHER THAN RE-DERIVED. A second tool wanted the same
|
|
4072
|
+
* fact and the first attempt at it re-read the ledger itself, which is how the
|
|
4073
|
+
* two halves of one `os lint` run came to point in opposite directions: the
|
|
4074
|
+
* CLI's i18n coverage walker demanded `flows.*` translation keys for a group
|
|
4075
|
+
* whose ledger row is `planned` + `authorWarn`, so omitting the keys drew
|
|
4076
|
+
* `i18n/missing-flow` and authoring them drew `liveness-planned-property` — in
|
|
4077
|
+
* the SAME run, with no third option for the author. A demand side and a warn
|
|
4078
|
+
* side that disagree can only be kept honest by reading ONE verdict, so this
|
|
4079
|
+
* returns the very map `checkItem` iterates rather than a parallel reading of
|
|
4080
|
+
* the same JSON.
|
|
4081
|
+
*
|
|
4082
|
+
* Keys are the ledger's own property paths, `children` flattened one level as
|
|
4083
|
+
* `parent.child` — the shape `checkItem` resolves. Unreadable or absent ledger
|
|
4084
|
+
* ⇒ the empty set, which is also the state in which `lintLivenessProperties`
|
|
4085
|
+
* warns on nothing: the two sides go quiet together rather than one of them
|
|
4086
|
+
* going quiet alone.
|
|
4087
|
+
*
|
|
4088
|
+
* Deliberately NOT memoized, for the same reason `lintLivenessProperties`
|
|
4089
|
+
* re-reads on every call: a cached verdict outlives the ledger edit that
|
|
4090
|
+
* changes it, and a stale "nothing warns here" is the silent state this whole
|
|
4091
|
+
* family exists to prevent.
|
|
4092
|
+
*/
|
|
4093
|
+
declare function authorWarnedProperties(type: string): ReadonlySet<string>;
|
|
2792
4094
|
/**
|
|
2793
4095
|
* Lint the compiled stack for authored properties the liveness ledger flags as
|
|
2794
4096
|
* misleading. Advisory only — returns findings, never throws. Covers every
|
|
2795
4097
|
* governed metadata type: objects (incl. `enable.*`) and their fields walk
|
|
2796
|
-
* bespoke nesting
|
|
2797
|
-
*
|
|
4098
|
+
* bespoke nesting, and translation bundles walk their locale entries (#11288);
|
|
4099
|
+
* the remaining types are flat stack collections. Container properties fan out
|
|
4100
|
+
* over arrays (each flow node, each dataset measure). The
|
|
2798
4101
|
* mechanism stays ledger-driven — coverage grows by marking more entries
|
|
2799
4102
|
* `authorWarn` rather than touching this code.
|
|
2800
4103
|
*/
|
|
@@ -2829,6 +4132,7 @@ type AnyRec = Record<string, any>;
|
|
|
2829
4132
|
declare const VIEW_KEY_COLLISION = "view-key-collision";
|
|
2830
4133
|
declare const VIEW_REF_FORM_TARGET_MISSING = "view-ref-form-target-missing";
|
|
2831
4134
|
declare const VIEW_REF_FORM_TARGET_KIND = "view-ref-form-target-kind";
|
|
4135
|
+
declare const VIEW_REF_NAV_VIEW_MISSING = "view-ref-nav-view-missing";
|
|
2832
4136
|
declare function lintViewRefs(stack: AnyRec): ViewRefFinding[];
|
|
2833
4137
|
|
|
2834
4138
|
/**
|
|
@@ -2881,10 +4185,20 @@ interface LintIssue {
|
|
|
2881
4185
|
* `f.where ?? f.path` at the adapter) is the same discipline — a consumer-side
|
|
2882
4186
|
* fallback would let the next rule ship the positional spelling again, silently.
|
|
2883
4187
|
*
|
|
2884
|
-
* `path` is unchanged and stays positional: it is the slot
|
|
2885
|
-
* be a config path, `os validate` prints it after `at
|
|
2886
|
-
*
|
|
2887
|
-
*
|
|
4188
|
+
* `path` is unchanged and stays positional AT THE RULE LEVEL: it is the slot
|
|
4189
|
+
* that is SUPPOSED to be a config path, `os validate` prints it after `at`
|
|
4190
|
+
* (where the index resolves against the author's own config file), and the
|
|
4191
|
+
* runtime gate's `fingerprint` reads `where` and `path` together (making
|
|
4192
|
+
* `where` more specific cannot merge two findings that were distinct).
|
|
4193
|
+
*
|
|
4194
|
+
* [#10064] On the runtime gate's WIRE surface (`RuntimeAuthoringIssue.path`,
|
|
4195
|
+
* the 422 `issues[]` / 2xx `advisories`), the top-level collection index of a
|
|
4196
|
+
* collection-resident finding is rewritten to the entry's NAME
|
|
4197
|
+
* (`objects[417].sharingModel` → `objects.acme_invoice.sharingModel`) after
|
|
4198
|
+
* the differential — a runtime caller has no array to resolve `417` against;
|
|
4199
|
+
* that index numbers the gate's private per-write snapshot. The rewrite lives
|
|
4200
|
+
* in `runtime-gate.ts` (`nameKeyFindingPath`); rules keep emitting positional
|
|
4201
|
+
* paths and need no awareness of it.
|
|
2888
4202
|
*/
|
|
2889
4203
|
interface LocatedLintIssue extends LintIssue {
|
|
2890
4204
|
/** Human-readable location, e.g. `object "sys_account" · index 'uniq_org_email'`. */
|
|
@@ -2980,4 +4294,64 @@ declare function lintLegacyOrganizationComposites(objects: any[]): LocatedLintIs
|
|
|
2980
4294
|
*/
|
|
2981
4295
|
declare function lintDataModel(objects: any[]): LintIssue[];
|
|
2982
4296
|
|
|
2983
|
-
export { ACTION_BODY_WRITE_EXCLUSIONS, ACTION_BODY_WRITE_PATTERNS, ACTION_BODY_WRITE_PATTERN_IDS, ACTION_BODY_WRITE_UNKNOWN_FIELD, ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR, ACTION_NAME_UNDEFINED, ACTION_NO_PLACEMENT, ACTION_RECORD_WRITE_DISCARDED, ACTION_RECORD_WRITE_PATTERNS, ACTION_RECORD_WRITE_PATTERN_IDS, AGENT_AUTHORING_WITHDRAWN, AI_SKILL_SURFACE_MISMATCH, AI_SKILL_TOOL_UNRESOLVED, APPROVAL_APPROVERS_MAY_RESOLVE_EMPTY, APPROVAL_APPROVER_CROSS_ORG_UNSUPPORTED, APPROVAL_APPROVER_NOT_MEMBERSHIP_TIER, APPROVAL_APPROVER_TYPE_DEPRECATED, APPROVAL_APPROVER_TYPE_UNKNOWN, APPROVAL_APPROVER_TYPE_UNSUPPORTED, APPROVAL_DECISION_OUTPUTS_RESERVED, APPROVAL_ESCALATION_REASSIGN_NO_TARGET, APPROVAL_EXPRESSION_INVALID, APPROVAL_EXPRESSION_NO_EMPTY_POLICY, AUTONUMBER_LITERAL_TOKEN, AUTONUMBER_OPTIONAL_FIELD, AUTONUMBER_SELF_REFERENCE, AUTONUMBER_UNKNOWN_FIELD, type ActionBodyWriteExclusion, type ActionBodyWriteFinding, type ActionBodyWriteSeverity, type ActionLocationsFinding, type ActionLocationsSeverity, type ActionNameRefFinding, type ActionNameRefSeverity, type AiAgentAuthoringFinding, type AiAgentAuthoringSeverity, type AiSurfaceAffinityFinding, type AiSurfaceAffinitySeverity, type AiToolRefFinding, type AiToolRefSeverity, type ApprovalApproverFinding, type ApprovalApproverSeverity, type AutonumberLintFinding, type BodyWritePatternExclusion, CAPABILITY_REFERENCE_UNKNOWN, CHART_AXIS_NOT_SELECTED, CHART_CONFIG_MISSING, CHART_DATASET_UNKNOWN, CHART_DIMENSION_UNKNOWN, CHART_FIELD_UNKNOWN, CHART_MEASURE_UNKNOWN, COMPONENT_PROPS_INVALID, COMPONENT_PROPS_UNKNOWN_KEY, type CapabilityRefFinding, type CapabilityRefSeverity, type ChartBindingFinding, type ChartBindingSeverity, type ComponentPropsFinding, type ComponentPropsSeverity, DASHBOARD_ACTION_ROUTE_UNRESOLVED, DASHBOARD_ACTION_TARGET_UNDEFINED, DASHBOARD_FILTER_FIELD_UNKNOWN, DASHBOARD_FILTER_FIELD_UNPROVISIONED, DEFAULT_AGENT_OUTSIDE_ROSTER, type DashboardActionRefFinding, type DashboardActionRefSeverity, type EmptyCombinatorFinding, type EmptyCombinatorSeverity, type ExprIssue, type ExtractedHookBodyWrite, type ExtractedHookBodyWriteSet, FIELD_GROUP_EMPTY, FIELD_GROUP_SHADOWED, FIELD_GROUP_UNDECLARED, FILTER_EMPTY_COMBINATOR, FILTER_EMPTY_NODE, FILTER_PRESET_COMPARAND, FILTER_TOKEN_UNKNOWN, FLOW_APPROVAL_REVISE_DEAD_END, FLOW_APPROVAL_REVISE_DISABLED, FLOW_APPROVAL_REVISE_TARGET_NOT_SERVICE_OWNED, FLOW_APPROVAL_REVISE_UNMARKED_BACKEDGE, FLOW_BARE_DOLLAR_REF, FLOW_BRANCH_LABEL_UNMATCHED, FLOW_DATE_EQUALITY_FILTER, FLOW_DECISION_UNCONDITIONAL_BRANCH, FLOW_DEFAULT_EDGE_WITH_CONDITION, FLOW_DOUBLE_BRACE_INTERP, FLOW_DRAFT_STATUS_AMBIGUOUS, FLOW_ERROR_LABEL_NOT_FAULT, FLOW_INERT_NODE_CONDITION, FLOW_MULTIPLE_DEFAULT_EDGES, FLOW_MULTI_WRITE_UNFILTERED, FLOW_NODE_WRITE_UNKNOWN_FIELD, FLOW_NODE_WRITE_UNPROVISIONED_ANCHOR, FLOW_PHANTOM_AGGREGATION, FLOW_RUNAS_UNSCOPED, FLOW_TEMPLATE_FIELD_UNPROVISIONED, FLOW_TEMPLATE_LOOKUP_TRAVERSAL, FLOW_TEMPLATE_UNKNOWN_FIELD, FLOW_TIME_RELATIVE_ANTIPATTERN, FLOW_TIME_RELATIVE_DESCRIPTOR_INVALID, FLOW_TIME_RELATIVE_DESCRIPTOR_UNROUTABLE, FLOW_TRIGGER_UNKNOWN_EVENT, FLOW_TRIGGER_UNKNOWN_OBJECT, FLOW_TRIGGER_UNROUTABLE, FLOW_UPDATE_READONLY_FIELD, FLOW_UPDATE_READONLY_WHEN_FIELD, FLOW_WRITE_NODE_TYPES, FLOW_WRITE_NODE_TYPES_DEFERRED, FORM_COLSPAN_ABSOLUTE, FORM_FIELD_UNKNOWN, type FilterTokenFinding, type FilterTokenSeverity, type FlowLintFinding, type FlowNodeWriteFinding, type FlowNodeWriteSeverity, type FlowTemplatePathFinding, type FlowTemplatePathSeverity, type FlowTriggerReadinessFinding, type FlowTriggerReadinessSeverity, type FlowWriteNodeDeferral, type FormLayoutFinding, type FormLayoutSeverity, type FunctionalCompletenessFinding, type FunctionalCompletenessSeverity, HOOK_BODY_WRITE_EXCLUSIONS, HOOK_BODY_WRITE_PATTERNS, HOOK_BODY_WRITE_PATTERN_IDS, HOOK_BODY_WRITE_UNKNOWN_FIELD, HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR, type HookBodyWriteFinding, type HookBodyWritePattern, type HookBodyWriteSeverity, type JsxPageFinding, type JsxPageSeverity, LIST_VIEW_FILTERS_IN_VIEWS_MODE, LIVENESS_DEAD_PROPERTY, LIVENESS_EXPERIMENTAL_PROPERTY, type LintIssue, type ListViewModeFinding, type ListViewModeSeverity, type LivenessLintFinding, type LocatedLintIssue, MANAGED_API_METHOD_UNAFFORDABLE, MAX_SCHEMA_WALK_DEPTH, MEASURE_AGGREGATE_INCOHERENT, type ManagedApiMethodFinding, NAV_OBJECT_UNGRANTED, NAV_OBJECT_UNSERVABLE, NAV_TARGET_UNRESOLVED, NULL_GUARD_HINT, type NavAccessFinding, type NavAccessSeverity, type NavObjectServabilityFinding, type NavTargetRefFinding, type NavTargetRefSeverity, type NullGuardFinding, type NullGuardOptions, OBJECT_REFERENCE_UNKNOWN, OBJECT_REFERENCE_UNREGISTERED_PLATFORM, OPEN_VOCABULARY_PROBES, ORG_AXIS_CROSS_ORG_BU_GRANT, ORG_AXIS_PERMISSION_INHERITANCE, type ObjectRefFinding, type ObjectRefSeverity, type OrgAxisFinding, type OrgAxisSeverity, PAGE_FIELD_UNKNOWN, PAGE_FIELD_UNPROVISIONED, PAGE_SOURCE_CLASSNAME, PREDICATE_PATH_UNRESOLVED, PREDICATE_PATH_UNROOTED, PREDICATE_RHS_PATH_SHAPED, PRE_SEAL_PHASES, type PageFieldFinding, type PageFieldSeverity, type PredicatePathFinding, type PredicatePathOptions, type PredicatePathSeverity, type PresetComparandFinding, type PresetComparandSeverity, REACT_BLOCK_NEEDS_RECORD_CONTEXT, REACT_CHART_AGGREGATE_INVALID, REACT_CHART_AXIS_UNKNOWN, REACT_CHART_DRILLDOWN_INVALID, REACT_CHART_FIELD_UNKNOWN, REACT_CHART_FIELD_UNPROVISIONED, REFERENCE_INTEGRITY_RULES, RLS_PREDICATE_OVER_BUDGET, RLS_PREDICATE_UNENFORCEABLE, RLS_PREDICATE_UNPARSEABLE, RUNTIME_AJV_OPTIONS, type ReactPageFinding, type ReactPageSeverity, type ReactPropFinding, type ReactPropSeverity, type ReadonlyFlowWriteFinding, type ReadonlyFlowWriteSeverity, type RecordTitleFinding, type RecordTitleSeverity, type ReferenceIntegrityFinding, type ReferenceIntegrityRule, type ReferenceIntegritySeverity, type RlsPredicateFinding, type RlsPredicateSeverity, type RuleCompilabilityFinding, type RuleCompilabilitySeverity, type RuleSchemaFormatFinding, type RuleSchemaFormatSeverity, SEAL_MARKERS, SEARCHABLE_FIELD_UNKNOWN, SEARCHABLE_FIELD_UNPROVISIONED, SEARCHABLE_FIELD_UNSEARCHABLE, SECURITY_ANCHOR_HIGH_PRIVILEGE, SECURITY_BOOK_AUDIENCE_UNKNOWN_SET, SECURITY_CBP_NO_RELATION, SECURITY_DELEGATION_MISSING_REASON, SECURITY_EXTERNAL_WIDER, SECURITY_FLS_UNQUALIFIED_KEY, SECURITY_GRANT_EXPIRED_AT_AUTHORING, SECURITY_MASTER_DETAIL_UNGRANTED, SECURITY_OWD_ALIAS, SECURITY_OWD_UNSET, SECURITY_PRIVATE_NO_READSCOPE, SECURITY_ROLE_WORD, SECURITY_WILDCARD_VAMA, SEED_INSERT_MODE_DUPLICATES_ON_REPLAY, SEED_VALUE_OUTSIDE_STATE_MACHINE, SEMANTIC_ROLE_FIELD_UNKNOWN, SEMANTIC_ROLE_FIELD_UNPROVISIONED, SHARING_RULE_OBJECT_CONTROLLED_BY_PARENT, SHARING_RULE_OBJECT_NOT_SHAREABLE, SHARING_RULE_RUNTIME_VARIABLE_CONDITION, SHARING_RULE_UNLOWERABLE_CONDITION, SORT_FIELD_UNKNOWN, SORT_FIELD_UNSORTABLE, STARTUP_OPEN_VOCABULARY_VERDICT, STARTUP_VERDICT_ASSERTIVE_WORDING, STARTUP_VERDICT_HINT, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type SearchableFieldFinding, type SearchableFieldRole, type SearchableFieldSeverity, type SecurityFinding, type SecuritySeverity, type SeedReplaySafetyFinding, type SeedReplaySafetySeverity, type SeedStateMachineFinding, type SeedStateMachineSeverity, type SemanticRoleFinding, type SemanticRoleSeverity, type Severity, type SharingRuleEnforceabilityFinding, type SharingRuleEnforceabilitySeverity, type SortableFieldFinding, type SortableFieldSeverity, type SourceStyleFinding, type SourceStyleSeverity, type StartupRegistryVerdictFinding, type StartupRegistryVerdictOptions, type StartupRegistryVerdictSeverity, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, TITLE_FORMAT_RETIRED, TITLE_UNRESOLVABLE, TRANSLATION_OPTION_KEY_UNKNOWN, TRANSLATION_SECTION_NAME_MISSING, TRANSLATION_TARGET_UNKNOWN, type TranslatableSectionFinding, type TranslatableSectionSeverity, type TranslationRefFinding, type TranslationRefSeverity, UNIQUE_DOUBLE_DECLARATION, UNIQUE_LEGACY_ORGANIZATION_COMPOSITE, UNIQUE_UNSCOPED_DECLARED_INDEX, VALIDATION_RULE_REGEX_UNCOMPILABLE, VALIDATION_RULE_SCHEMA_UNCOMPILABLE, VALIDATION_RULE_SCHEMA_UNKNOWN_FORMAT, VIEW_CONTAINER_SHAPE, VIEW_KEY_COLLISION, VIEW_REF_FORM_TARGET_KIND, VIEW_REF_FORM_TARGET_MISSING, VISIBILITY_BARE_IDENTIFIER, VISIBILITY_PREDICATE_OVER_BUDGET, VISIBILITY_PREDICATE_SYNTAX, VISIBILITY_ROOT_MISLAYERED, type ViewContainerFinding, type ViewContainerSeverity, type ViewRefFinding, type VisibilityFinding, type VisibilityLayer, type VisibilityOptions, type VisibilitySeverity, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_LEGACY_ANALYTICS_SHAPE, WIDGET_LEGACY_ANALYTICS_UNRENDERABLE, WIDGET_MEASURE_UNKNOWN, type WalkedComponent, type WalkedValidationRule, type WidgetBindingFinding, type WidgetBindingSeverity, buildAccessMatrix, checkSortDeclaration, diffAccessMatrix, extractHookBodyWriteSet, extractHookBodyWrites, findStartupRegistryVerdicts, findUnguardedNullableOperands, isSourceAuthoredPage, lintAutonumberFormats, lintDataModel, lintFlowPatterns, lintLegacyOrganizationComposites, lintLivenessProperties, lintUniqueDeclarations, lintUnscopedDeclaredIndexes, lintViewRefs, nearestRegisteredFormat, nullGuardMessage, validateActionBodyWrites, validateActionLocations, validateActionNameRefs, validateAiAgentAuthoring, validateAiSurfaceAffinity, validateAiToolReferences, validateApprovalApprovers, validateCapabilityReferences, validateChartBindings, validateComponentProps, validateDashboardActionRefs, validateEmptyCombinators, validateFilterTokens, validateFlowNodeWrites, validateFlowTemplatePaths, validateFlowTriggerReadiness, validateFormLayout, validateFunctionalCompleteness, validateHookBodyWrites, validateJsxPages, validateListViewMode, validateManagedApiMethods, validateNavAccess, validateNavObjectServability, validateNavTargetRefs, validateObjectReferences, validateOrgAxisRedLines, validatePageFieldBindings, validatePageSourceStyling, validatePredicatePathRefs, validatePresetComparands, validateReactPageProps, validateReactPages, validateReadonlyFlowWrites, validateRecordTitle, validateReferenceIntegrity, validateResponsiveStyles, validateRlsPredicateEnforceability, validateRuleCompilability, validateRuleSchemaFormats, validateSearchableFields, validateSecurityPosture, validateSecurityRoleWord, validateSeedReplaySafety, validateSeedStateMachine, validateSemanticRoles, validateSharingRuleEnforceability, validateSortableFields, validateStackExpressions, validateTranslatableSections, validateTranslationReferences, validateViewContainers, validateVisibilityPredicates, validateWidgetBindings, walkPageComponents };
|
|
4297
|
+
/** Which parse produced a finding — see the door table in the module header. */
|
|
4298
|
+
type EnvelopeAuditDoor = 'PageSchema' | 'PageComponentSchema' | 'ComponentPropsMap';
|
|
4299
|
+
/** One bare-expression position: authored as a string, normalized by a parse. */
|
|
4300
|
+
interface BareExpressionFinding {
|
|
4301
|
+
/** The consumer's page label, e.g. `<ExportName> (<page.name>)`. */
|
|
4302
|
+
page: string;
|
|
4303
|
+
/** Authored path from the page root, e.g. `slots.alerts[0].visibleWhen`. */
|
|
4304
|
+
path: string;
|
|
4305
|
+
/** The bare string the author wrote — what reaches the wire verbatim. */
|
|
4306
|
+
authored: string;
|
|
4307
|
+
/** Set when the parse also RENAMED the key (deprecated alias). */
|
|
4308
|
+
normalizedTo?: string;
|
|
4309
|
+
door: EnvelopeAuditDoor;
|
|
4310
|
+
}
|
|
4311
|
+
/** The three-door union over one page, findings and door preconditions both. */
|
|
4312
|
+
interface PageEnvelopeAudit {
|
|
4313
|
+
/** Every bare-expression position any door found, deduped by path, sorted. */
|
|
4314
|
+
findings: BareExpressionFinding[];
|
|
4315
|
+
/** Door 1 could not run — the page itself does not parse. */
|
|
4316
|
+
pageParseError?: string;
|
|
4317
|
+
/** Door 2 could not run for these components. */
|
|
4318
|
+
componentParseErrors: {
|
|
4319
|
+
path: string;
|
|
4320
|
+
type: string;
|
|
4321
|
+
issues: string;
|
|
4322
|
+
}[];
|
|
4323
|
+
/** Door 3 has no schema to parse these components' `properties` with. */
|
|
4324
|
+
unmappedTypes: {
|
|
4325
|
+
path: string;
|
|
4326
|
+
type: string;
|
|
4327
|
+
}[];
|
|
4328
|
+
/** Door 3 could not run — the authored bag is refused by its props schema. */
|
|
4329
|
+
unreadableProps: {
|
|
4330
|
+
path: string;
|
|
4331
|
+
type: string;
|
|
4332
|
+
issues: string;
|
|
4333
|
+
}[];
|
|
4334
|
+
/** How many components the walk reached (coverage floor for doors 2 and 3). */
|
|
4335
|
+
componentCount: number;
|
|
4336
|
+
}
|
|
4337
|
+
/**
|
|
4338
|
+
* Run all three parse doors over one authored page and union their findings.
|
|
4339
|
+
*
|
|
4340
|
+
* Read `findings` for the verdict, and assert the four precondition channels
|
|
4341
|
+
* separately (see the module header — a door that could not open reports
|
|
4342
|
+
* there, never as a silently smaller `findings`).
|
|
4343
|
+
*
|
|
4344
|
+
* Exported findings are deduped by path: a top-level component's
|
|
4345
|
+
* `visibleWhen` is legitimately seen by doors 1 AND 2, and reporting it twice
|
|
4346
|
+
* would read as two defects.
|
|
4347
|
+
*
|
|
4348
|
+
* @param page - The authored page object, exactly as exported (never parsed
|
|
4349
|
+
* first — the audit's whole subject is what the raw export serves).
|
|
4350
|
+
* @param pageLabel - How findings should name the page; consumers use
|
|
4351
|
+
* `<ExportName> (<page.name>)`.
|
|
4352
|
+
*/
|
|
4353
|
+
declare function auditPageExpressionEnvelopes(page: unknown, pageLabel: string): PageEnvelopeAudit;
|
|
4354
|
+
/** Render findings as the actionable red an author reads in CI. */
|
|
4355
|
+
declare function renderBareExpressionFindings(findings: readonly BareExpressionFinding[]): string;
|
|
4356
|
+
|
|
4357
|
+
export { ACTION_API_UPDATE_READONLY_WHEN_FIELD, ACTION_BODY_SOURCE_UNPARSEABLE, ACTION_BODY_WRITE_EXCLUSIONS, ACTION_BODY_WRITE_PATTERNS, ACTION_BODY_WRITE_PATTERN_IDS, ACTION_BODY_WRITE_UNKNOWN_FIELD, ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR, ACTION_NAME_UNDEFINED, ACTION_NO_PLACEMENT, ACTION_RECORD_WRITE_DISCARDED, ACTION_RECORD_WRITE_PATTERNS, ACTION_RECORD_WRITE_PATTERN_IDS, AGENT_AUTHORING_WITHDRAWN, AI_SKILL_SURFACE_MISMATCH, AI_SKILL_TOOL_UNRESOLVED, APPROVAL_APPROVERS_MAY_RESOLVE_EMPTY, APPROVAL_APPROVER_CROSS_ORG_UNSUPPORTED, APPROVAL_APPROVER_NOT_MEMBERSHIP_TIER, APPROVAL_APPROVER_TYPE_DEPRECATED, APPROVAL_APPROVER_TYPE_UNKNOWN, APPROVAL_APPROVER_TYPE_UNSUPPORTED, APPROVAL_DECISION_OUTPUTS_RESERVED, APPROVAL_ESCALATION_REASSIGN_NO_TARGET, APPROVAL_EXPRESSION_INVALID, APPROVAL_EXPRESSION_NO_EMPTY_POLICY, AUTONUMBER_LITERAL_TOKEN, AUTONUMBER_OPTIONAL_FIELD, AUTONUMBER_SELF_REFERENCE, AUTONUMBER_UNKNOWN_FIELD, type ActionBodyWriteExclusion, type ActionBodyWriteFinding, type ActionBodyWriteSeverity, type ActionLocationsFinding, type ActionLocationsSeverity, type ActionNameRefFinding, type ActionNameRefSeverity, type AiAgentAuthoringFinding, type AiAgentAuthoringSeverity, type AiSurfaceAffinityFinding, type AiSurfaceAffinitySeverity, type AiToolRefFinding, type AiToolRefSeverity, type ApprovalApproverFinding, type ApprovalApproverSeverity, type AutonumberLintFinding, type BareExpressionFinding, type BodyWritePatternExclusion, CAPABILITY_REFERENCE_UNKNOWN, CHART_AXIS_NOT_SELECTED, CHART_CONFIG_MISSING, CHART_DATASET_UNKNOWN, CHART_DIMENSION_UNKNOWN, CHART_FIELD_UNKNOWN, CHART_MEASURE_UNKNOWN, COMPONENT_PROPS_INVALID, COMPONENT_PROPS_UNKNOWN_KEY, COMPONENT_TYPE_UNKNOWN, type CapabilityRefFinding, type CapabilityRefSeverity, type ChartBindingFinding, type ChartBindingSeverity, type CheckedParse, type CheckedParseOptions, type ComponentPropsFinding, type ComponentPropsSeverity, type ComponentTypeFinding, DASHBOARD_ACTION_ROUTE_UNRESOLVED, DASHBOARD_ACTION_TARGET_UNDEFINED, DASHBOARD_FILTER_FIELD_NOT_INCLUDED, DASHBOARD_FILTER_FIELD_UNKNOWN, DASHBOARD_FILTER_FIELD_UNPROVISIONED, DATASET_FIELD_NOT_INCLUDED, DATASET_FIELD_UNKNOWN, DATASET_FILTER_FIELD_UNKNOWN, DATASET_INCLUDE_UNKNOWN, DEFAULT_AGENT_LEGACY_ALIAS, DEFAULT_AGENT_OUTSIDE_ROSTER, type DashboardActionRefFinding, type DashboardActionRefSeverity, type DatasetRefFinding, type DatasetRefSeverity, type EmptyCombinatorFinding, type EmptyCombinatorSeverity, type EnvelopeAuditDoor, type ExprIssue, type ExtractedHookBodyWrite, type ExtractedHookBodyWriteSet, FIELD_GROUP_EMPTY, FIELD_GROUP_SHADOWED, FIELD_GROUP_UNDECLARED, FIELD_RULE_BOUND_ROOTS, FILTER_EMPTY_COMBINATOR, FILTER_EMPTY_NODE, FILTER_PRESET_COMPARAND, FILTER_TOKEN_UNKNOWN, FLOW_APPROVAL_REVISE_DEAD_END, FLOW_APPROVAL_REVISE_DISABLED, FLOW_APPROVAL_REVISE_TARGET_NOT_SERVICE_OWNED, FLOW_APPROVAL_REVISE_UNMARKED_BACKEDGE, FLOW_BARE_DOLLAR_REF, FLOW_BRANCH_LABEL_UNMATCHED, FLOW_DATE_EQUALITY_FILTER, FLOW_DECISION_UNCONDITIONAL_BRANCH, FLOW_DEFAULT_EDGE_WITH_CONDITION, FLOW_DOUBLE_BRACE_INTERP, FLOW_DRAFT_STATUS_AMBIGUOUS, FLOW_ERROR_LABEL_NOT_FAULT, FLOW_INERT_NODE_CONDITION, FLOW_LOOP_BODY_UNCONTAINED, FLOW_MULTIPLE_DEFAULT_EDGES, FLOW_MULTI_WRITE_UNFILTERED, FLOW_NODE_WRITE_UNKNOWN_FIELD, FLOW_NODE_WRITE_UNPROVISIONED_ANCHOR, FLOW_PHANTOM_AGGREGATION, FLOW_RUNAS_UNSCOPED, FLOW_TEMPLATE_FIELD_UNPROVISIONED, FLOW_TEMPLATE_LOOKUP_TRAVERSAL, FLOW_TEMPLATE_UNKNOWN_FIELD, FLOW_TIME_RELATIVE_ANTIPATTERN, FLOW_TIME_RELATIVE_DESCRIPTOR_INVALID, FLOW_TIME_RELATIVE_DESCRIPTOR_UNROUTABLE, FLOW_TRIGGER_UNKNOWN_EVENT, FLOW_TRIGGER_UNKNOWN_OBJECT, FLOW_TRIGGER_UNROUTABLE, FLOW_TRY_CATCH_WITHOUT_CATCH, FLOW_UPDATE_READONLY_FIELD, FLOW_UPDATE_READONLY_WHEN_FIELD, FLOW_WRITE_NODE_TYPES, FLOW_WRITE_NODE_TYPES_DEFERRED, FORM_COLSPAN_ABSOLUTE, FORM_FIELD_UNKNOWN, FORM_SECTION_GROUP_UNKNOWN, type FieldPathAccount, type FieldPathVerdict, type FilterFieldKey, type FilterTokenFinding, type FilterTokenSeverity, type FlowLintFinding, type FlowNodeWriteFinding, type FlowNodeWriteSeverity, type FlowTemplatePathFinding, type FlowTemplatePathSeverity, type FlowTriggerReadinessFinding, type FlowTriggerReadinessSeverity, type FlowWriteNodeDeferral, type FormLayoutFinding, type FormLayoutSeverity, type FunctionalCompletenessFinding, type FunctionalCompletenessSeverity, type GraphField, type GraphObject, HOOK_API_UPDATE_READONLY_FIELD, HOOK_API_UPDATE_READONLY_WHEN_FIELD, HOOK_BODY_SOURCE_UNPARSEABLE, HOOK_BODY_WRITE_EXCLUSIONS, HOOK_BODY_WRITE_PATTERNS, HOOK_BODY_WRITE_PATTERN_IDS, HOOK_BODY_WRITE_UNKNOWN_FIELD, HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR, type HookBodyWriteFinding, type HookBodyWritePattern, type HookBodyWriteSeverity, type JsxPageFinding, type JsxPageSeverity, LIST_VIEW_FIELD_DOTTED, LIST_VIEW_FIELD_UNKNOWN, LIST_VIEW_FILTERS_IN_VIEWS_MODE, LIVENESS_DEAD_PROPERTY, LIVENESS_EXPERIMENTAL_PROPERTY, LIVENESS_LIVE_ELSEWHERE_PROPERTY, LIVENESS_PLANNED_PROPERTY, type LintIssue, type ListViewFieldRefFinding, type ListViewFieldRefSeverity, type ListViewModeFinding, type ListViewModeSeverity, type LivenessLintFinding, type LocatedLintIssue, MANAGED_API_METHOD_UNAFFORDABLE, MAX_SCHEMA_WALK_DEPTH, MEASURE_AGGREGATE_INCOHERENT, type ManagedApiMethodFinding, NAV_OBJECT_UNGRANTED, NAV_OBJECT_UNSERVABLE, NAV_TARGET_UNRESOLVED, NULL_GUARD_HINT, type NavAccessFinding, type NavAccessSeverity, type NavObjectServabilityFinding, type NavTargetRefFinding, type NavTargetRefSeverity, type NullGuardFinding, type NullGuardOptions, OBJECTUI_DERIVATION_PREDICATES, OBJECT_REFERENCE_UNKNOWN, OBJECT_REFERENCE_UNREGISTERED_PLATFORM, OPEN_VOCABULARY_PROBES, ORG_AXIS_CROSS_ORG_BU_GRANT, ORG_AXIS_PERMISSION_INHERITANCE, type ObjectGraph, type ObjectRefFinding, type ObjectRefSeverity, type OrgAxisFinding, type OrgAxisSeverity, PAGE_FIELD_UNKNOWN, PAGE_FIELD_UNPROVISIONED, PAGE_SECTION_GROUP_UNKNOWN, PAGE_SOURCE_CLASSNAME, PAGE_VISUALIZATION_WITHOUT_BINDING, PARSE_FAILURE_HINT, PREDICATE_PATH_UNRESOLVED, PREDICATE_PATH_UNROOTED, PREDICATE_RHS_PATH_SHAPED, PRE_SEAL_PHASES, type PageEnvelopeAudit, type PageFieldFinding, type PageFieldSeverity, type PageVisualizationFinding, type PageVisualizationSeverity, type PredicatePathFinding, type PredicatePathOptions, type PredicatePathSeverity, type PresetComparandFinding, type PresetComparandSeverity, REACT_BLOCK_NEEDS_RECORD_CONTEXT, REACT_CHART_AGGREGATE_INVALID, REACT_CHART_AXIS_UNKNOWN, REACT_CHART_DRILLDOWN_INVALID, REACT_CHART_FIELD_UNKNOWN, REACT_CHART_FIELD_UNPROVISIONED, REACT_PAGE_SOURCE_UNPARSEABLE, REACT_PROP_DEPRECATED, READONLY_ACTION_WRITE_EXCLUSIONS, READONLY_ACTION_WRITE_PATTERN_IDS, READONLY_HOOK_WRITE_EXCLUSIONS, READONLY_HOOK_WRITE_PATTERN_IDS, REFERENCE_INTEGRITY_RULES, RELATIONSHIP_FIELD_TYPES, RLS_PREDICATE_OVER_BUDGET, RLS_PREDICATE_UNENFORCEABLE, RLS_PREDICATE_UNPARSEABLE, RUNTIME_AJV_OPTIONS, type ReactPageFinding, type ReactPageSeverity, type ReactPropFinding, type ReactPropSeverity, type ReadonlyActionWriteFinding, type ReadonlyActionWriteSeverity, type ReadonlyFlowWriteFinding, type ReadonlyFlowWriteSeverity, type ReadonlyHookWriteFinding, type ReadonlyHookWriteSeverity, type RecordTitleFinding, type RecordTitleSeverity, type ReferenceIntegrityFinding, type ReferenceIntegrityRule, type ReferenceIntegritySeverity, type RlsPredicateFinding, type RlsPredicateSeverity, type RuleCompilabilityFinding, type RuleCompilabilitySeverity, type RuleSchemaFormatFinding, type RuleSchemaFormatSeverity, SEAL_MARKERS, SEARCHABLE_FIELD_UNKNOWN, SEARCHABLE_FIELD_UNPROVISIONED, SEARCHABLE_FIELD_UNSEARCHABLE, SECURITY_ANCHOR_HIGH_PRIVILEGE, SECURITY_BOOK_AUDIENCE_UNKNOWN_SET, SECURITY_CBP_AMBIGUOUS_RELATION, SECURITY_CBP_NO_RELATION, SECURITY_DELEGATION_MISSING_REASON, SECURITY_EXTERNAL_WIDER, SECURITY_FLS_UNQUALIFIED_KEY, SECURITY_GRANT_EXPIRED_AT_AUTHORING, SECURITY_MASTER_DETAIL_UNGRANTED, SECURITY_OWD_ALIAS, SECURITY_OWD_UNSET, SECURITY_PRIVATE_NO_READSCOPE, SECURITY_ROLE_WORD, SECURITY_WILDCARD_VAMA, SEED_INSERT_MODE_DUPLICATES_ON_REPLAY, SEED_VALUE_OUTSIDE_STATE_MACHINE, SEMANTIC_ROLE_FIELD_UNKNOWN, SEMANTIC_ROLE_FIELD_UNPROVISIONED, SHARING_RULE_OBJECT_CONTROLLED_BY_PARENT, SHARING_RULE_OBJECT_NOT_SHAREABLE, SHARING_RULE_RUNTIME_VARIABLE_CONDITION, SHARING_RULE_UNLOWERABLE_CONDITION, SORT_FIELD_UNKNOWN, SORT_FIELD_UNPROVISIONED, SORT_FIELD_UNSORTABLE, STARTUP_OPEN_VOCABULARY_VERDICT, STARTUP_SOURCE_UNPARSEABLE, STARTUP_VERDICT_ASSERTIVE_WORDING, STARTUP_VERDICT_HINT, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type SearchableFieldFinding, type SearchableFieldRole, type SearchableFieldSeverity, type SectionGroupFinding, type SectionGroupRef, type SecurityFinding, type SecuritySeverity, type SeedReplaySafetyFinding, type SeedReplaySafetySeverity, type SeedStateMachineFinding, type SeedStateMachineSeverity, type SemanticRoleFinding, type SemanticRoleSeverity, type Severity, type SharingRuleEnforceabilityFinding, type SharingRuleEnforceabilitySeverity, type SortableFieldFinding, type SortableFieldSeverity, type SourceParseFailure, type SourceStyleFinding, type SourceStyleSeverity, type StartupRegistryVerdictFinding, type StartupRegistryVerdictOptions, type StartupRegistryVerdictSeverity, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, TITLE_FORMAT_RETIRED, TITLE_UNRESOLVABLE, TRANSLATION_OPTION_KEY_UNKNOWN, TRANSLATION_SECTION_NAME_MISSING, TRANSLATION_TARGET_UNKNOWN, type TranslatableSectionFinding, type TranslatableSectionSeverity, type TranslationRefFinding, type TranslationRefSeverity, UNIQUE_DOUBLE_DECLARATION, UNIQUE_LEGACY_ORGANIZATION_COMPOSITE, UNIQUE_UNSCOPED_DECLARED_INDEX, VALIDATION_RULE_REGEX_UNCOMPILABLE, VALIDATION_RULE_SCHEMA_UNCOMPILABLE, VALIDATION_RULE_SCHEMA_UNKNOWN_FORMAT, VIEW_CONTAINER_SHAPE, VIEW_KEY_COLLISION, VIEW_PAGE_UNRESOLVED, VIEW_REF_FORM_TARGET_KIND, VIEW_REF_FORM_TARGET_MISSING, VIEW_REF_NAV_VIEW_MISSING, VISIBILITY_BARE_IDENTIFIER, VISIBILITY_PREDICATE_OVER_BUDGET, VISIBILITY_PREDICATE_SYNTAX, VISIBILITY_PREDICATE_UNKNOWN_FUNCTION, VISIBILITY_ROOT_MISLAYERED, type ViewContainerFinding, type ViewContainerSeverity, type ViewPageRefFinding, type ViewPageRefSeverity, type ViewRefFinding, type VisibilityFinding, type VisibilityLayer, type VisibilityOptions, type VisibilitySeverity, type VisualizationPredicate, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_FILTER_FIELD_NOT_INCLUDED, WIDGET_FILTER_FIELD_UNKNOWN, WIDGET_LEGACY_ANALYTICS_SHAPE, WIDGET_LEGACY_ANALYTICS_UNRENDERABLE, WIDGET_MEASURE_UNKNOWN, WIDGET_SORTBY_UNSELECTED, type WalkedComponent, type WalkedValidationRule, type WidgetBindingFinding, type WidgetBindingSeverity, auditPageExpressionEnvelopes, authorWarnedProperties, buildAccessMatrix, checkSectionGroupRefs, checkSortDeclaration, describeFieldPathVerdict, describeParseFailure, diffAccessMatrix, extractHookBodyWriteSet, extractHookBodyWrites, fieldRuleRootIssue, findStartupRegistryVerdicts, findUnguardedNullableOperands, indexObjectFieldGroups, indexObjectGraph, isSourceAuthoredPage, isUnjudgeable, joinablePrefixes, lintAutonumberFormats, lintDataModel, lintFlowPatterns, lintLegacyOrganizationComposites, lintLivenessProperties, lintUniqueDeclarations, lintUnscopedDeclaredIndexes, lintViewRefs, listNames, nearestName, nearestRegisteredFormat, nullGuardMessage, renderBareExpressionFindings, resolveFieldPath, sectionGroupRefs, suggestName, validateActionBodyWrites, validateActionLocations, validateActionNameRefs, validateAiAgentAuthoring, validateAiSurfaceAffinity, validateAiToolReferences, validateApprovalApprovers, validateCapabilityReferences, validateChartBindings, validateComponentProps, validateComponentTypes, validateDashboardActionRefs, validateDatasetReferences, validateEmptyCombinators, validateFilterTokens, validateFlowNodeWrites, validateFlowTemplatePaths, validateFlowTriggerReadiness, validateFormLayout, validateFunctionalCompleteness, validateHookBodyWrites, validateJsxPages, validateListViewFieldRefs, validateListViewMode, validateManagedApiMethods, validateNavAccess, validateNavObjectServability, validateNavTargetRefs, validateObjectReferences, validateOrgAxisRedLines, validatePageFieldBindings, validatePageSourceStyling, validatePageVisualizationBindings, validatePredicatePathRefs, validatePresetComparands, validateReactPageProps, validateReactPages, validateReadonlyActionWrites, validateReadonlyFlowWrites, validateReadonlyHookWrites, validateRecordTitle, validateReferenceIntegrity, validateResponsiveStyles, validateRlsPredicateEnforceability, validateRuleCompilability, validateRuleSchemaFormats, validateSearchableFields, validateSecurityPosture, validateSecurityRoleWord, validateSeedReplaySafety, validateSeedStateMachine, validateSemanticRoles, validateSharingRuleEnforceability, validateSortableFields, validateStackExpressions, validateTranslatableSections, validateTranslationReferences, validateViewContainers, validateViewPageRefs, validateVisibilityPredicates, validateWidgetBindings, walkFilterFieldKeys, walkPageComponents };
|