Files
zeekay c29a2c4a20 A retired number is still an address
Deleting a proposal that is wrong or superseded is right — a document nobody
should follow does not earn a place in the index by being renamed Superseded.
But deletion frees the NUMBER, and the number is the part that outlives the
document: it is cited in code, linked from outside, quoted in commits.

Sixteen numbers were freed here today, and one was spent again within hours.
HIP-0138 was published as "Cloud — Unified Hanzo Binary", deleted, then
reissued for "One Manifest, Five Kinds" — so every existing reference to
HIP-0138 resolved to a specification it never made. The generator could not
see it: it refuses two files claiming one number, but a number claimed once
and then again looks identical to a number claimed once.

spent.json records what each freed number was. validate() refuses a file that
claims one under a different title, and says what the number already means.
The document still goes; only the address is kept, at one line each.

"One Manifest, Five Kinds" moves to HIP-0142, the first genuinely unused
number, with its own H1 corrected — a heading that disagrees with its
front matter is how a stale number survives a renumber.

docs/SCALE_STANDARD.md cited the deleted file for the per-replica capacity
budget. That text lives in HIP-0122, which the lint could not know because the
citation is a backticked path rather than a link. Repointed to the HIP that
actually carries it, without the section number, which did not survive.
2026-08-28 18:49:06 -07:00

207 lines
8.0 KiB
Python

#!/usr/bin/env python3
"""Prove the generator REFUSES. Run: python3 scripts/test-index.py
A generator that can quietly emit a short table is worse than no generator,
because the short table looks authoritative and nobody re-derives it. So every
guard in scripts/index.py is exercised here by breaking a scratch copy of the
corpus on purpose, and each case asserts BOTH halves:
the run exits 1 with REFUSED, and README.md is byte-identical afterwards.
The second half is the one that matters. A guard that reports a problem after
half-writing the file has not protected anything.
The last case asserts the opposite: on a clean corpus the projection is stable,
so running the generator twice changes nothing and --check agrees.
"""
from __future__ import annotations
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
INDEX = os.path.join(ROOT, "scripts", "index.py")
SUBJECT = "hip-0517-branch-naming.md"
STANDARDS_SUBJECT = "hip-0119-hanzo-service-conventions.md"
def run(root: str, *args: str) -> tuple[int, str]:
env = dict(os.environ)
env.setdefault("HANZO_CAPABILITIES",
os.path.join(os.path.dirname(ROOT), "openapi", "capabilities.yaml"))
p = subprocess.run([sys.executable, INDEX, "--root", root, *args],
capture_output=True, text=True, env=env)
return p.returncode, p.stdout + p.stderr
def read(root: str, name: str) -> str:
return open(os.path.join(root, "HIPs", name), encoding="utf-8").read()
def write(root: str, name: str, text: str) -> None:
open(os.path.join(root, "HIPs", name), "w", encoding="utf-8").write(text)
def readme(root: str) -> str:
return open(os.path.join(root, "README.md"), encoding="utf-8").read()
# Each case mutates the scratch root; the runner asserts REFUSED + no write.
def m_empty(root):
for f in os.listdir(os.path.join(root, "HIPs")):
os.remove(os.path.join(root, "HIPs", f))
def m_field(root):
write(root, SUBJECT, re.sub(r"^author:.*$", "", read(root, SUBJECT), count=1, flags=re.M))
def m_status(root):
write(root, SUBJECT,
re.sub(r"^status:.*$", "status: Shipped", read(root, SUBJECT), count=1, flags=re.M))
def m_type(root):
write(root, SUBJECT,
re.sub(r"^type:.*$", "type: Freeform", read(root, SUBJECT), count=1, flags=re.M))
def m_category(root):
write(root, STANDARDS_SUBJECT,
re.sub(r"^category:.*$", "category: Vibes",
read(root, STANDARDS_SUBJECT), count=1, flags=re.M))
def m_no_category(root):
write(root, STANDARDS_SUBJECT,
re.sub(r"^category:.*$", "", read(root, STANDARDS_SUBJECT), count=1, flags=re.M))
def m_requires(root):
s = read(root, STANDARDS_SUBJECT)
write(root, STANDARDS_SUBJECT,
re.sub(r"^(requires:.*)$", r"\1, HIP-8888", s, count=1, flags=re.M)
if re.search(r"^requires:", s, re.M)
else s.replace("\nstatus:", "\nrequires: HIP-8888\nstatus:", 1))
def m_duplicate(root):
shutil.copy(os.path.join(root, "HIPs", SUBJECT),
os.path.join(root, "HIPs", "hip-0517-a-second-claim.md"))
def m_shrink(root):
os.remove(os.path.join(root, "HIPs", SUBJECT))
def m_no_section(root):
p = os.path.join(root, "README.md")
s = open(p, encoding="utf-8").read()
open(p, "w", encoding="utf-8").write(s.replace("## HIP Index", "## The Table", 1))
def m_no_capabilities(root):
# The reading order's capability grouping comes from a sibling repo. Losing
# a section because an input was not checked out is the exact silent
# truncation this file exists to prevent, so it must be an error.
os.environ["HANZO_CAPABILITIES"] = os.path.join(root, "nowhere.yaml")
def m_reissue(root):
"""Claim a number that was published under another title and retired.
Deleting the document is right; reissuing its NUMBER is not. Every
reference already in the wild -- in code, in a commit, in someone else's
link -- would silently resolve to a different specification.
"""
spent = os.path.join(root, "spent.json")
json.dump({"9999": "A Retired Proposal"}, open(spent, "w", encoding="utf-8"))
src = os.path.join(root, "HIPs", SUBJECT)
body = open(src, encoding="utf-8").read()
body = body.replace("hip: 0517", "hip: 9999", 1)
open(os.path.join(root, "HIPs", "hip-9999-a-different-thing.md"), "w",
encoding="utf-8").write(body)
CASES = [
("an empty HIPs/", m_empty, None),
("a required front-matter field removed", m_field, None),
("a status outside the vocabulary", m_status, None),
("a type outside the vocabulary", m_type, None),
("a category outside the vocabulary", m_category, None),
("a Standards Track HIP with no category", m_no_category, None),
("requires: a HIP that does not exist", m_requires, None),
("two files claiming one number", m_duplicate, None),
("a HIP deleted without saying so", m_shrink, None),
("README's index heading renamed", m_no_section, None),
("capabilities.yaml not checked out", m_no_capabilities, None),
("a retired number reissued for a different proposal", m_reissue, None),
]
def main() -> int:
base = tempfile.mkdtemp(prefix="hips-index-test-")
capabilities = os.environ.get("HANZO_CAPABILITIES")
try:
clean = os.path.join(base, "clean")
os.makedirs(clean)
shutil.copytree(os.path.join(ROOT, "HIPs"), os.path.join(clean, "HIPs"))
for f in ("README.md", "vocabulary.json"):
shutil.copy(os.path.join(ROOT, f), clean)
shutil.copytree(os.path.join(ROOT, "scripts"), os.path.join(clean, "scripts"))
rc, out = run(clean, "--check")
if rc != 0:
print("CONTROL FAILED: the committed README does not match HIPs/.")
print(out)
return 1
print("control: README projects the corpus exactly (--check exit 0)\n")
failures = []
for what, mutate, _ in CASES:
root = os.path.join(base, re.sub(r"\W+", "-", what)[:40])
shutil.copytree(clean, root)
mutate(root)
# AFTER the mutation: one case renames a README heading, and what is
# being asserted is that the generator wrote nothing, not that the
# mutation wrote nothing.
before = readme(root)
rc, out = run(root)
if capabilities:
os.environ["HANZO_CAPABILITIES"] = capabilities
refused = rc == 1 and "REFUSED" in out
intact = readme(root) == before
ok = refused and intact
note = "" if ok else (
" <- did not refuse" if not refused else " <- WROTE README ANYWAY")
print(f" {'PASS' if ok else 'FAIL'} refuses: {what}{note}")
if not ok:
failures.append((what, rc, out))
# And the other direction: a clean run is stable and idempotent.
root = os.path.join(base, "stable")
shutil.copytree(clean, root)
rc1, _ = run(root)
once = readme(root)
rc2, _ = run(root)
rc3, _ = run(root, "--check")
stable = rc1 == 0 and rc2 == 0 and rc3 == 0 and readme(root) == once
print(f"\n {'PASS' if stable else 'FAIL'} a clean corpus projects, twice, identically")
if not stable:
failures.append(("idempotent", rc1, f"{rc1} {rc2} {rc3}"))
# Every guard the file can raise should be reachable from a case above.
raised = set(re.findall(r"raise Refused\(", open(INDEX, encoding="utf-8").read()))
if not raised:
print(" FAIL scripts/index.py raises no guard at all")
failures.append(("guards", 0, ""))
if failures:
print(f"\n{len(failures)} guards did not hold:\n")
for what, rc, out in failures:
print(f"--- {what} exit={rc} ---\n{out[-600:]}")
return 1
print(f"\nall {len(CASES)} guards refuse and leave README.md untouched")
return 0
finally:
shutil.rmtree(base, ignore_errors=True)
if __name__ == "__main__":
raise SystemExit(main())