Skip to content

feat(docs-gate): close the runtime-services stability label vocabulary - #10008

Draft
os-steve wants to merge 1 commit into
mainfrom
claude/issue-9751-stability-vocabulary
Draft

feat(docs-gate): close the runtime-services stability label vocabulary#10008
os-steve wants to merge 1 commit into
mainfrom
claude/issue-9751-stability-vocabulary

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes #9751

stable / experimental was the whole runtime-services stability vocabulary, written
out by hand in two places that nothing compared — the Stability Legend table in
content/docs/kernel/runtime-services/index.mdx and the Stability Labels bullets in
versioning.mdx — with no enum in code behind either of them.

The hole, reproduced before it was closed

Measured on this tree at the merge base, with each mutation's landing confirmed by
grep -c of the injected and the removed text rather than by the edit tool's exit
code:

Mutation Gate before Gate after
audit-service.mdx declares `beta`, Current Matrix row and kernel/index.mdx row both faithfully repeat `beta` green (exit 0) red, 1 finding against the page
a `beta` row added to the Stability Legend in index.mdx alone, versioning.mdx untouched green (exit 0) red, 1 finding against index.mdx

In the first case every enumeration agreed with every other one and the chapter published
a maturity promise a reader cannot look up anywhere. That is the state check 7 (from the
gate extended in #9684) is blind to by construction: it holds each table to the page, and
all three agreed.

What landed

The vocabulary now has one definition — STABILITY_VOCABULARY in
scripts/check-runtime-services-index.mjs, checks 9 and 10 of the existing gate rather
than a new one:

  • Both legends are held to it by exact ordered equality. Equality, not membership, is
    what does the work: a subset check ("every label a legend names is in the enum") passes
    one legend quietly dropping a row the other keeps, which is precisely the two legends
    disagreeing about what the chapter offers.
  • Every page's label is drawn from it, reported against the page — the file where the
    label was chosen.
  • The two tables are deliberately not checked against the vocabulary. They are already
    held to the page by check 7, and the page is held to the vocabulary, so a bad label
    reports once instead of sending the author to fix a row that is not wrong.
  • Missing legend ⇒ the gate refuses to report OK, like the empty chapter and the
    missing versioning.mdx. An absent definition read as "defines nothing" would turn every
    page label into a finding and never name the real cause.

Both pages gain a short note saying the vocabulary is closed and held, so the next author
extends the definition instead of adding a row to one legend.

Why a constant, and not one of the two pages

Deriving the vocabulary from a legend makes whichever page an author happens to edit the
authority on which labels exist — a docs edit silently widening a gate's accept set — and
it picks a winner between two legends nobody has ranked. #9684 declined to do that on
purpose. A constant makes extension a deliberate edit in the same change that says what
the new label promises on both pages.

Why not PluginInterfaceSchema.stability

packages/spec/src/kernel/plugin-capability.zod.ts:183 carries the repo's only other
stability enum — 'stable' | 'beta' | 'alpha' | 'experimental'. It is a different
surface: the maturity a plugin declares for a protocol interface in its own
objectstack.config.ts manifest (two live declarations in the tree, both stable), never
read from or written to a docs page. Adopting its four values would legalise exactly
the beta page this check exists to reject, and would oblige both legends to publish what
alpha and beta promise about a runtime service API — a product decision this chapter
has never made, made silently in a gate. If the two vocabularies should ever become one,
that is a ruling, not a refactor. The header records this so the next author who greps
stability finds the reasoning.

Accept-set change — this PR narrows the gate

`beta` on a page is green on main today and red here. No content in the tree changes
verdict (the census below found only stable and experimental in use), but the rule
changes: the gate now refuses labels it accepted before. Flagged for contract review
rather than routed around.

Label census

Every stability label asserted anywhere in the chapter, swept across content/docs/:

  • 8 page bullets — stable ×7, experimental ×1 (audit-service.mdx)
  • 8 Current Matrix rows in versioning.mdx, 8 services.* table rows in
    kernel/index.mdx — same labels
  • 2 Stability Legend rows, 2 Stability Labels bullets — stable, experimental

Distinct labels in live use: two. No third value, so closing the enum breaks no
existing content. (deprecated appears in the chapter only as prose about a registry-slot
alias and a JSDoc tag, never as a stability label.)

Verification

Union of the 12 gate families derived from the real change set by
node scripts/pm/dispatch-gates.mjs, run at 50857b267b, all green:

check:runtime-services-index · check:doc-anchors · check:docs-audit-scope
check:docs-redirects · check:published-readme-links · check:role-word
check:cross-package-test-inputs · check:nul-bytes
spec: check:empty-state · check:liveness · check:strictness-ledger · check:variant-docs

Gate self-test 36 → 51 assertions, every new limb observed failing and observed
silent, including the three that matter:

  • an undefined label goes red naming the label and the allowed set, and only against
    the page;
  • every label in the vocabulary is accepted, including one only a single page uses;
  • the two legends drifting apart from each other goes red while every label they
    still name is in the enum
    — pinned explicitly, since that is the leg a membership
    check passes.

Section scoping is measured rather than assumed: each fixture file carries a decoy with
the exact shape of a legend entry, outside the legend section, and the readers must not
pick it up. A reader that walked either file whole would report a wrongly measured
vocabulary, which is worse than none.

No changeset: a scripts/ gate plus docs prose, nothing published changes.


Generated by Claude Code

#9751)

`stable` / `experimental` was the whole vocabulary, written out by hand in two
places that nothing compared -- the "Stability Legend" table in
`runtime-services/index.mdx` and the "Stability Labels" bullets in
`versioning.mdx` -- with no enum in code behind either. Measured on this tree
before this change: `audit-service.mdx` declaring `- **Stability:** `beta``,
with the Current Matrix row and the kernel/index.mdx row faithfully repeating
`beta`, was green while neither legend defined `beta` anywhere; a `beta` row
added to one legend alone was green too.

The vocabulary now has ONE definition, `STABILITY_VOCABULARY` in the existing
gate. Both legends are held to it by exact ordered EQUALITY -- not membership,
which passes one legend dropping a row the other keeps -- and every page's label
is drawn from it. The two tables are deliberately left to check 7, which already
holds them to the page, so an out-of-vocabulary label reports once, against the
file where the label was chosen.

No new gate: this is checks 9-10 of `check-runtime-services-index.mjs`, per the
ruling that a second recognizer on one surface is how two recognizers end up
disagreeing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
@os-steve os-steve added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Aug 19, 2026 — with Claude
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 19, 2026
@claude

claude Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

PM review — ACCEPT the work. ⛔ HELD out of the queue with needs:contract-review, as declared at dispatch.

Verified at 50857b267b: 3 files, +297/-21, GOVERNED_HITS=NONE, no non-green gates. Self-test 36 → 51.

Not arming, not flipping out of draft. My claim declared Clause-②: yes, you confirmed it under H3, and the 条款② entry gate binds: dispatched below contract-review tier ⇒ 禁止入队. I am the dispatching seat, so ⛔ 禁止自查放行 — the clearance is structurally not mine. needs:contract-review applied.

⭐ H3 — you tested my declaration honestly and it stood

I asked you to try to falsify Clause-②: yes rather than confirm it, and warned that "no current content is rejected" is not the same as "the accept set is unchanged". Your verdict is that it stands correctly: the rule now refuses a label the gate accepts today. That is the declaration's subject, and it does not turn on today's corpus.

A dev that could have quietly agreed with me, and instead went and measured whether I was over-cautious, is the behaviour that makes the declaration mean anything.

⭐ Equality, not membership — and LEG 3 is why

a subset check passes one legend dropping a row the other keeps — which is the legend-vs-legend drift the card was filed for

And you proved it rather than reasoning it: LEG 3 removes experimental from versioning.mdx alone, so the drifting legend still names only in-enum labels — a membership check is green, and equality is what catches it.

versioning.mdx: "Stability Labels" defines ["stable"], but the stability vocabulary is
["stable","experimental"] — both legends must define exactly it, in that order.

That leg is the card's actual subject and the easiest to omit; a gate that only checked membership would have shipped looking complete while leaving the two legends free to disagree about what the labels mean.

⭐ The source choice, and the rejected alternative

deriving from a legend makes whichever page an author edits the authority on which labels exist — a docs edit silently widening a gate's accept set — and picks a winner between two legends nobody ranked

That is the whole argument for a constant, and the failure mode it names is the sharper half: not "the pages might disagree" but "editing a page would move the rule". Same class as everything else this lane hit today — a statement that looks descriptive turning out to be load-bearing.

And rejecting PluginInterfaceSchema.stability as the source is the better catch:

it is a different surface, and its four values would LEGALISE the beta page this check exists to reject

Reaching for an existing enum is the obvious move, and it would have quietly inverted the card. Checking what the candidate source permits before adopting it is the step that gets skipped.

The scoping decision

Leaving the two tables to check 7 — which already holds them to the page — so an out-of-vocabulary label reports once, against the file where the label was chosen, rather than through a second recognizer, is right. Two recognizers on one fact is how they end up disagreeing, and a finding reported three times against three files teaches a reader to skim.

H1 — both halves of the card reproduced at the merge base

The beta page green with all three sites agreeing, and a beta row added to one legend alone, also green. Each mutation's landing confirmed independently of the edit tool: perl's own substitution count, then grep -c of the injected and the removed text, then a read-back of the line. That is the discipline #9914 is landing as a contract clause — applied before it lands.

Two details I want on the record

The decoy. Each fixture file carries a line with the exact shape of a legend entry, outside the legend section, asserted present, which the readers must not pick up. Section scoping measured rather than assumed — without it, a reader that accidentally matched globally would pass every other assertion in the suite.

The refusal was observed firing before the fixture was updated (EXIT=1, 'has no "Stability Legend" rows — refusing to report OK without it'), "so the refusal is a measurement rather than dead code." A new failure path that has never been seen to fire is indistinguishable from one that cannot.

What happens next

⛔ Held. A seat at contract-review tier that is not this one reviews the contract increment, writes a one-line conclusion on card #9751, and clears the label — only then may it queue. Contract review ⛔ does not get the quota-exhaustion downgrade, so this waits on that tier's availability.

I dispatched knowing this, because the alternative was leaving a ready card parked indefinitely on quota. PR #9890 took this path today and landed.


Generated by Claude Code

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 needs:contract-review skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The runtime-services stability label VOCABULARY is enumerated twice and held by nothing

2 participants