Repository navigation
Commit b616c0a
Fixes #16094
Clause-②: no
Ruling-ref: 5560227939
## What changes
`shouldWarn` in `packages/lint/src/lint-liveness-properties.ts` now
admits a ledger row whose status is `dead`, `live-elsewhere` or
`experimental`, or that opts in with `authorWarn: true`. Before, only
`experimental` warned without an opt-in. `describe()`'s mapping to
`liveness-dead-property` / `liveness-live-elsewhere-property` is
unchanged. Before the change, both of those rule ids were exported and
could never be produced: across the shipped ledgers, not one `dead` or
`live-elsewhere` row set `authorWarn`.
`checkItem` changes only for rows the ruling newly admits. A row that
warns only because of its `dead` / `live-elsewhere` verdict (no
`authorWarn`) shows its `authorHint`, else the verdict's existing
default hint, and never its ledger `note`. Rows that opt in with
`authorWarn`, and `experimental` rows, keep their hint exactly as
before. This is the seat's Q3 answer (card comment 5925292339).
Other changes:
- **Stale comments.** Comments the flip made false are corrected in the
same file. The one stale line above the `lintLivenessProperties`
registry entry in `authoring-rules.ts` is corrected too.
- **Changeset.** One `minor` changeset for `@objectstack/lint`, carrying
`Clause-②: no` in its body.
## Recount on `main`
Measured at base `6073bb96b8`, all depths:
| Rows | Count |
|---|---|
| `dead` | 109 |
| `live-elsewhere` | 1 |
| Of those 110, `retiredKey` tombstones (`check:liveness --json`) | 97 |
| Authorable | 13 (12 `dead`, 1 `live-elsewhere`) |
| Authorable and reachable through the rule's walk | 4 |
The 4 reachable rows are `view.name`, `view.label`,
`permission.rowLevelSecurity.label` and
`permission.rowLevelSecurity.description`.
The other 9 authorable rows sit in types the walk never visits:
- `connector.metadata`
- `manifest.runtime` and `manifest.integrity`
- six `realtime_subscription` rows
The warn map grows by 105 entries, at depth 1 or less. None of the 105
carries an `authorHint`, and 80 of their notes cite a tracker id. That
is why the hint selection changed in this PR.
## The live-elsewhere end-to-end pin: measured impossible, replaced by
an equivalent invariant plus a reach sentinel
The ruling asks for an end-to-end pin proving
`liveness-live-elsewhere-property` is produced against the shipped
ledger from its one `live-elsewhere` row. That row is
`manifest.runtime`.
**Measured: no stack can produce that id through
`lintLivenessProperties`.**
- `manifest` is not in `TYPE_COLLECTIONS`, and it is not one of the
bespoke walks (objects/fields, translation bundles).
- `stack.manifest` is a single object, not a collection.
- `authorWarnedProperties` has one caller outside its package,
`packages/cli/src/utils/i18n-extract.ts`, and that caller asks only
about `translation`. No translation row is newly admitted.
**The invariant that replaces the literal pin is equivalent for the
ruling's purpose, plus a reach sentinel.** Three pins on the shipped
ledger hold it:
1. The shipped row is admitted:
`authorWarnedProperties('manifest').has('runtime')`.
2. The shipped row maps to `LIVENESS_LIVE_ELSEWHERE_PROPERTY`:
`checkItemAgainstWarnMap` over the row as read from the shipped
`manifest.json`.
3. **No walk visits `manifest`.** `lintLivenessProperties({ manifest: {
runtime, integrity } })` says nothing about either key. This pin goes
red the day any walk visits `manifest`, and its comment names
`manifest.integrity`.
Pin 3 matters because `manifest.integrity` is `dead` in the ledger, yet
`os plugin publish` reads its map and refuses on a digest mismatch
(`packages/cli/src/commands/plugin/publish.ts:134`). A manifest walk
added before that row is re-graded would tell authors a gate-read key is
inert. Pin 3 makes that walk a deliberate, visible change.
Because `manifest` is not walked, the flip does not surface
`manifest.integrity` to any author.
## `--strict`, and why `Clause-②` stays `no`
Nothing is refused, and nothing changes without `--strict`. `os lint
--strict` and `os validate --strict` go from exit 0 to exit 1 on a stack
that was otherwise warning-clean and authors a view container
`label`/`name` or an RLS policy `label`/`description`.
Measured with the CLI built from source on fixture stacks, before (base)
and at this head:
| fixture | `os lint` | `os lint --strict` | `os validate` | `os
validate --strict` |
|---|---|---|---|---|
| clean | 0 → 0 | 0 → 0 | 0 → 0 | 0 → 0 |
| view container `label` | 0 → 0 | **0 → 1** | 0 → 0 | **0 → 1** |
| RLS policy `label` + `description` | 0 → 0 | **0 → 1** | 0 → 0 | **0 →
1** |
| raw config (no `defineStack`) with tombstoned `list.striped` | 0 → 0 |
**0 → 1** | 1 → 1 | 1 → 1 |
The `Clause-②` criterion is "widens the accept set or enlarges the
public surface". A new warning does neither, and both rule ids were
already exported. The seat ruled on this as Q1 (card comment
5925292339), citing two precedents:
`.changeset/20654-flow-credential-literal-advisory.md` and lint
`d753744`.
Other doors:
- `os build` has no warning-promoting flag.
- The runtime publish door runs this rule for `email_template` /
`mapping` / `datasource` only, and none of those gains a row.
- The `os lint --eval` corpus has no views and no RLS.
- `check:i18n-coverage` counts only `i18n/` rules.
- No repo gate asserts a zero-warning count on the examples.
**Bump: minor.** The at-tier contract review (`5925918475`) set this,
and it corrects the earlier `patch`. No export is added or removed, and
the default-face accept set is unchanged. But two rule ids become
producible, which a consumer sees as two new advisories, and a non-zero
exit becomes reachable under `--strict`. The repository grades that
shape `minor`: `.changeset/20654` is a new advisory with `Clause-②: no`
at `minor`, and the cli 17.5.0 entry grades "the new advisories and the
newly reachable non-zero exit" `minor`. `d753744` is a fix to an
existing finding, not a precedent for this. The changeset carries
`Clause-②: no` in its body.
## Who starts warning
These are this repository's example apps, run with `os lint --json` and
`os validate --json`, at base and at this head:
| example | new findings | exit codes (4 modes) |
|---|---|---|
| `app-showcase` | 2 × `liveness-dead-property`: permission
`showcase_contributor`, `rowLevelSecurity.label` and `.description` |
unchanged |
| `app-crm` | 0 | unchanged |
| `app-todo` | 0 | unchanged |
| `app-multi-package` | 0 | unchanged |
All four examples already exit 1 under `--strict`.
**The two showcase findings' printed hint.** At this head, both read:
`Remove it — it is declared in the spec but not consumed at runtime.`
That is 69 characters.
Before the hint fix, both printed the ledger note instead (939 and 270
characters of maintainer prose). The 270-character one ended "Benign,
not authorWarn'd."
The showcase's two existing `liveness-planned-property` findings
(`externalSharingModel`) are byte-identical, message and fix, between
base and this head.
## Retired keys
A `retiredKey` tombstone keeps its `dead` row.
- **Commands that parse.** `os validate`, `os build` and the runtime
gate refuse the key first, so it gets no second report. A `defineStack`
config with a retired key fails `os lint` at load as before.
- **`os lint` on a raw config.** `os lint` does not Zod-parse. A raw
config without `defineStack` that `os lint` accepts, and that carries a
retired key, now gets a `liveness-dead-property` warning with the
default "Remove it" hint. Before, it got nothing.
Ten existing pins asserted silence on such keys. That silence came from
the opt-in, not from the tombstone. They are re-judged to assert the
`dead` grade, with the parse control alongside: `ViewSchema` refuses
`list.striped`.
## Tests
All results below are at head `49fb6cfad3`, after merging `origin/main`
at `9c8b65aa23` (which carries #21047).
- **Scoped file.** `pnpm --filter @objectstack/lint exec vitest run
--maxWorkers=2 src/lint-liveness-properties.test.ts`: 93 passed, against
84 at base. There are 9 new pins, and the 10 silence pins are re-judged.
- **Whole package.** `@objectstack/lint`: 118 files, 5486 tests passed.
`pnpm --filter @objectstack/lint typecheck`: exit 0.
- **CLI.** Unit tier: 240 files, 3419 tests passed. Integration tier:
all 69 files, run as 4 lock-held runs, 591 passed and 1 skipped.
`lint-per-package-authoring-parity` passes 5/5.
- **Gates.** `dispatch-gates --commands`: 61 families, all exit 0.
`--ran`: 61 derived, 61 run, 0 NOT-MEASURED.
- **eslint.** A narrowed run over the 4 changed `.ts` files: 0 errors, 0
warnings.
The new pins, end to end against the shipped ledger:
- an authorable `dead` key, `rowLevelSecurity.label` / `.description`,
produces `liveness-dead-property`;
- the live keys beside it stay silent;
- the dotted path fans out past index 0;
- the fixture parses through `PermissionSetSchema`;
- a tombstoned key is refused at parse;
- the three `manifest` pins described above.
**Hint pins, ledger-wide:**
- All 105 verdict-triggered rows show a hint that is not their note and
that carries no tracker id. Anti-vacuity: 80 of those notes do carry
one.
- **Control:** all 9 rows that warned before the ruling (`authorWarn` or
`experimental`) keep `authorHint ?? note` byte for byte. Anti-vacuity: 6
of them show their note today.
- Synthetic pins hold the precedence per verdict and opt-in.
**Ablations**, through `scripts/ablation-replace.mjs`: the anchor hit
and the blob moved each time. Every restore left the blob equal to HEAD
and `git diff HEAD` empty, under a trap.
| Mutation | Result | What it proves |
|---|---|---|
| Verdict set reduced to `{experimental}` (pre-ruling behaviour) | 12
red, including both new verdict pins | the verdict pins depend on the
flip |
| Hint selection reverted to `authorHint ?? note ?? default` | 3 red:
the three Q3 pins | the Q3 pins catch the old precedence |
| Hint selection widened to never show any note | 3 red, including the
byte-for-byte control | the control can fail |
The test subject resolves to `src` through the relative import, so no
dist build was involved in any ablation.
**Narrowed eslint** (the 4 changed `.ts` files, as in Tests above):
- Population: `eslint.config.mjs`'s `**/*.{ts,…}` and
`packages/**/*.{ts,…}` blocks select all four; the changeset is not
linted.
- Invariance: the config enables no type-aware linting (no
`parserOptions.project` or `projectService`), so this diff cannot move
any untouched file's verdict.
**Control bytes.** A self-scan of the 5 changed files finds none.
`check:nul-bytes` exits 0.
## The CLI parity fixture, re-judged
The #18778 fixture in
`packages/cli/test/lint-per-package-authoring-parity.test.ts` declared
view containers as `{ name, label, object, list }`. `view.name` and
`view.label` are `dead`, so after the flip the union run raised 4
warnings, which broke the fixture's premise that the union raises
nothing. CI on `64e0275024` failed two tests (`expected 5 to be 1`, at
`:241` and `:279`). The containers now bind by `object` alone
(`list.label` is live and stays), with a comment saying why. Each test
keeps its intent: the union is clean, there is one per-package survivor,
and `--strict` fails with 1.
## Acceptance notes
- **Pre-existing note leak on opted-in rows (not touched here, as
ruled).** `object.externalSharingModel` is `planned` + `authorWarn` with
no `authorHint`, so authoring it prints its ledger note as the fix. That
note is 728 characters and cites `#2696`. Measured on `app-showcase` at
this head: 2 findings (`showcase_account`, `showcase_announcement`).
Across the shipped ledgers, 6 of the 9 rows that warned before the
ruling show their note. AGENTS.md keeps tracker numbers out of anything
an author is shown.
- **A manifest walk, and the `manifest.integrity` re-grade it would need
first.** These have zero measured pull. No example or plugin config in
this repository authors `runtime` or `integrity`; `os plugin build`
writes `integrity`. The note that deferred the row's status answers 404.
No carrier.
- **`connector` is not walked.** After the flip, `connector` (a real
stack collection, `connectors`) carries a warn-worthy row, `metadata`.
By its own note it is an uninterpreted extension bag whose specification
is "no consumer", and `connector` is not in `TYPE_COLLECTIONS`.
Registering it would warn on every authored `metadata` bag. No carrier.
- **#21047 landed** (`88b484e00c`) and is merged here. The field-set pin
is measured, not assumed, at `['conditionalRequired']`: `field.picklist`
is `live` with no `authorWarn` after #21047, and
`field.conditionalRequired` is a `dead` tombstone.
- **Merge state.** `origin/main` was merged at `9c8b65aa23`
(`b0705c1530`). The 6 later `main` commits touch neither
`packages/lint`, `packages/spec/liveness` nor the CLI parity test.
- **`mapping.connectorSource` crash on `main`, not this PR's.** `main`
ships `mapping.connectorSource` as `live` + `authorWarn` (8368f1c),
and that crashes `os lint` and `os validate` on any stack authoring it:
`describe()` throws its integrity sentinel. This PR's hint control is
scoped to rows `describe()` answers, and its COVERAGE pin keeps the
sentinel loud. Filed as #21127.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JAhu8u8QfBvRjVZDox7CP9)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1bd14c9 commit b616c0a
5 files changed
Lines changed: 378 additions & 79 deletions
File tree
- .changeset
- packages
- cli/test
- 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
154 | 159 | | |
155 | 160 | | |
156 | | - | |
| 161 | + | |
157 | 162 | | |
158 | 163 | | |
159 | 164 | | |
160 | | - | |
| 165 | + | |
161 | 166 | | |
162 | 167 | | |
163 | 168 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1494 | 1494 | | |
1495 | 1495 | | |
1496 | 1496 | | |
1497 | | - | |
1498 | | - | |
1499 | | - | |
| 1497 | + | |
| 1498 | + | |
| 1499 | + | |
1500 | 1500 | | |
1501 | 1501 | | |
1502 | 1502 | | |
| |||
0 commit comments