Skip to content

Commit 7dbb89d

Browse files
fix(lint): one-line fix lines for 16 more author-time rule ids; os explain RULE_ID carries what they enumerated (#22909)
Part of #22161 Clause-②: no Stage 2 of the card, slice 12: the over-long `fix:` lines, continued in slice 11's shape. Each finding's `hint` (the CLI's `fix:` line, the runtime issue's `hint`) of 16 more `packages/lint` rule ids, in ten whole rule files, is now one remedy line of at most 200 characters, and what the lines used to enumerate or explain is each id's `RULE_EXPLANATIONS` entry behind `os explain RULE_ID`. The card stays open: 91 author-time ids still carry a hint over 200 characters (the last section). ## What changes - **One remedy line per finding.** The 16 ids, every hint the lint suite fires, before → after: | rule id | file | lint suite: hints · range, before → after | runtime door | |---|---|---|---| | `validation-rule-json-schema-unknown-format` | `validate-rule-schema-formats.ts` | 10 · 606–646 → 10 · 143–183 | `object` write, 422 | | `measure-aggregate-field-type-refused` | `validate-dataset-measure-aggregates.ts` | 694 · 574–645 → 694 · 111–178 | `dataset` write, 422 | | `dimension-json-stored-field-refused` | same | 5 · 177–262 → 5 · 161–177 | `dataset` write, 422 | | `predicate-rhs-path-shaped` | `validate-predicate-path-refs.ts` | 12 · 381–643 → 12 · 153–184 | `view` write, 422 (dotted) / 2xx advisory (bare word) | | `predicate-path-unrooted` | same | 1 · 214 → 1 · 122 | `view` write, 422 | | `predicate-path-unresolved` | same | 10 · 84–201 → 10 · 20–153 | `view` write, 422 | | `rls-predicate-unknown-user-variable` | `validate-rls-predicate-enforceability.ts` | 6 · 566–618 → 3 · 158–193 | `permission` write, 422 | | `rls-predicate-unparseable` | same | 1 · 523 → 1 · 171 | `permission` write, 422 | | `hook-api-update-readonly-when-field` | `validate-readonly-hook-writes.ts` | 1 · 607 → 1 · 173 | CLI only | | `action-name-undefined` | `validate-action-name-refs.ts` | 32 · 181–560 → 29 · 91–195 | CLI only | | `ai-skill-tool-unresolved` | `validate-ai-tool-references.ts` | 22 · 308–548 → 22 · 150–189 | CLI only | | `org-axis-permission-inheritance` | `validate-org-axis-red-lines.ts` | 1 · 542 → 1 · 145 | CLI only | | `org-axis-cross-org-bu-grant` | same | 1 · 315 → 1 · 141 | CLI only | | `object-reference-unknown` | `validate-object-references.ts` | 23 · 254–522 → 2 · 123–134 | `dataset` write (its `object`), 422 | | `object-reference-unregistered-platform` | same | 8 · 366–500 → 1 · 187 | `dataset` write (its `object`), 2xx advisory | | `filter-empty-node` | `validate-empty-combinators.ts` | 3 · 170–515 → 3 · 69–113 | `flow` / `report` write, 422 | A count that falls is several old hints that differed only in what the new line no longer carries: the stack's object or action list, the site's consequence sentence, the kernel key list's position against the suggestion. - **Lists are no longer echoed.** `object-reference-unknown` drops "Defined objects: …" and `action-name-undefined` drops "Defined actions: …" and the related object's action list: the verdict's "Did you mean" already names the nearest one. `validation-rule-json-schema-unknown-format` names the nearest registered format and moves the full registered set (26 names) into its explanation, written out there and held to `registeredFormatNames()` by the rule's test. `ai-skill-tool-unresolved` moves the family prefixes into its explanation, held to `PLATFORM_TOOL_FAMILY_PREFIXES`. `rls-predicate-unknown-user-variable` keeps the six kernel keys and the nearest one, so its line holds no author text at all (a test holds the worst-case suggestion under 200). - **The reasoning moves, the remedy stays.** - `object-reference-*`: each `check()` site passed a consequence sentence that closed every hint (the record picker, the view container's index, the dashboard filter, the dataset's reports, the nav gate). Those are now one paragraph in both ids' explanations; the `check()` argument is a `remedy`, which only the view-container site passes (the namespace-prefixed spelling, which is then the whole line). - `measure-aggregate-field-type-refused`: the line names the aggregates the declaration accepts (the "accepts: count." pins hold); why `count` / `count_distinct` reach further, the stored-numeric route and the two doors that refuse the pair later (`DATASET_COMPILE_DOOR` / `CUBE_QUERY_DOOR`, now deleted with the `door` parameter) are the explanation's. - `hook-api-update-readonly-when-field`: the two routes and "runAs: 'system' and sudo() do not waive this lock"; why (the system context, the sandbox), the drop-the-field alternative and "never blocks a build" are the explanation's. - `rls-predicate-unparseable`: the SQL-to-CEL translations in one line; the subquery route (ADR-0055, ADR-0105 D11) is the explanation's. - `predicate-rhs-path-shaped`: both spellings in one line, and the bare-word arm keeps "Do NOT simply add the root in place" (pinned by `runtime-gate.test.ts`); why quoting changes nothing today and why two paths cannot be compared are the explanation's. - `filter-empty-node`: `OMIT_THE_KEY` / `MATCH_NONE_SPELLING` are deleted from the rule and their text is the explanation's. - `org-axis-*`, `ai-skill-tool-unresolved`, `action-name-undefined`, the two other predicate ids, `dimension-json-stored-field-refused` (multi-value arm) and `validation-rule-json-schema-unknown-format`: likewise, one line each. - **Two ids gain an explanation entry**, because their reasoning had no entry to move into: `object-reference-unknown` ("how an object name resolves, and what a miss costs") and `org-axis-permission-inheritance` ("why access never walks the organization tree"). Their `rule:` line now ends with the `os explain` pointer, which `explainPointer` prints for every id the table holds. The two object-reference entries share the sites, the ladder and the per-site cost paragraphs; the two org-axis entries share the ADR-0105 D6 opening. The other 14 entries gain paragraphs. - **Nothing else moves.** Every `message`, rule id, severity and `path`, and what each rule accepts or refuses, is unchanged: every hunk in the ten rule files is a `hint` expression, a comment, a deleted hint constant, or (two places) a parameter that only fed a hint (`door`, and `fix` renamed `remedy` with a default). No condition, branch, skip or dedupe moved. - `.changeset/22161-lint-slice-12-one-line-fix.md`: `@objectstack/lint` `patch`, `Clause-②: no`, naming every door that prints `fix:` and the runtime `hint` FROM → TO. ## The runtime wire (Zone 2 item 3) Measured at the door with `runRuntimeAuthoringRules` on the base dist and on the rebuilt one (a scratch probe, 28 writes): - an `object` write: `validation-rule-json-schema-unknown-format` (422 `issues[].hint`, 638 / 606 → 175 / 143, with and without a nearest name); - a `dataset` write: `measure-aggregate-field-type-refused` (422, 606 / 581 → 147 / 122), `dimension-json-stored-field-refused` (422, multi-value 259 → 166, structured JSON 177 unchanged), `object-reference-unknown` on the dataset's `object` (422, 331 → 134) and `object-reference-unregistered-platform` (2xx `advisories[].hint` and the `[Protocol] authoring advisory` log line, 449 → 187); - a `view` write of a schema-bound metadata form: `predicate-rhs-path-shaped` (422 dotted 384 → 156; 2xx advisory bare word 631 → 180), `predicate-path-unrooted` (422, 214 → 122), `predicate-path-unresolved` (422, 100 → 36 with a nearest key); - a `permission` write: `rls-predicate-unparseable` (422, 523 → 171), `rls-predicate-unknown-user-variable` (422, 566 / 590 / 634 → 158 / 180 / 190, without a nearest key, with `email`, with `organization_id`); - a `flow` or `report` write: `filter-empty-node` (422, 515 / 278 / 170 → 113 / 107 / 69); - no write reaches `hook-api-update-readonly-when-field`, `action-name-undefined`, `ai-skill-tool-unresolved` or the two `org-axis-*` ids (a `hook`, `skill`, `page`, `sharing_rule`, `view` bulk-action and `permission` write were probed), nor the object-reference ids on an `object` or `view` write. Readers of `hint`, read-only against objectui `main` at `f3d5408141` (fetched into a ref of its own, `git grep`, no edit): Studio's `saveAdvisoryToast.ts` `formatFinding` renders `[rule] where — message hint` for every 2xx advisory; `DraftChangesPanel.tsx` renders `p.hint || p.message` for the in-browser security-posture lint (none of the 16); `metadata-client.ts` `readSaveAdvisories` checks only that `hint` is a string; the 422 `issues` renderers read `path` and `message`. REST (`PUT /api/v1/meta`, `SaveMetaItemResponseSchema.advisories`, the error envelope's `issues`) forwards the array as JSON; `packages/mcp` has no metadata write tool, so an MCP or AI author reaches the gate through REST. In this repository `toIssue` forwards `hint` verbatim, the advisory log line prints it, and `build-probes.ts` reads `hint` only for `object-field-ref-unknown` (not one of the 16). **No reader in either repository matches on `hint` text.** `check:watch-hint-literal` guards something else of the same name (every `*_WATCH_HINTS` population declaration in a gate script is a literal array): exit 0, 72 declarations across 4 rostered names, standalone and inside the gate set. ## Census (taken first, before any edit, at the base `63739521d8`) Method: slices 4 to 11's scratch preload (`NODE_OPTIONS=--import`, never committed) patching `Array.prototype.push` to record every finding-shaped object, deduped by (rule, hint) per process, with its push site. Lengths are the `hint` alone. Runs, each before and after: - **`packages/lint` suite** (137 files, 6,497 tests at the base): 240 ids fire, **131 carry a hint over 200** = 21 of the 26 fenced ids, 3 repo-gate ids (`lint-startup-registry-verdict.ts`) and 107 author-time ids; the 16 here match slice 11's census lengths exactly. After, at the merged head `26fe94f3e1` with the lint dist rebuilt: **115 over 200** = 21 fenced + 3 repo-gate + **91 author-time**. - **Runtime publish gate**, the whole `@objectstack/metadata-protocol` suite reading `@objectstack/lint` from its built dist (base dist before, rebuilt after): fires `measure-aggregate-field-type-refused` (588 → 129) and `rls-predicate-unparseable` (523 → 171) of the 16. - **`packages/cli` unit tier** (283 files): fires `hook-api-update-readonly-when-field` (604 → 170) and `object-reference-unknown` (7 hints · 242–348 → 1 · 134). - **`os validate`** (the built CLI) on `app-crm`, `app-todo`, `app-showcase`, `app-multi-package`, exit 0 each, before and after: none of the 16 fires on any of them; each app prints the same 9 / 7 / 70 / 3 `rule:` lines, and the printed output is byte-identical before and after. ## Tests Every heavy run went through `scripts/pm/os-verify-lock.sh` (slot `issue-22161-s12`); its VERDICT lines are in the report. - **Each rule's own suite pins the new shape.** Every touched suite already records what its cases fire; a new last block per file holds every recorded `fix:` line of its converted ids to one line of at most 200 characters behind a coverage control (each arm fired: both format arms; the measure id on a type row and a flagged declaration and the dimension id on both classes; both unresolved arms and both right-hand arms; the variable id with and without a nearest key; the generic action arm with both placements and both related-list arms; both ai-skill arms; both org-axis ids; the object-reference id with and without the namespace remedy; all three empty-node positions), plus a check per id that `explainRule(id)` holds what the line stopped saying. The three RLS sibling files' bounds now cover every `rls-predicate-*` id. Pins that read the old hint text moved to the explanation, text-only, except two that needed a new reading: the related-list drawn-locations pin now reads its own paragraph (the new fix paragraph names the header's `record_header` / `record_more`), and the artifact-context pin reads the verdict's "Did you mean" on a near miss instead of the dropped object list. - **Lint suite** at the merged head `26fe94f3e1`: Test Files 137 passed (137), Tests 6,528 passed (6,528); VERDICT command-exit 0 (6,518 before the merge of `origin/main`). - **Lint build + typecheck:** `pnpm --filter @objectstack/lint build` (`check-dts-emitted` 6/6), rebuilt with its closure after the merge, and `run typecheck` at `9d56f0f431` (`check:test-typecheck` OK, 2 files / 6 errors / 2 pinned signatures held), VERDICT command-exit 0 each. The rebuilt dist carries the new text (`constraint with \`pattern\`, which is enforced` and `test an app-staged membership` 1 hit each in `index.js`, `index.cjs`, `runtime.js`, `runtime.cjs`; the old `The registered names are: ${` 0). - **CLI unit tier** on the rebuilt dist: Test Files 283 passed (283), Tests 4,265 passed (4,265); VERDICT command-exit 0. No `packages/cli` or `packages/metadata-protocol` test asserts these ids' hint text, so the declared rider went unused. - **Runtime gate:** the whole `@objectstack/metadata-protocol` suite, after: Test Files 226 passed, 3 skipped (229), Tests 28,171 passed, 19 skipped; VERDICT command-exit 0. - **Ablation** (one-shot, from the committed head `9d56f0f431`, through `scripts/ablation-replace.mjs` under the lock, with a shell `trap` restoring `HEAD` by absolute path). The rule suites import the rule source, so there is no dist leg. `rls-predicate-unparseable`'s pre-slice line was restored: `hint: UNPARSEABLE_FIX,` → the 523-character SQL-to-CEL hint as one literal (anchor x1 → x0, blob `1e997b3fc7cc` → `371d8e008841`). Predicted before the run: exactly the new bound pin goes red, and the older `canonical CEL` / `&&` pins stay green. Observed: `src/validate-rls-predicate-enforceability.test.ts` Tests 1 failed, 132 passed (133), exactly that pin, which read 523. Restored: blob `1e997b3fc7cc` == HEAD, `git diff HEAD` empty, `git status --porcelain` empty. - **Corpus pins over `packages/lint/src`:** `pnpm --filter @objectstack/spec test:repo` at `9d56f0f431`: Test Files 55 passed (55), Tests 971 passed (971); VERDICT command-exit 0. No new text carries an `os migrate meta --from` sentence, and no tracker id is in a printed hint or explanation (citations stay in comments and test names). - **ESLint, narrowed:** `npx eslint --no-inline-config --format json` over the 24 changed `.ts` files: 24 files in the report, 0 errors, 0 warnings. The population is `eslint.config.mjs`'s `**/*.{ts,…}` block plus its `packages/**` blocks; the config never enables type-aware linting (no `parserOptions.project`), so no untouched file's verdict can move. A control-character scan of every changed file found no match. - **Gates:** one merge of `origin/main` through `scripts/pm/os-regen-merge.sh` (`0cc2194a2b` → `26fe94f3e1`, onto `9fdb12752c`; no conflict, and the branch edits no generated artifact). `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` with no paths at the final head `9d56f0f431` (25 paths vs the merge base) derived 62 commands; 60 ran, each exit code captured before any pipe, all 0 (longest: `check:query-options-erasure` 348s, `check:slot-lookup` 175s, `check-comment-mask-corpus` 142s). NOT MEASURED, as dispatched: `check:dual-build-cjs-loads` (needs every package's dist) and `check:type-check-debt` (its script is `--re-measure`; `check:type-check-coverage` ran). `--ran`: `Run reconciliation — 62 derived, 60 run, 2 NOT-MEASURED, 0 UNRUN.` The artifact-roster block (51 families): the 48 that need no pull request exited 0 at `9d56f0f431`, `check:published-readme-exports` after building its four missing dists (`client-react`, `embedder-openai`, `knowledge-ragflow`, `organizations`, 54 tasks) under the lock (its first answer was the exit-3 prerequisite refusal, not a verdict); the three PR-context guards run against this PR and report on the card. - **Which tree each reading is from:** the lint suite and the census at `26fe94f3e1` (the merge; `9d56f0f431` adds one changeset figure only); the CLI unit tier, the metadata-protocol suite, `os validate` and the runtime probe on the pre-merge head `0cc2194a2b`, whose hint text the merge did not touch (main brought no change to the ten rule files or `rule-explanations.ts`). ## Acceptance notes - **A long name can still lengthen a line.** The bound holds on every variant the suites, the runtime probe and the four example apps fire; the names in a hint (the measure's column, the dimension's object and field, the right-hand text, the action and related-object names, the readonlyWhen field and object) are the author's. The closest measured case is `action-name-undefined` at 195. `rls-predicate-unknown-user-variable` and `rls-predicate-unparseable` carry no author text at all. - **The dropped lists now print nowhere on the CLI.** A near miss still gets the verdict's "Did you mean"; a far miss of an object or action name no longer sees the stack's whole list on the `fix:` line. The card's "Asked for" names no list, and slice 11 made the same call for `rls-predicate-unknown-field`'s field list. - **Two `rule:` lines gain the `os explain` pointer** (`object-reference-unknown`, `org-axis-permission-inheritance`), through the new entries. That is the printer's own rule for an id the table holds, not a printer change. - **The bare-word right-hand line is two sentences**: the remedy, then "Do NOT simply add the root in place", kept verbatim because `runtime-gate.test.ts` (outside this slice's files) pins it. - **The namespace remedy replaces the generic line on both object-reference arms.** A platform-prefixed short name whose namespaced spelling the stack declares gets that spelling alone, where it used to get the generic platform text and then the spelling. ## Remaining for this card **91 author-time `packages/lint` ids still carry a hint over 200 characters** in the final lint census (at `26fe94f3e1`: 115 over 200 = 91 + 21 fenced + 3 repo-gate), by file, longest first: - `lint-flow-patterns.ts` (2): `flow-double-brace-interpolation` 1339, `flow-bare-dollar-reference` 1231 - `lint-flow-credential-literals.ts` (1): `flow-credential-literal` 646 - `data-model-rules.ts` (6): `unique/legacy-organization-composite` 578, `unique/unscoped-declared-index` 380, `object/missing-name-field` 327, `rollup/non-numeric-aggregand` 282, `unique/double-declaration` 279, `field/select-missing-options` 207 - `validate-widget-bindings.ts` (11): `dashboard-filter-field-unprovisioned` 561, `chart-field-unknown` 415, `dashboard-filter-field-unknown` 403, `chart-measures-missing` 369, `widget-legacy-analytics-shape` 338, `widget-measures-missing` 330, `widget-filter-field-unknown` 322, `widget-legacy-analytics-unrenderable` 320, `table-count-only` 311, `chart-dimensions-missing` 291, `dashboard-filter-field-not-included` 218 - `validate-readonly-flow-writes.ts` (2): `flow-update-readonly-when-field` 549, `flow-update-readonly-field` 400 - `validate-action-dispatch-contract.ts` (1): `action-dispatch-contract-mismatch` 514 - `validate-security-posture.ts` (5): `security-controlled-by-parent-ambiguous-relation` 496, `security-master-detail-ungranted` 314, `security-controlled-by-parent-no-relation` 294, `security-fls-unknown-field` 293, `security-wildcard-vama` 218 - `validate-sharing-rule-enforceability.ts` (2): `sharing-rule-object-not-shareable` 482, `sharing-rule-object-controlled-by-parent` 395 - `validate-page-visualization-bindings.ts` (1): `page/visualization-without-binding` 480 - `validate-readonly-action-writes.ts` (1): `action-api-update-readonly-when-field` 470 - `validate-flow-trigger-readiness.ts` (5): `flow-api-trigger-secret-missing` 461, `flow-time-relative-descriptor-unroutable` 441, `flow-trigger-unroutable` 295, `flow-trigger-unknown-event` 268, `flow-trigger-unknown-object` 210 - `validate-searchable-fields.ts` (3): `searchable-field-unknown` 448, `searchable-field-unprovisioned` 362, `searchable-field-unsearchable` 328 - `validate-flow-filter-tokens.ts` (1): `flow-filter-token-unknown` 427 - `validate-action-locations.ts` (1): `action-no-placement` 424 - `validate-mapping-target-fields.ts` (1): `mapping-target-field-unknown` 405 - `validate-dashboard-widget-options.ts` (1): `unconsumed-widget-option` 396 - `validate-sortable-fields.ts` (3): `sort-field-unprovisioned` 378, `sort-field-unsortable` 332, `sort-field-unknown` 301 - `validate-visibility-predicates.ts` (3): `visibility-predicate-unknown-function` 375, `visibility-bare-identifier` 221, `visibility-predicate-syntax` 214 - `validate-nav-access.ts` (1): `nav-object-ungranted` 370 - `validate-hook-body-writes.ts` (3): `hook-body-write-unprovisioned-anchor` 365, `hook-body-write-unknown-field` 275, `hook-body-source-unparseable` 254 - `validate-ai-agent-authoring.ts` (2): `agent-authoring-withdrawn` 363, `default-agent-outside-roster` 237 - `validate-react-page-props.ts` (4): `react-chart-field-unprovisioned` 362, `react-prop-missing-required` 277, `react-page-source-unparseable` 254, `react-block-needs-record-context` 228 - `validate-translatable-sections.ts` (1): `translation-section-name-missing` 359 - `validate-action-body-writes.ts` (4): `action-body-write-unprovisioned-anchor` 358, `action-body-write-unknown-field` 323, `action-record-write-discarded` 309, `action-body-source-unparseable` 254 - `validate-flow-node-writes.ts` (2): `flow-node-write-unprovisioned-anchor` 358, `flow-node-write-unknown-field` 289 - `validate-managed-api-methods.ts` (1): `object/managed-api-method-unaffordable` 357 - `validate-nav-object-servability.ts` (1): `nav-object-unservable` 348 - `validate-rule-compilability.ts` (2): `validation-rule-json-schema-uncompilable` 341, `validation-rule-regex-uncompilable` 300 - `validate-dataset-references.ts` (2): `dataset-include-unknown` 336, `dataset-field-unknown` 206 - `validate-translation-references.ts` (1): `translation-target-unknown` 317 - `validate-approval-approvers.ts` (3): `approval-approver-not-membership-tier` 313, `approval-expression-no-empty-policy` 294, `approval-expression-invalid` 292 - `validate-seed-replay-safety.ts` (1): `seed-insert-mode-duplicates-on-replay` 309 - `validate-chart-bindings.ts` (1): `chart-measure-unknown` 285 - `validate-object-field-refs.ts` (1): `object-field-ref-unknown` 284 - `validate-capability-references.ts` (1): `capability-reference-unknown` 273 - `validate-record-title.ts` (2): `title-format-retired` 267, `title-unresolvable` 237 - `validate-list-view-field-refs.ts` (2): `list-view-field-unknown` 258, `list-view-field-dotted` 233 - `validate-view-containers.ts` (1): `view-container-shape` 256 - `validate-seed-state-machine.ts` (1): `seed-value-outside-state-machine` 251 - `validate-ai-surface-affinity.ts` (1): `ai-skill-surface-mismatch` 235 - `validate-page-source-styling.ts` (1): `page-source-className-tailwind` 231 - `lint-liveness-properties.ts` (1): `liveness-planned-property` 230 - `validate-list-view-mode.ts` (1): `list-view-filters-in-views-mode` 219 Not in the 91: the 26 fenced ids as PR #22448 lists them (21 fire a hint over 200), the 3 repo-gate ids (`startup-open-vocabulary-verdict`, `startup-source-unparseable`, `startup-verdict-assertive-wording`), and the `packages/cli` ids on #22841. --- _Generated by [Claude Code](https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7de0265 commit 7dbb89d

25 files changed

Lines changed: 734 additions & 250 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"@objectstack/lint": patch
3+
---
4+
5+
fix(lint): 16 more author-time rule ids print a `fix:` line of one remedy sentence, and `os explain <rule-id>` carries what those lines used to enumerate
6+
7+
Clause-②: no
8+
9+
- **Shorter fix lines.** Each finding of these 16 rule ids now carries a `hint` (the CLI's `fix:` line, the runtime issue's `hint`) of one remedy sentence of at most 200 characters on every variant the lint suite fires (the lengths here are the `hint` alone; the CLI prints it after `fix: `). The longest of each, before → after:
10+
- validation rules: `validation-rule-json-schema-unknown-format` (646 → 183);
11+
- datasets and cubes: `measure-aggregate-field-type-refused` (645 → 178) and `dimension-json-stored-field-refused` (262 → 177);
12+
- form predicates: `predicate-rhs-path-shaped` (643 → 184), `predicate-path-unrooted` (214 → 122) and `predicate-path-unresolved` (201 → 153);
13+
- RLS predicates: `rls-predicate-unknown-user-variable` (618 → 193) and `rls-predicate-unparseable` (523 → 171);
14+
- hook bodies: `hook-api-update-readonly-when-field` (607 → 173);
15+
- action names: `action-name-undefined` (560 → 195);
16+
- AI skills: `ai-skill-tool-unresolved` (548 → 189);
17+
- organization axis: `org-axis-permission-inheritance` (542 → 145) and `org-axis-cross-org-bu-grant` (315 → 141);
18+
- object names: `object-reference-unknown` (522 → 134) and `object-reference-unregistered-platform` (500 → 187);
19+
- filters: `filter-empty-node` (515 → 113).
20+
- **Lists are no longer echoed.** `object-reference-unknown` no longer lists every object the stack and its artifact define, and `action-name-undefined` no longer lists every action of the stack or of the related object: the verdict's "Did you mean" already names the nearest one. `validation-rule-json-schema-unknown-format` names the nearest registered format and no longer lists all of them, and `ai-skill-tool-unresolved` no longer lists the tool family prefixes. `rls-predicate-unknown-user-variable` names the kernel keys and the nearest one, and so holds no author text.
21+
- **One line, one remedy.** Where a fix line explained its remedy, the explanation moved: why a missing object costs what it costs at each site (the record picker, the view container's index, the dashboard filter, the dataset's reports, the nav entry), the doors that refuse a refused aggregate pair later (a dataset's compile, a cube's query), how an app stages an RLS membership set, why no system context waives a `readonlyWhen` lock, and the declared "match nothing" filter spelling. Where the namespace-prefixed object exists, a view container's fix line is that spelling alone (`write "my_app_order_line", not "order_line"`).
22+
- **`os explain <rule-id>` carries the rest.** What the fix lines stopped saying is each id's `RULE_EXPLANATIONS` entry in `@objectstack/lint`, which `os explain <rule-id>` prints. Two ids had no entry and gain one, so their `rule:` line now ends with the `os explain` pointer: `object-reference-unknown` ("how an object name resolves, and what a miss costs") and `org-axis-permission-inheritance` ("why access never walks the organization tree"). The other 14 entries gain paragraphs. The registered format names and the tool family prefixes are written out in their entries and held to the live sets by the rules' tests.
23+
- **Where the new fix lines print.** On the CLI: the `fix:` line under each finding of `os validate`, `os build` (and `os compile`, which `os dev` runs at startup and, under `--verbose`, on each watch recompile), the scaffold check `os init` runs, and `os verify`'s refusal list; `os lint` prints it as `→ fix:` under `--fix` and as `fix` in `--json`; `os validate --json` (`errors`) and `os build --json` (author-time `issues`) carry it on an error-severity finding; their `warnings` carry an advisory as one `where: message` string, without it. At the runtime publish gate (Studio, REST `PUT /api/v1/meta`, and the MCP and AI authors that write through that door), the `hint` FROM → TO, measured at the door:
24+
- an `object` write: `validation-rule-json-schema-unknown-format` (422 `issues[].hint`, 638 / 606 → 175 / 143, with and without a nearest name);
25+
- a `dataset` write: `measure-aggregate-field-type-refused` (422, 606 / 581 → 147 / 122), `dimension-json-stored-field-refused` (422, multi-value 259 → 166, structured JSON 177 unchanged), `object-reference-unknown` (422 on the dataset's `object`, 331 → 134) and `object-reference-unregistered-platform` (2xx `advisories[].hint` and the `[Protocol] authoring advisory` log line, 449 → 187);
26+
- a `view` write of a schema-bound metadata form: `predicate-rhs-path-shaped` (422 on a dotted right-hand side, 384 → 156; 2xx advisory on a bare word, 631 → 180), `predicate-path-unrooted` (422, 214 → 122) and `predicate-path-unresolved` (422, 100 → 36 with a nearest key);
27+
- a `permission` write: `rls-predicate-unparseable` (422, 523 → 171) and `rls-predicate-unknown-user-variable` (422, 566 / 634 → 158 / 190, without and with a nearest key);
28+
- a `flow` or `report` write: `filter-empty-node` (422, 515 / 278 / 170 → 113 / 107 / 69 for the whole filter, a `$or` branch and a `$and` branch).
29+
30+
Studio's save-advisory toast prints an advisory's `hint` after its message, so the platform-object and bare-word toasts shorten; no Studio, REST or MCP reader in this repository or in objectui matches on `hint` text. A tool that did should match on `rule` and `path` instead.
31+
- **Never at the runtime gate:** `hook-api-update-readonly-when-field`, `action-name-undefined`, `ai-skill-tool-unresolved` and the two `org-axis-*` ids run on the CLI only, as do the object-reference ids on every site but a dataset's `object`.
32+
- **Unchanged:** every `message` (the verdict), rule id, severity and `path`, and what each rule accepts or refuses.

0 commit comments

Comments
 (0)