Skip to content

Record the pipeline's scientific decisions in astra.yaml, linked to the code by @sc tags - #875

Merged
cailmdaley merged 43 commits into
developfrom
docs/astra-decision-record
Sep 28, 2026
Merged

cailmdaley merged 43 commits into
developfrom
docs/astra-decision-record

Conversation

@cailmdaley

@cailmdaley cailmdaley commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

astra.yaml at the repo root records ShapePipe's scientific decisions: the choices in the code and in the committed workflow/config/cfis/ configs that change which objects enter the shear catalogue, or the numbers attached to them. Each decision carries its rationale, the alternatives, and the option develop selects (pinned in universes/committed.yaml). CI checks that the record and the code agree, so a scientific change cannot land without its record change. Format: ASTRA (uvx astra-tools@0.2.17 guide).

How the record connects to the code

A decision is linked to the code and config that implement it by an @sc tag at the site, where an editor sees it:

# @sc [decision:detection.detection_threshold_policy]
DETECT_THRESH    1.0
ANALYSIS_THRESH  1.0
def position_seed(ra, dec, ccd):
    """...
    @sc [decision:shape_measurement.ngmix_seed_mode]
    """

A # tag governs the settings below it up to the next blank line, or the whole section when it sits directly above a [SECTION] header; a docstring tag governs its function or class. The record keeps the why, and pins committed values by key:

rationale: >-
  Tiles use the MegaPipe tile-catalogue threshold, minimum area and filter, ...
  Values:
  default_tile.sex#DETECT_THRESH = 1.0;
  default_exp.sex#DETECT_THRESH = 1.5; ...

tests/unit/test_decisions.py runs in CI inside the image and fails when:

  • a tag cites a decision the record lacks, or a decision has no tagged site;
  • a Values: entry does not resolve to exactly one of its decision's tagged sites, or the value there differs. A governed key set twice in its file, a tagged site deleted, or a gate key turned off (WEIGHT_IMAGE = False) all fail;
  • a key the record asserts = absent (e.g. SATUR_LEVEL, MASK_EXT == 0 in star selection) appears.

tests/unit/test_committed_configs_run.py runs SExtractor and PSFEx with the committed configs on a small synthetic image, so a tag that breaks a reader (SExtractor wants CONV on the first line of a .conv file) fails CI.

python -m tests.helpers.decisions <path>[:<line>] lists the decisions and local contracts governing a line; --decision <id> lists a decision's sites.

The configs gain only comment and blank lines, and the code only docstring lines; no active key, value or statement changes.

What is in the record

  • 46 decisions in six sub-analyses (masking, detection, preparation, star selection + PSF, shape measurement, catalogue assembly) plus three cross-cutting (stamp size, zero-point, per-unit completeness), with 163 tags at the sites and 151 pinned values. Workflow policy (manifests, chunking, allocation) is out; it lives in Snakemake orchestration for shapepipe #848.
  • 19 local contracts (@sc with an id and prose): constraints at one site that no decision states, such as "the CCD candidate prefilter must be a superset of the strict bounds test". src/shapepipe/utilities/CONTRACTS holds the one import boundary (utilities never import modules).
  • Markers: [HARDCODED] (15), a scientific value fixed in code with no config key; [LINT] (3), the code disagreeing with itself or its docs.
  • The masked-pixel decisions (defect_fill, blend_handling, epoch_masked_fraction_cut, central_defect_veto) carry the DES/Rubin literature with verified quotes and the bias measurements from ngmix: noise-fill defects under both BLEND_HANDLING options, drop epochs with a defect near the object #915/ngmix: opt-in DEFECT_FILL = interpolate for narrow defects, with a 7 px veto #916; defaults are what develop does today.
  • Science tests in tests/science/ carry @pytest.mark.decision(...) naming the decision they protect (all currently shape_measurement.metacal_scheme).

Writing the record surfaced defects that are now fixed (#907, #909, #918 merged; #911 open) or filed (#912, #913, #914, #919).

Changing a scientific choice

Change the code or config, then amend its decision in the same PR (rationale, Values:, and universes/committed.yaml if the option changes). CI names the decision, the key, and the expected and actual values. The rule is in CLAUDE.md.

Reviewing

Every default was checked against the code, the rationales had independent line-by-line reads, and the checker was mutation-tested on the real configs (each mutation above fails for the stated reason; reordering keys inside a paragraph passes). A downloaded tile and exposure settled the one question the repo could not: both carry a SATURATE card (tile 9558.7, exposure CCDs 65535).

Still open: fit_priors's [LINT]. The ngmix template pins PIXEL_SCALE = 0.186 while star selection uses 0.187, and the WCS fallback that would replace the key is broken: pixel_scale_from_wcs meets the merged-headers file's leading TILE_ID string and raises AttributeError. The override hides the crash; the value only sets the centroid-prior width.

🤖 Generated with Claude Code

@cailmdaley
cailmdaley marked this pull request as draft August 31, 2026 01:49
Base automatically changed from feat/snakemake-orchestration to develop September 9, 2026 12:53
@cailmdaley
cailmdaley force-pushed the docs/astra-decision-record branch from 9aa0629 to e901cc9 Compare September 26, 2026 00:50
cailmdaley and others added 5 commits September 26, 2026 03:05
ShapePipe's scientific choices — detection thresholds, masking geometry,
star selection, PSF model, ngmix priors and seeding, flag semantics,
completeness floors — live in code and committed configs with their
reasoning nowhere, or spread across PRs, papers and comments. astra.yaml
gathers them: 50 decisions across eight sub-analyses, each with its
rationale, the alternatives that were rejected and why, and a greppable
anchor back to the code or config that implements it.
universes/committed.yaml pins the option this branch selects for every one.

The record is ASTRA (astra-tools; `uvx astra-tools@0.2.17 guide`), applied
here at codebase level rather than to a single analysis. Conventions are
stated in the file's header: anchors as `path::symbol` / `path#SECTION.KEY`
and never line numbers, [HARDCODED] for a scientific value with no config
exposure, [LINT] for a place where the record and the code — or the code and
itself — disagree, [PENDING #NNN] for state not yet on develop.

Authoring it surfaced nine such lints, two of which #873 fixes, and mapped
ten places where the published Guinot+22 / Farrens+22 descriptions have
drifted from the code since publication; 16 decisions carry verbatim
paper quotes as prior insights.

CLAUDE.md gains the standing instruction: a scientific change is not
finished until the record is, amended in the same PR. The membership test
is whether a different defensible choice would change which objects enter
the shear catalogue, or the numbers attached to them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2muA2sRojbxRNxU2SKQeP
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- masking: describe healsparse queries (mask_query MASK_EXT on exposures,
  make_cat MASK_<band> on tiles) and the instrument flag image as the only
  pixel mask, replacing the deleted in-house mask generation
- detection: tiles follow the MegaPipe (Gwyn) SExtractor parameters (#896);
  option ids no longer encode the retired values
- shape_measurement: import defect_fill, blend_handling and
  epoch_masked_fraction_cut from the digital twin with their literature
  insights; defaults are what the committed code selects
- prune to the membership test: drop psf_diagnostics, survey_geometry, the
  workflow-policy decisions and the root findings; split compound decisions;
  reserve excluded for considered-and-rejected; strip chronology
- re-point anchors to the current configs; the anchor test passes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bvNTrAmZxcfb1ee83ApPK
@cailmdaley
cailmdaley force-pushed the docs/astra-decision-record branch from e901cc9 to a6eff5e Compare September 26, 2026 01:06
cailmdaley and others added 6 commits September 26, 2026 03:22
- epoch_provenance: names keep their trailing p; EXP_PREFIX is a no-op [LINT]
- fit_initialisation: only the PSF guesser takes the catalogue flux; an
  exception in Ngmix.process drops the object with no row
- star_galaxy_classification: thresholds come from SM_STAR_THRESH /
  SM_GAL_THRESH, which the committed config does not set
- psf_train_validation_split: seeded from the unit's file number
- stamp_positioning: an out-of-image stamp centre raises
- object_position_columns: tile stamps are cut at XWIN_IMAGE (COORD=PIX)
- mark the PSFEx built-in SAMPLE_* behaviour and the 33-px trim unverified
- record the galaxy prior reused for PSF fits and the silent epoch drops
  before the 1/3 cut; carry stale completeness, exposure.smk, _mode and
  pixel-scale comments as [LINT]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bvNTrAmZxcfb1ee83ApPK
…ntinels and completeness precision

Follow-up to the correction pass: claims the repo cannot check are stated as
what the config assumes; five Guinot+22 prior insights with page-verified
quotes replace bare paper citations; failure_sentinels says an
NGMIX_N_EPOCH > 0 cut removes failed objects; per_unit_completeness counts
only rules that run shapepipe_run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/helpers/contracts.py parses @sc/@cc contracts with sc-list's line
grammar from Python docstrings, Snakemake comment blocks and CONTRACTS
files under src/, workflow/ and scripts/. test_contracts.py fails on
malformed lines, missing or duplicate ids, tag lines hidden in .py
comments (invisible to sc-list), and decision: metas naming no decision
in astra.yaml. A report-only test prints decisions no contract cites and
contracts off the record's anchored symbols.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bvNTrAmZxcfb1ee83ApPK
Sixteen @sc contracts in the docstrings of declarations astra.yaml
anchors, each citing its decision: star-selection mode and split
seeding, SExtractor weight wiring and epoch membership bounds, CCD
splitting and WCS source, epoch provenance, stamp rounding, the PSF
acceptance gate, catalogue classification scope, never-fit sentinels,
mask-column and mask-flag semantics, and per-unit completeness. Where
the record carries a [LINT] at the declaration, the contract states the
intended behaviour and names the lint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bvNTrAmZxcfb1ee83ApPK
- detection.saturation_level: SATUR_KEY SATURATE with no SATUR_LEVEL sets
  the FLAGS saturation bit that star selection rejects on; header presence
  on exposures and tiles is unverified here
- psf_model_complexity: PSF_ACCURACY 0.01 with its anchor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bvNTrAmZxcfb1ee83ApPK
src/shapepipe/utilities/CONTRACTS declares utilities-do-not-import-modules
(forbid: shapepipe.utilities.* -> shapepipe.modules.*). test_contracts.py
reads the forbid rule from that file and resolves every import under
src/shapepipe/utilities with ast, relative imports included. It holds
today; loom's check-imports agrees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bvNTrAmZxcfb1ee83ApPK
- shape_measurement.central_defect_veto: default disabled (committed
  develop has no veto); radius_10px, implemented on
  feat/symmetrized-defect-fill, is the smallest radius with |m| < 1%
- defect_fill: the recommended option is the 4-fold OR
  (symmetrized_4fold_noise); a single rot90 leaves coherent c2 of
  -0.006 to -0.012 for off-centre columns, 4-fold gives |c| < 2e-4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bvNTrAmZxcfb1ee83ApPK
cailmdaley and others added 3 commits September 26, 2026 03:57
Resolve semicolon-separated, directory-relative governed refs with the shared ASTRA anchor resolver. Reject empty refs and repeated metadata keys, and include config contracts in the report-only coverage check.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Check active config values and static Python literals independently of anchor resolution. Normalize numeric and boolean spellings while retaining list shape and SETools comparison operators. Document the grammar and exercise it on PSF_NOISE.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Keep canonical contracts beside the CFIS configs, with an inherited workflow contract beside the PSF selector. Cover the 17 previously uncovered decisions and the exposure pixel-scale/diagnostic coupling without changing scientific settings; retain the known config inconsistencies explicitly.

Match contract coverage against anchor locators through the shared parser, including the value-assertion grammar added concurrently. Keep the tile-overlap config projection visible as a report-only anchor gap.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Assert 130 values across 29 decisions without changing defaults or option ids. Cover coupled stamp sizes, detection, star cuts, PSF settings, and literal ngmix priors/metacal settings. Clarify that the CCD's 2048-index span is inclusive, whereas the committed cut excludes both endpoints.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
… the measured design

- defect_fill: noise on the unsymmetrized defect set stays default;
  interpolate (feat/defect-interpolation) describes the bounded-run fill
  with quarter-turn weight orbit; four-fold symmetrization is excluded on
  its measured m and c1; the model option is dropped
- central_defect_veto: fixed radii (10 px noise, 7 px interpolated) on
  the defect mask only; size-scaled radius excluded; calibration and known
  limits stated; default stays disabled
- epoch_masked_fraction_cut: the branch counts the raw defect set, with
  EPOCH_MASKED_FRACTION_CUT configurable; default stays 1/3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bvNTrAmZxcfb1ee83ApPK
cailmdaley and others added 21 commits September 28, 2026 02:56
The merged fixes made six record lints false and broke four value
assertions and one contract ref. Re-anchor the MCCD exposure chain to
what it now reads (split image/weight/flag, mask_query before setools),
the setools FWHM plot to 0.187, and CFIS EXP_PREFIX to a location-only
ref (blank). Pin the MCCD completeness counts the rationale now names.
Drop the resolved lints from the record, the @sc blocks and CONTRACTS;
the IMAFLAGS_ISO export (#912), ngmix's 0.186 pixel scale against star
selection's 0.187, and the ngmix noisefill/noise-window doc lints remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rationales no longer restate values their Anchor sentence asserts, and
literature comparisons that a cited insight already carries become
pointers. The header keeps the anchor grammar and markers; the value
grammar's fine print moves to tests/helpers/astra_record.py, beside the
parser that enforces it. Anchors, ids, options and evidence are unchanged
(206 tests, astra validate).

Corrected while tightening: the fit_initialisation default label (the
galaxy guess takes its flux from a PSF-flux fit), and the blend_handling
`none` description, which now claims only what Jarvis et al. 2016 support.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkrwnKzU1wfUaHUPt3yYk3
… history

A sampled tile and exposure carry SATURATE, so the SExtractor fallback
level never applies; the exposure's FSCALE matches its PHOTZP against
the tiles' zero-point 30. The fit_priors lint now names what #858
settled (the WCS is the source of truth) and what still overrides it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cailmdaley cailmdaley changed the title Record the pipeline's scientific decisions in astra.yaml Record the pipeline's scientific decisions in astra.yaml, linked to the code by @sc tags Sep 28, 2026
Carry the decision record onto develop: psf_model tags move to the
per-machine machines: table, make_cat keeps develop's N_EPOCH_SLOTS, and
the tag scanner skips symlinks so cfis_image_sims/ (symlinks into cfis/
plus untagged overlays, not yet in scope) adds no duplicate sites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uemfjv9ybCwtKtprksZbVY
@cailmdaley
cailmdaley marked this pull request as ready for review September 28, 2026 14:36
@cailmdaley
cailmdaley merged commit ac2f53f into develop Sep 28, 2026
3 checks passed
cailmdaley added a commit that referenced this pull request Sep 28, 2026
Takes develop's decision record (#875 as merged) over the pre-squash copy this
branch carried, and drops that copy's anchor test (tests/unit/test_astra_anchors.py,
tests/helpers/astra_record.py), superseded by tests/unit/test_decisions.py.
detection.detection_source_mode: final_cat.param no longer requests
IMAFLAGS_ISO, so the [LINT] (issue #912) is resolved in the rationale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cailmdaley added a commit that referenced this pull request Sep 28, 2026
Brings develop (#875's decision record) in through the updated base, and
amends the record for this branch:
- defect_fill: the interpolate option is implemented (DEFECT_FILL =
  interpolate); noise stays the committed fill.
- central_defect_veto: EPOCH_INTERPOLATED_DEFECT_RADIUS = 7 joins the
  Values, on its now-tagged module constant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cailmdaley added a commit that referenced this pull request Sep 28, 2026
… feat/wire-external-masks

Resolved by keeping develop's side everywhere #879's pre-squash content
conflicted, then re-applying this branch's own changes (a846e08..dc11dab):

- astra.yaml: the mask-cut decision moves into the masking sub-analysis as
  masking.mask_default_cut, in the #875 format (no Anchor sentence; the
  make_cat contract tag cites it). masking.sky_mask_application now selects
  catalogue_columns, since the committed config sets MASK_EXT_PATHS;
  config_tile_Mc.ini's MASK_EXT_PATHS carries its tag. universes pinned.
- cfis_image_sims/config_tile_Mc.ini: rebuilt from develop's cfis copy
  (spread model gone), still without MASK_EXT_PATHS or per-epoch slots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cailmdaley added a commit that referenced this pull request Sep 28, 2026
…nt-defect-map

Resolved by taking the updated #886 tree and re-applying this branch's own
changes (dc11dab..5f7332d):

- astra.yaml: defect_map_from_flags drops its Anchor sentence for the #875
  format: @sc tags on exp_defect_map, defect_map_merge and config.yaml's
  defect_map block (Values pin nside, nside_coverage, oversample), the two
  script contracts re-cite masking.defect_map_from_flags, and the
  fragment-contains-flags test carries its decision marker. The
  masked_measurement_inputs output keeps #886's mask_default_cut.
- exposure.smk docstring: the defect-map export paragraph, with develop's
  MASK_EXT description of mask_query (FLAG_EXT is gone).
- config_tile_Mc.ini: the defect-map note sits above the MASK_EXT_PATHS tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cailmdaley added a commit that referenced this pull request Sep 28, 2026
… feat/retire-versioning

Resolved by keeping develop's side where #879's pre-squash content
conflicted, then re-applying this branch's own changes (a846e08..bfb7efc).
Two by hand: docs/source/pipeline_canfar.md stays retired (develop's one-line
path fix goes with it), and the rebuilt tutorial drops the spread-model step
that #911 removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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