Repository navigation
Commit 762db99
fix(lint): one-line verdicts for the sort, search, form-predicate, bulk-dispatch, component-type and preset-comparand rules;
Part of #22161
Clause-②: no
Stage 2 of the card, slice 6: the 12 rule ids of six whole
`packages/lint` source files — the list-view sort and search rules, the
metadata-form predicate-path rule, the bulk-dispatch contract rule, the
component-type rule and the preset-comparand rule. The card stays open
for the later slices listed under "Remaining for later slices" below.
## What changes
- **One verdict line per finding.** Each finding of the 12 ids prints a
`message` of one verdict sentence. Every finding the rules' own suites
fire is now 193 characters or fewer, and the runtime publish gate's
suites (the whole `@objectstack/metadata-protocol` suite) 137 or fewer;
the longest of each id was 287 to 927 before.
- **The verdict no longer repeats what `where` names:** the view that
wires an action (`action-dispatch-contract-mismatch`'s `where` is `view
"task" · list · bulkActions`). This follows slice 4's accepted design
call.
- **Two shared clauses, one wording each:**
- the unprovisioned-anchor ids (`sort-field-unprovisioned`,
`searchable-field-unprovisioned`) print the same one-clause cause the
converted anchor rules already print, `unprovisionedAnchorVerdict()`
from `system-fields.ts` (slice 2's short form);
- the two virtual-entry ids (`sort-field-unsortable`, the virtual arm of
`searchable-field-unsearchable`) state the storage fact through one new
clause, `virtualFieldClause()`, exported from
`validate-searchable-fields.ts` (not on the barrel; the sort file
already imports from it).
- **A quoted text is quoted to its verdict, never rewritten:**
- a retired component type (`user:profile`, `element:filter`,
`element:form`, `ai:chat_window`) is quoted to the HEAD of its
`RETIRED_PAGE_COMPONENT_TYPES` prescription — the clause before its
first dash, else its first sentence (`retiredTypeHead()`, exported, not
on the barrel) — and the verdict says the parse refuses the node by name
and that `os validate` prints the whole prescription. The rule still
authors no second copy: the test holds that the head is a prefix of the
map's text, where it used to hold byte equality.
- `filter-preset-comparand` opens with the first sentence of the refusal
the schema door shares (`bareDateRangePresetComparandMessage`, cut at
its first period: `"last_30_days" is a dashboard date-range PRESET name,
not a filter value`), then the operator and the preset's `{date-macro}`
window (`presetComparandVerdict()`, exported, not on the barrel). The
window is read from `DATE_RANGE_PRESET_MACRO_WINDOWS` in the refusal's
own spelling, and a test holds the two equal for all 13 presets; another
holds every preset under the longest operator spelling
(`greater_than_or_equal`) to the 200 bound.
- `searchable-field-unsearchable` quotes at most three names of an
object's declared set, then `(and N more)` (`declaredRoster()`); the
`fix:` line still names `OBJECT.searchableFields`.
- **The reasoning moves to `RULE_EXPLANATIONS`**
(`packages/lint/src/rule-explanations.ts`), 12 new entries, so `os
explain RULE_ID` prints it and the CLI's `rule:` line ends with the
pointer for these ids. No CLI source changes: `explainPointer()` and `os
explain` resolve any key the table holds
(`packages/cli/test/explain-rule-id.test.ts` iterates every key; run
below); `node bin/run-dev.js explain sort-field-unknown` prints the
entry. The 75 entries already in the table are byte-equal at the head;
the one existing constant this slice reuses,
`UNPROVISIONED_ANCHOR_CAUSE`, changes only its `//` doc comment (it now
names the two new ids it serves).
- **Nothing else moves.** Rule ids, severities, `path`, `hint` (the
`fix:` line) and what each rule accepts or refuses are unchanged. No
condition, branch, dedupe key or skip moved; every hunk in the six rule
files is a `message` expression, a comment, an import, a verdict helper,
or the removal of a value that only fed the old message. The helper and
plumbing hunks, each message-only:
- `validate-action-dispatch-contract.ts`: `check()` loses its
`viewLabel` parameter, which fed only the message (the two call sites
drop the argument; `where` still carries the label); `CONTRACT_PROSE`
(record of strings) becomes `DELIVERS` (record of strings) and
`MISFIRE`'s two strings are shortened. `collectDeclaredContracts`, the
`declared === wired` early return and both skips are untouched.
- `validate-searchable-fields.ts`: the auto-default arm's `why` for a
system/audit column loses its trailing clause (`'a system/audit column,
which the auto-default set never includes'` → `'a system/audit
column'`); its three-way choice and the `continue` on an unreadable type
are unchanged. The `SEARCHABLE_TEXTUAL_TYPES` / `SEARCHABLE_ENUM_TYPES`
imports, which fed only the old message, go (the explanation writes the
list out; the rule's test holds it equal to the two constants).
- `validate-sortable-fields.ts` and `validate-searchable-fields.ts`:
`unprovisionedAnchorCause` → `unprovisionedAnchorVerdict` (string →
string).
- `validate-predicate-path-refs.ts`: a `FAILS_OPEN` constant, the
consequence clause both resolution verdicts end with.
- No helper changed shape (no string → boolean); no truth table is
involved.
- `.changeset/22161-lint-slice-6-one-line.md`: `@objectstack/lint`
`patch`, naming every door that prints the new text (below). `Clause-②:
no` follows the contract review on slice 1 (its section ②):
`RULE_EXPLANATIONS` entries are data in an existing export.
- **One docs transcript follows the text:**
`content/docs/data-modeling/schema-design.mdx` quoted
`searchable-field-unknown`'s old message as what `os validate` prints
for a dotted entry; its two message lines now read the new verdict (the
quoted `hint:` is unchanged). This file is outside the claim's file
surface; see "Acceptance notes".
- **Rider:** one text-only assertion in
`packages/lint/src/validate-react-page-props.test.ts` (the react page's
ListView `searchableFields` prop runs the shared searchable core); no
`packages/cli/test` or `packages/metadata-protocol` file asserts these
ids' message text (the consumers pin rule ids and `path`), so neither is
touched.
The verdict forms, the longest of each id as the suites fired them
(census below):
```text
list-view sort orders by "nope", which is not a field on object "showcase_ext_customer", so every load of this view answers 400 INVALID_SORT. Did you mean "name"?
list-view sort orders by "expected_revenue" on object "crm_opportunity", a virtual 'formula' field: its value is computed on read and never stored, so there is no column to ORDER BY
list-view sort: 'organization_id' is an injected column with no storage on external object 'showcase_ext_customer', so its ORDER BY is dropped and every fetch returns rows in an arbitrary order
list-view searchableFields entry "email" is not a field on object "crm_account", so the engine drops it from the search. Did you mean "billing_email"?
list-view searchableFields entry "account_id" is of type 'lookup', which 'search' cannot scan, so the auto-default search set excludes it and list search answers 400 INVALID_FIELD
list-view searchableFields entry "status" is outside the declared searchableFields (subject, case_number, description), so list search answers 400 INVALID_FIELD
searchableFields: 'owner_id' is an injected column with no storage on external object 'ext_customer', so 'search' never matches it and the searchable set is narrower than it declares
predicate references `data.__no_such_key__`, but `__no_such_key__` is not a key of `data`, so the predicate never evaluates and the console fails open: the element always renders
predicate names `type` bare, but it is a key of the edited schema with its `data.` root dropped, so the predicate never evaluates and the console fails open: the element always renders
predicate compares against `record.active` on the RIGHT of `==`, which this form reads as the literal string "record.active", so the result never depends on that path's value
predicate compares against the unquoted word `active` on the RIGHT of `==`, which reads as a literal today but is outside the declared subset and breaks under the real CEL evaluator
action "recalc_selection" declares execution: 'aggregate', wired as execution: 'perRecord' (one call per row: `recordId`, no `_selectedIds`), so its body takes its single-record branch
`record:approval_decison` is not a declared type of the reserved `record:` namespace, so the page draws a placeholder in its place. Did you mean 'record:approval_decision'?
`element:filter` was removed in @objectstack/spec 17 (ADR-0049), so the parse refuses this node by name (`os validate` prints the prescription)
"last_quarter" is a dashboard date-range PRESET name, not a filter value as a bare "nin" comparand; its window is { $between: ['{last_quarter_start}', '{last_quarter_end}'] }
```
## Shared prose: written once (the dispatch's mechanism assumption 2)
| shared sentence | ids | verdict clause (rule files) | explanation
(`rule-explanations.ts`) |
|---|---|---|---|
| what an unprovisioned anchor is | `sort-field-unprovisioned`,
`searchable-field-unprovisioned` (and slices 2 and 5's two anchor ids) |
`unprovisionedAnchorVerdict()` (`system-fields.ts`, existing) |
`UNPROVISIONED_ANCHOR_CAUSE` (existing, text byte-equal) |
| a formula field has no stored column | `sort-field-unsortable`,
`searchable-field-unsearchable` (virtual arm) | `virtualFieldClause()`
(new) | `VIRTUAL_FIELD_STORAGE` |
| what the list-view field rules do not judge | the three sort ids,
`searchable-field-unknown` | — | `LIST_VIEW_FIELD_SKIPS` |
| a list view's narrowing becomes the `$searchFields` override |
`searchable-field-unknown`, `searchable-field-unsearchable`
(`searchable-field-unprovisioned` states its own consequence) | — |
`SEARCH_FIELDS_ECHO` |
| a dead predicate fails open | `predicate-path-unresolved`,
`predicate-path-unrooted` | `FAILS_OPEN` | `PREDICATE_FAIL_OPEN` |
| which form predicates are judged, against which schema |
`predicate-path-unresolved`, `predicate-path-unrooted` | — |
`PREDICATE_FORM_SCOPE` |
| a preset name's refusal | `filter-preset-comparand` and the schema
door in `@objectstack/spec` | `presetComparandVerdict()` cuts the shared
refusal's first sentence | — (the explanation states the rest once) |
| a retired type's prescription | `component-type-unknown` and the parse
door | `retiredTypeHead()` cuts the map's entry | — (the explanation
names where the whole prescription prints, and restates none; a test
holds that) |
The explanation module imports nothing, so the facts it writes out are
held to their sources by the rules' tests: the auto-default type list to
`SEARCHABLE_TEXTUAL_TYPES` + `SEARCHABLE_ENUM_TYPES`, the virtual-type
claim (`formula` alone) to `SEARCH_VIRTUAL_TYPES`.
## Census (taken first, before any edit, at the base `61bea24207`)
Method: slice 4's scratch preload (`NODE_OPTIONS=--import`, never
committed), which patches `Array.prototype.push` to record every
finding-shaped object (`rule` + `message`) of the 12 ids, deduped by
(rule, message), with its push site, in every vitest worker. Lengths are
`message` alone; the printed line adds `where` and `: `. Rows from `os
lint`'s own printer (`commands/lint.ts`) are excluded.
- **`packages/lint` suite** at the base: 135 files, 6,264 passed (the
CLI closure build put the `dist/` its dist-reading tests wait for in
place). **All 12 ids fired, every one over 200**, from 25 push sites;
the longest of each matches the claim's figures exactly.
- **`packages/cli` unit tier**, the whole project (279 files) against
the base's lint dist: 277 files and 4,097 tests passed; the two that did
not load (`published-subpath-console.pin`,
`published-subpath-hook-body.pin`) need `packages/cli/dist`, which was
not built then (they refuse before running, naming the build). **It
fires none of the 12 ids.** Positive control, same preload:
`field-no-consumers` added to the recorded set is recorded (64
characters). The integration tier is declared to CI: no file in it names
these ids, and its fixture shapes (`visibleWhen` on object fields, a
`record.*` scope) are not ones these rules judge.
- **Runtime publish gate:** the whole `@objectstack/metadata-protocol`
suite (224 files, 221 run, 28,047 passed), which reads
`@objectstack/lint` from its built `dist/`. The before run read a lint
`dist/` built from the base, on a clean tree (the closure build above,
before the first edit); the after run read the dist rebuilt at the
slice's commit (marker: `so the engine drops it from the search` 1 hit
each in `dist/index.js`, `index.cjs`, `runtime.js`, `runtime.cjs`; the
old `The declaration is stale: searching it can never match` 0). It
fires two ids, from a flattened list overlay's `sort`
(`runtime-authoring-gate.public-form-intake.test.ts`) and an object's
`searchableFields`
(`protocol-publish-drafts-object-field-refs.test.ts`); both suites pin
the rule id and `path`, never the text.
- Not measured: the example apps (outside the dispatch's list; slice 5
measured them for its own ids).
| rule id | lint suite before: messages · longest | lint suite after:
messages · range | runtime-gate suite before → after |
|---|---|---|---|
| `sort-field-unknown` | 8 · 287 | 8 · 129–162 | 262 → 137 |
| `sort-field-unsortable` | 6 · 369 | 6 · 161–181 | — |
| `sort-field-unprovisioned` | 6 · 844 | 6 · 183–193 | — |
| `searchable-field-unknown` | 14 · 295 | 14 · 108–150 | 256 → 111 |
| `searchable-field-unsearchable` | 12 · 449 | 12 · 137–179 | — |
| `searchable-field-unprovisioned` | 3 · 512 | 3 · 171–182 | — |
| `predicate-path-unresolved` | 6 · 371 | 6 · 156–178 | — |
| `predicate-path-unrooted` | 1 · 434 | 1 · 184 | — |
| `predicate-rhs-path-shaped` | 12 · 648 | 12 · 160–181 | — |
| `action-dispatch-contract-mismatch` | 5 · 927 | 3 · 174–184 | — |
| `component-type-unknown` | 16 · 807 | 16 · 120–172 | — |
| `filter-preset-comparand` | 43 · 669 | 43 · 136–174 | — |
All 25 push sites fire before and after. One lint test reads the
package's `dist/` and fires `filter-preset-comparand` through it; the
after run was taken on the rebuilt dist, so it reports the new text too
(a first after run on the base dist recorded that one old message, and
was discarded). `action-dispatch-contract-mismatch` loses two distinct
messages because the old message echoed the view label, which is the
finding's `where`.
## Doors that print the new text
Read from the registry (`authoring-rules.ts`: every entry is `commands:
ALL`; `validatePresetComparands` is `CLI_AND_RUNTIME` with
`runtimeTypes: ['dashboard', 'view', 'object', 'page', 'flow',
'report']`, `validatePredicatePathRefs` with `['view']`,
`validateComponentTypes` is `CLI_ONLY`; the reference-integrity suite,
`CLI_AND_RUNTIME`, dispatches `validateSearchableFields` on `['flow',
'view', 'object']`, `validateSortableFields` on `['flow', 'view']` and
`validateActionDispatchContract` on the default `['flow']`) and the
CLI's callers (`commands/validate.ts`, `compile.ts`, `lint.ts`,
`verify.ts`, `utils/scaffold-validate.ts`, which `init.ts` and
`generate.ts` run):
| door | ids | what changes |
|---|---|---|
| `os validate`, `os build` (and `os compile`, which `os dev` runs per
compile), `os lint`, `os verify`, the `os init` / `os generate` scaffold
check | all 12 | the text-face verdict line; the `rule:` line gains the
`os explain` pointer; `os validate --json` `errors` and `os build
--json` author-time `issues` carry the new `message` |
| runtime publish gate (Studio, REST `/meta`, MCP) | the three
`searchable-field-*` ids on a `view`, `object` or `flow` write; the
three `sort-field-*` ids on a `view` or `flow` write; the three
`predicate-*` ids on a `view` write; `filter-preset-comparand` on a
`dashboard`, `view`, `object`, `page`, `flow` or `report` write | the
422 issue `message` and the `OS_ALLOW_UNLINTED_METADATA_WRITES` refusal
log line |
| never at the runtime gate | `component-type-unknown` (CLI only);
`action-dispatch-contract-mismatch` (it runs there only on a `flow`
write, whose snapshot carries no actions, so its declaration map is
empty and it returns before judging) | |
| unchanged | every `hint`; every other rule id | |
Every row is named in the changeset.
## Tests
The rule suites import the rule source; the CLI and runtime-gate suites
read the rebuilt `@objectstack/lint` dist.
- **Each rule's own suite pins the new shape**, as in slices 2–5. All
six test files wrap their rule import (and `checkSortDeclaration` /
`checkSearchableFieldList`, the exported cores) and record every finding
their cases fire; a final case in each holds every recorded verdict of
its ids to one line of at most 200 characters, behind a coverage control
(each id fired; for `component-type-unknown`, the retired arm fired for
every retired type). A second block per file pins, per id, that
`explainRule(id)` exists and still names what the verdict stopped
saying, and holds the explanation's written-out lists to their spec
sources. New exact pins: the sort virtual verdict, the two predicate
resolution verdicts (one consequence clause), both dispatch directions,
the namespace verdict, the preset verdict for an injected `created_at`;
`retiredTypeHead` has its own cases (dash, first sentence, never
rewrites). Every refusal assertion, rule id, severity, `path`, `where`
and hint pin is unchanged. The measured-consequence pins that read the
old text (ADR-0015 in the anchor messages, the skill-parity full-message
pins of the searchable verdicts, the preset refusal's full text) moved
to the new text or to the explanation.
- **Lint suite:** `pnpm --filter @objectstack/lint exec vitest run
--maxWorkers=2` at `8ad8e8bdf4` (lint source byte-identical to the head
`cd82744e3f`, which changes only the changeset): Test Files 135 passed
(135), Tests 6,295 passed (6,295); lock VERDICT command-exit 0 (31 new
cases).
- **Lint build + typecheck:** `pnpm --filter @objectstack/lint build &&
pnpm --filter @objectstack/lint run typecheck`: VERDICT command-exit 0;
`check:test-typecheck` OK, 2 files, 6 errors and 2 pinned signatures
held (the test layer compiles under `tsconfig.test.json`, so the six
test files are covered).
- **CLI**, against the rebuilt lint dist: `pnpm --filter
@objectstack/cli exec vitest run --maxWorkers=2
test/explain-rule-id.test.ts test/commands.test.ts
test/validate-build-gate-parity.test.ts
test/migrate-meta-engine-guidance.test.ts`: Test Files 4 passed (4),
Tests 91 passed (91). `explain-rule-id.test.ts` iterates every
`RULE_EXPLANATIONS` key, so all 12 new ids resolve through `os explain`,
`explainPointer` and the listing. `migrate-meta-engine-guidance.test.ts`
is the tracker-id refusal (mechanism assumption 4).
- **Runtime gate:** the whole `@objectstack/metadata-protocol` suite,
before and after (census above): Test Files 221 passed | 3 skipped
(224), Tests 28,047 passed | 19 skipped, both runs; lock VERDICT
command-exit 0.
- **Spec `repo` project (mechanism assumption 3):** `pnpm --filter
@objectstack/spec exec vitest run --project repo --maxWorkers=2` (=
`test:repo`): Test Files 55 passed (55), Tests 971 passed (971); lock
VERDICT command-exit 0, at `8ad8e8bdf4`. Two of its tests walk
`packages/lint/src` as a corpus:
`src/shared/retired-key-migrate-sentence.test.ts` (the `os migrate meta`
sentence scanner; the new text carries no such sentence, grep 0) and
`src/identity/position-delegatable-enforcer.pin.test.ts` (exported
`security-*` rule id constants; this slice declares none). The other
`lint/src` mentions under `packages/spec/src` name
`validate-component-props.test.ts` in path lists or are comments.
- **Tracker ids (mechanism assumption 4):** no `#` plus digits in any
printed message (0 in the census's 142 after messages) or explanation
(`rule-explanations.test.ts` refuses one in every paragraph); the
citations stay in `//` comments and test names.
- **Ablation** (one-shot, from the committed state `8ad8e8bdf4`, through
`scripts/ablation-replace.mjs` wrap mode under the verify lock, plus a
shell `trap` restoring `HEAD`). The rule suites import the rule source,
so there is no dist leg. `sort-field-unknown`'s pre-slice message was
restored verbatim (the mutated block checked equal to the base file's)
through the anchor of its new message: anchor x1 to x0, blob
`ace03ff2b4d3` to `8373465e586c`. Predicted before the run: exactly the
bound pin goes red, since the old text still carries every fragment the
other cases pin (`Did you mean "amount"?`, `400 INVALID_SORT`).
Observed: `src/validate-sortable-fields.test.ts` gave Test Files 1
failed (1), Tests 1 failed | 45 passed (46); the one red case was "every
verdict the cases above fired is one line of at most 200 characters"
(269 characters). Restored: blob after restore `ace03ff2b4d3` == blob at
HEAD, `git diff HEAD` empty, `git status --porcelain` empty.
- **ESLint, narrowed:** `npx eslint --no-inline-config --format json`
over the 14 changed `.ts` files gave 14 files in the report, 0 errors, 0
warnings. `eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, no typed rules), so no untouched file's verdict
can move. A control-character scan of every changed file found no match.
- **Gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` with no paths, on the head `cd82744e3f`
(merge base `61bea24207`; the derived set is identical to the one at
`8ad8e8bdf4`), derived 90 commands. I ran 88 of them sequentially, each
with its own log and its exit code captured before any pipe: all 88
exited 0. Two are NOT MEASURED, as the dispatch directs: `pnpm
check:dual-build-cjs-loads` (it needs the whole-workspace build), and
`pnpm check:type-check-debt`, which is `check-type-check-coverage.mjs
--re-measure` in `package.json` (it builds every package); its
non-re-measuring half is the same command as
`check:type-check-coverage`, which ran and exited 0. `--ran` gave `Run
reconciliation — 90 derived, 88 run, 0 NOT-MEASURED, 2 UNRUN` (those
two). I also ran the 51-family "Artifact rosters" block the same run
prints, 47 of them (one, `check:engine-double-contract`, is also a
derived command): all exited 0 (14 of the 51 are checker-health
`--self-test` rows, which grade a checker and not this diff; four keep
their roster in a directory this diff touches — `check-changeset-fixed`,
`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity` — all green). The three PR-context guards
(`check-closing-target-claim`, `check-partof-closing-keyword`,
`check-single-claim-paths`) need this PR's number and body, so they run
against this PR after it opens; their readings are in the report on the
card. On a first pass at `8ad8e8bdf4` (the same 135 commands, all 0 at
the second pass), `check:skill-examples` and
`check:published-readme-exports` refused with `PREREQUISITE NOT MET`
(exit 3, nothing measured: seven packages' `dist/*.d.ts` absent); I
built exactly those seven (`client-react`, `cli`, `organizations`,
`service-knowledge`, `knowledge-memory`, `knowledge-ragflow`,
`embedder-openai`; lock VERDICT 0, 48s) and both exited 0. The long ones
on the shared box: `check:query-options-erasure` 267s,
`check:slot-lookup` 135s, `check-comment-mask-corpus` 103s.
## Remaining for later slices — 44 ids in `packages/lint`, by file
PR #22700's list minus this slice's six files and 12 ids. The lengths
are slice 2's (PR #22448, census at `05c7c3fa3b`; "cli" marks a length
only the cli suite reached), not re-measured here.
`validate-approval-approvers.ts` is PR #22703's (another seat), in
flight.
- `data-model-rules.ts` (6): `unique/legacy-organization-composite` 480
cli, `relationship/master-detail-required` 462,
`unique/unscoped-declared-index` 427, `unique/double-declaration` 402
cli, `rollup/non-numeric-aggregand` 364, `relationship/delete-behavior`
201 cli
- `validate-ai-agent-authoring.ts` (3): `default-agent-legacy-alias`
466, `default-agent-outside-roster` 388, `agent-authoring-withdrawn` 352
- `lint-view-refs.ts` (2): `view-ref-nav-view-missing` 437,
`view-key-collision` 278 cli
- `validate-approval-approvers.ts` (2):
`approval-approvers-may-resolve-empty` 451,
`approval-approver-not-membership-tier` 272
- `validate-chart-bindings.ts` (2): `chart-measure-unknown` 442,
`chart-axis-not-selected` 327
- `validate-dashboard-action-refs.ts` (2):
`dashboard-action-route-unresolved` 242,
`dashboard-action-target-undefined` 239
- `validate-empty-combinators.ts` (2): `filter-empty-combinator` 352,
`filter-empty-node` 226
- `validate-list-view-field-refs.ts` (2): `list-view-field-dotted` 445,
`list-view-field-unknown` 355
- `validate-translation-references.ts` (2): `translation-target-unknown`
345, `translation-option-key-unknown` 230
- `lint-flow-credential-literals.ts` (1): `flow-credential-literal` 390
- `validate-action-name-refs.ts` (1): `action-name-undefined` 409
- `validate-ai-surface-affinity.ts` (1): `ai-skill-surface-mismatch` 281
- `validate-ai-tool-references.ts` (1): `ai-skill-tool-unresolved` 441
- `validate-capability-references.ts` (1):
`capability-reference-unknown` 219
- `validate-flow-filter-tokens.ts` (1): `flow-filter-token-unknown` 278
- `validate-managed-api-methods.ts` (1):
`object/managed-api-method-unaffordable` 412
- `validate-mapping-target-fields.ts` (1):
`mapping-target-field-unknown` 466
- `validate-nav-access.ts` (1): `nav-object-ungranted` 353
- `validate-nav-object-servability.ts` (1): `nav-object-unservable` 528
- `validate-nav-target-refs.ts` (1): `nav-target-unresolved` 426
- `validate-object-field-refs.ts` (1): `object-field-ref-unknown` 320
- `validate-object-references.ts` (1):
`object-reference-unregistered-platform` 324
- `validate-org-axis-red-lines.ts` (1): `org-axis-cross-org-bu-grant`
365
- `validate-page-visualization-bindings.ts` (1):
`page/visualization-without-binding` 629
- `validate-retired-permission-residue.ts` (1):
`permission-retired-lifecycle-residue` 235
- `validate-seed-replay-safety.ts` (1):
`seed-insert-mode-duplicates-on-replay` 222
- `validate-seed-state-machine.ts` (1):
`seed-value-outside-state-machine` 320
- `validate-semantic-roles.ts` (1): `semantic-role-field-unprovisioned`
291
- `validate-translatable-sections.ts` (1):
`translation-section-name-missing` 553
- `validate-view-containers.ts` (1): `view-container-shape` 290
The 26 ids slice 2 fenced, the 9 ids owned by `packages/cli`, and the
`action-governance.ts` boot-log lines stay as PR #22448's body lists
them, and `react-prop-deprecated` as PR #22700's notes it. This slice
touched none of them.
## Acceptance notes
- **One more `fix:` line now points at text the verdict quotes only in
part:** `component-type-unknown`'s retired arm ends its hint "Apply the
prescription above", and the verdict now quotes the prescription's head
and says `os validate` prints the whole of it (`os validate` and `os
build` refuse the same name at the parse with the whole prescription;
`os lint`, which does not parse, prints the head). This joins the card's
open hint call next to the three slice 5 named
(`react-chart-drilldown-invalid`, `react-chart-aggregate-invalid`,
`flow-time-relative-descriptor-invalid`) and
`sharing-rule-runtime-variable-condition`'s length.
`filter-preset-comparand`'s hint ("write the {date-macro} window the
message names") stays true: the verdict names the window.
- **The schema door still prints the whole preset refusal.**
`bareDateRangePresetComparandMessage`
(`packages/spec/src/data/date-range-presets.ts`) is shared by the schema
door and this rule, and its docblock says so ("one condition keeps one
wording"). The rule now opens with that refusal's own first sentence and
names the same window, and the rest is its `os explain` entry; the
schema door, outside this file surface, keeps the long text. Converging
it on the same verdict belongs to a slice that takes
`@objectstack/spec`'s own refusals (as slice 1 did for the
functional-completeness ones).
- **Two files outside the claim's file surface, both text-only:**
`content/docs/data-modeling/schema-design.mdx` quoted the old
`searchable-field-unknown` message as `os validate` output, which this
change makes false, so its two message lines are updated;
`packages/lint/src/validate-react-page-props.test.ts` asserted the old
`searchable-field-unprovisioned` text through the react ListView surface
(one assertion). No other doc, skill or test quotes the 12 ids' text
(grep over `content/docs`, `skills` and every package). The searchable
suite's skill-parity block said `skills/objectstack-ui` quotes the two
verdicts verbatim; it names them by rule id only
(`rules/list-views.md`), so that comment is corrected and the skill
needs no edit.
- **A long name can still lengthen a line.** The 200 bound holds on
every variant the suites fire, and on every preset under every operator
spelling the rule reports (pinned); the object, field, action and
component names in a verdict are the author's.
- **Process:** the base CLI closure build held the shared lock 6m26s;
the base `packages/cli` unit-tier census 9m23s; each
`@objectstack/metadata-protocol` census 5m24s / 5m57s; the spec `repo`
project 15m38s.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF)_
---------
Co-authored-by: Claude <noreply@anthropic.com>os explain RULE_ID carries their reasoning (#22717)1 parent fd2768a commit 762db99
16 files changed
Lines changed: 975 additions & 176 deletions
File tree
- .changeset
- content/docs/data-modeling
- packages/lint/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
167 | 167 | | |
168 | 168 | | |
169 | 169 | | |
170 | | - | |
171 | | - | |
172 | | - | |
173 | | - | |
| 170 | + | |
| 171 | + | |
174 | 172 | | |
175 | 173 | | |
176 | 174 | | |
| |||
0 commit comments