Skip to content

docs(spec): the liveness README's author-warning section states the verdict model the lint ships - #21450

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-21135-liveness-readme-author-warnings
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-21135-liveness-readme-author-warnings

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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.
  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. feat(lint): a ledger-dead or live-elsewhere key warns without an authorWarn opt-in, and never shows the ledger note (#16094) #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 feat(lint): a ledger-dead or live-elsewhere key warns without an authorWarn opt-in, and never shows the ledger note (#16094) #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

…erdict model the lint ships

A dead, live-elsewhere or experimental ledger row warns its author on its
own; authorWarn only opts a planned row in. Every warning shows the row's
authorHint or the verdict's default hint, never the note. Rule 1 is restated
for the verdict, rule 2 for any materialized schema default, and the
coverage paragraph names the walk's real reach. Two sentences elsewhere in
the README that tied a dead row's warning to authorWarn are corrected.

Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/m label Oct 2, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/liveness/README.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/liveness/README.md) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 53fd35e3e3a0b18b790ab79bd2c65f353e11ab65 → packageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

2 participants