state-hub/scripts/sync_classification_allowed.py
tegwick ac21accd7a
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 25s
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
2026-08-24 23:46:05 +02:00

91 lines
3.1 KiB
Python
Executable file

#!/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())