Maintained: 2026-09-23 (UTC). Companion docs: AGENTS.md (spec), DATA_SOURCES.md (verified service catalog).
Purpose#
Layer 2 is the purpose-built forensics layer: the programs we create to find subtleties that standard survey pipelines discard, under the falsification-first discipline of AGENTS.md. Layers:
- Layer 1 — off-the-shelf science libraries (use, don't rebuild):
lightkurve,astroquery,astropy, TLS (transitleastsquares),wotan,celerite2,batman,pyLIMA,photutils/sep/astroscrappy,skyfield/JPL Horizons,emcee/dynesty,stingray. - Layer 2 — custom forensics modules (this package,
src/cygnus/). - Layer 3 — deliberately not built: full LSST/Gaia-DPAC-style pipelines; we consume their outputs and test them with our independent reductions.
Substrate (built first; shared by every module)#
ledger.py— SQLite provenance database:products,runs,measurements,candidates,prior_art. Every measurement must reference a logged run and product rows; every dossier section must trace to ledger rows. Enforces AGENTS.md rules 4/5 mechanically instead of by memory.candidate_record.py— standard output of every detector. Audit states limited topassed|failed|inconclusive|not_tested; any non-passedstate pins the record tounverified_lead; rendering never invents values (absent fields render "(none recorded)"); strict validation refuses empty provenance/audit.instruments.py— per-instrument profiles (plate scale, cadence, quirks). PSF size is not preloaded as a universal cutoff:psf_fwhm_px=Noneforces measurement or a library value per campaign — "no universal pixel-width cuts" per AGENTS.md Domain IV and falsification-matrix discipline.config.py— single source of truth for paths: worktree ([private path]), scratch root ([private path], envCYGNUS_SCRATCH, nonsynced/disposable), ledger DB (state/ledger.sqlite, envCYGNUS_LEDGER). Credentials never enter code/config; they live in uncommitted env files.ingest/— thin archive clients (MAST/Tesscut, later IRSA/Gaia/ESO/ Horizons). Idempotent by checksum, cached in scratch, every product logged to the ledger. Lazy heavy imports: unit tests run without the science stack (pyproject extras install it:pip install "cygnus[mast]"etc.).priorart.py— Known-Object Gate (§ below).reporting/— dossier renderer + leads board, driven only by ledger and candidate records.
Layer-2 module register (build status)#
| Module | Function | Status |
|---|---|---|
ledger.py | provenance DB, checksums, runs/measurements | implemented (scaffold) |
candidate_record.py | standard detector output + invariant rules | implemented (scaffold) |
instruments.py | instrument profiles, no-universal-cutoff policy | implemented (scaffold) |
config.py | paths (worktree/scratch/state), env overrides | implemented (scaffold) |
ingest/scratch.py | scratch resolve + safe cleanup inside scratch root | implemented (scaffold) |
ingest/mast.py | MAST SPOC LC search+download, TESScut cutouts | implemented; live-verified 2026-09-23 (astroquery 0.4.11; TESScut pi Men: sector 12, 5×5×1289 cadences, ledgered) |
priorart.py | Known-Object Gate; service adapters w/ not_tested defaults | implemented: SkyBoT plus cone-search adapters for NASA Exoplanet Archive, TESS TOI, VSX, SIMBAD (2026-09-24); ExoFOP, ADS and others still not_tested |
campaign/ | spec-driven, ledgered, resumable campaign runner; steps: target_queue, fetch_products, residual_screen, calibrate_screen, bls_recovery, prior_art (docs/CAMPAIGNS.md) | implemented 2026-09-24 |
targets.py | ranked target queue from a declared TOI pool, rationale per target | implemented 2026-09-24 |
reporting/dossier.py | dossier markdown emission (never invents) | implemented (scaffold) |
reporting/leads_board.py | evidence-level ranked table | implemented (scaffold) |
cli.py | doctor, dossier commands | implemented (scaffold) |
publish/ | public repository site: curated manifests → static site, leak-scanned (docs/PUBLISHING.md) | implemented 2026-09-24 |
analysis/ | bounded, in-memory reanalysis pilots: reduction disagreements, pixel controls, imaging residuals, epoch-aware crossmatches, Gaia NSS triage; read-only manifest/FITS adapter | implemented research utilities; synthetic tests only — see docs/ANALYSIS_SUITE.md |
domains/timeseries/detrend_lab.py | multi-recipe detrending robustness score | designed — Sprint 1 |
domains/timeseries/singletransit.py | monotransit posteriors, coverage constraints | designed — Sprint 1 |
domains/timeseries/ttvs.py | per-transit O–C with GP noise floor | designed — Sprint 1 |
domains/imaging/zogydiff.py | PSF-matched difference imaging (ZOGY-style) | designed — Sprint 2 |
domains/imaging/blendmap.py | difference-image centroids, BEB verdicts | designed — Sprint 1 |
domains/imaging/lsblab.py | Background2D + masked extraction, SB limits | designed — Sprint 2 |
domains/auditing/jitterwatch.py | pointing/centroid/quaternion correlation | designed — Sprint 1 |
domains/auditing/crwatch.py | PSF-profile + streak/cosmic-ray discrimination | designed — Sprint 2 |
domains/moving/mover.py | parallax-aware multi-epoch linking | designed — Sprint 3 |
domains/moving/orbitfit.py | multi-epoch LSQ orbit + error propagation | designed — Sprint 3 |
domains/modeling/massfunction.py | dark-companion M2 posteriors vs alternatives | designed — Sprint 3 |
domains/validate/injection_recovery.py | detection-efficiency curves per method | minimal version implemented for the residual screen (campaign step calibrate_screen); other detectors — Sprint 1 |
domains/validate/nulllab.py | empirical nulls → calibrated FAPs | sign-flip null implemented for the residual screen (calibrate_screen); others Sprint 1–3 |
Sprint order: S0 substrate (this scaffold) → S1 monotransit campaign minimum (detrend/singletransit/blendmap/jitterwatch, minimal injection recovery, prior-art gate hardening) → S2 imaging campaigns → S3 movers and dark companions.
Bounded reanalysis suite (implemented research utilities, not domain pipelines)#
src/cygnus/analysis/ now offers in-memory reduction-disagreement ranking, small TESScut aperture/centroid counterfactuals, cutout model-residual islands, epoch-aware optical–IR matching and Gaia NSS control/mass-function triage. A NumPy-only nearest-control ML ranker and checksum-gated, read-only Tier-1/FITS adapters support a manually mounted Colab pilot. All outputs are descriptive and either inconclusive or explicitly limited; no archived-data calibration, credible FAP, full survey discovery, Gaia orbit posterior or completed Known- Object Gate is supplied. Synthetic tests live in tests/test_analysis_*.py; methods, data prerequisites and limits are specified in docs/ANALYSIS_SUITE.md. The designed domains/... register above remains designed, not implemented by these pilots. A Colab mount or verified pack file alone is not a vetting step.
Execution pattern#
Campaigns are declarative YAML in campaigns/ (schema cygnus.campaign/1); the runner (python -m cygnus.campaign run <spec>, implemented 2026-09-24, docs/CAMPAIGNS.md) executes their steps in order, each as a ledgered run keyed by config hash. Every step is idempotent; interrupt/resume recomputes nothing already ledgered. Bulk data stays in scratch; curated outputs and reports go to Drive [private Drive store] via rclone (see DATA_SOURCES.md).
Prior-Art / Known-Object Gate#
Goal: never brand a rediscovery as new. Three leak paths, three kill mechanisms:
- Watchlists first (Gate 1): per-domain known-object registries collected and pinned with versions at campaign start, before screening:
- planetary: NASA Exoplanet Archive (TOI/KOI/K2), ExoFOP, SPOC per-sector DV reports, Villanova Kepler EB catalog;
- variables: VSX, GCVS, ASAS-SN variable catalog, ZTF periodic catalogs;
- dark companions:
gaiadr3.nss_two_body_orbitTAP queries plus ADS sweep of papers analyzing that table (Gaia BH1/BH2/BH3 came from DR3 data); - brown dwarfs: UltracoolSheet-style compilations, Montreal WD DB, Y-dwarf lists; SSOs: MPC + SkyBoT per-epoch (via
astroquery.imcce) + NEOCP; - microlensing/FFP: OGLE EWS, KMTNet; transients: TNS (free acct), ATel, ALeRCE, Gaia alerts.
- Epoch-aware matching (Gate 2): propagate Gaia proper motion/parallax to the observation epoch before any position match; no-match without epoch propagation is recorded
not_tested, neverpassed. - Event-aware matching (Gate 3): transits are checked against known transits that could masquerade through blending (
blendmap.pyfeeds inputs); moving objects are matched against ephemeris at each image epoch. - Recorded literature sweep (Gate 4): ADS/bulletin queries stored verbatim in the ledger's
prior_arttable; claims use "not found in sources searched as of <date>", never bare "uncataloged". - Ambiguity escalation (Gate 5): partial matches block advancement with a
known ?working label; silent dismissal is forbidden; rejected leads stay in the search log.
Claim rule: new = full artifact audit + gates 1–4 (pinned) + ambiguity check. Lead evidence caps: any gate not_tested ⇒ at most unverified_lead at emission. Publication lag/embargo means novelty can never be guaranteed — only a dated, reproducible record of no contradiction when checked. Cross-campaign dedupe runs inside prior_art against our own past candidates.
Testing culture#
- Tiny synthetic fixtures for units; published monotransit recoveries as end-to-end calibration targets (we quote our recovery rate on known truth).
injection_recovery.pyruns early (even rough) so leads tables cite completeness curves, not bare sigma.- Modules with
not_testeditems cannot raise evidence level pastunverified_lead— enforced incandidate_record.py. - Loud failures over silent drops (AGENTS.md); pytest markers:
not networkby default, explicit-m networkfor archive smoke tests.
Environment & verification record#
- Python 3.13 venv:
[private path](disposable; recreate withpython -m venvplus the extras groups frompyproject.toml). - Historical record (2026-09-23):
python -m pytestreported 27 offline unit tests; extras plus live MAST probes reported 30 passed. These figures describe that earlier checkout, not the present suite. - Current development verification of the reanalysis additions:
python -m pytest -q -m 'not network'reported 132 passed, 2 deselected. This was an offline synthetic/integration run; no Colab mount, Drive inventory, real-product science calibration or live archive smoke was performed. - Live probe record (
tests/probe_data_check.py): TESScut pi Men (ra 219.757, dec −80.531): sector 12 camera 3 ccd 1; 1289 FFI cadences over ~27.9 d; FLUX finite fraction 1.000; product checksummed + ledgered with 2 measurements; run closedcompleted. astroquery >=0.4.11 API notes:Tesscut.get_cutouts(coordinates, size, product, sector); sector resolved live fromget_sectors; a requested sector not covering the position raises loudly instead of returning empty data.