mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-05-07 10:08:50 -05:00
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>
111 lines
3.2 KiB
Python
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())
|