Repository navigation
Commit 2e11d90
fix(lint): one-line fix lines for 15 author-time rule ids;
Part of #22161
Clause-②: no
Stage 2 of the card, slice 11: the over-long `fix:` lines, item 3 of
slice 10's landing record as triage routed it. Each finding's `hint`
(the CLI's `fix:` line, the runtime issue's `hint`) of 15
`packages/lint` rule ids is now one remedy sentence of at most 200
characters, and what the lines used to enumerate is each id's
`RULE_EXPLANATIONS` entry behind `os explain RULE_ID`. The card stays
open: 107 author-time ids still carry a hint over 200 characters
(below).
## What changes
- **One remedy line per finding.** The 15 ids, the longest hint the lint
suite fires, before → after:
| rule id | file | lint suite: hints · range, before → after | runtime
door |
|---|---|---|---|
| `sharing-rule-runtime-variable-condition` |
`validate-sharing-rule-enforceability.ts` | 1 · 2,280 → 1 · 195 | CLI
only |
| `sharing-rule-unlowerable-condition` | same | 3 · 387–968 → 3 ·
172–199 | CLI only |
| `approval-approvers-may-resolve-empty` |
`validate-approval-approvers.ts` | 2 · 440–2,243 → 2 · 198–200 | `flow`
write, 2xx advisory |
| `rls-predicate-unenforceable` |
`validate-rls-predicate-enforceability.ts` | 27 · 191–1,056 → 27 ·
62–191 | `permission` write, 422 |
| `rls-predicate-over-budget` | same | 3 · 1,037–1,044 → 1 · 197 |
`permission` write, 422 |
| `rls-predicate-unknown-field` | same | 5 · 651–766 → 1 · 154 |
`permission` write, 422 |
| `filter-empty-combinator` | `validate-empty-combinators.ts` | 3 ·
647–764 → 3 · 125–154 | `flow` / `report` write, 422 |
| `permission-retired-lifecycle-residue` |
`validate-retired-permission-residue.ts` | 2 · 573–681 → 1 · 14 |
`permission` write, 2xx advisory |
| `visibility-predicate-over-budget` |
`validate-visibility-predicates.ts` | 2 · 645–651 → 2 · 177–179 | `view`
write, 422 |
| `hook-api-update-readonly-field` | `validate-readonly-hook-writes.ts`
| 8 · 604–647 → 8 · 153–182 | CLI only |
| `react-chart-aggregate-invalid` | `validate-react-page-props.ts` | 3 ·
111–363 → 3 · 113–191 | CLI only |
| `react-chart-drilldown-invalid` | same | 2 · 114–128 → 2 · 128–181 |
CLI only |
| `flow-time-relative-descriptor-invalid` |
`validate-flow-trigger-readiness.ts` | 1 · 342 → 1 · 176 | `flow` write,
422 |
| `component-type-unknown` | `validate-component-types.ts` | 16 · 32–229
→ 16 · 32–158 | CLI only |
| `field-no-consumers` | `validate-field-consumers.ts` | 15 · 68–189 →
16 · 68–175 (showcase `os validate`: 24 · 68–624 → 24 · 68–199) | CLI
only |
A count that falls is several old hints that differed only in what the
new line no longer lists (the over-budget bound's name, the object's
field list, the retired key's name). The drill-down hint grows: it
carried a pointer, and now carries the remedy.
- **The named five (slice 8's four, plus slice 10's).**
- `sharing-rule-runtime-variable-condition` (2,280): the hint was the
whole mechanism essay. It is now "keep only the record's own properties
in `condition` and name the audience in `sharedWith`; what replaces
`current_user` depends on the object's sharingModel". ⛔ The one-line
hint recommends no RLS at all, because an RLS policy narrows only
`public_read` / `public_read_write` and grants nothing on `private`. The
model-qualified split, the AND-composition, the ADR-0057 D1 depth
scopes, the `position` over-broad-grant trap and the record-relative gap
are the explanation's, with the #14234 wording legs moved there (an RLS
sentence must name the models it holds for) and a new leg that holds the
hint to naming no RLS.
- Slice 5's three "above" hints, which pointed at verdict text slice 5
cut: `react-chart-drilldown-invalid` and `react-chart-aggregate-invalid`
("the rejection above carries the fix") now say "use the rename it
suggests, or delete / move a key real on another surface / one layer
out"; `flow-time-relative-descriptor-invalid` ("satisfies each message
above") now says to start with the key the verdict names and re-run
until the schema accepts it.
- `component-type-unknown`'s "Apply the prescription above": the retired
arm now carries the prescription's own "Delete the …" sentence, cut
(never rewritten) by `retiredTypeRemedy()` from
`RETIRED_PAGE_COMPONENT_TYPES`, at its first dash only when the sentence
would pass 200 (for `element:form`: "Delete the `element:form` component
and use the object-bound `object-form` block instead").
- `approval-approvers-may-resolve-empty`'s `manager` arm (2,243 on
`main` after #22850): "Set every submitter's sys_user.manager_id (Setup
→ Users → Set Manager, or the admin user import), or add an approver
that cannot resolve empty". `MANAGER_ONLY_REMEDY` /
`MANAGER_ONLY_ROUTES` move, verbatim and under the same names, into the
id's explanation; the rule file keeps the measurement docblock behind
each graded route. The group arm (463) is one line too.
- `field-no-consumers`' carrier list (629 printed, 624 as the hint): the
first carrier site and `(and N more)`, the shape its verdict's same-name
clause already uses.
- **Enumerations cut to the first item and a count, elsewhere.**
`rls-predicate-unenforceable` rewrites the first faulty site and counts
the rest; for a bare `current_user` it names the key slot instead of
listing every kernel key. `rls-predicate-unknown-field` drops the
object's field list (the verdict's "Did you mean" names the nearest
column). `permission-retired-lifecycle-residue` carries the tombstone
prescription's "Delete the key" sentence, cut by `retiredKeyRemedy()`
(the clause after its dash, on what stays denied, goes to the
explanation), never retyped.
- **The explanations gain paragraphs; no entry is added or removed.**
The lowerable subset and its traps, the comparison classes with their
types (a new shared `CROSS_CLASS_LISTING` paragraph, held equal to
`CROSS_FIELD_CLASS_LISTING` by both rules' cross-class tests), the rule
each `current_user` holding breaks, the null-comparand rewrites, the
four ways to shrink an RLS predicate (with the top-level `&&` warning
kept on the one-line hint as well), the declared match-nothing filter
spelling, the drop-the-field alternatives for a readonly hook write, and
what deleting a retired permission key leaves. `CROSS_CLASS_REMEDY`
(module export, not on the barrel) is gone; each rule's line names its
own alternatives.
- **Nothing else moves.** Every `message`, rule id, severity and `path`,
and what each rule accepts or refuses, is unchanged: every hunk in the
11 rule files is a `hint` expression, a comment, a hint constant, or a
string-in / string-out cut helper (`retiredTypeRemedy`,
`retiredKeyRemedy`, `firstPathAndCount`, `firstAndCount`);
`typeFaultRewrite` keeps one rewrite per site and `kernelKeysHolding`
(which fed only the removed alternatives) is deleted. No condition,
branch, skip or dedupe moved.
- `.changeset/22161-lint-slice-11-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 4)
Measured at the door with `runRuntimeAuthoringRules` on the base dist
and on the rebuilt one (scratch probe):
- a `flow` write: `flow-time-relative-descriptor-invalid` (422
`issues[].hint`, 342 → 176), `filter-empty-combinator` (422, 647 / 764 →
134 / 154), `approval-approvers-may-resolve-empty` (2xx
`advisories[].hint` and the `[Protocol] authoring advisory` log line,
2,243 / 440 → 198 / 200);
- a `permission` write: `rls-predicate-unenforceable` (422, 912 / 312 →
186 / 121), `rls-predicate-unknown-field` (422, 708 → 154),
`permission-retired-lifecycle-residue` (2xx advisory, 573 / 681 → 14);
`rls-predicate-over-budget` runs on that write but fires only once the
platform CEL bounds refuse (the suite holds it with `atGa`), so the
probe drew no finding either side;
- a `view` write: `visibility-predicate-over-budget` (422, 651 → 179);
- a `hook` write runs none of the 15; the sharing-rule, component-type,
react-chart, `field-no-consumers` and `hook-api-update-readonly-field`
ids are CLI only.
Readers of `hint`, measured read-only against objectui `main` at
`dca25af` (`git grep`, no edit): Studio's `saveAdvisoryToast.ts`
`formatFinding` renders `[rule] where — message hint` for every 2xx
advisory, and `DraftChangesPanel.tsx` renders `p.hint || p.message` for
the in-browser security-posture lint (none of the 15).
`metadata-client.ts` `readSaveAdvisories` checks only that `hint` is a
string. The 422 `issues` renderers (`ResourceEditPage.tsx`) 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. **No reader in
this repository or in objectui matches on `hint` text**; the one text
match in this repository's sources (`check-yaml-examples.ts`) reads an
unrelated object.
`check:watch-hint-literal` guards something else of the same name: that
every `*_WATCH_HINTS` population declaration in a gate script is a
literal array (a computed one drops out of `dispatch-gates`). It ran
green before the gate set (72 declarations across 4 rostered names) and
inside it.
## Census (taken first, before any edit, at the base `31b5a5f7f5`)
Method: slices 4 to 10's scratch preload (`NODE_OPTIONS=--import`, never
committed), patching `Array.prototype.push` to record every
finding-shaped object, here 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,455 tests at the base): 240
ids fire; **144 carry a hint over 200**: 21 of the 26 fenced ids, 3
repo-gate ids (`lint-startup-registry-verdict.ts`), and **120
author-time ids**. The named five's 7 ids and the 8 longest others
(excluding the two `lint-flow-patterns.ts` ids, below) are this slice's
15.
- **Runtime publish gate**, the whole `@objectstack/metadata-protocol`
suite reading `@objectstack/lint` from its built dist (base dist before,
rebuilt after): fires `rls-predicate-unenforceable` (300 → 139) and
`permission-retired-lifecycle-residue` (573–681 → 14) of the 15.
- **`packages/cli` unit tier** (281 files): fires
`hook-api-update-readonly-field` (622 → 175) and `field-no-consumers`'
unchanged inert line (68).
- **`os validate`** (the built CLI) on `app-crm`, `app-todo`,
`app-showcase`, `app-multi-package`, exit 0 each, before and after:
`field-no-consumers` crm 68–336 → 68–179, todo 263–339 → 168–196,
showcase 68–624 → 68–199, multi-package 68 → 68;
`approval-approvers-may-resolve-empty` fires on the showcase, 440–2,243
→ 198–200 (FROM measured on `main` after #22850). Each app prints the
same number of `rule:` lines before and after (9 / 7 / 70 / 3).
## Tests
Every heavy run went through `scripts/pm/os-verify-lock.sh`; its VERDICT
lines are in the report.
- **Each rule's own suite pins the new shape.** Every touched suite
records what its cases fire; a new last block per id holds every
recorded `fix:` line of the converted ids to one line of at most 200
characters behind a coverage control (each arm fired: the three
unlowerable arms across the main file and its two siblings; both
empty-slate arms; the shape, type-fault and null arms; all three
combinator shapes; both retired keys; both readonly-hook verbs; every
retired component type and the own-namespace arm; the counted carrier
arm), plus exact pins per arm and a pin per id that `explainRule(id)`
holds what the line stopped saying. Pins that read the old hint text
moved to the explanation, text-only; the four sibling files' bounds are
scoped to the converted ids (the anchor and `unknown-user-variable` /
`unparseable` ids keep longer hints).
- **Lint suite** at the final head: Test Files 137 passed (137), Tests
6,496 passed (6,496); VERDICT command-exit 0.
- **Lint build + typecheck:** `pnpm --filter @objectstack/lint build`
(`check-dts-emitted` 6/6) and `run typecheck` (`check:test-typecheck`
OK, 2 files / 6 errors / 2 pinned signatures held), VERDICT command-exit
0. The rebuilt dist carries the new text (`Staff at least one target` 1
hit in each of `index.js`, `index.cjs`, `runtime.js`, `runtime.cjs`;
`Apply the prescription above` 0).
- **CLI unit tier** on the rebuilt dist: Test Files 281 passed (281),
Tests 4,196 passed (4,196); VERDICT command-exit 0. No `packages/cli` or
`packages/metadata-protocol` test asserts these ids' hint text, so the
declared rider went unused; the runtime-gate test that reads the residue
hint asserts its length over 10 (`Delete the key` is 14).
- **Runtime gate:** the whole `@objectstack/metadata-protocol` suite,
after: Test Files 225 passed, 3 skipped (228), Tests 28,131 passed, 19
skipped; VERDICT command-exit 0.
- **Corpus pins over `packages/lint/src`:** `pnpm --filter
@objectstack/spec test:repo`: Test Files 55 passed (55), Tests 971
passed (971); VERDICT command-exit 0. No new text carries an `os migrate
meta --from` sentence (the residue explanation names `os migrate meta`
without one), and no tracker id is in a printed hint or explanation
(citations stay in comments and test names).
- **Ablation** (one-shot, from the committed head `b4cf329ada`, 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.
`permission-retired-lifecycle-residue`'s pre-slice line was restored:
`hint: retiredKeyRemedy(prescription),` → `hint: prescription,` (anchor
x1 → x0, blob `52140529de29` → `c568d4a53e68`). Predicted before the
run: exactly the exact-remedy case and the new bound pin go red.
Observed: `src/validate-retired-permission-residue.test.ts` Tests 2
failed, 21 passed (23), exactly those two; the bound pin read 573.
Restored: blob `52140529de29` == HEAD, `git diff HEAD` empty, `git
status --porcelain` empty.
- **ESLint, narrowed:** `npx eslint --no-inline-config --format json`
over the 28 changed `.ts` files: 28 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:** two merges of `origin/main` through
`scripts/pm/os-regen-merge.sh` (to `12b9daf749`, then `55382dc02a`;
neither touched a line this slice edits, 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
`b4cf329ada` derived 62 commands; 60 ran, each exit code captured before
any pipe, all 0. 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 `b4cf329ada`,
`check:published-readme-exports` after building its four missing dists
(`client-react`, `embedder-openai`, `knowledge-ragflow`,
`organizations`) under the lock; the three PR-context guards run against
this PR and report on the card.
- **Round 2** (seat order `6111995055`):
- the conflict merge `bf78c173d3` with #22850 (`69d4218058`, the
organization administrators' slate). #22850's messages and its first
explanation paragraph win. The two one-line fixes and the moved
paragraphs are restated against it, and a test block holds that no fix
line or explanation describes a retired rescue path;
- the merge `4ca9154ae6`;
- head `6e2c2f068c`.
- Readings: lint 137 files / 6,497 tests; the approval file 56 / 56;
metadata-protocol 225 files + 3 skipped / 28,131 tests; spec `test:repo`
55 / 971; `check:generated` 14 up to date. The no-path union is 109
derived, 107 run, 2 NOT MEASURED (`dual-build-cjs-loads`,
`type-check-debt`), 0 UNRUN, and the 3 PR-context guards are green.
## Remaining for this card
- **107 author-time `packages/lint` ids still carry a hint over 200
characters** in the final lint census (at `b4cf329ada`: 131 over 200 =
107 + 21 fenced + 3 repo-gate). The longest, by file:
`lint-flow-patterns.ts`'s `flow-double-brace-interpolation` 1,339 and
`flow-bare-dollar-reference` 1,231 (not taken here: PR #22832, then
open, edited `lint-flow-patterns.test.ts`, and the slice stayed off that
file; #22832 has since landed);
`validation-rule-json-schema-unknown-format` 646;
`flow-credential-literal` 646; `measure-aggregate-field-type-refused`
645; `predicate-rhs-path-shaped` 643;
`rls-predicate-unknown-user-variable` 618;
`hook-api-update-readonly-when-field` 607;
`unique/legacy-organization-composite` 578;
`dashboard-filter-field-unprovisioned` 561; `action-name-undefined` 560;
`flow-update-readonly-when-field` 549; `ai-skill-tool-unresolved` 548;
`org-axis-permission-inheritance` 542; `rls-predicate-unparseable` 523;
`object-reference-unknown` 522; `filter-empty-node` 515; then 90 more
down to 201. The full list, by file, is in the report on the card.
- **The 26 fenced ids**, as PR #22448 lists them (21 fire a hint over
200), and the 3 repo-gate ids.
- **The `packages/cli` ids** live on #22841.
## Acceptance notes
- **A long name can still lengthen a line.** The bound holds on every
variant the suites and the four example apps fire; the names in a hint
(object, field, carrier path, rewritten predicate) are the author's. The
closest measured case is `field-no-consumers` on
`showcase_project_membership.allocation_percent` at 199, whose first
carrier path is 80 characters.
- **`field-no-consumers`' carrier list beyond the first now prints
nowhere on the CLI.** The finding object still holds every site in
`carriers` for a programmatic caller, but `os validate --json` carries
an advisory as one `where: message` string and `os lint --json` maps the
finding to `fix` without `carriers`. The card's "Asked for" names no
carrier list at all; the first site plus the count is the dispatch's
route.
- **The spec's pointer moved with the remedy (round 2, the
seat-authorized rider, order `6111995055`).** `ApproverType`'s
`.describe()` (`packages/spec/src/automation/approval.zod.ts`) now
points at `os explain approval-approvers-may-resolve-empty`, and its
comment names `rule-explanations.ts` as the carrier. The reference page
`content/docs/references/automation/approval.mdx` is regenerated, and
`.changeset/22161-spec-approver-type-describe.md` (`@objectstack/spec`
`patch`, `Clause-②: no`) carries it. This PR now publishes
`@objectstack/lint` `patch` and `@objectstack/spec` `patch`.
- **`rls-predicate-unknown-field`'s old hint said a missing column is
"an outage in one position and an open door in the other"**, which the
explanation's own history note records as no longer true (the
compiled-predicate guard fails closed everywhere). The sentence is not
carried into the explanation.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF)_
---------
Co-authored-by: Claude <noreply@anthropic.com>os explain RULE_ID carries what they enumerated (#22878)1 parent aa94566 commit 2e11d90
32 files changed
Lines changed: 1235 additions & 423 deletions
File tree
- .changeset
- content/docs/references/automation
- packages
- lint/src
- spec/src/automation
| 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 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
| 56 | + | |
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
| |||
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
86 | | - | |
| 86 | + | |
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
| 96 | + | |
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
| 127 | + | |
128 | 128 | | |
129 | 129 | | |
130 | 130 | | |
| |||
0 commit comments