feat(deploy): ship the canon classification vocabulary with the release
The API validates repo classification against the-custodian canon allowed values. A container has no such checkout, so every classification write failed with a 500 and classification could only ever be written from a workstation. Mounts the vocabulary as a ConfigMap and points REPO_CLASSIFICATION_ALLOWED_PATH at it. The copy is the risk, so it is owned rather than trusted: scripts/sync_classification_allowed.py regenerates it from canon and --check fails on drift. make check-classification-allowed and make railiance-state-hub-render both refuse to proceed when the copy diverges, so a release cannot silently validate against a stale vocabulary. The container volumeMounts and env blocks are merged rather than appended — a second pair would have produced duplicate YAML keys as soon as sweep was re-enabled. Refs CUST-WP-0067-T09 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
This commit is contained in:
parent
05dda8d276
commit
ac21accd7a
6 changed files with 270 additions and 2 deletions
91
scripts/sync_classification_allowed.py
Executable file
91
scripts/sync_classification_allowed.py
Executable file
|
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Sync the canon classification allowed-values into the chart (CUST-WP-0067-T09).
|
||||
|
||||
The State Hub validates repository classification against
|
||||
``the-custodian/canon/standards/repo-classification.allowed.yaml``. That file is
|
||||
canon and must stay authoritative, but a container has no the-custodian
|
||||
checkout, so a copy has to travel with the release.
|
||||
|
||||
A copy that nobody checks is a copy that silently drifts. This script owns the
|
||||
copy: ``--check`` fails when it diverges from canon, so the release can refuse
|
||||
to ship a stale vocabulary rather than validate against yesterday's rules.
|
||||
|
||||
Usage:
|
||||
python scripts/sync_classification_allowed.py # write the copy
|
||||
python scripts/sync_classification_allowed.py --check # verify, exit 1 on drift
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
CHART_COPY = (
|
||||
REPO_ROOT
|
||||
/ "deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml"
|
||||
)
|
||||
CANON_CANDIDATES = (
|
||||
Path("/home/worsch/the-custodian/canon/standards/repo-classification.allowed.yaml"),
|
||||
Path("/home/tegwick/the-custodian/canon/standards/repo-classification.allowed.yaml"),
|
||||
REPO_ROOT.parent / "the-custodian/canon/standards/repo-classification.allowed.yaml",
|
||||
)
|
||||
|
||||
HEADER = (
|
||||
"# GENERATED — do not edit.\n"
|
||||
"# Synced from the-custodian/canon/standards/repo-classification.allowed.yaml\n"
|
||||
"# by scripts/sync_classification_allowed.py (CUST-WP-0067-T09).\n"
|
||||
"# Canon is authoritative; this copy exists only so the container has one.\n"
|
||||
)
|
||||
|
||||
|
||||
def find_canon() -> Path:
|
||||
for candidate in CANON_CANDIDATES:
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
raise SystemExit(
|
||||
"ERROR: canon allowed-values not found. Looked in:\n "
|
||||
+ "\n ".join(str(c) for c in CANON_CANDIDATES)
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="Verify the chart copy matches canon; exit 1 on drift",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
canon = find_canon()
|
||||
desired = HEADER + canon.read_text(encoding="utf-8")
|
||||
|
||||
if args.check:
|
||||
if not CHART_COPY.is_file():
|
||||
print(f"DRIFT: {CHART_COPY} is missing; run without --check to create it")
|
||||
return 1
|
||||
current = CHART_COPY.read_text(encoding="utf-8")
|
||||
if current != desired:
|
||||
print(f"DRIFT: chart copy differs from canon ({canon})")
|
||||
sys.stdout.writelines(
|
||||
difflib.unified_diff(
|
||||
current.splitlines(keepends=True),
|
||||
desired.splitlines(keepends=True),
|
||||
fromfile="chart copy",
|
||||
tofile="canon",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
print(f"OK: chart copy matches canon ({canon})")
|
||||
return 0
|
||||
|
||||
CHART_COPY.parent.mkdir(parents=True, exist_ok=True)
|
||||
CHART_COPY.write_text(desired, encoding="utf-8")
|
||||
print(f"Synced {canon} -> {CHART_COPY}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue