Skip to content

Commit 9b7a0ef

Browse files
docs(spec): the liveness README's author-warning section states the verdict model the lint ships (#21450)
Fixes #21135 Clause-②: no ## What this changes `packages/spec/liveness/README.md`, section "Author warnings — closing the loop", now describes the model `packages/lint/src/lint-liveness-properties.ts` ships on `main`: - A `dead`, `live-elsewhere` or `experimental` verdict warns on its own. `authorWarn` opts in a `planned` row, and nothing else. - Every warning shows the row's `authorHint`, or else the verdict's default hint. The ledger `note` is never shown. - Rule 1 is about the verdict, not the marker. Rule 2 (booleans) covers any materialized default. - The coverage paragraph states the walk's real reach. Two sentences outside the section restated the old opt-in model, so they are fixed too (dispatch Zone 2, item 4). There is also one `@objectstack/spec` `patch` changeset. ⛔ No ledger row changes. ⛔ No lint code change. The paragraph "And one the gate enforces for you: never on a `live` row" was re-checked against the code and is unchanged (see Premise check, item 1). ## Premise check (dispatch Zone 2, measured at `535d1d25ab`) 1. **The hint chain had moved again since the card's table. The section is written to the code, not to the card or the triage.** - `shouldWarn` (`lint-liveness-properties.ts:173-176`) admits a row whose status is `dead`, `live-elsewhere` or `experimental`, or whose `authorWarn` is `true`. - `checkItem` (`:407-410`) builds the hint as `entry.authorHint ?? defaultHint`, for every row. Commit `327391c3af` (PR #21169, landed for #21096) removed the `note` fallback that opted-in and `experimental` rows still had. So triage's chain ("`note` only for opted-in and `experimental` rows") no longer holds. The README now says the `note` is never shown, on any row. - `live` + `authorWarn`: `describe()` throws its integrity sentinel (`:283`). `check:liveness` refuses the combination at every depth (`scanAuthorWarnRows` in `check-liveness.mts`, from commit `9bdc6d3faa`, PR #21176, landed for #21127). The README paragraph #21176 added says exactly that, so it stays as it is. - **Probe** (tsx over `src`, against the shipped ledgers): - A view container `name` / `label` → `liveness-dead-property`, hint "Remove it — it is declared in the spec but not consumed at runtime." - `rowLevelSecurity[].label` / `.description` → the same. - `object.externalSharingModel` (`planned` + `authorWarn`, no `authorHint`) → `liveness-planned-property`, default "Keep it — …" hint. - `action.outcomeMessages` (`planned` + `authorWarn` + `authorHint`) → its `authorHint`. - `agent.lifecycle` / `tool.outputSchema` (`experimental`) → the default hint, not the `note`. - `manifest.runtime` (`live-elsewhere`) is admitted to the warn map, but `lintLivenessProperties({ manifest })` gives 0 findings: no walk visits `manifest`. - `dashboard`'s warn map holds no depth-2 key (`widgets.chartConfig.*` are `dead`), so the lint reads one level of `children`. - Synthetic: a `dead` row gives byte-identical findings with and without `authorWarn`. A `live` + `authorWarn` row throws. 2. **Stale sentences re-located.** At `535d1d25ab` they sit at `README.md:560` ("opt-in per entry"), `:564` (the `experimental`-only default), `:565` ("falls back to `note`"), `:569-572` (rule 1), `:573-577` (rule 2) and `:587-591` (coverage). The same section also had two false statements the card did not list: - "(see `enable.searchable`'s `_authorWarnSkipped`)": no ledger row at any depth carries `_authorWarnSkipped` (census over all 41 ledgers, 940 rows), and `enable.searchable` is `live`. - "It covers **every governed type**": 11 governed types with warning rows are not walked (`manifest`, `connector`, `analytics_cube`, `query`, `realtime_subscription`, `api` and the five `RestServerConfig` families). 3. **No generated file lifts this section.** `pnpm --filter @objectstack/spec run check:generated` passes with "All 15 generated artifacts are up to date", `check:docs` included. The README's only programmatic readers are `check-liveness.mts`, `build-state-counts.mts` and `readme-table.mts`, and all three read the *Current state* table only. `git grep` for the section's phrases over `content/`, `apps/` and `packages/spec/scripts` gives 0 hits. 4. **The old model elsewhere.** `git grep -n "opt-in per entry\|authorWarn"` over `packages/spec/liveness/` and `content/docs/`: - `README.md:379` ("the honest status is `dead` + `authorWarn`") and `:966-969` ("every misleading entry carries `authorWarn`", plus "must also be registered in `TYPE_COLLECTIONS`") state the old model as current. Both are fixed here. - `:348` / `:360` (the dated 2026-07 tally), `:434`, and the *Current state* Notes cells are dated history, not the current model. They are unchanged. - `content/docs/`: the only hits are `releases/v14.mdx:245` and `releases/v17/index.mdx:175`. Both are release-owned history, so there is no owner to notify. `permissions/authorization.mdx:524` and `ui/translations.mdx:390` describe warnings in terms that still hold. ## Every sentence changed, before → after **Section heading** - Before: `## Author warnings — closing the loop (`authorWarn`)` - After: `## Author warnings — closing the loop (verdicts, and `authorWarn`)` **Intro paragraph** - Before: "Classification is also fed back to the *author* at build time. The CLI `compile` lint (`packages/lint/src/lint-liveness-properties.ts`) reads these ledgers and emits an advisory **warning** when an authored object/field sets a property that is misleading — "you set this expecting it to do something; at runtime it does nothing / isn't enforced" — with a corrective hint. Never fails the build." - After: "Classification is also fed back to the *author*. The liveness lint (`packages/lint/src/lint-liveness-properties.ts`) reads these ledgers and emits a **warning** when an authored item sets a property whose row warns — "you set this expecting it to do something; at runtime it does nothing / isn't enforced / isn't read yet" — under a rule id per verdict, with a corrective hint. `os lint`, `os validate` and `os build` / `os compile` run it, and so does the runtime metadata write door for `email_template`, `mapping` and `datasource` items. It is never an error and never fails `os build`, but `os lint --strict` and `os validate --strict` turn every warning into exit 1." - Sources: `AUTHORING_COMMANDS` and `runtimeTypes` in `authoring-rules.ts`, and `failing = errors.length + (strict ? warnings.length : 0)` in `lint.ts`. #21092 measured the `--strict` 0 → 1 flip at both doors. **The opt-in line** - Before: "Signal over noise is the whole point, so warnings are **opt-in per entry**:" - After: "**The verdict decides which rows warn.** `authorWarn` only opts in a row whose verdict does not warn on its own:" It is followed by a new five-row table: `dead`, `live-elsewhere` and `experimental` always warn, `planned` only with `authorWarn`, and `live` never. Each row names its rule id. **`authorWarn` field row** - Before: "warn when this property is authored (in addition, any `experimental` entry warns by default — it's a declared-but-unenforced guarantee)." - After: "makes a `planned` row warn. On a `dead`, `live-elsewhere` or `experimental` row it changes nothing, because the verdict already warns. So leaving it off never keeps such a row quiet." **`authorHint` field row** - Before: "the corrective one-liner shown under the warning (falls back to `note`)." - After: "the corrective one-liner shown under the warning, on every row that warns. Without one, the verdict's default hint is shown: `dead`'s says "Remove it", `planned`'s and `live-elsewhere`'s say "Keep it", and `experimental`'s says the guarantee is not yet enforced. The row's `note` is never shown to an author, on any row: it is written for this ledger's maintainers." **Rules intro** - Before: "Two rules keep it false-positive-free, **both of which the marker author must respect**:" - After: "Two rules keep the warnings truthful. **Both bind whoever grades a row, not only whoever marks one**:" **Rule 1** - Before: "**Only mark genuinely *misleading* dead props** — ones that imply a capability/behavior that doesn't exist (`versioning`, `field.columnName`, `softDelete`). Benign display/doc metadata that's "dead" (no runtime reader) — `description`, `tags`, `icon` — must NOT be marked; an author isn't misled by them." - After: "**Grading a row `dead`, `live-elsewhere` or `experimental` is an author-facing act.** The verdict warns every author who sets the key, and fails their `--strict` run. No marker keeps it quiet. That includes benign display or doc metadata with no runtime reader (`description`, `tags`, `icon`): graded `dead`, it warns "Remove it" like any other dead key." It is followed by three bullets: - Measure a display key against *Designer previews count as consumers* before writing `dead`. A key that something shows to a person is `live`, and that measurement, not a missing marker, is what keeps it quiet. - A display key still `dead` after that warns, and the warning is the measurement speaking. ⛔ Never grade a row `live` or `planned` to silence it. - When the verdict's default hint is the wrong corrective, give the row an `authorHint`. The `note` cannot do that job. **Rule 2** - Before: "**Booleans: only mark `default(false)` flags.** The lint warns on a boolean only when set `true`, and it can't tell author-set-`true` from a schema default. A `default(true)` flag (`enable.searchable`) would then warn on *every* object that has an `enable` block — so leave those unmarked (see `enable.searchable`'s `_authorWarnSkipped`). Object/string/array props warn when merely present, so this caveat is boolean-only." - After: "**Booleans and materialized defaults.** The lint warns on a boolean only when it is `true`, and on any other value when it is present at all. Where the stack it reads has been parsed (`os validate`, `os build`, any `defineStack` config), schema defaults have already filled in, so it cannot tell an authored value from a default. A `default(true)` flag, or any key whose default materializes, would warn on every item that carries the default, whoever wrote it. So `authorWarn` stays off such a row, and a warning verdict on one warns every such item. `enable.searchable` (`default(true)`) is the shape; it is graded `live`, so the question does not arise for it. `mapping.errorPolicy` and `batchSize` were dead keys of this kind, and no warning could reach their authors truthfully, so they were retired instead (the `mapping` row below)." - The old "boolean-only" sentence contradicted the README's own `mapping` row, which calls those two keys "the non-boolean instance of the default(true) rule". On parsing: the rule's input tier is `parsed`, and `defineStack` runs `ObjectStackDefinitionSchema.safeParse` by default. `os lint`'s own comment says its `parsed` tier fills no defaults for a raw config, which is why the sentence names the doors where defaults do fill. **Coverage paragraph** - Before: "The lint is ledger-driven: coverage grows by marking more entries `authorWarn`, not by touching the lint code. It covers **every governed type**: objects (incl. `enable.*`) and their fields walk bespoke nesting; flows/actions/agents/tools/skills/datasets/permissions/hooks/pages are checked as flat stack collections, and container properties fan out over arrays (each flow node, each dataset measure)." - After: "The lint is ledger-driven: coverage grows with the ledger's verdicts (grading a row `dead`, `live-elsewhere` or `experimental`) and with `authorWarn` opt-ins on `planned` rows, not by touching the lint code. That holds only inside the types its walk visits: objects (incl. `enable.*`) and their fields walk bespoke nesting, translation bundles walk their locale entries, and every type listed in the lint's `TYPE_COLLECTIONS` is checked as a flat stack collection, with container properties fanning out over arrays (each flow node, each dataset measure). It reads a row and its direct `children`, no deeper. A governed type the walk does not visit (`manifest`, `connector` and `realtime_subscription` are three) warns no author through this lint, whatever its rows say." **Outside the section, `README.md:379`** - Before: "When in doubt, the honest status is `dead` + `authorWarn`: an author who gets a" - After: "When in doubt, the honest status is `dead`, which warns its authors with no `authorWarn` marker (see *Author warnings* below): an author who gets a" **Outside the section, below the *Current state* table** - Before: "The `dead` set across types is the enforce-or-remove worklist (ADR-0049); every misleading entry carries `authorWarn` so authors hear about it at compile time (governed types with warn entries must also be registered in the CLI lint's `TYPE_COLLECTIONS` — see lint-liveness-properties.ts)." - After: "The `dead` set across types is the enforce-or-remove worklist (ADR-0049); a `dead` row warns its authors at compile time by its verdict alone, with no `authorWarn` marker (see *Author warnings* above). That reaches an author only in a type the lint walks: a governed type's warning rows are heard only once the lint visits the type, which for a flat stack collection means registering it in the lint's `TYPE_COLLECTIONS` (see lint-liveness-properties.ts)." - Measured: 0 `dead` rows carry `authorWarn` today. The only `authorWarn` rows are 3 `planned` ones: `action.outcomeMessages`, `object.externalSharingModel` and `translation.flows`. The old "must also be registered" did not hold either: `manifest` is deliberately unwalked, and #21092 pinned that. ## Changeset: measured, not assumed - `npm pack --dry-run --json --ignore-scripts` in `packages/spec` lists 2077 files, and `liveness/README.md` is one of them (control: `liveness/view.json`, also listed). The README ships, so it gets a `patch` changeset for `@objectstack/spec`: `.changeset/21135-liveness-readme-author-warnings.md`, carrying `Clause-②: no`. - The `Check Changeset` job (`pr-automation.yml`) has no path exemption. It is skipped only by the `skip-changeset` label or the release PR. ## Verification (all at head `a3aed7da52`) | What | Command | Result | |---|---|---| | spec build (empty closure) | `pnpm --filter @objectstack/spec build` (under the verify lock) | exit 0 | | generated artifacts | `pnpm --filter @objectstack/spec run check:generated` | exit 0, "All 15 generated artifacts are up to date" | | spec tests (the card's done-when) | `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2` (lock) | exit 0: 600 files, 17684 passed, 1 todo | | the lint the section documents (not touched; evidence that the model described is the pinned one) | `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/lint-liveness-properties.test.ts` (lock) | exit 0, 95 passed | | derived gates | `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths; 2 paths vs merge base `535d1d25a`), each command run, exit captured before any pipe | 60 derived: 58 exit 0, 2 exit 3 (below) | | reconciliation | `dispatch-gates.mjs --ran ran.list` | "60 derived, 58 run, 2 NOT-MEASURED, 0 UNRUN" | | roster gates whose roster sits in a directory this diff touches | `check-changeset-fixed`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity` | all exit 0 | | liveness gates | `check:liveness`, `check:empty-state`, `check:strictness-ledger`, `check:variant-docs` (in the derived set) | all exit 0 | | nul bytes, plus a hand scan for control bytes | `pnpm check:nul-bytes`, and a `grep -P` control-byte scan over both files | exit 0; 0 hits | **NOT MEASURED (declared to CI).** Both report `PREREQUISITE NOT MET`: they load built `dist/` entry points this worktree does not have. Neither reads anything this diff changes, since a markdown file and a changeset are emitted into no `dist/`. The `Lint & Repo Gates` job measures both on a fresh full build. - `pnpm check:dual-build-cjs-loads` needs every package's `dist`. - `pnpm check:lean-entry-closure` needs the `@objectstack/objectql` closure's `dist`. **Typecheck not run (declared).** The diff has no TypeScript, and neither `.md` file is in any tsc program. **eslint, narrowed and measured.** `pnpm exec eslint --no-inline-config --format json` was run over both changed paths. - Population, from eslint's own answer: 2 files, and both report "File ignored because no matching configuration was supplied". The config's file globs are code extensions only (`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`). - Count, from the JSON output: 2 results, 0 errors, 0 lint messages. - Invariance: `eslint.config.mjs` enables no type-aware linting (no `parserOptions.project` / `projectService`), so this diff cannot move the verdict on any file it does not touch. ## Acceptance notes These are observations, not filed. - **Stale `_authorWarnSkipped` mentions elsewhere.** No ledger carries the field any more. It is still named in spec source comments (`data/mapping.zod.ts:30`, `ui/app.zod.ts:1153`, `conversions/registry.ts:3013`) and in two dated *Current state* Notes cells (`mapping`, `qa`). These are comments and history, with no runtime or author-facing effect. Carrier: none. - **Ledger notes on `view.label`, `rowLevelSecurity.label` and `.description`** end with "Not authorWarn'd" / "Benign, not authorWarn'd". That is true of the marker, but no longer means silence: the verdict warns. This card touches no ledger JSON, so they are unchanged. The rows themselves were measured under the designer-previews ruling (objectui `db11afd4967`), so the restated rule 1 implies no re-grade. Carrier: none. - **Stale reasoning in code comments.** `kernel/metadata-plugin.zod.ts:947` reasons from "`datasource.json` carries 0 `authorWarn` rows". Its conclusion still holds, because `datasource` has no warning-verdict row, but the reason is the old model. The lint's own docblock (`lint-liveness-properties.ts:720`) says "Covers every governed metadata type", which the coverage measurement above contradicts. Both are comments, and lint code is out of this card's scope. Carrier: none. - **Upstream since `535d1d25ab`.** `origin/main` moved 2 commits, touching `liveness/analytics_cube.json` and `liveness/dataset.json` only. Neither touches a sentence here or a file in this diff. --- _Generated by [Claude Code](https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent b94a2a7 commit 9b7a0ef

2 files changed

Lines changed: 82 additions & 31 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
Liveness ledger README: the "Author warnings" section now describes the model the liveness lint ships. A `dead`, `live-elsewhere` or `experimental` verdict warns on its own, and `authorWarn` only opts a `planned` row in.
6+
7+
Clause-②: no
8+
9+
- The section said warnings were opt-in per ledger row, and that only `experimental` warned without the marker. That stopped being true when the lint made a `dead` or `live-elsewhere` verdict warn on its own. The section now has one table of which verdicts warn, and under which rule id.
10+
- `authorHint` no longer "falls back to `note`". Every warning shows the row's `authorHint`, or else the verdict's default hint. The `note` never reaches an author.
11+
- Rule 1 now talks about the verdict, not the marker. Grading a row `dead`, `live-elsewhere` or `experimental` warns every author who sets the key, and fails their `os lint --strict` / `os validate --strict` run. No marker keeps it quiet, so a benign display key is measured against the designer-previews ruling before it is graded `dead`.
12+
- Rule 2 (booleans) now covers any key whose schema default materializes. It no longer points at an `_authorWarnSkipped` marker, which no ledger carries.
13+
- The coverage paragraph states the walk's real reach: the types it visits, one level of `children`, and that a governed type it does not visit warns no author through this lint.
14+
- Two sentences elsewhere in the README said a `dead` row needs `authorWarn` to warn. Both are corrected the same way.
15+
- ⛔ Documentation only: no ledger row, schema, export or lint behaviour changes.

0 commit comments

Comments
 (0)