mirror of
https://github.com/hanzo-apps/HIPs.git
synced 2026-10-30 14:47:28 +00:00
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.
210 lines
8.4 KiB
Python
210 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Prove the lint fails. Run: python3 scripts/test-lint-hips.py
|
|
|
|
A check that has only ever been seen to pass has not been shown to work -- it is
|
|
indistinguishable from a check that returns success unconditionally, and this
|
|
repository has already shipped one of those (the GitHub workflow that printed
|
|
every missing field and then exited 0, so a HIP with no status went green).
|
|
|
|
So: copy the corpus, break it on purpose one way at a time, and require the lint
|
|
to report the specific code for that break. Every check the lint declares must
|
|
appear here, and the last test asserts exactly that -- if someone adds a check
|
|
and no test, this file fails.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
LINT = os.path.join(ROOT, "scripts", "lint-hips.py")
|
|
|
|
# A file that is structurally clean and small enough to mutate predictably.
|
|
SUBJECT = "hip-0517-branch-naming.md"
|
|
STANDARDS_SUBJECT = "hip-0119-hanzo-service-conventions.md"
|
|
|
|
|
|
def run(root: str) -> tuple[int, str]:
|
|
p = subprocess.run([sys.executable, LINT, "--root", root],
|
|
capture_output=True, text=True)
|
|
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)
|
|
|
|
|
|
# Each case: code, description, and a mutation taking the scratch root.
|
|
def m_fm001(root):
|
|
write(root, SUBJECT, read(root, SUBJECT).split("\n---\n", 1)[1])
|
|
|
|
def m_fm002(root):
|
|
write(root, SUBJECT, re.sub(r"^created:.*$", "", read(root, SUBJECT), count=1, flags=re.M))
|
|
|
|
def m_fm003(root):
|
|
write(root, SUBJECT, re.sub(r"^hip:.*$", "hip: 0999", read(root, SUBJECT), count=1, flags=re.M))
|
|
|
|
def m_fm004(root):
|
|
write(root, SUBJECT, re.sub(r"^status:.*$", "status: Baked", read(root, SUBJECT), count=1, flags=re.M))
|
|
|
|
def m_fm005(root):
|
|
write(root, SUBJECT, re.sub(r"^type:.*$", "type: Freeform", read(root, SUBJECT), count=1, flags=re.M))
|
|
|
|
def m_fm006(root):
|
|
write(root, STANDARDS_SUBJECT,
|
|
re.sub(r"^category:.*$", "category: Vibes", read(root, STANDARDS_SUBJECT), count=1, flags=re.M))
|
|
|
|
def m_fm007(root):
|
|
s = read(root, STANDARDS_SUBJECT)
|
|
write(root, STANDARDS_SUBJECT, re.sub(r"^(requires:.*)$", r"\1, HIP-8888", s, count=1, flags=re.M))
|
|
|
|
def m_fm008(root):
|
|
write(root, SUBJECT,
|
|
re.sub(r"^status:(.*)$", r"status:\1\nsuperseded-by: HIP-0106",
|
|
read(root, SUBJECT), count=1, flags=re.M))
|
|
|
|
def m_fm009(root):
|
|
os.rename(os.path.join(root, "HIPs", SUBJECT),
|
|
os.path.join(root, "HIPs", "hip-0517-Branch_Naming.md"))
|
|
|
|
def m_st001(root):
|
|
write(root, SUBJECT, re.sub(r"^# HIP-.*$", "", read(root, SUBJECT), count=1, flags=re.M))
|
|
|
|
def m_st002(root):
|
|
write(root, SUBJECT,
|
|
re.sub(r"^# HIP-.*$", "# HIP-0517: Something Else Entirely",
|
|
read(root, SUBJECT), count=1, flags=re.M))
|
|
|
|
def m_st003(root):
|
|
# Duplicate the heading every Standards Track HIP is REQUIRED to carry. This
|
|
# used to rename '## Motivation', which is advisory -- so when a corpus edit
|
|
# removed that heading from the subject, the replace matched nothing, the
|
|
# lint had no duplicate to report, and the check stopped proving anything
|
|
# while still being counted. Anchor a mutation on what the lint requires,
|
|
# never on what a document happens to have.
|
|
s = read(root, STANDARDS_SUBJECT)
|
|
write(root, STANDARDS_SUBJECT, s + "\n## Abstract\n\nA second one, on purpose.\n")
|
|
|
|
def m_st004(root):
|
|
s = read(root, STANDARDS_SUBJECT)
|
|
write(root, STANDARDS_SUBJECT, s.replace("### §2 ", "### §1 ", 1))
|
|
|
|
def m_st005(root):
|
|
s = read(root, STANDARDS_SUBJECT)
|
|
write(root, STANDARDS_SUBJECT, s.replace("\n## Abstract\n", "\n## Preface\n", 1))
|
|
|
|
def m_st006(root):
|
|
s = read(root, STANDARDS_SUBJECT)
|
|
write(root, STANDARDS_SUBJECT, s + "\n\n```go\nfunc dangling() {}\n")
|
|
|
|
def m_st007(root):
|
|
s = read(root, STANDARDS_SUBJECT)
|
|
write(root, STANDARDS_SUBJECT,
|
|
s.replace("### §2 The entry point", "### §2 The entry point\n\n#### §9.1 Misfiled", 1)
|
|
if "### §2 The entry point" in s
|
|
else s.replace("## Specification", "## Specification\n\n### 1 One\n\n#### 4.2 Misfiled", 1))
|
|
|
|
def m_lk001(root):
|
|
s = read(root, STANDARDS_SUBJECT)
|
|
write(root, STANDARDS_SUBJECT,
|
|
s.replace("## Abstract", "## Abstract\n\nSee [HIP-0106](./hip-0106-gone.md).", 1))
|
|
|
|
def m_ix003(root):
|
|
shutil.copy(os.path.join(root, "HIPs", SUBJECT),
|
|
os.path.join(root, "HIPs", "hip-0517-duplicate-claim.md"))
|
|
|
|
def m_pl001(root):
|
|
s = read(root, STANDARDS_SUBJECT)
|
|
write(root, STANDARDS_SUBJECT,
|
|
s.replace("## Abstract", "## Abstract\n\nThis is as good as Terraform.", 1))
|
|
|
|
def m_pl002(root):
|
|
s = read(root, STANDARDS_SUBJECT)
|
|
write(root, STANDARDS_SUBJECT,
|
|
s.replace("## Abstract", "## Abstract\n\nSee `hanzo-inc/payments` for the rest.", 1))
|
|
|
|
|
|
CASES = [
|
|
("FM001", "front matter removed", m_fm001),
|
|
("FM002", "created: removed", m_fm002),
|
|
("FM003", "hip: renumbered away from the filename", m_fm003),
|
|
("FM004", "status: set to a word not in the vocabulary", m_fm004),
|
|
("FM005", "type: set to a word not in the vocabulary", m_fm005),
|
|
("FM006", "category: set to a word not in the vocabulary", m_fm006),
|
|
("FM007", "requires: a HIP that does not exist", m_fm007),
|
|
("FM008", "a superseded-by tombstone field added", m_fm008),
|
|
("FM009", "filename given capitals and an underscore", m_fm009),
|
|
("ST001", "H1 deleted", m_st001),
|
|
("ST002", "H1 title made to disagree with front matter", m_st002),
|
|
("ST003", "a second '## Abstract' introduced", m_st003),
|
|
("ST004", "two sections numbered §1", m_st004),
|
|
("ST005", "'## Abstract' renamed to '## Preface'", m_st005),
|
|
("ST006", "a code fence left open", m_st006),
|
|
("ST007", "a subsection numbered outside its parent", m_st007),
|
|
("LK001", "a body link pointed at a HIP file that does not exist", m_lk001),
|
|
("IX003", "a second file claiming HIP-0517", m_ix003),
|
|
("PL001", "'as good as Terraform' added", m_pl001),
|
|
("PL002", "a private-org repository cited", m_pl002),
|
|
]
|
|
|
|
|
|
def main() -> int:
|
|
base = tempfile.mkdtemp(prefix="hips-lint-test-")
|
|
try:
|
|
clean_root = os.path.join(base, "clean")
|
|
os.makedirs(clean_root)
|
|
shutil.copytree(os.path.join(ROOT, "HIPs"), os.path.join(clean_root, "HIPs"))
|
|
shutil.copy(os.path.join(ROOT, "README.md"), clean_root)
|
|
shutil.copy(os.path.join(ROOT, "vocabulary.json"), clean_root)
|
|
|
|
rc, out = run(clean_root)
|
|
if rc != 0:
|
|
print("CONTROL FAILED: the unmutated corpus does not lint clean.")
|
|
print(out)
|
|
return 1
|
|
print(f"control: clean corpus lints clean (exit 0)\n")
|
|
|
|
failures = []
|
|
for code, what, mutate in CASES:
|
|
root = os.path.join(base, code)
|
|
shutil.copytree(clean_root, root)
|
|
mutate(root)
|
|
rc, out = run(root)
|
|
caught = rc == 1 and re.search(rf"^ERROR {code}\b", out, re.M) is not None
|
|
print(f" {'PASS' if caught else 'FAIL'} {code} {what}")
|
|
if not caught:
|
|
failures.append((code, what, rc, out))
|
|
|
|
declared = set(re.findall(r'^\s*\("([A-Z]{2}\d{3})",',
|
|
open(LINT, encoding="utf-8").read(), re.M))
|
|
tested = {c for c, _, _ in CASES}
|
|
untested = declared - tested
|
|
print(f"\n {'PASS' if not untested else 'FAIL'} every declared check has a test"
|
|
+ (f" -- missing {sorted(untested)}" if untested else ""))
|
|
if untested:
|
|
failures.append(("COVERAGE", "declared but untested", 0, str(sorted(untested))))
|
|
|
|
if failures:
|
|
print(f"\n{len(failures)} of {len(CASES) + 1} checks did not fail as required:\n")
|
|
for code, what, rc, out in failures:
|
|
print(f"--- {code} ({what}) exit={rc} ---")
|
|
print("\n".join(out.splitlines()[-12:]) if isinstance(out, str) else out)
|
|
return 1
|
|
print(f"\nall {len(CASES)} checks fail on a deliberate break, and the clean "
|
|
f"corpus passes")
|
|
return 0
|
|
finally:
|
|
shutil.rmtree(base, ignore_errors=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|