Files
cs249r_book/interviews/staffml/scripts/validate-vault.py
Vijay Janapa Reddi c824ac6ed1 refactor(staffml): retire prod static-fallback; opt-in dev-only (#1598)
The bundled corpus.json was serving as a prod safety net behind the
Cloudflare Worker. Post-cutover the Worker has been the real data
source, and the static path was silently degrading rather than helping
(corpus.json is a generated artifact whose prose `details` are blank
in corpus-summary.json). This change:

- Stops emitting corpus.json in the publish-live workflow
- Removes the Worker-error fallback in getQuestionFullDetail — errors
  now propagate to useFullQuestion and the UI shows a "details
  unavailable" banner instead of silently filling blanks
- Drops the localhost auto-trigger in shouldUseStaticDetails — the
  static path now requires explicit NEXT_PUBLIC_VAULT_FALLBACK=static
- Switches taxonomy.ts to corpus-summary.json (was corpus.json)
- Rewrites the publish-live smoke tests against corpus-summary.json
- Collapses validate-vault.py to sparse-only (per-question deep
  validation lives in `vault check --strict`)

Static-fallback remains as an OPT-IN local-dev affordance: set
NEXT_PUBLIC_VAULT_FALLBACK=static and run `vault build --legacy-json`
to materialize corpus.json. The Function-constructor dynamic import
keeps Turbopack from requiring corpus.json at build time.

useFullQuestion hook signature changed from `Question | undefined` to
`{ question, status }`. Callers updated: practice and plans pages
(both render an amber "details unavailable" banner when status
is 'error').

Deleted dead cutover scaffolding: corpus-source.ts (router with no UI
consumers), corpus-vault.ts (worker-only mirror, never wired up),
useVaultQuestion.ts (unused migration hook), vault-fallback.ts (only
consumer was corpus-source.ts).

Deleted stale docs: staffml/scripts/DEPRECATED.md, vault-cli/docs/
CUTOVER_QA.md, three vault/docs/RESUME_PLAN_*.md.

Verified locally: tsc clean, vitest 37/37, next build produces all
15 static routes.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 18:47:03 -04:00

111 lines
3.2 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 ``interviews/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 interviews/staffml/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:
warn(
f"trackDistribution sum ({s}) != questionCount ({qc}) — check manifest"
)
else:
ok("trackDistribution sums to questionCount")
ver = manifest.get("version", "?")
h = manifest.get("contentHash", "?")
ok(f"Vault v{ver} — hash {h}")
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())