Skip to content

Smokescreen-fork blinding: per-part-at-birth blind, hash-commitment custody, CAMB↔CCL cross-check (PR 6)#253

Draft
cailmdaley wants to merge 7 commits into
feat/sacc-4-cosmo-val-saccfrom
feat/sacc-6-blinding
Draft

Smokescreen-fork blinding: per-part-at-birth blind, hash-commitment custody, CAMB↔CCL cross-check (PR 6)#253
cailmdaley wants to merge 7 commits into
feat/sacc-4-cosmo-val-saccfrom
feat/sacc-6-blinding

Conversation

@cailmdaley

@cailmdaley cailmdaley commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #252. Stacked on #251 (feat/sacc-4-cosmo-val-sacc).

Wires per-part, at-birth Smokescreen blinding onto the migrated cosmo_val DAG. The three blindable intermediate SACC products — reporting ξ±, integration ξ±, analysis pseudo-Cℓ — are concealed the moment the pipeline computes them, so only blinded parts persist on disk. The terminal {version}.sacc is assembled only from concealed parts and asserts one shared blind commitment. Mock runs bypass blinding entirely.

The blinding machinery itself (three-verb custody, three CCL theory backends, CAMB↔CCL cross-check) is #253's existing content, reconciled onto #245's canonical sacc_io. This description covers the DAG wiring added on top.

The custody surface (three verbs + one assertion)

sp_validation/blinding.py carries the machinery; scripts/blind_data_vector.py is the CLI.

  • blind-init (once per catalogue version) — draws an OS-entropy seed, writes the repo-committable commitment.json (sha256(seed) + a config digest binding envelope + fiducial), and encrypts the seed into a Fernet bundle. The plaintext seed is never written.
  • blind-part (per intermediate, at birth) — reads the encrypted seed (verifying both hashes), conceals the part through its matching CCL theory backend, escrows the true vector into a per-part bundle beside the blinded output, stamps concealed=True + blind_commitment, strips seed_smokescreen.
  • unblind — verifies both sha256(seed) and the config digest before subtracting; fails closed on either mismatch.
  • Terminal assembly asserts blind_commitment is identical across every blindable part (assert_consistent_blind), failing closed on a mixed or divergent-seed assembly.

DAG wiring (this PR)

The pre-migration rules on this branch were replaced wholesale by #251's SACC workflow (clean merge — this branch never touched the migrated rules). On top of that:

  • rule blind_init fires once per catalogue version (data runs only).
  • rule blind_part is generic over the three blindable stems. Its plaintext input is a temp() output of the producing rule (common.maybe_temp), so on a data run only the blinded sibling persists — the true vector is escrowed first. blind_init / blind_part enter the DAG only when a consumer binds to a *_blinded part.
  • common.blindable_part is the single switch: a data run binds ξ-derived consumers (the terminal assemble) to the blinded siblings; a mock run binds to the plaintext and the whole blinding subgraph vanishes.
  • assemble_sacc asserts the shared commitment across parts and stamps it on {version}.sacc.
  • npatch wildcard is now constrained to \d+ so a producer's ξ± output cannot absorb the _blinded suffix (which made rule xi ambiguous with blind_part).

The cosmo_val assemble DAG dry-runs: blind_init ×2, blind_part ×5, assemble consuming the blinded ξ± + pseudo-Cℓ parts.

Born-blinded derived statistics

COSEBIs and pure-E/B are born blinded with no blinding-aware estimator code. The consumers (cv_cosebis / cv_pure_eb) read the ξ± SACC parts from disk (integration for COSEBIs; reporting + integration for pure-E/B) and re-derive the E-modes through the values-only seams b_modes.cosebis_from_xi / pure_eb_from_xi. On a data run those inputs bind to the *_blinded siblings, so the derived E-modes inherit the blind automatically. This holds for every catalogue version: xi_highres runs per version (see the #251 base). The raw estimator still runs once per version for the blind-invariant pieces — Bn and the covariance — and every serialized E-mode (SACC part and .npz sidecar) is part-derived; raw En never reaches disk on a data run. The derived parts are stamped concealed with the shared commitment (truthfully: their values derive from blinded inputs), so the terminal assembly's commitment assertion covers them.

Other flags

  • Residual unblinded ξ± .txt. The raw TreeCorr ξ± .txt byproduct stays unblinded because the CosmoCov covariance seam reads it — an acknowledged plaintext-ξ± tension until the covariance path is migrated. The fine / glass-mock pseudo-Cℓ variants (validation intermediates, not terminal-file parts) are left untouched.

Tests

test_blinding_wiring.py (new): the common.py path helpers mirror blinding.init_paths / part_paths (drift guard), version_of on all blindable stems, the run-type switch, and the data-run fail-closed assembly (refuses an unblinded part, passes when every part is concealed under one commitment, refuses a blinded/plaintext mix and divergent commitments). A candide-only test asserts the blinding subgraph resolves in the cosmo_val dry-run. test_blinding.py / test_camb_ccl_crosscheck.py / test_sacc_io.py stay green. The -m "not slow" suite passes (268 passed) bar the known environmental candide-path guard.

— Claude (opus) on behalf of Cail.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EaL7prmKUHwJQcDyW3LoxD

@cailmdaley cailmdaley changed the title Smokescreen blinding: seeded data-vector blind, hash-commitment custody, blinded-COSEBIs derivation (PR 6) Smokescreen-fork blinding: seeded master-SACC blind, hash-commitment custody, CAMB↔CCL cross-check (PR 6) Jul 10, 2026
@cailmdaley
cailmdaley force-pushed the feat/sacc-6-blinding branch from 971767b to 11b8895 Compare July 10, 2026 22:52
@cailmdaley
cailmdaley changed the base branch from feat/sacc-5-firecrown-likelihood to feat/sacc-2-sacc-io July 10, 2026 22:52
@cailmdaley cailmdaley changed the title Smokescreen-fork blinding: seeded master-SACC blind, hash-commitment custody, CAMB↔CCL cross-check (PR 6) Smokescreen-fork blinding: per-part-at-birth blind, hash-commitment custody, CAMB↔CCL cross-check (PR 6) Jul 11, 2026
cailmdaley added a commit that referenced this pull request Jul 11, 2026
…ll image from lock (#266)

* felt: fork-implementation — #243 green, #253 rework under fix; fork PRs reviewed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf

* felt: fork-implementation — constitution absorbs #241 re-rulings (one-file layout, per-part blinding at birth)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KpaRHk3QwN13myduQ3hJyf

* deps: declare cosmo_numba + numba, adopt committed uv.lock, install image from lock

Make sp_validation's environment reproducible so an image build can never
re-resolve numpy past numba's ceiling — the drift that silently upgraded numpy
to 2.5 and broke numba/ngmix.

- Declare cosmo-numba (aguinot/cosmo-numba@main; not on PyPI) — the numba
  B-mode kernels b_modes.py imports. main carries numpy-2 FFT support via its
  rocket-fft dep and declares its own deps, so numba's numpy window reaches the
  resolver. Also pin numba directly: the one load-bearing constraint, made
  visible and resilient to cosmo-numba's dep metadata (which has emptied out
  between refs).
- Declare the other imported-but-undeclared deps the audit found: matplotlib,
  pandas, pyyaml (core, src/); fitsio (glass extra); a new `workflow` extra for
  snakemake + mpi4py. cv_runner and unions_wl left undeclared (no resolvable
  source) with a NOTE.
- requires-python and ruff target -> 3.12 (the container's Python; cosmo-numba's
  floor).
- Commit uv.lock (un-ignored) as the SSOT; scope it to Linux via [tool.uv].
  numpy resolves to 2.4.6, inside numba 0.66's <2.5 window.
- Dockerfile installs from the lock: `uv sync --frozen --inexact` into the base
  image's /app/.venv (--inexact keeps the ShapePipe stack). Drops the ad-hoc
  snakemake layer and the cs_util `--upgrade` workaround — the lock pins
  cs_util 0.2.2 (with cs_util.size), decoupling us from the base image's cs_util.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vtw1qcgTrQ6YuMvxwYzNup

* felt: uv-lock-cosmo-numba — @main resolution, install model, cosmo-numba gotcha finding

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vtw1qcgTrQ6YuMvxwYzNup

* test: don't load base image's stale pytest-pydocstyle/pycodestyle plugins

uv sync installs a newer pytest than the base ShapePipe image's
pytest-pydocstyle/pytest-pycodestyle (>=2.4) expect; their pytest_collect_file
hooks use the removed `path` arg and crash collection. sp_validation lints with
ruff, so disable both via addopts (`-p no:`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vtw1qcgTrQ6YuMvxwYzNup

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@cailmdaley
cailmdaley force-pushed the feat/sacc-2-sacc-io branch from 55cb97d to 4489dd4 Compare July 16, 2026 00:39
cailmdaley and others added 2 commits July 16, 2026 11:40
…abulary

Rebuild the per-part-at-birth blinding stack on top of feat/sacc-2-sacc-io.
sacc_io.py is now PR-2's canonical module verbatim + a single appended
gather(): the terminal assembly delegates its tracer/point/covariance
assembly to PR-2's merge() and adds only the blind-custody call
(assert_consistent_blind) and shared-stamp write.

The fail-closed load gate lives in sacc_io.load() per the PRD ("sacc_io fails
closed on load"); the blinding tooling uses allow_unblinded=True explicitly
where it must read a not-yet-blinded real vector (blind_part). save() now
carries the required type= (inherited from each part's provenance). Grid
vocabulary swept coarse->reporting, fine->integration across blinding.py,
blinding_theory.py, b_modes.py, blind_data_vector.py, and the blinding tests.

_extract_block/_set_values stay index-based (a code comment says why):
Smokescreen's ConcealDataVector aligns theory_fn output to the sub-SACC mean
by row position, so the block must be carved/written by contiguous integer
index, not by PR-2's tag-matching extract()/update_statistic().

Escrow/custody/CAMB<->CCL machinery is preserved exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzUt7VbtXwr2SCHUdiQTyt
…nfig

Three integration-drift fixes surfaced by the reconciled base:

- test_ac6_ac8 / test_ac8_dotted: the end-to-end fixtures now pass
  type="mock" to sio.save (PR-2 requires the provenance stamp); the parts
  are mocks, blind_part re-saves inheriting that type.
- test_ac13: the CosmoSIS halofit config moved to
  cosmo_inference/cosmosis_config/templates/ on develop; point the AC13
  assertion at the new path (token unchanged: mead2020_feedback).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzUt7VbtXwr2SCHUdiQTyt
@cailmdaley
cailmdaley force-pushed the feat/sacc-6-blinding branch from 4b058f7 to c09b063 Compare July 16, 2026 09:55
Base automatically changed from feat/sacc-2-sacc-io to develop July 21, 2026 12:24
cailmdaley added a commit that referenced this pull request Jul 21, 2026
Fold the integration-grid ξ± into the single terminal {version}.sacc, and
make part loading fail closed on unblinded real data.

Integration ξ± (grid='integration') is no longer its own terminal product.
The xi_highres part is now gathered by rule assemble_sacc into {version}.sacc
as tagged points, next to the reporting ξ± block. It is fiducial-only (the
10k-bin MPI run emits only the fiducial part), so it joins the fiducial
version's terminal file alone. assemble_sacc.py adds xi_integration to
CANONICAL; its own DiagonalCovariance passes straight through.

assemble_sacc.py no longer loads every part with allow_unblinded=True. The
run type (data|mock, from config, default data) gates it: mock runs load
freely, data runs fail closed unless a part carries the concealed=True stamp.
This is the seam for PR #253's blind-at-birth — a concealed data part then
assembles with allow_unblinded=False untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaL7prmKUHwJQcDyW3LoxD
cailmdaley and others added 2 commits July 21, 2026 15:14
Author the Snakemake rules that realise sp_validation.blinding's three-verb
custody surface on the migrated cosmo_val workflow (issues #247/#252).

- rule blind_init (once per catalogue version): draws the seed, publishes
  commitment.json + the encrypted seed bundle. Refuses to overwrite state.
- rule blind_part (generic over the three blindable stems — reporting ξ±,
  integration ξ±, analysis pseudo-Cℓ): conceals the part at birth, escrows the
  true vector beside the blinded output. The plaintext part is a temp() output
  of its producer on a data run, so only its blinded sibling persists.
- common.py: run-type-aware path helpers (blindable_part / maybe_temp /
  blind_state_paths / version_of) so a data run binds ξ-derived consumers to the
  blinded siblings and a mock run bypasses blinding entirely — the whole
  blinding subgraph appears or vanishes with RUN_TYPE.
- assemble_sacc: assert_consistent_blind across parts (assembly-time commitment
  check of #252), stamping the shared blind on the terminal file.
- Constrain the npatch wildcard to \d+ so a producer's ξ± output cannot absorb
  the _blinded suffix (which made rule xi ambiguous with blind_part).

The cosmo_val assemble DAG dry-runs: blind_init x2, blind_part x5, assemble
consuming the blinded ξ± + pseudo-Cℓ parts. Born-blinded COSEBIs/pure-E/B and
the ρ/τ concealed pass-through follow in the next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaL7prmKUHwJQcDyW3LoxD
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@cailmdaley
cailmdaley changed the base branch from develop to feat/sacc-4-cosmo-val-sacc July 21, 2026 13:47
- common.py path helpers mirror sp_validation.blinding init_paths/part_paths
  (drift guard), version_of on all three blindable stems, run-type switch.
- Data-run fail-closed assembly: assemble_sacc refuses an unblinded type=data
  part, passes when every part is concealed under one commitment, and refuses a
  blinded/plaintext mix and divergent commitments.
- Candide-only: the blinding subgraph (blind_init/blind_part) resolves in the
  cosmo_val assemble dry-run and binds the blinded ξ± + pseudo-Cℓ siblings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaL7prmKUHwJQcDyW3LoxD
cailmdaley added a commit that referenced this pull request Jul 21, 2026
Keep the integration-grid ξ± as its own per-part intermediate
({version}_xi_integration.sacc) rather than folding it into the terminal
{version}.sacc. Per the #247 ruling (comment 5033716753), the terminal file
carries the analysis vector only; COSEBIs/pure-E/B consume the integration
part directly, and Snakemake provenance covers its traceability. This keeps
the terminal file at tens-of-MB scale.

Removes xi_integration from CANONICAL, the cv_xi_integration_sacc helper, and
the fiducial-gated assemble input. Keeps the fail-closed allow_unblinded gate,
glass A/B/C comments, and dependency restorations from the prior rework. The
integration part stays blinded at birth on data runs (per #253).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaL7prmKUHwJQcDyW3LoxD
Merge the updated feat/sacc-4-cosmo-val-sacc, which removes the integration-grid
ξ± from the terminal {version}.sacc gather (per the #247 ruling: it persists as
its own per-part intermediate, consumed by COSEBIs/pure-E/B, with Snakemake
provenance covering traceability).

Blinding wiring: the blind_part rule and its BLINDABLE_STEM regex still cover the
integration ξ± (it remains blindable at birth as a per-part file). Only the
assemble subgraph changes — cv_assemble_inputs no longer requests the blinded
integration part, so the dry-run wiring test asserts _xi_integration_blinded.sacc
is NOT pulled into the assemble subgraph.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaL7prmKUHwJQcDyW3LoxD
cailmdaley added a commit that referenced this pull request Jul 21, 2026
…-version xi_highres

COSEBIs and pure-E/B now derive their E-mode outputs from the born-as-SACC ξ±
parts on disk instead of only the raw catalogue recompute. This makes born
blinding automatic downstream: whatever the part carries (blinded on data runs
in PR #253) flows into the E-modes with no blind-aware consumer code.

b_modes: add the values-only seams cosebis_from_xi and pure_eb_from_xi (copied
from the sacc-6b spur, de-blinded) — same cosmo_numba kernels as the raw path.

cosebis_to_sacc_part / pure_eb_to_sacc_part: add en_override / eb_override so the
E-mode En (COSEBIs) and the six pure-mode arrays (pure-E/B) written to the SACC
part come from the consumed part; Bn and the covariance stay blind-invariant from
the raw estimator run. No concealment machinery (that is PR #253's).

cv_cosebis.py / cv_pure_eb.py: keep the raw plot_* run (Bn + jackknife covariance
need the catalogue and are blind-invariant), then load the integration part
(COSEBIs) / reporting + integration parts (pure-E/B), re-derive the E-modes
through the seams, and override both the SACC part and the diagnostic npz. pure-E/B
reads the reporting-grid bin edges from the raw reporting gg (SACC stores centers
only). Unconditional and version-agnostic.

xi_highres: per version (was fiducial-hardcoded); in-container single-process at
the config-driven 1000-bin grid (the 10k-bin bare-host MPI path is unnecessary).
Grid comes from a dedicated cosmo_val.integration block ([0.08, 300] @ 1000) so
the one part serves both consumers (pure-E/B full range, COSEBIs scale-cuts to
0.9); decoupled from covariance.smk's FIDUCIAL grid. Shared twopoint.smk falls
back to the fiducial integration grid for configs without a cosmo_val section
(e.g. papers/bmodes). The raw .txt byproduct is left undeclared to avoid an
AmbiguousRuleException with rule xi; nothing in the DAG consumes it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaL7prmKUHwJQcDyW3LoxD
…g-aware

Merge the updated feat/sacc-4-cosmo-val-sacc (cv_cosebis/cv_pure_eb consume the
ξ± SACC parts via the b_modes seams; per-version in-container xi_highres on the
dedicated cosmo_val.integration grid; the cosmo_inference zombie purge; the
1000-bin default).

On this blinding branch the consumers bind the *_blinded* parts on data runs and
stamp their derived output concealed, so born blinding flows end to end with no
blind-aware consumer code:
- cv_cosebis/cv_pure_eb inputs wrap the ξ± parts in blindable_part (→ _blinded
  sibling on a data run, plaintext on a mock run) and bind commitment.json on a
  data run — per version, not fiducial-gated.
- cv_cosebis.py/cv_pure_eb.py thread commitment_path to the *_to_sacc_part
  writers; cosebis_to_sacc_part/pure_eb_to_sacc_part re-derive En / the six
  pure-mode arrays from the (blinded) part and stamp the emitted part concealed.
- xi_highres keeps its maybe_temp blindable wrapper on {version}_xi_integration.sacc,
  so blind_part produces the _blinded sibling the consumers now request.

The dry-run wiring test asserts _xi_integration_blinded.sacc IS in the cosmo_val
subgraph again (the consumers are its explicit consumers on a data run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaL7prmKUHwJQcDyW3LoxD
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.

Use CCL's CAMB backend so blinding and inference share one Boltzmann path Smokescreen blinding wiring (fork protocol, three theory backends, custody)

1 participant