Skip to content

feat: verify ephemeris kernel integrity on load#12

Merged
wiyth00 merged 1 commit into
mainfrom
feat/ephemeris-kernel-integrity
Jul 25, 2026
Merged

feat: verify ephemeris kernel integrity on load#12
wiyth00 merged 1 commit into
mainfrom
feat/ephemeris-kernel-integrity

Conversation

@wiyth00

@wiyth00 wiyth00 commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

load_ephemeris() auto-downloads de440s.bsp with no integrity check, yet the entire validation chain rests on kernel identity (work plan item P3 / 13).

One honest deviation from the plan: the plan says "add a known-good SHA-256", but NAIF publishes no checksums for the generic kernels, and I refuse to pin a digest I couldn't independently verify. So the design is:

  • Trust-on-first-use. The first load records a de440s.bsp.sha256 sidecar next to the kernel; every later load recomputes the SHA-256 and must match it. A silently truncated or corrupted re-download now fails loudly instead of poisoning a multi-hour run.
  • KERNEL_SHA256_PINS — an explicit pin table that takes precedence over the sidecar. It ships empty with instructions; fill it from your trusted local copy (sha256sum data/ephemeris/de440s.bsp) in a one-line follow-up commit. A fresh recording never counts as "verified"; only a match against a pre-existing pin or sidecar does.
  • allow_unverified=True on load_ephemeris / verify_kernel_integrity is the escape hatch: mismatches are tolerated but reported as unverified.
  • EphemerisContext.kernel_sha256 / .kernel_verified (defaulted fields, so existing constructors are unaffected) expose the digest and status so run_full's manifest can record verified: true/false.

Not included here: wiring --allow-unverified-ephemeris and the manifest verified field through cli.py — that belongs with the item-15 CLI work and is noted on #9.

Scientific impact

None. Verification happens after Skyfield loads the kernel and changes no numeric path. On an existing checkout the first load with this branch records the sidecar and proceeds exactly as before.

  • No change to computed results
  • Results change (described above)

Checklist

  • ruff check . passes
  • pytest passes locally
  • New or changed behaviour is covered by a test
  • Docs / README updated if user-facing behaviour changed (behaviour documented in docstrings; no README workflow change)

tests/test_kernel_integrity.py covers seven scenarios (first-load recording, sidecar verification, tamper detection, allow_unverified bypass, case-insensitive pin match, pin mismatch overriding a matching sidecar, pin+sidecar precedence) against small temp files — no kernel or network needed. The verification logic was additionally executed standalone in a sandbox before pushing: all scenarios pass. Full pytest/ruff could not be run locally, so CI is the first integrated execution.

NAIF publishes no checksums for the generic kernels, so integrity is
trust-on-first-use: the first load records a <kernel>.sha256 sidecar and
every later load must match it. A KERNEL_SHA256_PINS table supports pinning
a known-good digest from a trusted local copy, which takes precedence over
the sidecar. load_ephemeris exposes allow_unverified to bypass mismatches
explicitly, and EphemerisContext now carries kernel_sha256/kernel_verified
so manifests can record the verification status.
@wiyth00
wiyth00 merged commit 51ced31 into main Jul 25, 2026
1 check passed
wiyth00 added a commit that referenced this pull request Jul 25, 2026
…--resume

Item 15:
- argparse --version prints package version, MODEL_VERSION, and kernel name.
- Stage progress messages use the logging module (INFO default, --quiet for
  warnings only, --verbose for debug). Data outputs (json dumps, headline)
  stay on stdout. terminal_summary.txt is byte-identical.

Deferred #12 follow-ups:
- --allow-unverified-ephemeris is wired through every mode's load_ephemeris.
- events.json records the kernel digest from the verified context (no
  re-hash) plus the new verified flag.
- run_manifest.json command string reflects the real entry point (the old
  string referenced run_search.py, deleted in #10) and records resume use.

Item 16:
- --resume reuses per-stage artifacts in --output when a resume_manifest
  records matching model_version, config sha256, and kernel sha256;
  refuses to resume across any mismatch. Stages 1 (enumeration/screening),
  2 (validation), 3 (optimized design), and 5 (stability) are
  checkpointable; stage 4 is always recomputed because fixed-system ground
  tracks are not serialized and downstream figures need them.
- New tests cover parser flags, the version string, a --version subprocess
  run, and the pure resume-guard helper.
wiyth00 added a commit that referenced this pull request Jul 25, 2026
#14)

Item 15:
- argparse --version prints package version, MODEL_VERSION, and kernel name.
- Stage progress messages use the logging module (INFO default, --quiet for
  warnings only, --verbose for debug). Data outputs (json dumps, headline)
  stay on stdout. terminal_summary.txt is byte-identical.

Deferred #12 follow-ups:
- --allow-unverified-ephemeris is wired through every mode's load_ephemeris.
- events.json records the kernel digest from the verified context (no
  re-hash) plus the new verified flag.
- run_manifest.json command string reflects the real entry point (the old
  string referenced run_search.py, deleted in #10) and records resume use.

Item 16:
- --resume reuses per-stage artifacts in --output when a resume_manifest
  records matching model_version, config sha256, and kernel sha256;
  refuses to resume across any mismatch. Stages 1 (enumeration/screening),
  2 (validation), 3 (optimized design), and 5 (stability) are
  checkpointable; stage 4 is always recomputed because fixed-system ground
  tracks are not serialized and downstream figures need them.
- New tests cover parser flags, the version string, a --version subprocess
  run, and the pure resume-guard helper.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant