|
| 1 | +--- |
| 2 | +"@objectstack/lint": minor |
| 3 | +"@objectstack/cli": minor |
| 4 | +--- |
| 5 | + |
| 6 | +feat(lint, cli): one-line author-time rule verdicts, and `os explain <rule-id>` for the reasoning |
| 7 | + |
| 8 | +Clause-②: yes (widening) |
| 9 | + |
| 10 | +- **Shorter warnings.** `field-no-consumers` and `security-owd-unset` now print one verdict sentence and one fix. `os validate`, `os build` and `os dev` used to print `field-no-consumers` as a single line of about 860 characters, and `os build` added a second paragraph of about 700; the warning now reads: |
| 11 | + |
| 12 | + ```text |
| 13 | + ⚠ object "my_app_ticket" · field "description": declared, but nothing in this stack displays or reads it (inert) |
| 14 | + fix: add it to a view column or a form section, or remove the declaration |
| 15 | + rule: field-no-consumers at objects[1].fields.description — `os explain field-no-consumers` for what counts as a consumer |
| 16 | + ``` |
| 17 | + |
| 18 | + The finding's `message` no longer restates its `where`, and no longer carries the list of consumer and carrier kinds, the exemptions or the scanned roots; `hint` is the fix alone (for a `carrier-only` verdict it still names each carrier site a removal must clean). The `verdict`, `carriers` and `rootsScanned` fields of a `field-no-consumers` finding are unchanged. A tool that matched the old message text should match on `rule`, `where` and `path` instead. |
| 19 | + |
| 20 | + `security-owd-unset` also changes on the runtime wire. It runs at the metadata write door's publish gate for `object` writes (Studio, REST `/meta`, MCP), and an active write of a custom object (neither `isSystem: true` nor `sys_`-named) with no `sharingModel` is refused with a 422 whose `security-owd-unset` issue now carries the message `custom object declares no sharingModel (OWD); the runtime falls back to 'private', but the baseline must be an authored decision` and the hint `declare sharingModel: 'private' (owner + shares; recommended), 'public_read', 'public_read_write', or 'controlled_by_parent' (master-detail children)`. The old message named the object, which the issue's `where` and `path` still carry, and told the leave_request incident, which `os explain security-owd-unset` now prints. |
| 21 | +- **`os explain <rule-id>`.** `os explain` takes an author-time rule id as well as a schema name — `os explain field-no-consumers`, `os explain security-owd-unset` — and prints the reasoning the warning no longer carries; `--json` prints `{ rule, covers, paragraphs }`. A schema name resolves exactly as before. With no argument it also lists the rule ids that have an explanation (`--json` adds `rules: [{ id, covers }]`). The `rule:` line names the command only for those rule ids. An argument that is neither still exits 1, and its error string changes from `Unknown schema: "X"` to `Unknown schema or rule id: "X"`, followed by a second list, `Rules with an explanation: …`; the `--json` `error` field changes the same way, from `Unknown schema: X` to `Unknown schema or rule id: X`. |
| 22 | +- **`os explain constructor` (or `__proto__`) is refused as an unknown id** instead of printing `Schema: Object … undefined` and crashing with `schema.required is not iterable`: both lookups read own keys only. |
| 23 | +- **`os validate` prints the `fix:` and `rule:` lines** under each author-time warning, the way `os build` does, and the hint line under every author-time finding (`os build`, `os validate`, `os verify`, `os init`) is now labelled `fix:`. `os lint` adds the same `os explain` pointer to its rule line. |
| 24 | +- **The `fix:` line is always a fix.** `expression-invalid` no longer puts the authored source in `hint`, where it printed as `fix: source: …`: the source now ends the finding's `message` as `` — source: `…` `` (the spelling the flow engine's runtime refusals use), so the CLI verdict line still carries it, and so does the issue `message` at the runtime publish gate for `flow`, `action`, `hook` and `object` writes (Studio, REST `/meta`, MCP): in the 422 for an `error`, in the 2xx `advisories` for a `warning`. Its `hint` is empty, so no `fix:` line prints and the runtime issue's `hint` is `''`. `component-props-invalid`, `flow-time-relative-descriptor-invalid`, `react-prop-missing-required` (where the component contract describes the binding) and `liveness-experimental-property` carried context in `hint`; each now leads with the instruction, and `component-props-invalid`'s message states its consequence (nothing refuses it today, so the renderer receives the props as written). Two of the four run at the runtime publish gate. `flow-time-relative-descriptor-invalid` is an `error` on `flow` writes, so its new hint reaches the 422 issue `hint`. `liveness-experimental-property` runs on `email_template`, `mapping` and `datasource` writes as a `warning`, so its hint would ride the 2xx `advisories`, but none of those three ledgers has an `experimental` row today, so it reaches no runtime response yet. `component-props-invalid` is CLI-only, and `react-prop-missing-required` judges no `page` write at the gate, so their new text reaches the CLI only. |
| 25 | +- **New exports in `@objectstack/lint`:** `RULE_EXPLANATIONS`, `explainRule(ruleId)` and the type `RuleExplanation`, from the root entry and from the import-free `@objectstack/lint/rule-explanations` entry. |
0 commit comments