mirror of
https://github.com/hanzoai/hips.git
synced 2026-10-30 15:04:16 +00:00
The CI rule needed correcting twice in one pass, and the second correction came from measuring rather than from the brief. `hanzoai/hips` moved its own workflows to `.github/workflows` on 2026-09-08 with a sound reason: the forge reads that directory natively and github.com reads nothing else, so one directory serves both planes. Measured across the first-party checkouts, twelve keep their caller in `.hanzo/workflows/` and two forks carry an upstream's inherited `.github/workflows` — 24 files in datastore, six in kms, none of them ours. So the rule is not the directory's name. It is that a repository keeps exactly ONE, because the forge reads the first that exists rather than the union: a split silently retires half the pipeline, and the checks that would go red are the ones no longer running. The rest of this pass takes the per-app stores out of six more HIPs. The admin console had Postgres+Prisma, ClickHouse, Redis+BullMQ and MinIO of its own; serverless had a `hanzo_functions` database; commerce tracked idempotency in "Redis"; the zero-trust HIP named SPIFFE identities for `postgres` and `redis`, which are the upstreams we fork rather than the services we run. Payment processing also had Commerce POSTing balances into IAM through `add-balance` and `add-transaction`, which contradicts both HIP-0111's forbidden-verb list and the correction that Commerce owns the position and IAM mirrors it. Deployment carried the last `/api/` paths and the corpus's only `v2` (`api.hanzo.ai/v2`, offered as an example of path routing), plus a `resources:` block that provisioned a per-app Postgres and Redis. It now names which rank of the shared tier an app is a tenant of. HIP-0144 replaces the number this HIP was drafted at: 0138 was published as "Cloud — Unified Hanzo Binary" and retired, and the index generator refused the reuse — correctly, since every existing reference to HIP-0138 would have resolved to a different specification. Two self-test guards were reported as not holding. Both were the harness's own: the FM011 case appended a second `implementation-go:` key to a subject that already declared one, so the later key won and the contradiction was never built; and test-index restored HANZO_CAPABILITIES only when the caller had exported it, so every case after the capabilities case ran against a missing input. A check whose test is a no-op has never been shown to work. The corpus now passes lint, index --check and both self-tests, from 23 errors. Co-authored-by: Hanzo Dev <[email protected]>
237 lines
9.8 KiB
Python
237 lines
9.8 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_progress(root):
|
|
write(root, SUBJECT,
|
|
re.sub(r"^status:(.*)$", r"status:\1\nimplementation-rust: mostly",
|
|
read(root, SUBJECT), count=1, flags=re.M))
|
|
|
|
def m_final_without_go(root):
|
|
# STANDARDS_SUBJECT is Final, which is half the contradiction already. Doing
|
|
# this to a Draft would prove nothing.
|
|
#
|
|
# REPLACE any implementation-go: it already declares. Appending a second one
|
|
# proved nothing: the front-matter readers here take one line at a time, so
|
|
# the later key won, the subject stayed `shipped`, and the contradiction this
|
|
# case exists to build was never built. The guard then correctly did not
|
|
# fire and the suite reported it as broken -- a check whose test is a no-op
|
|
# is a check that has never been shown to work.
|
|
text = re.sub(r"^implementation-go:.*\n", "", read(root, STANDARDS_SUBJECT), flags=re.M)
|
|
write(root, STANDARDS_SUBJECT,
|
|
re.sub(r"^status:(.*)$", r"status:\1\nimplementation-go: none",
|
|
text, 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),
|
|
("an implementation progress outside the vocabulary", m_progress, None),
|
|
("a Final HIP declaring implementation-go: none", m_final_without_go, 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-")
|
|
# Resolve the capabilities path the way run() does, NOT just from the caller's
|
|
# environment. m_no_capabilities points HANZO_CAPABILITIES at a file that does
|
|
# not exist and the runner restores it afterwards -- but the restore was
|
|
# guarded on a value that is None whenever the caller had not exported the
|
|
# var, which is the ordinary case. Every case after m_no_capabilities then ran
|
|
# against a missing input, and the two that follow it reported failures that
|
|
# were the harness's own.
|
|
capabilities = os.environ.get("HANZO_CAPABILITIES") or os.path.join(
|
|
os.path.dirname(ROOT), "openapi", "capabilities.yaml")
|
|
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())
|