Skip to content

fix(lint): a liveness finding's fix text never comes from the ledger note, for any row class - #21169

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-21096-liveness-hint-no-note
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-21096-liveness-hint-no-note

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21096

Clause-②: no

What

checkItem in packages/lint/src/lint-liveness-properties.ts now builds the fix text as entry.authorHint ?? defaultHint. The row's ledger note is never author-facing, for every row class (opted-in authorWarn, experimental, dead, live-elsewhere, planned). The now-unused isVerdictTriggered helper and the comments describing the old split are removed. describe() already carries a default hint for all four verdicts a finding can carry (experimental, planned, dead, live-elsewhere), so no prose was invented. A live row with authorWarn still throws the integrity sentinel, unchanged.

No ledger row under packages/spec/liveness/** is edited: this is the structural direction from triage, not per-row authorHint discipline.

Measured end to end

os lint --json on examples/app-showcase (built from this tree, before and after rebuilding @objectstack/lint), the two liveness-planned-property findings, fix field:

The two liveness-dead-property findings on showcase_contributor are unchanged ("Remove it — it is declared in the spec but not consumed at runtime."). Exit code 0 before and after.

Tests (lint-liveness-properties.test.ts)

  • The real-ledger object.externalSharingModel pin now asserts the planned default hint, no # followed by digits, and that the row's note text is absent (with an anti-vacuity guard that the row is still opted-in, has no authorHint and a note with a tracker id).
  • New real-ledger pin over every shipped opted-in and experimental row: a row with an authorHint prints it exactly (control); a row without one prints neither its note nor a tracker id.
  • New synthetic pin: authorHint wins, otherwise the verdict default, never the note, for dead, live-elsewhere, planned and experimental, with and without authorWarn.
  • Two feat(lint): a ledger-dead or live-elsewhere key warns without an authorWarn opt-in, and never shows the ledger note (#16094) #21092 pins that encoded the old "opted-in rows show the note" behavior are rewritten.

Results: pnpm --filter @objectstack/lint test 118 files / 5486 tests pass; the liveness file alone 93 pass; pnpm --filter @objectstack/lint typecheck green.

Ablation (one-line revert to entry.authorHint ?? entry.note ?? defaultHint, through scripts/ablation-replace.mjs, anchor hit 1 time, blob moved, restore verified byte-identical to HEAD): 6 tests go red (the two real-ledger pins, the two synthetic pins, the live-elsewhere precedence pin, the dead RLS end-to-end pin); 87 stay green. Restored; git diff HEAD empty.

Other sites where a ledger note could reach an author

None found in packages/lint/src or packages/cli/src besides this expression. The other .note reads in validate-react-page-props.ts are deprecation records, not ledger rows. Nothing else reads the ledger's note as author text.

Gates

node scripts/pm/dispatch-gates.mjs --commands derived 60 families; each was run and recorded with its exit code, then reconciled with --ran: 59 exit 0, 1 NOT MEASURED (check:dual-build-cjs-loads, exit 3, PREREQUISITE NOT MET: eight unrelated packages have no dist/ in this worktree; nothing was measured, and the diff touches no exports or build output). CI owns the rest.

Coordination

#21135 (the liveness README) states the same model. It has no open PR and no branch at this time; whichever lands second keeps the two consistent. This PR does not touch the README.


Generated by Claude Code

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 4 documentable anchor(s).

⛔ 2 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v14.mdx (via authorWarn (literal, a string literal in a comment on a changed line))
  • content/docs/releases/v17/index.mdx (via authorWarn (literal, a string literal in a comment on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 4 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 f0cc16e8d55b02269c7db708254e2917ba7e84e2 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3a8e95838807d9a98c9d997f2b631d9add3ffc3c — the merge of head 88dc06a2012516079c9d5b70230ce23a2ee18df5 into base f0cc16e8d55b02269c7db708254e2917ba7e84e2, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3a8e95838807d9a98c9d997f2b631d9add3ffc3c && git checkout 3a8e95838807d9a98c9d997f2b631d9add3ffc3c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f0cc16e8d55b02269c7db708254e2917ba7e84e2 88dc06a2012516079c9d5b70230ce23a2ee18df5 && git checkout -B drift-repro f0cc16e8d55b02269c7db708254e2917ba7e84e2 && git merge --no-ff 88dc06a2012516079c9d5b70230ce23a2ee18df5

node scripts/docs-audit/affected-docs.mjs --json f0cc16e8d55b02269c7db708254e2917ba7e84e2

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs f0cc16e8d55b02269c7db708254e2917ba7e84e2 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 88dc06a2012516079c9d5b70230ce23a2ee18df5
Local-runs: none

① Derived judgments

The checkout has no node_modules, so the one permitted vitest file was not run. Every judgment below comes from reading the diff, the branch source, the main-side test and the shipped ledger JSON.

Code (packages/lint/src/lint-liveness-properties.ts).

  • const hint = entry.authorHint ?? defaultHint; is correct for every class that can reach it.
    • describe(entry) returns a non-empty defaultHint for experimental, planned, dead and live-elsewhere.
    • Any other status (live + authorWarn: true, or a missing status + authorWarn: true) throws the integrity sentinel inside describe() before hint is computed.
    • The shipped ledgers have no live + authorWarn row at the depth the warn map reads (720 live, all unwarned), so the sentinel is not newly reachable.
  • Nothing is left dangling.

Tests (packages/lint/src/lint-liveness-properties.test.ts).

  • The object.externalSharingModel real-ledger pin guards that the row is still authorWarn: true, has no authorHint, and carries a note matching /#\d+/; all three hold, and the note cites ADR-0090: Permission Model v2 — concept convergence, final naming, AI-authoring safety (tracking) #2696. It then asserts the planned default hint, no tracker id, and not the note. This is strictly stronger than main's not.toMatch(/^Remove it/), and it would fail on main.
  • The opted-in/experimental real-ledger pin.
    • Its three anti-vacuity guards are non-vacuous against the shipped ledgers: 1 opted-in row and 5 experimental rows have a note and no authorHint, and 1 of those notes carries a tracker id.
    • The authorHint rows (3) are pinned byte for byte. Main's CONTROL (authorHint ?? note) is correctly inverted, not weakened.
    • For rows without an authorHint, this pin asserts only "not the note, no tracker id". The synthetic every-class pin and the externalSharingModel pin assert the default.
  • The synthetic every-class pin covers dead, live-elsewhere, planned and experimental, each with and without authorWarn, and asserts the default. A control asserts that authorHint wins. It keeps every main-side assertion for verdict-triggered rows and inverts exactly the opted-in/experimental ones.
  • The feat(lint): a ledger-dead or live-elsewhere key warns without an authorWarn opt-in, and never shows the ledger note (#16094) #21092 pins. The verdict-triggered real-ledger pin and the end-to-end dead-RLS pin are untouched. The live-elsewhere precedence pin moves from toBe('N') to toContain('sibling repo'), which is the required inversion. No pin was dropped without a stronger replacement.

Changeset (.changeset/21096-liveness-hint-never-note.md).

  • "728-character maintainer note that cites a tracker id": TRUE. packages/spec/liveness/object.json → props.externalSharingModel.note is 728 characters and contains #2696.
  • "the two liveness-planned-property findings for externalSharingModel" on app-showcase: TRUE (announcement.object.ts:34, account.object.ts:26).
  • The quoted fix text: TRUE, byte-identical to the planned defaultHint in describe().
  • "an opted-in row and an experimental row with no authorHint printed its note": TRUE of the main-side expression.
  • "No rule id, message, severity or exit code changes": TRUE; only the hint expression changed.
  • "a row that has an authorHint prints it exactly as before": TRUE, pinned by the CONTROL loops.
  • The prose names no tracker id other than (#21096): TRUE.

Merge-cleanliness. git merge-tree --write-tree origin/main FETCH_HEAD exits 0 (tree 7f3621751107ec08904c113c2671097e86aee959), with no conflicts.

② Semver level

patch for @objectstack/lint is right: a bug fix in a released package, with no authorable surface or rule id changed. Clause-②: no is right.

③ Boundary flags

Implemented-by: claude/issue-21096-liveness-hint-no-note
Reviewed-by: session_01VDtqoecgES7ScQYGbFVDRv

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 1, 2026 11:56
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 1, 2026 11:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 327391c Oct 1, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21096-liveness-hint-no-note branch October 1, 2026 12:28
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…erdict model the lint ships (objectstack-ai#21450)

Fixes objectstack-ai#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 objectstack-ai#21169, landed for
objectstack-ai#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 objectstack-ai#21176, landed for objectstack-ai#21127). The README paragraph objectstack-ai#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`. objectstack-ai#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 objectstack-ai#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>
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 tests tooling

Projects

None yet

1 participant