Skip to content

Data-validation + audit tooling

Reference for Orrery's scripts/ validation and audit suite. Validators are fail-closed gates: they exit non-zero on a violation so npm run preflight (and therefore the husky pre-push hook + CI) refuses the push. Audits are on-demand diagnostics — most report and exit non-zero, but they're run by hand, not chained into the gate.

The gate entry point is npm run validate-data, which runs scripts/validate-data-runner.ts — a parallel runner (Promise.all) that spawns every gating validator concurrently and re-throws the worst exit code. It replaced the old serial && chain (GH #272) so preflight doesn't pay wall-clock for independent checks. Adding a new gate = add a Task entry to that runner's TASKS array.

Cross-references (not duplicated here): ADR-069 (overlay-completeness fail-closed contract), AGENTS.md §"No-stub validation" + §"Image pipeline — gotchas", docs/adr/TA.md §pipelines.

ValidatorGate?What it checks
validate-data.tsyesajv schemas for all static/data/** + ~20 runtime consistency invariants
validate-diagrams.tsyesevery /science section diagram field has its SVG on disk
validate-satellites.tsyessatellites.json schema + gallery + per-locale overlay coverage
validate-hero-coverage.tsyesevery surface index id has a 01.jpg hero (with fallback ladder)
validate-image-dupes.tsyescross/same-surface SHA-256 byte-dupes in static/images/
validate-image-phash-dupes.tsyesperceptual near-dupes (Hamming ≤ 6) from the phash cache
validate-gallery-counts.tsyes*-galleries.json counts match on-disk slot reality
validate-provenance-walker.tsyeswalker schema-sync: no FilePath/undefined, NASA-CDN rows carry nasa_id
validate-allowlist-discipline.tsyesdupe-allowlist growth vs main stays within budget unless authorized
validate-credits-bundling.tsyes (informational)/credits bundling integrity; only hard-fails on FilePath/undefined
audit-image-mime.tsinline only.jpg files start with JPEG SOI bytes (same check runs inside validate-data)
license-allowlist.tsn/a (module)shared license allowlist consumed by validate-data
audit-image-usage.tsnoevery shipped image is referenced/used somewhere
audit-report-launches.tsnolaunches provenance gaps + unmapped rocket families (HTML report)
audit-gallery-counts.tsnogallery counts below threshold (agency-balance diagnostic)
audit-heroes.tsnohero-quality rubric across all surfaces (markdown reports)
audit-fleet-heroes.tsnofleet hero rubric — mutates sidecar + swaps slots
audit-site-imagery.mjsno/earth /moon /mars site imagery categorisation (read-only)
build-audit-report.tsnoimage-pipeline HTML report (vision + curation + cost ledger)

validate-data.ts

Commandnpm run validate-data (runs it as one of the parallel tasks); standalone tsx scripts/validate-data.tsWhat — The core validator. Compiles ~45 JSON schemas (Ajv({strict:true, strictRequired:false})) and validates every reference file, mission, overlay, fleet entry, science section, launch, audio manifest, etc. Then layers on ~20 runtime invariants the schema can't express. When — automatically in npm run validate-data → preflight → CI. Fails on (any of, summed at the end → process.exit(1)): schema violation; cislunar/interplanetary waypoint invariant breach (MET-0 anchor, strictly-increasing, window cap, ≤200 pts); fleet↔mission↔site bidirectional drift + orphan/category mismatch; doc gating-sentence missing (PRD "Why this is a PRD", RFC "Why this is an RFC", ADR "> Status ·"); tcm_count drift; provenance license-not-allowed / missing-file / duplicate-path; text-sources + source-logos dupes/missing; link-provenance unknown-source/tracker-survivor/dup/bad-BCP47; launches missing-primary/missing-citation/orphan-launcher-ref; oversized image > 8 MiB workbox cap; .jpg non-JPEG bytes; image-vision/curation malformed; surface-hotspot Tier-2 consumed-variant missing on disk; missing en-US overlay (earth-objects/fleet/moon-sites/mars-sites/science/fleet-galleries); audio + episode-sources cross-ref breaks. Gotchas

  • Fails CLOSED on a missing en-US overlay (ADR-069 / GH #83): adding a registry id without its i18n/en-US/.../<id>.json breaks the build here, not just at e2e. Other locales fall back to en-US so only en-US presence is enforced.
  • Doc gating-sentence check is scoped to git ls-files (issue #136) — untracked PRD/RFC drafts a parallel agent left don't block your push; git add them to re-cover. Falls back to a filesystem walk when git metadata is unavailable.
  • WORKBOX_CACHE_LIMIT_BYTES (8 MiB) must stay in sync with maximumFileSizeToCacheInBytes in vite.config.ts — bump both together. 5–8 MiB is a soft warn band.
  • The surface-hotspot variant check mirrors the runtime pickVariant(entry,'thumbnail') resolve: a hotspot base re-fetched without regenerating its *.1x1.jpg variants 404s at deepest zoom. MANDATORY after any hotspot re-fetch: node scripts/hotspots/regenerate-tier3-variants.mjs <base>.jpg.
  • Link-provenance freshness + launches-curation dangling refs are WARN-only (don't fail the build). State — Healthy, the workhorse gate. It has accreted ~20 inline runtime blocks; opportunity: extract the image/overlay blocks into their own runner tasks so they parallelise instead of running serially inside this one process.

validate-data-helpers.ts

Command — n/a (imported by validate-data.ts) What — Pure detector functions for the cross-reference integrity checks: findBidirectionalFleet{Mission,Site}Drift, findProvenanceFailures, findLaunchesMissing{PrimaryProvenance,Citations}, findLaunchesOrphan{LauncherRefs,RocketMappingTargets}. Keeps detection logic pure (testable) while validate-data owns the console templating. State — Healthy; the seam that makes the integrity rules unit-testable.

validate-diagrams.ts

Commandnpm run validate-diagrams; also a parallel task inside validate-dataWhat — Walks every base /science section JSON and asserts any declared diagram field resolves to an SVG at static/diagrams/science/<diagram> (ADR-035). When — automatically in npm run validate-data; also exposed standalone. Fails on — any diagram reference with no matching SVG on disk. Gotchas — skips _index.json; exits 0 (skip) when static/data/science/ is absent. State — Healthy, narrow scope.

validate-satellites.ts

Commandtsx scripts/validate-satellites.ts (parallel task in validate-data) What — Schema + coverage for the natural-satellite layer (#304): required fields + library tier/kind enums on satellites.json, satellite-galleries.json coverage, and a per-locale overlay file for every satellite × locale. When — automatically in npm run validate-data. Fails on — missing required field, duplicate id, invalid library tier/kind, missing gallery entry, or a missing overlay file in any locale. Gotchas — orphan gallery entries and missing per-locale satellites/ dirs are WARN, not fail. Skips packed-lines. State — Healthy.

validate-hero-coverage.ts

Commandtsx scripts/validate-hero-coverage.ts (parallel task in validate-data) What — Walks every surface index (missions/fleet/moon-sites/mars-sites/earth-objects/planets) and verifies a 01.jpg hero exists, following the same cross-surface fallback ladder as the runtime gallery loader. When — automatically in npm run validate-data. Fails on — any id with no hero on disk AND no fallback hit AND not in that surface's KNOWN_*_GAPS allowlist. Gotchas — all KNOWN_*_GAPS sets are currently empty (#342 cleared the cruise-phase backlog). The fallback ladder is bidirectional (surface↔missions↔fleet) so a byte-dedup pass that dropped a redundant copy doesn't false-flag. Adding an id to a gap set requires a real fetch + a docs/provenance/image-hero-inventory.md entry. State — Healthy / current. Coupled to the runtime loader's ladder — keep FALLBACK_LADDER in sync with src/lib/data.ts.

validate-image-dupes.ts

Commandtsx scripts/validate-image-dupes.ts (parallel task in validate-data) What — SHA-256-hashes every base .jpg in static/images/ and fails on any 2+ files sharing bytes (cross-surface OR same-surface) unless the 8-char prefix is in the inline ALLOWLIST. Exports ALLOWLIST for the phash validator to import. When — automatically in npm run validate-data. Fails on — any un-allowlisted byte-dupe group. Gotchas — skips .1x1/.4x3/.16x9 variant crops (only base images). main() runs only when invoked directly, so the phash sibling can import ALLOWLIST without triggering a scan. The allowlist is large and growing — every legitimate editorial cross-surface reuse needs a one-line prefix + comment; this is the duplicate-manifest-entry trap that validate-allowlist-discipline.ts polices. State — Healthy but the allowlist is heavy (~250 entries). Each is human-justified; growth is gated.

validate-image-phash-dupes.ts

Commandtsx scripts/validate-image-phash-dupes.ts (parallel task in validate-data) What — Reads the static/data/image-phashes.json cache (built by compute-phash.ts), does an O(n²) Hamming scan, fails on any pair with d ≤ 6 not in INLINE_ALLOWLIST + phash-baseline-allowlist.json. Auto-skips byte-identical pairs already signed off in the byte-allowlist. When — automatically in npm run validate-data. Fails on — any un-allowlisted near-dupe at d ≤ 6. Gotchasphash cache staleness: this reads a precomputed cache, not live pixels — re-run compute-phash.ts after image changes or the scan validates stale state. Threshold 6 is hand-tuned to the current corpus; it catches re-encodes/crops, NOT "same subject different shoot" (those land at d≈25-40). The dual-allowlist (inline categorised + JSON baseline snapshot) means a near-dupe can be silenced in two places. State — Healthy; depends on cache freshness being maintained out-of-band.

Commandtsx scripts/validate-gallery-counts.ts (parallel task in validate-data) What — Asserts fleet-galleries.json + mission-galleries.json counts equal the on-disk count of NN.jpg base slots per id. The count IS the contract the gallery loader uses to enumerate slot URLs. When — automatically in npm run validate-data. Fails on — any id where manifest count ≠ disk count (over → 404, under → silently-hidden images). Gotchas — only counts base \d{2}\.jpg; covers fleet + mission manifests only (satellite/site/planet count-checks live elsewhere — see audit-image-usage's broader manifest map). State — Healthy. Opportunity: it covers 2 of the ~11 count manifests audit-image-usage.ts knows about; the others aren't gated against disk here.

validate-provenance-walker.ts

Commandtsx scripts/validate-provenance-walker.ts [--manifest=...] (parallel task in validate-data) What — Enforces the build-image-provenance walker's schema-sync contract: no entry has image_url ending Special:FilePath/undefined, and every NASA-CDN-referencing entry carries a non-null nasa_id. When — automatically in npm run validate-data. Fails on — any FilePath/undefined survivor or NASA-CDN row with null nasa_id. Gotchas — defends against two specific historical walker bugs (Slice A v3); the FilePath/undefined check is also re-asserted defensively in validate-credits-bundling.ts. State — Healthy, tightly scoped to known regressions.

validate-allowlist-discipline.ts

Commandtsx scripts/validate-allowlist-discipline.ts [--base=main --max=5] (parallel task in validate-data) What — Diffs the dupe-allowlist entries (byte + phash) against origin/main/main and fails when more than MAX_AUTOLAND (5) new entries land on the branch without an ALLOWLIST_AUTHORIZED: token in a commit message. Stops the "silence the build by padding the allowlist" pattern that shipped 707 bad swaps. When — automatically in npm run validate-data. Fails on — net-new allowlist entries > max AND no ALLOWLIST_AUTHORIZED: token in branch commits. Gotchas — skips silently when no baseline ref is resolvable (initial setup / shallow clone). Authorization is a literal commit-message token after curator review, not a code change. State — Healthy; the meta-gate over the two dupe validators.

validate-credits-bundling.ts

Commandtsx scripts/validate-credits-bundling.ts [--top=N --fail-on-split] (parallel task in validate-data) What — Mirrors src/lib/credits-grouping.ts to report how /credits collapses provenance entries into bundles: cross-route reuse, cross-source "split" bundles. Defensively re-checks for FilePath/undefined. When — automatically in npm run validate-data. Fails onFilePath/undefined survivors (always); cross-source splits only when --fail-on-split is passed (off in the gate). GotchasagencyToSourceId + SOURCE_TYPE_TO_ID are hand-mirrored from credits-grouping.ts; if either drifts, split detection becomes inaccurate (still informative). In the gate it's effectively a FilePath/undefined guard + a report. State — Healthy but mostly informational in CI. Opportunity: the manual mirror of the grouping logic is a drift risk — import the real functions instead.

audit-image-mime.ts

Commandnpx tsx scripts/audit-image-mime.ts (report) · --repair (re-encode in place) · --quietWhat — Walks static/images/**/*.jpg and verifies each starts with the JPEG SOI marker (ff d8 ff). GH #251: 110 fleet files had .jpg extensions but raw PNG bytes, silently breaking Anthropic vision scoring. When — the check runs inline inside validate-data (so it gates); this standalone script is the on-demand reporter + repair tool. Fails on — any .jpg/.jpeg whose first 3 bytes aren't JPEG. Gotchas--repair re-encodes via coerceToJpeg() then re-verifies. Fetchers should call coerceToJpeg() to prevent regression. Browsers + sharp sniff and render anyway, so the mismatch is otherwise invisible. State — Healthy; the gate copy and the standalone repair tool share lib/image-bytes.ts.

license-allowlist.ts

Command — n/a (module) What — Canonical license allowlist for image + text provenance (ADR-046 Milestone C). Exports isAllowedLicense, getAllowlistEntry, normaliseLicenseShortName. Includes the deliberate non-Western-agency precedents (CNSA-EDU, ISRO-EDU, JAXA-OPEN, SpaceIL-EDU) and Orrery-Original for in-house prose. When — consumed by validate-data's provenance + text-sources gates. Gotchas — anything not in the allowlist AND not waived in license-waivers.json fails closed. Prefer extending the allowlist (with a rationale) over filing a waiver. State — Healthy / current; the single source of truth for license stewardship.


Audit family (on-demand — not in validate-data)

audit-image-usage.ts

Commandnpm run audit:images · npm run audit:images -- --write-approvedWhat — Proves the RFC-029 invariant: every shipped image under static/images/ (minus gitignored _staging/) is referenced by a display manifest, source code, a gallery count-cap, a hero slot, or is a body/card/hotspot. --write-approved (re)derives image-approved.json from the same classifier. When — on-demand. Strong candidate to promote into the gate (it's the broadest coverage check and already exits 1 on orphans). Fails on — any UNKNOWN orphan not in ALLOWED_ORPHANS (currently empty). Gotchas — variant-aware (a used base covers all its crops). BOOKKEEPING regex excludes all-path manifests so they don't mark everything "referenced". Knows all ~11 gallery count manifests — broader than the gated validate-gallery-counts.ts. State — Current; the natural next promotion into validate-data.

audit-report-launches.ts

Commandnpm run audit:launches (also npm run docker:audit-launches) What — Writes gitignored static/audit-report-launches.html: provenance gaps (rows where agency-direct returned nothing, source ll2), unmapped rocket families, tier-reason distribution (RFC-023 §10). When — on-demand, after npm run fetch:launches. State — Current; pure reporter, never exits non-zero.

Commandnpx tsx scripts/audit-gallery-counts.ts [--json] [--non-nasa] [--threshold N]What — Reports mission + fleet gallery counts below a threshold (default 3), grouped by agency — surfaces non-NASA coverage gaps for agency balance. When — on-demand. Exits non-zero when any entry is below threshold (a diagnostic, not wired into the gate). State — Current.

audit-heroes.ts

Commandnpx tsx scripts/audit-heroes.ts [--surface=<name>] [--list-clean]What — Applies the hero-quality rubric (URL deny-list, subject deny-list, vision category/score rules, subject↔id mismatch) across all 8 surfaces and writes docs/provenance/<surface>-hero-audit.md with swap proposals / needs-sourcing / accepted-overrides. Read-only on manifests; does not apply swaps. When — on-demand, no API calls. Gotchas<surface>-hero-overrides.json wins over rubric output silently; the report lists overrides to keep them reviewable. State — Current; the quality counterpart to the coverage gate.

audit-fleet-heroes.ts

Commandnpx tsx scripts/audit-fleet-heroes.tsWhat — Older, fleet-only hero auditor. Mutates: applies auto-swaps to disk + rewrites fleet-image-sources.json, writes docs/provenance/fleet-hero-audit.md. ACCEPTED_OVERRIDES holds the no-clean-photo cohort (Energia, Tiangong-2, Salyut-7, Soyuz 7K-OK). When — on-demand only — it writes files, so never in CI. Gotchas — superseded in scope by the read-only audit-heroes.ts; this one still has the destructive swap path. Run deliberately. State — Needs-attention — overlaps audit-heroes.ts (which is read-only + multi-surface). Candidate for retirement once its swap utility is folded into a non-destructive apply step.

audit-site-imagery.mjs

Commandnode scripts/audit-site-imagery.mjsWhat — Categorises every /earth /moon /mars site by own-vs-mission-vs-fleet hero byte-size into KEEP_OWN / DELETE_OWN_USE_MISSION / FALLBACK_OK / HAND_SOURCE etc. Writes /tmp/site-image-audit.json. Read-only. When — on-demand. State — Current; a one-shot triage helper (size-heuristic, not vision-based).

build-audit-report.ts

Commandnpx tsx scripts/build-audit-report.ts [--threshold N]What — Renders gitignored static/audit-report.html from image-curation.json + image-vision.json + cost-ledger.json: flagged deny-list, low-score images, fallback/rejected, recent scoring runs + total spend (PRD-018 M6 / RFC-022 §8). When — on-demand. State — Current; the image-pipeline operator dashboard.

Orrery — architecture documentation · MIT · No tracking