Files
Vijay Janapa Reddi f1ffe5b6fa Rename interviews/ to staffml/ and drop the repeated prefix inside it
interviews/ held StaffML and nothing else, so the directory now carries the
product's name. Three names inside it repeated that prefix, and now line up
with vault/, vault-cli/, and paper/:

  interviews/staffml/               -> staffml/app/
  interviews/staffml-vault-worker/  -> staffml/vault-worker/
  interviews/staffml-vault-types/   -> staffml/vault-types/

Every repository path moved with it: workflow paths: triggers and variable
fallbacks, pre-commit hooks, .gitignore, contributor tooling, vault-cli path
joins, the app's tsconfig mapping to the shared types, and the docs.

Left alone on purpose:
- the published /interviews/ redirect to /staffml/ and the gh-pages subsite
  lists that protect it; those are URLs, not repository paths
- package and worker names (staffml-vault-worker, @staffml/vault-types) and
  the vitest job name, which branch protection may key on
- question YAMLs and generated track JSON, which are content-addressed.
  Three questions carry an older corruption, "recall @interviews/vault/
  questions/...", that predates this change
- the applied D1 migration 0001 and the vault CHANGELOG, which are history

codegen-hashes.txt is re-recorded; the three pinned artifacts changed only in
path strings. vault-cli: 97 passed, codegen --check clean.

interviewer.py's __main__ loader was one parent short and pointed at a
corpus.json that never existed; it now resolves staffml/app/src/data/
corpus.json. tools/validate_playbook.py and generate_interview_flashcard.py
still expect per-track directories that no longer exist; they point at the
new root but remain dead.

The GitHub repository variables STAFFML_ROOT, VAULT_DIR, and VAULT_CLI_DIR
still name the old paths and must change when this reaches origin.
2026-09-10 14:48:04 +02:00

126 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""Sparse vault sanity check for the StaffML deploy.
Validates the small committed metadata files that ship in the repo:
``taxonomy.json`` and ``vault-manifest.json``. Confirms taxonomy has
concepts, manifest has a question count, and track distributions add up.
Per-question deep validation (schema, chain integrity, math, etc.) is
covered by ``vault check --strict`` (run in CI via
``staffml-validate-vault.yml``), which validates directly against the
YAML source files in ``staffml/vault/`` rather than a generated JSON
artifact. This script is the cheap pre-deploy gate; ``vault check`` is
the authoritative one.
Exit code 0 = all checks pass, 1 = errors found.
Usage: python3 staffml/app/scripts/validate-vault.py
"""
import json
import sys
from pathlib import Path
STAFFML_DATA = Path(__file__).parent.parent / "src" / "data"
errors: list[str] = []
warnings: list[str] = []
def error(msg: str) -> None:
errors.append(msg)
print(f" ❌ {msg}")
def warn(msg: str) -> None:
warnings.append(msg)
print(f" ⚠️ {msg}")
def ok(msg: str) -> None:
print(f" ✅ {msg}")
def main() -> int:
taxonomy_path = STAFFML_DATA / "taxonomy.json"
manifest_path = STAFFML_DATA / "vault-manifest.json"
print("\n🔍 Sparse vault check (committed metadata only)")
print(
" Per-question deep validation lives in `vault check --strict` "
"(staffml-validate-vault.yml).\n"
)
if not taxonomy_path.exists():
error(f"taxonomy.json not found at {taxonomy_path}")
return 1
if not manifest_path.exists():
error(f"vault-manifest.json not found at {manifest_path}")
return 1
with open(taxonomy_path, encoding="utf-8") as f:
taxonomy = json.load(f)
with open(manifest_path, encoding="utf-8") as f:
manifest = json.load(f)
concepts = taxonomy.get("concepts", [])
if not concepts:
error("taxonomy has no concepts")
else:
ok(f"taxonomy: {len(concepts)} concepts")
qc = manifest.get("questionCount")
if qc is None or not isinstance(qc, int) or qc < 1:
error("manifest.questionCount missing or invalid")
else:
ok(f"manifest: questionCount = {qc}")
td = manifest.get("trackDistribution") or {}
if isinstance(td, dict) and td:
s = sum(int(v) for v in td.values() if isinstance(v, int))
if s != qc:
error(
f"trackDistribution sum ({s}) != questionCount ({qc}) — check manifest"
)
else:
ok("trackDistribution sums to questionCount")
ld = manifest.get("levelDistribution") or {}
if isinstance(ld, dict) and ld:
s = sum(int(v) for v in ld.values() if isinstance(v, int))
if s != qc:
error(
f"levelDistribution sum ({s}) != questionCount ({qc}) — check manifest"
)
else:
ok("levelDistribution sums to questionCount")
release_id = manifest.get("releaseId")
release_hash = manifest.get("releaseHash")
if not isinstance(release_id, str) or not release_id:
error("manifest.releaseId missing or invalid")
elif not isinstance(release_hash, str) or len(release_hash) < 16:
error("manifest.releaseHash missing or truncated")
else:
ok(f"Vault v{release_id} — hash {release_hash}")
print(f"\n{'=' * 50}")
print(f" Errors: {len(errors)}")
print(f" Warnings: {len(warnings)}")
print(f"{'=' * 50}")
if errors:
print("\n❌ Sparse validation failed")
return 1
print(
"\n🎯 Sparse checks passed — for deep per-question validation run "
"`vault check --strict` (or rely on staffml-validate-vault in CI)."
)
if warnings:
print(f" ({len(warnings)} warnings — review recommended)")
return 0
if __name__ == "__main__":
sys.exit(main())