Files
zeekay ad5a010f79 Three words for what a proposal is, and a generator that refuses
The same fact lived in nine hand-kept places and they disagreed about 58
proposals. This makes the file the only authority and everything else a
projection of it.

ONE VOCABULARY, in vocabulary.json, read by the lint, the generator and the
doc site. Three words, each answering "can I rely on this?" and nothing else:

    Draft    nothing implements it yet, or its own text says the work is ahead
    Final    the thing it specifies exists in the code
    Living   governance and process: amended in place, never finished

Eight spellings folded in. `Active` was the one doing real damage: on four Meta
and Process HIPs it meant living, on 48 others it meant shipped, and one word
doing two jobs is why nothing agreed. `Review` and `Last Call` described a
queue rather than a document, and a proposal under review is still one you
cannot build against.

STATUS IS EARNED, not declared. Every Standards Track proposal was checked
against the estate: 235 have their implementation, and eight do not and say so.
HIP-0095 carries a twelve-week plan ending at "mainnet launch" and has no code.
HIP-0525 specifies an all-machine meeting while apps/meet refuses a machine a
seat. HIP-0078 names a prover at a "planned location" that does not exist.
HIP-0114 is the cog.* procedure set, and that procedure set returns nothing.
HIP-0003, HIP-0102, HIP-0305 and HIP-0901 likewise: looked, found nothing.

SIXTEEN TOMBSTONES DELETED rather than statused. A dead document kept alive in
the index is one concept wearing two names. Each successor exists and carries
the text, and the 90 references that pointed at a tombstone now point at it --
whole markdown links at once, so link text cannot outlive its target. Three of
the sixteen were pure compatibility: HIP-0028 specified a migration off a
Bitnami chart, HIP-0029 dual-engine MySQL, HIP-0030 a schema-deprecation
policy. We ship no backwards compatibility, so we do not specify it.

Nine links were already broken and had been since the corpus was renumbered:
short-form ./hip-5.md paths that never existed. Two named the wrong proposal --
one cited "HIP-1: Hanzo Multimodal Models" where multimodal is HIP-0003.

THE GENERATOR REFUSES more often than it writes, because a short table looks
exactly as authoritative as a complete one. Rows must equal files; a shrinking
index must say by how much; a section whose input is missing is an error, not
an omission; nothing is written until all of it validates. Its self-test breaks
the corpus eleven ways and requires each to refuse AND leave README byte-
identical -- a guard that reports after half-writing has protected nothing.
The lint's second implementation of the same comparison is gone.

The site rendered 52 proposals with a fallback grey badge and counted them
toward none of its three stat tiles, because docs/lib/source.ts declared a
status union by hand: it admitted a `Stagnant` no file has ever carried and
omitted the one 52 files did. Five components each kept their own copy of the
colours. All six now read vocabulary.json.
2026-08-28 18:05:41 -07:00

41 lines
1.7 KiB
TypeScript

// The status vocabulary, read from the one file that holds it.
//
// Five page components used to carry their own map of status to badge colour,
// and lib/source.ts declared a seventh spelling as a TypeScript union. All six
// listed `Last Call`, `Withdrawn` and `Stagnant` -- words no proposal has ever
// carried -- and none of them listed the word 52 proposals DID carry, so every
// one of those rendered with the fallback grey badge and counted toward none of
// the three stat tiles on the home page.
//
// ../vocabulary.json is the corpus's own machine copy; scripts/lint-hips.py and
// scripts/index.py read the same file. Adding a status is one edit, there.
import vocabulary from '../../vocabulary.json';
// A tone, not a colour: the vocabulary says what a status MEANS, and this is the
// only place that decides what that looks like.
const TONE: Record<string, string> = {
amber: 'bg-amber-500/10 text-amber-500 border-amber-500/20',
green: 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20',
blue: 'bg-sky-500/10 text-sky-500 border-sky-500/20',
};
const NEUTRAL = 'bg-muted text-muted-foreground border-border';
type Entry = { means: string; tone: string };
const STATUS = vocabulary.status as Record<string, Entry>;
/** Every status a proposal may carry, in lifecycle order. */
export const STATUSES = Object.keys(STATUS);
/** Tailwind classes for a status badge. Unknown or absent renders neutral. */
export function statusClass(status?: string | null): string {
const entry = status ? STATUS[status] : undefined;
return entry ? TONE[entry.tone] ?? NEUTRAL : NEUTRAL;
}
/** What the status means, for a tooltip or a legend. */
export function statusMeans(status?: string | null): string {
return (status && STATUS[status]?.means) || '';
}