Stage 3: foundation completion + per-instrument config wiring#114
Conversation
Closes the foundational gaps that should have shipped in Stages 0-2 and
wires enough per-instrument config + static data so a real image runs
through the pipeline without hard-coded defaults misclassifying it.
Adds INFO+DEBUG logging across every NavModel, NavTechnique, and the
orchestrator.
Highlights:
- Renumber config_NN_*.yaml -> config_NNN_*.yaml (3-digit prefix bands:
0NN global, 1NN catalogues, 3N0 ring catalogues, 4N0 per-instrument,
9NN downstream products).
- Convert bootstrap angle fields to degrees (max_phase_angle_deg, etc).
- Add per-camera data_units / noise / image_quality_thresholds /
mag_offset / source_image_filter / fit_camera_rotation /
max_rotation_deg blocks to every config_4N0_inst_*.yaml; add a
parallel cassini_iss_calib block for I/F products. Cassini loader
picks the calibrated-IF block when filename contains _CALIB
(regression: CALIB images were misclassified as 'blank' against
raw-DN thresholds).
- Add nav.nav_orchestrator.instrument_config.instrument_settings_from_obs
translating per-camera YAML into a frozen InstrumentSettings.
- Replace hard-coded _instrument_full_well_dn with a config consumer;
calibrated-IF instruments emit a one-line WARNING + empty saturation
mask when no preserved raw-saturation flags exist.
- Populate Provenance.{rms_nav_git_sha, spice_kernels,
static_data_hashes} via the new collect_provenance_metadata helper.
- Wire NavModelStars.to_features through obs.inst_config.mag_offset;
fix psf_sigma_px to read psfmodel.GaussianPSF per-axis sigma_x/sigma_y
(regression: every Cassini star extraction raised AttributeError).
- Per-NavTechnique confidence_spec / confidence_attributes declared;
validate_registered_confidence_specs runs at config-load time.
evaluate_sigmoid_combination(..., return_breakdown=True) returns a
per-term breakdown; log_confidence_breakdown logs at DEBUG always
and at INFO when confidence <= 0.1 (so calibration bugs surface in
the operator log).
- Wire STATUS_REASON_INFO_TEMPLATE through every NavResult.failed via
the new _fail / _log_status_reason helpers. Ensemble failure paths
now log actual measured values (combined-confidence vs threshold,
best-vs-runner-up gap, sigma vs tier max, etc).
- INFO+DEBUG logging across every NavModel, NavTechnique, and the
orchestrator, with section headers via logger.open carrying the
per-instance context. NavModelStars emits the legacy-format per-star
INFO listing via _star_short_info.
- Initial config_220_body_shape.yaml with 10 bodies. Per Part 0 §74
anti-hallucination rule: every numeric field is null with a
PLACEHOLDER citation; runtime fallback handles null values.
Citations populated in Stage 10.
- Config._load_yaml strips _sources blocks at load time so citations
live alongside values in source for human review without bloating
runtime config.
Documentation:
- docs/developer_guide_static_data.rst (citation rules,
anti-hallucination procedure, validator tests).
- docs/developer_guide_logging.rst (pdslogger conventions, INFO/DEBUG
split, capsys vs caplog).
- docs/introduction_configuration.rst rewritten for the renumbered
config_NNN_*.yaml set.
- AUTONAV_PLAN.md updated: Stage 3 marked complete with a "What
shipped" subsection and a "Logging conventions established in Stage
3 (binding)" subsection capturing the per-section-header / INFO /
DEBUG / failure-narrative / confidence-breakdown rules for future
NavModel and NavTechnique additions.
- phase_03_review/ — seven CRITIQUE_*.md reports per the per-phase
definition of done.
Verification: ruff check + format clean; mypy --strict clean (256
files); pytest -n auto --dist=loadfile passes 1043 fast tests; sphinx
-W clean; pymarkdown clean; ./scripts/run-all-checks.sh green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalkthroughPhase 3 deliverables update autonomous-navigation with configuration file renumbering (2-digit to 3-digit prefixes), YAML loader key stripping, instrument-specific noise/magnitude/image-quality settings, provenance metadata collection (git SHA, SPICE kernels, static hashes), confidence-spec validation at config load, enhanced confidence breakdown computation/logging, and comprehensive developer documentation. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## rf_core_rewrite #114 +/- ##
==================================================
Coverage ? 49.33%
==================================================
Files ? 126
Lines ? 15129
Branches ? 2069
==================================================
Hits ? 7464
Misses ? 7135
Partials ? 530 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
CI doesn't install ``rms-cloud-tasks`` (it's not a declared dev dep), so mypy errors out on every ``from cloud_tasks.worker import Worker`` site. Add a ``cloud_tasks.*`` override matching the existing pattern for other optional/external deps. Also annotate the one site that already carried ``# type: ignore[attr- defined]`` with ``unused-ignore`` so the ignore is accepted in both environments — the attribute is real when ``cloud_tasks`` is installed locally (ignore is needed) and the line type-checks cleanly when the package is missing on CI (ignore would otherwise be flagged unused). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cloud-tasks CLI entry points (nav_offset_cloud_tasks, nav_backplanes_cloud_tasks, nav_create_bundle_cloud_tasks, nav_mosaic_cloud_tasks) all import ``cloud_tasks.worker`` at module top — that's a runtime requirement, not an optional extra. The package is typed (ships ``py.typed``), so mypy gets the real types once it's installed; no ``ignore_missing_imports`` override is appropriate. Reverts the previous commit's ``cloud_tasks.*`` mypy override and the ``unused-ignore`` annotation on ``nav_mosaic_cloud_tasks._data.nav_results_root_path``. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 13
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@AUTONAV_PLAN.md`:
- Around line 2253-2256: The document shows "Phase 3 — Foundation completion +
per-instrument config wiring (complete)" but later snapshot still lists Phase 3
items as pending; update the snapshot/pending bullets so they no longer conflict
by either marking those specific checklist entries as completed or adding a
clear "historical / superseded" annotation that references the shipped branch
`core_rewrite_catchup` and the "Phase 3 — Foundation completion + per-instrument
config wiring (complete)" heading; locate the pending bullets under the later
snapshot section and either change their status to complete, remove duplicate
pending entries, or prepend "Superseded by core_rewrite_catchup (shipped)" to
each affected bullet so the plan has a single, unambiguous source of truth.
In `@docs/introduction_configuration.rst`:
- Around line 42-45: The phrasing uses ambiguous numeric range notation "3N0"
and "4N0" which can be misread as numbers ending in 0; update the sentence that
currently mentions "3N0" and "4N0" to use explicit range notation such as "3xx
(300–399)" and "4xx (400–499)" or "300–399" and "400–499" so readers clearly
understand these are full 3xx/4xx ranges rather than values ending in 0; keep
the surrounding examples for "0NN", "1NN", and "9NN" consistent with this
clearer format.
In `@src/nav/config_files/config_420_inst_nhlorri.yaml`:
- Around line 15-35: Add a top-level "_sources" metadata block documenting
provenance for the new static calibration keys introduced here (noise and
mag_offset) so reviewers can trace the Phase 10 placeholder values;
specifically, add entries under "_sources" that reference the "noise" and
"mag_offset" keys (and any subkeys like "read_noise_dn" / "saturation_dn" /
"mag_offset_table") with brief provenance notes, since Config._load_yaml()
strips underscore-prefixed keys and this will not affect runtime behavior.
In `@src/nav/config/config.py`:
- Around line 126-129: Replace non-ASCII punctuation in the docstrings/comments
that mention documentation-only ``_sources`` blocks: change the section symbol
and em-dash to ASCII equivalents (e.g., "Part 0 §74" -> "Part 0, Section 74" or
"Part 0 Section 74"; and replace the em-dash "—" with a hyphen "-" or the word
"dash") so all comments/docstrings in src/nav/config/config.py (including the
lines referencing _sources and the nearby comment group) use only ASCII
punctuation per the repository rule; update any similar occurrences around the
same block (the other comment group that spans the next paragraph) to match.
In `@src/nav/nav_model/stars/detection.py`:
- Line 401: The detect_sources docstring is out of date: the code now derives
PSF sigma via psf_sigma_px(psf) instead of only using a numeric sigma or fwhm(),
so update the detect_sources docstring to reflect the shared helper contract;
state that the PSF may be provided as a PSF object and that sigma is obtained by
calling psf_sigma_px(psf) (or accept a numeric sigma), describe expected
types/units returned by psf_sigma_px, and adjust parameter/returns/examples to
reference psf_sigma_px and the PSF input (mention detect_sources as the function
to edit and psf_sigma_px(psf) as the helper).
In `@src/nav/nav_orchestrator/instrument_config.py`:
- Around line 77-88: _required_float currently allows non-finite numeric values
(NaN/Inf) which can corrupt downstream logic; update _required_float to reject
non-finite values by checking math.isfinite(value) (import math) after the
isinstance numeric checks and raise a ValueError with a clear path-aware message
(e.g. "{location}.{key} must be a finite numeric value; got ...") when the value
is not finite so only finite floats are returned.
- Around line 114-139: instrument_settings_from_obs currently dereferences
inst_config, noise and image_quality_thresholds without type checks; update the
function to validate that inst_config is a mapping/dict before using
getattr/get, ensure data_units is a str (and one of 'raw_dn'|'calibrated_if'),
ensure iqt_block (image_quality_thresholds) is a mapping before constructing
ImageQualityThresholds, and when data_units == 'raw_dn' ensure noise is a
mapping/dict (and its expected numeric fields are numbers or raise
TypeError/ValueError). Reference symbols: inst_config,
data_units_raw/data_units, iqt_block, noise, InstrumentSettings and
ImageQualityThresholds; raise clear ValueError/TypeError with descriptive
messages when types or required blocks are invalid.
In `@src/nav/nav_orchestrator/provenance.py`:
- Around line 184-190: The loop in _resolve_static_data_hashes() currently calls
path.read_bytes() and lets any I/O exceptions propagate, which can abort the
whole run; change it to catch exceptions around reading/hashing each file
(inside the for path in sorted(config_dir.glob('*.yaml')) loop), log a warning
including the filename and exception, and skip that file so hashes (the dict
returned as MappingProxyType) only include successfully-read files; keep the
existing prefix filter using _STATIC_DATA_PREFIXES and preserve the return of
MappingProxyType(hashes).
In `@src/nav/nav_technique/nav_technique_body_terminator.py`:
- Around line 330-333: In the navigate method replace the hard-coded use of
_BODY_TERMINATOR_CONFIDENCE_SPEC when calling evaluate_sigmoid_combination with
the instance/class-level confidence_spec so the class-declared configuration is
honored; specifically update the evaluate_sigmoid_combination call (currently
passing _BODY_TERMINATOR_CONFIDENCE_SPEC) to pass self.confidence_spec (and keep
the other args like confidence_context and technique_name=self.name unchanged)
to prevent confidence-spec drift.
In `@src/nav/nav_technique/nav_technique.py`:
- Around line 52-73: The current logic uses a single "log = logger.info if
promote_to_info else logger.debug" which causes DEBUG output to be skipped for
hard_zero and low-confidence cases; change it so the detailed breakdown is
always emitted with logger.debug and, when promote_to_info is true (or hard_zero
path requires an INFO summary), also emit an INFO-level message. Concretely,
replace the use of the single "log" variable: always call logger.debug for the
summary line and for each term in breakdown.terms, and if promote_to_info is
true call logger.info with the same summary (and optionally the same per-term
lines) so INFO is an additional promotion; also in the breakdown.hard_zero
branch, emit the DEBUG summary before returning and then emit the INFO line
stating confidence forced to 0. Use the existing symbols breakdown,
low_threshold, promote_to_info, breakdown.hard_zero, breakdown.hard_cap_applied,
and breakdown.terms to find where to change the logging.
In `@src/nav/obs/obs_inst_cassini_iss.py`:
- Line 71: The current lookup inst_config =
config.category(inst_section)[detector] can raise a bare KeyError; update the
code around that lookup (the inst_config assignment in obs_inst_cassini_iss.py)
to explicitly validate that inst_section exists in config.category(...) and that
detector is a key in that returned mapping, and if either is missing raise a
ValueError containing both inst_section and detector values; e.g., fetch the
category dict into a local (e.g., category_dict =
config.category(inst_section)), check for detector in category_dict (and
wrap/validate config.category call so a missing section produces a clear
ValueError), and then assign inst_config from category_dict[detector] only after
validation so callers get a descriptive ValueError instead of KeyError.
In `@tests/nav/config_files/test_config_load.py`:
- Around line 34-50: The test_per_instrument_required_fields_present smoke test
omits the newhorizons_lorri instrument and uses a lax truthiness check for
mag_offset.fallback_combo; update the test to include
config.category('newhorizons_lorri') and assert exact expected values for all
required keys (data_units, noise.saturation_dn, fit_camera_rotation,
max_rotation_deg, and mag_offset.fallback_combo) rather than truthiness. Locate
the test_per_instrument_required_fields_present function and the
Config().category(...) usages, add a check block for newhorizons_lorri mirroring
the other instruments, and replace the assert
camera['mag_offset']['fallback_combo'] with an equality assertion to the
concrete expected fallback_combo value.
In `@tests/nav/nav_orchestrator/test_provenance.py`:
- Line 63: The assertion using dict | type(meta.static_data_hashes) is
tautological and should be replaced with a concrete check of the expected type
and basic contents; change the assertion to assert
isinstance(meta.static_data_hashes, dict) (or the precise expected type) and
optionally add a sanity check like len(meta.static_data_hashes) >= 0 or that
keys/values match the expected types (e.g., all keys are str and all values are
hashes) to ensure the test actually verifies behavior of
meta.static_data_hashes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 183e5085-7ba5-4e0d-a9a1-a405b18082c2
📒 Files selected for processing (64)
AUTONAV_PLAN.mddocs/developer_guide.rstdocs/developer_guide_logging.rstdocs/developer_guide_static_data.rstdocs/introduction_configuration.rstphase_03_review/CRITIQUE_CODEBASE.mdphase_03_review/CRITIQUE_DOCS.mdphase_03_review/CRITIQUE_FILECACHE.mdphase_03_review/CRITIQUE_LOGGING.mdphase_03_review/CRITIQUE_PYTHON.mdphase_03_review/CRITIQUE_SUMMARY.mdphase_03_review/CRITIQUE_TESTS.mdsrc/nav/config/config.pysrc/nav/config_files/config_010_general.yamlsrc/nav/config_files/config_020_offset.yamlsrc/nav/config_files/config_030_stars.yamlsrc/nav/config_files/config_040_bodies.yamlsrc/nav/config_files/config_050_rings.yamlsrc/nav/config_files/config_060_titan.yamlsrc/nav/config_files/config_070_bootstrap.yamlsrc/nav/config_files/config_07_bootstrap.yamlsrc/nav/config_files/config_100_satellites.yamlsrc/nav/config_files/config_220_body_shape.yamlsrc/nav/config_files/config_300_jupiter_rings.yamlsrc/nav/config_files/config_30_inst_coiss.yamlsrc/nav/config_files/config_310_saturn_rings.yamlsrc/nav/config_files/config_31_inst_gossi.yamlsrc/nav/config_files/config_320_uranus_rings.yamlsrc/nav/config_files/config_32_inst_nhlorri.yamlsrc/nav/config_files/config_330_neptune_rings.yamlsrc/nav/config_files/config_33_inst_vgiss.yamlsrc/nav/config_files/config_400_inst_coiss.yamlsrc/nav/config_files/config_40_sim.yamlsrc/nav/config_files/config_410_inst_gossi.yamlsrc/nav/config_files/config_420_inst_nhlorri.yamlsrc/nav/config_files/config_430_inst_vgiss.yamlsrc/nav/config_files/config_440_sim.yamlsrc/nav/config_files/config_900_backplanes.yamlsrc/nav/config_files/config_950_pds4.yamlsrc/nav/feature/reliability.pysrc/nav/nav_model/nav_model_body.pysrc/nav/nav_model/nav_model_rings.pysrc/nav/nav_model/stars/detection.pysrc/nav/nav_model/stars/nav_model_stars.pysrc/nav/nav_model/stars/predicted_snr.pysrc/nav/nav_orchestrator/ensemble.pysrc/nav/nav_orchestrator/instrument_config.pysrc/nav/nav_orchestrator/orchestrator.pysrc/nav/nav_orchestrator/provenance.pysrc/nav/nav_orchestrator/status_reason_info.pysrc/nav/nav_technique/confidence.pysrc/nav/nav_technique/nav_technique.pysrc/nav/nav_technique/nav_technique_body_limb.pysrc/nav/nav_technique/nav_technique_body_terminator.pysrc/nav/nav_technique/nav_technique_ring_edge.pysrc/nav/obs/obs_inst_cassini_iss.pytests/nav/config_files/__init__.pytests/nav/config_files/test_body_shape_citations.pytests/nav/config_files/test_config_load.pytests/nav/inst/test_inst_cassini_iss.pytests/nav/nav_model/stars/test_predicted_snr.pytests/nav/nav_orchestrator/test_instrument_config.pytests/nav/nav_orchestrator/test_provenance.pytests/nav/nav_technique/test_nav_technique.py
💤 Files with no reviewable changes (6)
- src/nav/config_files/config_40_sim.yaml
- src/nav/config_files/config_33_inst_vgiss.yaml
- src/nav/config_files/config_07_bootstrap.yaml
- src/nav/config_files/config_30_inst_coiss.yaml
- src/nav/config_files/config_32_inst_nhlorri.yaml
- src/nav/config_files/config_31_inst_gossi.yaml
11 review findings; each verified against current code, fixed when needed. - AUTONAV_PLAN.md: snapshot's "Pending" subsection still listed items that Phase 3 shipped (per-camera mag-offset wiring, NavContext shared derivatives, Provenance population, INFO logging cadence, static-data file renumbering + per-camera noise/mag_offset blocks + initial config_220_body_shape.yaml, two of the documentation pages). Each affected bullet group is now annotated with "Superseded by core_rewrite_catchup (shipped)" pointing at the Phase 3 status heading; entries that ship in a later phase are kept pending inline. - docs/introduction_configuration.rst: replaced ambiguous "0NN" / "1NN" / "3N0" / "4N0" / "9NN" notation with explicit ranges (0xx / 1xx / 3xx / 4xx / 9xx with the numeric range in parens). - config_420_inst_nhlorri.yaml: added a documentation-only ``_sources`` block citing every numeric value in noise / image_quality_thresholds / mag_offset (Part 0 §74 binding extends to per-camera blocks). Stripped at load time by Config._load_yaml. - detection.py detect_sources: PSF docstring updated to reference psf_sigma_px helper (handles per-axis sigma_x/sigma_y, single sigma, fwhm()) instead of the stale "Only sigma or fwhm() is consulted". - instrument_config._required_float: rejects non-finite (NaN/+-Inf) values with a path-aware ValueError so non-finite YAML values can not propagate into orchestrator thresholds. - instrument_settings_from_obs: added isinstance(Mapping) guards on inst_config / iqt_block / noise; data_units narrowed via cast(), with isinstance(str) check before the literal-membership test. - nav_technique_body_limb / body_terminator / ring_edge: switched evaluate_sigmoid_combination to use ``self.confidence_spec`` (class attribute) instead of the module-level _BODY_LIMB_CONFIDENCE_SPEC / _BODY_TERMINATOR_CONFIDENCE_SPEC / _RING_EDGE_CONFIDENCE_SPEC constants, eliminating the drift risk. - log_confidence_breakdown: now emits the per-term DEBUG breakdown on every call (including the hard_zero path) and additionally an INFO summary + per-term INFO when ``confidence <= low_threshold``, matching the docstring's promise. - obs_inst_cassini_iss: replaced the bare KeyError on missing config section / detector with an explicit ValueError naming both inst_section and detector and listing available detectors. - test_config_load: added newhorizons_lorri to the per-instrument field-presence check; replaced truthiness assertions on mag_offset.fallback_combo with concrete equality assertions per instrument. - test_provenance: replaced the tautological ``isinstance(meta.static_data_hashes, dict | type(meta.static_data_hashes))`` assertion with explicit Mapping + MappingProxyType + key/value-type checks. Verification: ruff + format + mypy --strict clean (256 files), pytest 1043 passed, sphinx -W clean, pymarkdown clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Provenance metadata is best-effort — the other resolvers in this module (_resolve_git_sha, _resolve_spice_kernels) already catch and fall back on per-source failures, but _resolve_static_data_hashes let ``Path.read_bytes`` and ``hashlib.sha256(...)`` propagate, so a transient I/O error (file disappearing between glob and read, permission denied, OS read failure) would abort the whole navigation. Wrap the read+hash in a try/except OSError; on failure log a WARNING naming the file and exception and skip it, so successful files still populate the returned MappingProxyType. Adds test_static_data_hashes_skips_unreadable_files exercising the PermissionError path with a monkeypatched Path.read_bytes — asserts the offending file is dropped, other files still hash, and the WARNING is on the per-image log. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Closes Stage 3 of the autonomous-navigation overhaul (
AUTONAV_PLAN.md): foundational gaps from Stages 0–2 plus per-instrument config wiring so a real image runs through the pipeline without hard-coded defaults misclassifying it. A second pass added INFO+DEBUG logging across every NavModel, NavTechnique, and the orchestrator.What shipped (Phase 3 specification)
config_NN_*.yamlto three-digit prefixes (010/020/.../950); ring catalogues moved to the3xxband, per-instrument blocks to the4xxband; bootstrap scalar angle fields converted to degrees.data_units/noise:/image_quality_thresholds:/mag_offset:/source_image_filter:/fit_camera_rotation:/max_rotation_deg:blocks added to everyconfig_4N0_inst_*.yaml. A parallelcassini_iss_calib:block was added because the Cassini loader picks the calibrated-IF block when the filename contains_CALIB; raw-DN and CALIB I/F products no longer share the same blank / saturation / noisy thresholds (regression: CALIB images were misclassified asblankagainst raw-DN thresholds).nav.nav_orchestrator.instrument_config.instrument_settings_from_obs(obs)translates the per-camera YAML block into a frozenInstrumentSettingsdataclass; missingdata_units, missingnoise.saturation_dn(raw_dn), and missingimage_quality_thresholdsblocks fail fast at navigate time.DEFAULT_FULL_WELL_DN_12_BITconstant is gone;_make_contextnow reads the per-instrument saturation DN, image-quality thresholds, and source-image filter fromobs.inst_config. Calibrated-IF instruments without preserved raw-saturation flags emit a one-line WARNING and an empty saturation mask.nav.nav_orchestrator.provenance.collect_provenance_metadata()returns the runtime-derived git SHA (git rev-parse --short HEADplus--porcelaindirty-detection), loaded SPICE kernel basenames (viacspyce), and sha256 hex digests of every shipped static-data YAML. The orchestrator's_make_provenancepopulatesProvenance.{rms_nav_git_sha, spice_kernels, static_data_hashes}.STATUS_REASON_INFO_TEMPLATEis wired through everyNavResult.failedshort-circuit via the new_failhelper.NavModelStars.to_featuresreadsobs.inst_config.mag_offset.{fallback_combo, mag_offset_table}per image and feeds the resolvedmag_offsetintopredicted_snr. The legacy hard-codedmag_offset=0.0is gone. The PSF-sigma helper handles the actualpsfmodel.GaussianPSFinterface (sigma_x/sigma_yper-axis attributes);nav.nav_model.stars.predicted_snr.psf_sigma_pxis called directly from every site.NavTechniquedeclaresconfidence_spec: ClassVar[ConfidenceSpec | None]andconfidence_attributes: ClassVar[frozenset[str]].validate_registered_confidence_specs()runs atConfig.read_configtime. Validation caught a real bug —BodyTerminatorNavreferencedmean_phase_angle_factorandmean_albedo_penaltywithout declaring them.evaluate_sigmoid_combination(..., return_breakdown=True)returns(confidence, ConfidenceBreakdown)carrying per-term raw / normalized / alpha / contribution plus the sigmoid argument and thehard_zero/hard_cap_appliedflags.nav.nav_technique.nav_technique.log_confidence_breakdown(logger, breakdown)always logs the breakdown at DEBUG and additionally at INFO whenconfidence <= 0.1, so calibration bugs surface in the default operator log.Config._load_yamlstrips every mapping key starting with_. Documentation-only_sourcescitation blocks live alongside values in source for human review without bloating the runtimeConfigobject.config_220_body_shape.yamlwith 10 bodies. Per Part 0 §74 anti-hallucination rule, every numeric value isnullpaired with a'PLACEHOLDER — no source found, calibrate in Phase 10'_sourcesentry; the runtime fallback (10 % radius default + reliability cap 0.3) handlesnullvalues.Logging additions
logger.open(...)carry the per-instance context.NavModelBodyopens'CREATE BODY MODEL FOR: <BODY>'/'EMIT BODY FEATURES: <BODY>';NavModelRingsopens'CREATE RINGS MODEL'/'EMIT RINGS FEATURES';NavModelStarsopens'CREATE STARS MODEL'/'EMIT STARS FEATURES'; everyNavTechnique.navigatebody openswith self.logger.open(f'TECHNIQUE: {self.name}'):. Inside a section the per-instance prefix is dropped — the section header already carries that context._star_short_info(mirrors the legacy NavModelStars format) / conflict counts; technique features-consumed / converged offset / RMS / inliers / confidence / spurious / at_edge; orchestrator image-classifier verdict / pass-1 prior / pass-2 result counts / final offset+sigma+confidence+rank+per-technique-fusion-count.combined %.3f < threshold %.3f,gap %.3f < agreement_gap %.3f (best %.3f, runner-up %.3f); conflicted = combined %.3f x multiplier %.3f,combined %.3f, sigma (dv,du) = (%.3f, %.3f) px (max %.3f); tier_thresholds = {...}, all-techniques-spurious technique-name list, unobservable-offset input count.Plan & docs
AUTONAV_PLAN.mdPhase 3 section ends with a "What shipped" subsection plus a binding "Logging conventions established in Phase 3" subsection capturing the section-header / INFO / DEBUG / failure-narrative / star-list / confidence-breakdown / pdslogger-only /%-as-format-placeholder rules so future NavModel and NavTechnique additions inherit the conventions automatically. The historical "Pending" snapshot earlier in the file is annotated with "Superseded bycore_rewrite_catchup(shipped)" on each affected bullet group.docs/developer_guide_static_data.rst(citation rules, anti-hallucination procedure, validator tests).docs/developer_guide_logging.rst(pdslogger conventions, per-status-reason INFO templates,caplogvscapsys).docs/introduction_configuration.rstrewritten for the renumbered set; band notation uses explicit ranges (0xx (000–099)/1xx (100–199)/3xx (300–399)/4xx (400–499)/9xx (900–999)) to avoid ambiguity with_N0mid-range values.Build / CI
rms-cloud-tasks>=0.2.0added as a runtime dependency (the cloud-tasks CLI entry points importcloud_tasks.worker; the package ispy.typed, so mypy gets the real types — no override needed).Review-finding fixes (post-open follow-ups)
_resolve_static_data_hasheswrapsread_bytes/hashlib.sha256intry/except OSErrorand logs a WARNING per skipped file; provenance is best-effort and a transient I/O error must not abort the run._required_floatrejects non-finite (NaN/+-Inf) values with a path-aware ValueError so non-finite YAML values cannot propagate into orchestrator thresholds.instrument_settings_from_obsvalidatesinst_config/iqt_block/noiseare mappings viaisinstance(Mapping)anddata_unitsis a string before the literal-membership test.BodyLimbNav/BodyTerminatorNav/RingEdgeNavuseself.confidence_spec(class attribute) instead of the module-level_*_CONFIDENCE_SPECconstants, eliminating drift risk.log_confidence_breakdownnow emits the per-term DEBUG breakdown on every call (including the hard-zero path) and additionally an INFO summary whenconfidence <= low_threshold, matching the docstring.obs_inst_cassini_issraises a descriptiveValueErrorwhen the config section or detector lookup fails, naming both and listing available detectors, instead of a bareKeyError.config_420_inst_nhlorri.yamlcarries a documentation-only_sourcesblock citing every numeric value (Part 0 §74 binding extends to per-camera blocks; stripped at load time byConfig._load_yaml).detect_sourcesPSF docstring updated to reference thepsf_sigma_pxhelper (handles per-axissigma_x/sigma_y, singlesigma,fwhm()).test_config_loadadds thenewhorizons_lorriinstrument check and replaces truthiness assertions onmag_offset.fallback_combowith concrete equality assertions per instrument.test_provenancereplaces the tautologicalisinstance(meta.static_data_hashes, dict | type(meta.static_data_hashes))assertion with explicitMapping+MappingProxyType+ key/value-type checks.Test plan
ruff check src testscleanruff format --check src testscleanmypy src testsclean (256 files, 0 issues)pytest -n auto --dist=loadfile— 1044 fast tests passtests/nav/inst/test_inst_cassini_iss.py::test_cassini_iss_calib_filename_selects_calib_inst_configpasses against the real PDS holdingssphinx-build -W -b html docs docs/_buildcleanpymarkdown scan docs/ .cursor/ README.md CONTRIBUTING.mdclean./scripts/run-all-checks.shgreen end-to-endCalibration items still parked for Stage 10
# PLACEHOLDERline inconfig_4N0_inst_*.yaml(read noise, expected noise, mag-offset tables).nullvalue inconfig_220_body_shape.yaml(≤ 10 bodies per PR per the human-review rule).per_edge_dt_rms_summedun-divided and saturates to confidence 0 on multi-edge fits even when the underlying RMS is sub-pixel;log_confidence_breakdownsurfaces this in the default INFO log when it fires.blank_max_if,saturation_threshold_if,noisy_threshold_if) ship as PLACEHOLDER guesses.🤖 Generated with Claude Code