Skip to content

M3/E5/#17 — SoLEXS parsers - #20

Merged
Rexy-5097 merged 1 commit into
mainfrom
feat/m3-e5-issue-17-solexs-parsers
Jul 30, 2026
Merged

M3/E5/#17 — SoLEXS parsers#20
Rexy-5097 merged 1 commit into
mainfrom
feat/m3-e5-issue-17-solexs-parsers

Conversation

@Rexy-5097

Copy link
Copy Markdown
Owner

Canonical identifier: Milestone M3 · Epic E5 · Issue #17 — SoLEXS parsers
Dependencies: #12 ✔, #15
Selection: eligibility from origin/main:docs/tis/TIS-v1.0.md §15 — complete {1…16}, eligible {#17, #22, #23, #25, #26, #27, #39}; lowest Order selects #17.

Three parsers, one per SoLEXS product, each implementing its section of SPEC-parsers@r6 — a document that is a contract: "a deviation requires a logged amendment, not a code change."

lc    §2.1   total-band counts per second. NaN is data, not an error.
pi    §2.2   340-channel PI spectra per second. Ordinal channels; keV is prohibited.
gti   §2.3   inclusive second-marks; Σ(STOP−START+1) == EXPOSURE, exactly.

No default on a missing key — the rule the spec opens with

§5: "No default-on-missing-key (header.get(K, default) is banned for any physically meaningful key — that idiom is the direct cause of v1's thirty-sprint failure)."

The concrete failure was F-05: v1 defaulted MJDREF to 58484 where the archive declares 40587 — a ~49-year timestamp error that nothing detected. So _fits.py has no get. Every lookup raises. Five tests delete a keyword and require the parse to abort.

NaN is data. Zero is a measurement. They never meet.

§2.1 as amended at r2, binding: NaN is the missing-data sentinel; NaN values pass through unchanged, are never imputed, never converted to zero, never removed; zero remains a valid physical count.

Mapped onto the domain exactly: NaN → None (observed to be absent, ADR-0017/L-07), 0.0 → 0.0. The property is quantified over every position and in both directions, because collapsing the two is the one error that would be undetectable in every downstream artifact. No interpolate/smooth/fill/impute/resample/repair exists in any of the three modules, and a test asserts none appears.

F-19 is checked after the NaN branch, which is what makes §2.1's note true that the rule is "inherently NaN-safe (NaN < 0 is False)".

The keV prohibition, enforced by absence

§2.2: no RMF/ARF exists anywhere in the archive, so PI-channel → keV is impossible from archive contents alone, and "no v2 artifact may state a SoLEXS energy in keV until a response file is acquired."

There is no keV field, no conversion function and no calibration constant — a test scans the module and its three classes for any of them. A comment is not a constraint; an absent field is.

Exact means exact

F-09 requires Σ(STOP−START+1) == EXPOSURE with tolerance 0 s. §5 says why: "the relation is definitional, and a tolerance would re-admit the ambiguity that produced CONTRADICTION-001." A one-second discrepancy — precisely what the superseded exclusive reading produces — aborts. The convention is verified for 2024-05-14 SDD2 only (§8 A-8) and is not widened here.

Verified against the real archive — every OBSERVED value reproduced

Re-measured from the 2024-05-14 SDD2 products, the X8.7 flare day:

Measured Spec clause
86,400 rows §2.1 NAXIS2
5 NaN at offsets [0, 5, 30072, 30078, 83951] §2.1 OBSERVED, exactly
86,395 finite §2.1, == EXPOSURE
5 GTI intervals, Σ(STOP−START+1) = 86395.0 §2.3, zero error
GTI-excluded set == NaN set §2.1 OBSERVED, this day
SDD1 NAXIS2 = 0detector_active=False F-12, the one non-fatal rule
TSTART 1715644800.0 = 2024-05-14T00:00:00Z §2.1 epoch anchor
DETCHANS 340, CHANTYPE PI, map 0..339 §2.2
.pi TSTART[0] == .lc TSTART V-PI-3 / F-06

These are the specification's own OBSERVED claims, independently reproduced by this code. A drift in either the parser or the archive is now a red test rather than a quiet disagreement between a document and reality.

astropy granted to Ingest — the line #13 predicted

Reading FITS needs a FITS reader. M2/E4/#13 declined to pre-grant third-party roots and said why: "a context that later needs astropy should add one reviewable line rather than inherit a blanket permission nobody voted for (STD-11)."

Three existing tests pinned contexts.ingest.allow == {contracts, domain, kernel} and went red — the mechanism working. They are updated to be stricter, not looser: internal grants must still equal ADR-0026's exactly, and a new test enumerates every third-party grant and refuses any that names a governed root of this repository.

Scope

.hk (§2.4, v1.1 only, 5 of 436 days) is not implemented: TIS E5 §5 lists this epic's SoLEXS modules as parsers/solexs/{lc,pi,gti} and assigns no .hk module to any issue. Reported as an Engineering Plan Defect rather than filled by guessing at scope.

No aggregation to the 1-minute grid — §3's T1/T2 contract is the write path's (#19). No NaN ⇒ GTI-excluded evaluation — §2.1 places it at the day-assembly layer explicitly, "not inside the single-file .lc parser." No HEL1OS, no curation, no registry.

Verification

unit (kernel + domain + contexts)  470 passed   (+65)
tests/architecture                 563 passed   (+1)
tests/integration                  128 passed   (+14)
research/tests/v2                  188 passed
links / imports / contracts gates  clean
imports gate: 46 modules, 211 imports, 9 policies

clean export (git archive HEAD, archive absent):
  unit 468 passed, 2 skipped · integration 94 passed, 10 skipped
  → all 12 real-corpus tests SKIP, never fail: E5 §17 satisfied

CI impact

numpy + astropy added to the unit and integration jobs — without them the parser suites would error at import rather than run. No new job, no new required context; still eight.

🤖 Generated with Claude Code

Canonical: Milestone M3 · Epic E5 · Issue #17. Deps: #12, #15 (both complete).
Eligibility recomputed by parsing origin/main:docs/tis/TIS-v1.0.md §15 — complete {1..16},
eligible {#17, #22, #23, #25, #26, #27, #39}; lowest Order selects #17.

Three parsers, one per SoLEXS product, each implementing its section of SPEC-parsers@r6.
That document is a CONTRACT: "a deviation requires a logged amendment, not a code change."

  lc   §2.1  total-band counts per second. NaN is data, not an error.
  pi   §2.2  340-channel PI spectra per second. Ordinal channels; keV is prohibited.
  gti  §2.3  inclusive second-marks; Σ(STOP−START+1) == EXPOSURE, exactly.

NO DEFAULT ON A MISSING KEY — THE RULE THE WHOLE SPEC OPENS WITH

§5: "No default-on-missing-key (header.get(K, default) is banned for any physically
meaningful key — that idiom is the direct cause of v1's thirty-sprint failure)."

The concrete failure was F-05: v1 defaulted MJDREF to 58484 where the archive declares
40587, a ~49-YEAR timestamp error nothing detected. So `_fits.py` has no `get`. Every
lookup raises. Five tests delete a keyword and require the parse to abort.

NaN IS DATA. ZERO IS A MEASUREMENT. THEY NEVER MEET.

§2.1 r2, binding: SoLEXS COUNTS uses NaN as the missing-data sentinel; NaN values pass
through unchanged, are never imputed, never converted to zero, never removed; zero
remains a valid physical count.

Mapped onto the domain exactly: NaN -> None (observed to be absent, ADR-0017/L-07),
0.0 -> 0.0. The property is quantified over every position and both directions, because
collapsing the two is the one error that would be undetectable in every downstream
artifact. No interpolate/smooth/fill/impute/resample/repair function exists in any of the
three modules, and a test asserts none appears.

F-19 is checked AFTER the NaN branch, which is what makes §2.1's note true that the rule
is "inherently NaN-safe (NaN < 0 is False)".

THE keV PROHIBITION, ENFORCED BY ABSENCE

§2.2: no RMF/ARF exists anywhere in the archive, so PI-channel -> keV is impossible from
archive contents alone, and "no v2 artifact may state a SoLEXS energy in keV until a
response file is acquired". There is no keV field, no conversion, no calibration constant
— a test scans the module and its three classes for any of them. A comment is not a
constraint; an absent field is.

EXACT MEANS EXACT

F-09 requires Σ(STOP−START+1) == EXPOSURE with tolerance 0 s. §5 says why: "the relation
is definitional, and a tolerance would re-admit the ambiguity that produced
CONTRADICTION-001". A one-second discrepancy — precisely what the superseded exclusive
reading produces — aborts. The convention is verified for 2024-05-14 SDD2 only (§8 A-8)
and is NOT widened here; §2.3 requires any deviation to terminate validation.

VERIFIED AGAINST THE REAL ARCHIVE — EVERY OBSERVED VALUE REPRODUCED

Re-measured from the 2024-05-14 SDD2 products, the X8.7 flare day:

  86,400 rows                                      §2.1 NAXIS2
  5 NaN at offsets [0, 5, 30072, 30078, 83951]     §2.1 OBSERVED, exactly
  86,395 finite                                    §2.1 == EXPOSURE
  5 GTI intervals, Σ(STOP−START+1) = 86395.0       §2.3, zero error
  GTI-excluded set == NaN set                      §2.1 OBSERVED, this day
  SDD1 GTI NAXIS2 = 0 -> detector_active=False     F-12, the one non-fatal rule
  TSTART 1715644800.0 = 2024-05-14T00:00:00Z       §2.1 epoch anchor
  DETCHANS 340, CHANTYPE PI, channel map 0..339    §2.2
  .pi TSTART[0] == .lc TSTART                      V-PI-3 / F-06

These are the specification's own OBSERVED claims, independently reproduced by this code.
A drift in either the parser or the archive is now a red test rather than a quiet
disagreement between a document and reality.

astropy GRANTED TO INGEST — THE LINE #13 PREDICTED

Reading FITS needs a FITS reader. M2/E4/#13 declined to pre-grant third-party roots and
said why: "a context that later needs `astropy` should add one reviewable line rather
than inherit a blanket permission nobody voted for (STD-11)."

Three existing tests pinned `contexts.ingest.allow == {contracts, domain, kernel}` and
went red — the mechanism working. They are updated to be STRICTER, not looser: internal
grants must still equal ADR-0026's exactly, and a new test enumerates every third-party
grant and refuses any that names a governed root of this repository.

SCOPE

.hk (§2.4, v1.1 only, 5 of 436 days) is NOT implemented: TIS E5 §5 lists this epic's
SoLEXS modules as parsers/solexs/{lc,pi,gti} and assigns no .hk module to any issue.
Reported as an Engineering Plan Defect rather than filled by guessing at scope.

No aggregation to the 1-minute grid — §3's T1/T2 contract is the write path's (#19). No
`NaN => GTI-excluded` evaluation — §2.1 places it at the day-assembly layer explicitly,
"not inside the single-file .lc parser". No HEL1OS, no curation, no registry.

VERIFICATION

  unit (kernel + domain + contexts)  470 passed  (+65)
  tests/architecture                 563 passed  (+1)
  tests/integration                  128 passed  (+14)
  research/tests/v2                  188 passed
  links / imports / contracts gates  clean
  imports gate: 46 modules, 211 imports, 9 policies

Eight real-corpus tests run where the archive is present and skip where it is not
(STD-12, E5 §17). The guard tests for .gz PRODUCTS, never a directory: five v1.1 archive
directories exist in a clean checkout because their quicklook PNGs are tracked.

CI: numpy + astropy added to the unit and integration jobs. No new job, no new context;
still eight.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Rexy-5097
Rexy-5097 merged commit 6a96084 into main Jul 30, 2026
8 checks passed
@Rexy-5097
Rexy-5097 deleted the feat/m3-e5-issue-17-solexs-parsers branch July 30, 2026 16:23
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