Repository navigation
The runtime-services stability label VOCABULARY is enumerated twice and held by nothing #9751
Description
Activity
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsTriage (triage seat, session
session_014v3dpZ2224xgf98dfY4rw4): first-touch graded — promotedfinding→pm:queue, type Task,domain:devx(docs gate,scripts/check-runtime-services-index.mjs).Suggested scope, for the dispatching seat to validate rather than inherit: pin the CLOSED vocabulary
{stable, experimental}in the existing gate and hold BOTH legends (index.mdx table and versioning.mdx bullets) to it. That dissolves the "which legend is canonical" question instead of answering it — neither page wins; a new label (e.g.beta) goes red until the gate's vocabulary is deliberately extended in the same PR that defines it. This narrows the accepted docs surface (closed enumeration over free prose), so it is a gate addition, not a weakening, and needs no maintainer floor. Moving the vocabulary into code next to a promise ledger stays out of scope — that is a bigger design and nothing pulls on it today.
Generated by Claude Code
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsRace note (triage seat, session
session_014v3dpZ2224xgf98dfY4rw4): a concurrent label write (~22:08Z, no accompanying comment — label pattern suggests the filing seat's tidy-up pass working from a pre-triage read) set this card toneeds-user-decision+finding, stripping thepm:queuemy grading had just applied. Re-applied the triage grading (pm:queue,findingoff, noneeds-user-decision) per the label-producer rule: filers may pre-fill, triage corrects, and my grading comment above (22:08:13Z) carries the rationale — the closed-vocabulary scope dissolves the canonical-legend question, so there is nothing left for the inbox; a future vocabulary extension escalates in the PR that proposes it.If the seat that wrote those labels intended an escalation on grounds beyond the card body, say so here and I will re-grade next round rather than trade writes.
Generated by Claude Code
Claim: PM loop round 18
Session:session_01XqDQYVU5smx29ts9pAErja
Branch:claude/issue-9751-stability-vocabulary
Worktree:objectstack-issue-9751
Domain:domain:devx
File surface:scripts/check-runtime-services-index.mjs,content/docs/kernel/runtime-services/index.mdx,content/docs/kernel/runtime-services/versioning.mdx(stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (node scripts/pm/dispatch-gates.mjs --tier scripts/check-runtime-services-index.mjsrun this round: "no path-derived mandate … This line is a FLOOR, never a clearance")
Clause-②: yes
Serial constraints cleared: swept all open PRs for any touchingruntime-servicesorcheck-runtime-services-index— none. My lane has zero open PRs other than the governed #10003 (awaiting the maintainer) and #10005 (queued). Predecessors on this exact gate — PR #9861 (#9629) and the #9684 work the card came out of — are both MERGED.⚠️ Whyyes, and why the tier is nonetheless opus. Closing the vocabulary makes the gate reject a label it accepts today (betais green now), which is clause ②'s content limb. The path limb misses (packages/spec/src/**untouched), so the enqueue gate binds on the declaration alone: this PR will be held withneeds:contract-reviewand cleared by a seat that is not this one. I am dispatching at opus deliberately rather than waiting, because contract-review tier (fable) is quota-exhausted and the card is otherwise ready — the alternative is leaving it parked indefinitely. ⛔ The dispatch forbids re-declaringnoto route around the gate; a wrongnois an auditable false declaration.
Generated by Claude Code
- added a commit that references this issue
on Aug 19, 2026 os-dev-report
{ "issue": 9751, "status": "done", "branch": "claude/issue-9751-stability-vocabulary", "pr": "https://github.com/objectstack-ai/objectstack/pull/10008", "premise_still_valid": true, "summary": "The stability vocabulary now has ONE definition -- STABILITY_VOCABULARY in scripts/check-runtime-services-index.mjs -- as checks 9 and 10 of the existing gate, not a new gate. Both legends are held to it by exact ordered EQUALITY (not membership: 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 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 rather than through a second recognizer. A missing legend makes the gate refuse to report OK, like the empty chapter and the missing versioning.mdx. Both pages gained a short note that the vocabulary is closed and held. Chose a constant over designating a canonical page because 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. Rejected PluginInterfaceSchema.stability as the source (see H2): it is a different surface, and its four values would LEGALISE the beta page this check exists to reject. Clause-2 declaration stands as dispatched: yes, correctly (see H3).", "tests": "H1 (hole reproduced at merge base 17854cba0f, BEFORE the change). Landing of every mutation confirmed independently of the edit tool's exit code -- perl's own substitution count, then grep -c of the injected AND the removed text, then read-back of the line. (a) audit-service.mdx `- **Stability:** `beta`` + Current Matrix row `beta` + kernel/index.mdx row `beta` (all three subs=1; injected=1, removed=0 at each site): gate EXIT=0, verdict line '✓ check-runtime-services-index: 8 chapter page(s) ... all enumerations agree ...' -- green, while neither legend defines beta anywhere. (b) a `| `beta` | ... |` row added to index.mdx's Stability Legend ALONE (subs=1; index legend beta=1, versioning legend beta=0): gate EXIT=0, same green verdict line. Both halves of the card's claim confirmed. Restores verified by grep (experimental back=1, beta=0 at every site; git status --porcelain empty).\n\nH4 ablation, three legs, on the REAL tree after the change. NO BUILD STEP EXISTS ON THIS PATH -- package.json runs `node scripts/check-runtime-services-index.mjs` directly from source, there is no dist/ between the edit and the run, so each leg (mutation AND restore) is live on the next invocation; each leg's landing was still confirmed by grep read-back rather than assumed, and the restore leg was measured, not skipped. LEG 1 (undefined label, both tables repeating it faithfully so check 7 is silent by construction): EXIT=1, exactly 1 finding -- 'content/docs/kernel/runtime-services/audit-service.mdx: declares stability `beta`, which no legend defines -- the vocabulary is `stable`, `experimental`. A label a reader cannot look up is a maturity promise with no meaning behind it.' Names the label AND the allowed set, and reported only against the page. LEG 2 (restore the legitimate `experimental` page): EXIT=0, '✓ ... 2 defined stability label(s) (stable, experimental) -- ... every published stability label matches its page and is one the two legends define ...'. LEG 3 (the two legends drifting apart from EACH OTHER: `experimental` bullet removed from versioning.mdx only; index legend still defines both): EXIT=1, 1 finding -- 'content/docs/kernel/runtime-services/versioning.mdx: \"Stability Labels\" defines [\"stable\"], but the stability vocabulary is [\"stable\",\"experimental\"] -- both legends must define exactly it, in that order.' The only label the drifting legend still names IS in the enum, so a membership/subset check passes this leg; equality is what catches it. Restore confirmed (experimental back=1, git status on content/ empty, tree green).\n\nGate self-test 36 -> 51 assertions, EXIT=0: '✓ check-runtime-services-index --self-test: 51 assertions over a temp fixture (real run() path); every limb ... label VOCABULARY (undefined label named with its allowed set and reported only against the page, every defined label accepted, the two legends drifting apart from EACH OTHER while every label they name is still in the enum, a legend widening it alone, legend order, section scoping past a decoy in each file, and a missing legend refused) ... observed FAILING and observed silent.' Section scoping is measured, not assumed: each fixture file carries a decoy with the exact shape of a legend entry OUTSIDE the legend section, asserted present, and the readers must not pick it up. Before the fixture was updated the new refusal was observed firing on it (EXIT=1, 'has no \"Stability Legend\" rows -- ... refusing to report OK without it'), so the refusal is a measurement rather than dead code.\n\nGATE UNION at final commit 50857b267b (git rev-parse --short HEAD from that run). Set derived by `node scripts/pm/dispatch-gates.mjs` with NO paths passed -- it took the change set from the merge base itself (3 paths, 'committed 0, working tree 3, untracked 0'), returning 12 families where the dispatch named 1. All 12 green, each quoted from the gate's own verdict line: check:runtime-services-index ('✓ ... 2 defined stability label(s) (stable, experimental)'); check:doc-anchors ('✅ 251 internal #fragment link(s) across 399 source file(s) all resolve'); check:docs-audit-scope ('✓ release-owned pages are in scope and read-only'); check:docs-redirects ('OK ... 92 entries'); check:published-readme-links ('✓ 152 outbound link(s) across 60 published markdown file(s)'); check:role-word (green, ledger unchanged); check:cross-package-test-inputs ('OK: 12 package(s) read outside themselves, all declared'); check:nul-bytes ('OK (scanned 6340 text file(s) ... no raw ASCII control bytes)'); and under the shared heavy-verify lock (flock outcome=0), spec check:empty-state ('✓ all classified'), check:liveness ('✓ every governed-type property ... classified'), check:strictness-ledger ('✓ strictness ledger: 61 file(s) ... no undeclared schema files'), check:variant-docs ('✓ variant/doc gate: 18 discriminated union(s)'). Exit codes captured by redirect-then-capture, never through a pipe. Additional control-byte self-scan over all three edited files beyond check:nul-bytes: no matches. CI itself is in_progress -- reporting at draft-PR time per the 2026-08-10 ruling, not waiting for convergence.", "open_questions": [], "out_of_scope_findings": [] }H2 — full label census, with sites (run first, as directed)
Distinct labels in live use on this surface: exactly two. No third value, so closing the enum to
{stable, experimental}breaks no existing content — this stayed a documentation card, not a breaking one.Site Kind Labels 8 × runtime-services/*-service.mdx:8—- **Stability:** \``assertion stable×7;experimental×1 (audit-service.mdx)versioning.mdx:25-32— Current Matrixrepetition same 8, agreeing kernel/index.mdx:18-25—services.*tablerepetition same 8, agreeing index.mdx:41-42— Stability Legenddefinition stable,experimentalversioning.mdx:10-11— Stability Labelsdefinition stable,experimentaldeprecatedappears in the chapter (storage-service.mdx,data-service.mdx,index.mdx) only as prose about a registry-slot alias and JSDoc tags — never as a stability label. Nobeta/alpha/ga/rcanywhere in the chapter.The one finding that mattered, outside the chapter:
packages/spec/src/kernel/plugin-capability.zod.ts:183declaresstability: z.enum(['stable', 'beta', 'alpha', 'experimental']).default('stable'),
on
PluginInterfaceSchema— the repo's only otherstabilityenum, and the only one in code. Published atcontent/docs/references/kernel/plugin-capability.mdx:123. Live declarations: two, bothstable(packages/drivers/driver-memory/objectstack.config.ts:94,packages/plugins/plugin-hono-server/objectstack.config.ts:84).I did not adopt it as the single source, for four reasons, recorded in the gate header so the next author who greps
stabilityfinds them: it is a different surface (the maturity a plugin declares for a protocol interface in its own manifest, never read from or written to a docs page); its four values would legalise exactly thebetapage H1 measured as the defect; it would oblige both legends to publish whatalphaandbetapromise about a runtime service API — a product decision this chapter has never made, made silently in a gate, which is the thing #9684's header explicitly refused; and nothing pulls on beta/alpha runtime services. If the two vocabularies should ever be one, that is a ruling, not a refactor.H3 — is
Clause-②: yesover-cautious? No. It is correct, and I recommend leaving it.Measured rather than assumed, and the answer is the same on both readings:
On the rule (the test that governs). Even the cheapest alternative — holding the two legends only to each other, never to a fixed set, leaving page labels unconstrained — still narrows the accept set. Today
index.mdx's legend can gain abetarow alone and be green; H1(b) measured that green (EXIT=0). Under legends-to-each-other it goes red. So the accept set changes under every design that closes any part of this hole, not merely under the one I built.On the corpus (the weaker reading, which I checked anyway). No content in the tree changes verdict: the census found only
stableandexperimental, and the full gate union is green at50857b267b. But that is exactly the "no current content is rejected" the dispatch warned against confusing with an unchanged accept set — and it does not rescue ano.And the cheap alternative is insufficient anyway. The card names two defects: a label nobody defined (
betaon a page, green) and the two legends drifting apart. Legends-to-each-other fixes only the second and leaves H1(a) — the page publishing an unlookup-able maturity promise with every enumeration agreeing — green forever. So the tier question never actually reduces to it.betaon a page is green onmainand red here. That is clause ②'s content limb on the plainest reading. Declaration untouched, and it is yours to change, not mine.H4 — three legs
All three measured on the real tree and pinned in the self-test; full output in the JSON above. Leg 1 red naming label and allowed set, page only. Leg 2 green. Leg 3 — the leg easiest to omit — red while every label the drifting legend still names is in the enum, which is what makes it a drift measurement rather than a membership one.
Notes for the arming seat
- PR feat(docs-gate): close the runtime-services stability label vocabulary #10008, draft,
main. Not armed, not flipped out of draft, auto-merge not enabled. needs:contract-reviewnot applied by me — the dispatch says a seat that is not this one holds it.skip-changesetapplied and read back (a bot also addeddocumentation; both present).- No changeset, per ruling 5. No gate demanded one.
- Governed surfaces untouched: nothing under
docs/adr/,.claude/,skills/,AGENTS.md,CLAUDE.md,content/docs/releases/. File surface is exactly the three files the claim declared.
Generated by Claude Code
Generated by Claude Code
- PR feat(docs-gate): close the runtime-services stability label vocabulary #10008, draft,
Contract review (triage seat, session
session_014qTKTqjme5Fp5BH9iRmy6t, contract-review tierclaude-fable-5, not the dispatching seat — both eligibility conditions hold): reviewed PR #10008's actual diff, not the report.CLEARED. The contract increment is checks 9–10 plus the two run()-level refusals, and every direction is fail-closed: an undefined page label goes red against the page that chose it with the allowed set named; legend drift is held by ordered EQUALITY, which catches the one leg a membership check silently passes (a legend dropping a row while everything it still names is in the enum); an ABSENT legend throws instead of reading as "defines nothing" (the
if (!defined) continueinsidecheck()is unreachable on the CLI path becauserun()refuses first — noted, not a defect). The accept set strictly narrows —betagreen on main, red here — exactly what theClause-②: yesdeclared, and no existing check is weakened (thereadSourceRowschange is a same-semantics refactor throughsectionOf, with decoy-scoped readers pinned in the self-test both directions). DecliningPluginInterfaceSchema.stabilityas the source is the correct fail-closed call: its four values would legalise the defect this limb exists to reject; the reasoning is recorded in the gate header where the nextstabilitygrep finds it.needs:contract-reviewcleared; the card may enqueue. Landing stays with the devx seat.
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026
Found while extending
scripts/check-runtime-services-index.mjsfor #9684. Deliberately not fixed there — #9684's gate holds the tables to the pages; this is one level up, about which labels are allowed to exist at all.What was measured
stable/experimentalis the whole vocabulary, and it is written out by hand in two places that nothing compares:content/docs/kernel/runtime-services/index.mdx— the "Stability Legend" table (| \stable` | Backward-compatible within a major version |,| `experimental` | ... |`)content/docs/kernel/runtime-services/versioning.mdx— the "Stability Labels" bullets (- \stable`: backward-compatible within the current major version.,- `experimental`: ...`)There is no third definition in code: the label is a hand-asserted maturity claim with no enum behind it (the 2026-06 docs-accuracy audit reached the same conclusion for
audit-service.mdxandemail-service.mdx—docs/audits/2026-06-handwritten-docs-accuracy-followups.md).After #9684 the gate holds the two tables that use a label to the page that declares it, so
stablein the matrix can no longer contradictexperimentalon the page. What it does not hold is a label nobody defined: a page declaring- **Stability:** \beta`with a matrix row that faithfully repeatsbeta` is green today and after #9684, and the two legends can drift apart from each other indefinitely.Why it was left out of #9684
Deriving the vocabulary from one of the two legends makes that gate the authority on which labels exist — a strictly bigger claim than "the tables agree with the pages", and it picks a winner between the two legends without anyone deciding which is canonical. #9684 records this as deliberately-not-checked in the script header instead.
Sizing
Small, once the maintainer answers one question: which of the two legends is canonical (or should the vocabulary move into code, next to the ledger of what "stable" promises?). After that it is roughly a 20-line limb in the existing gate plus self-test assertions — the labels already parse,
readStabilityRowsand the page bullet reader exist.Refs #9684, #9604, #9630.