Repository navigation
Fix the retired targets config key — restore all 29 baseline models (S1–S6 of #85) - #94
Conversation
…CORE_GENOME views-pipeline-core retired the synthesised `targets` config key on 2026-08-02 (507ae11, refs #380/#381, shipped in 3.0.0) and migrated its own 13 read sites. views-baseline was never swept, so catalog.py kept reading config["targets"] at seven sites and every get_model() raised KeyError. All 29 baseline models in views-models have been unrunnable since that date. The suite could not catch it: tests/conftest.py manufactured the retired key, so 248 tests passed against a config shape that cannot exist in production — and which pipeline-core's combined_targets() now actively raises on. The fixture, not the dependency, defined the contract. Migrating the fixtures first turns 34 tests red with the production KeyError; the catalog fix turns them green. The target list is derived once in __init__ rather than re-read at each of the seven factory sites, so the next rename of the platform's target vocabulary is one edit. combined_targets() is deliberately not called: catalog.py sits under model/, which ADR-013 forbids from importing views-pipeline-core at module level, and passing targets down from the manager would leave one of seven config keys arriving by a different mechanism than the other six. classification_targets is not read — 0 of 29 baseline configs declare one, and the decision when one does is per-model, not catalog-wide. regression_targets and level are promoted to CORE_GENOME so their absence is a named contract violation at the boundary rather than a bare KeyError deeper in. ADR-009 recorded this exact gap as accepted debt in March 2026; it is what the outage was made of. Verified against all 29 shipped configs before promoting (29/29 declare both), so no coordinated views-models change is required — unlike the C-24 seed promotion. Two guards added after the #89 mutation audit found that deleting either new key from CORE_GENOME left the whole suite green. They assert the keys by name: a test parametrized over CORE_GENOME cannot catch a key being removed from it, because the parametrize list simply shrinks. Tightening two loose `match="steps"` assertions exposed a pre-existing defect: one config omitted prediction_format while its own comment claimed only steps was missing. The substring match had concealed that. Refs #85. Closes #84, #86, #87, #89. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PoczAeeAcC81UVMGMSiAvS
The conformance check that did not exist on 2026-08-02, and whose absence let every baseline model stay dead for five weeks. Seven configs copied from the shipped views-models baselines — zero_pgmbaseline, locf_pgmbaseline, average_pgmbaseline, light_strider, black_ranger, doctorish_dwarf (nb/none), bashful_dwarf (gamma/log1p) — merged the way get_combined_config merges them, with provenance and a refresh note in the docstring. Copied, never imported: nothing here reads across the views-models boundary. Per algorithm: audit_manifest accepts it, get_model constructs the right class, model.targets matches regression_targets, the target list is not aliased from the config, and the fixture carries no retired evaluation key — the property the old conftest fixture violated, and the reason a guard built on it proved nothing. test_every_catalogued_algorithm_has_a_conformance_fixture fails if an algorithm is added without a real config, so the guard cannot silently shrink as the catalog grows. Verified by reverting the catalog fix: 15 of 29 tests fail. No importorskip. The story proposed following the test_ensemble_consumption idiom, but this needs only the catalog and the gate, both numpy-only at import; skipping it where views_frames is absent would make it a no-op in exactly the environment (CI) where it matters most. Known limitation, stated in the docstring: a checked-in copy can drift. It catches a key rename — the failure that happened — not a value change. pipeline-core's own governance puts this check here: it registered the blindness (C-289, "cannot see a consumer") and its conformance suite explicitly exempts views-baseline. Refs #85. Closes #88. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PoczAeeAcC81UVMGMSiAvS
CI has been red since 2026-08-02. The header note in run_tests.yml blamed
views-pipeline-core not being on PyPI, and said the workflow was expected red
until it published. pipeline-core 3.0.0 published on 2026-08-03 — the day after —
and the workflow was never re-run, so the note went stale and the real cause went
unseen. Re-running it now (run 34331888893) takes 2m22s instead of the old 13s:
lint and build pass, and check fails for a different reason entirely.
Because pywin32{sys_platform == 'win32'}==227 has no wheels with a matching
Python version tag (e.g. cp311) and docker>=5.0.0,<=5.0.3 depends on
pywin32==227 ... and viewser>=6.6.4 depends on docker>=5.0.0,<6.0.0 ...
and views-pipeline-core>=3.0.0 depends on viewser>=6.6.4 ...
your project's requirements are unsatisfiable.
uv resolves a universal lockfile across every platform its markers admit,
including win32, so the win32 split fails on Linux CI for a platform CI never
runs on. Publishing pipeline-core would not have fixed this; the workflow would
have stayed red and nobody would have known, which is the same silence that hid
the retired-key outage.
[tool.uv] environments = ["sys_platform == 'linux'"] is uv's own documented remedy
— it is the hint uv emits. Verified locally: uv lock --python 3.11 resolves 118
packages in 1.32s, previously unsatisfiable. The published wheel is unaffected: it
stays py3-none-any. This bounds the dev/CI resolution, not the distribution.
No lockfile is committed. That would make CI deterministic and is arguably worth
doing, but it is a separate decision with its own maintenance cost and belongs to
#26.
Refs #85. Closes #90.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PoczAeeAcC81UVMGMSiAvS
…igration ADR-009's "what is not validated" bullet is struck through rather than deleted. What it recorded is the most useful thing in the document: in March 2026 it predicted, precisely, the failure that arrived in August — a KeyError from inside a factory method rather than from the validation check, with the audit reporting success one line earlier. Accepted debt in a boundary contract is a prediction, not an observation. Deleting it would erase the evidence that the register worked and the follow-through did not. Also in ADR-009: the MODEL_GENOMES snippet was stale twice over (it predated seed and both parametric algorithms), Boundary 2's mapping now reads regression_targets, and the gap table narrows to value-ranges. CICs: BaselineModelCatalog (parameter and error tables, all usage examples, evolution notes, known deviations), ReproducibilityGate (CORE_GENOME verbatim, and four examples that were wrong — the "passes silently" one omitted prediction_format and would have raised), BaselineForecastingModelManager (three configs and the self.config contract row). One deviation is now partly fixed and the CIC says exactly how much: post-construction config mutation still reaches the model-specific keys read lazily in get_model(), but no longer reaches targets, which are copied in __init__. Documented as partial rather than quietly upgraded. Register: C-42's tier is corrected in public. It was registered hours earlier as Tier 3 "diagnostics" with a hypothetical trigger; it was a live outage whose trigger had fired five weeks before. The entry records why it was mis-read — presence was assumed because the fixture supplied it, which is the trap C-44 describes one entry earlier. C-38's discharge condition is met (pipeline-core 3.0.0 on 2026-08-03 through 3.2.0 on 09-08). C-44 gains the confirmed instance. publishing-to-pypi.md: the dependency-not-on-PyPI warning is now historical, and the pending-publisher setup step is marked as done — views-baseline has been on PyPI since 2026-07-31. Noted, not fixed: docs/validate_docs.sh still never checks that a source path cited by a doc exists on disk. That is why the epic #47 reorg left eight ADRs pointing at deleted files. It stays C-40's job. Refs #85. Closes #91. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PoczAeeAcC81UVMGMSiAvS
…ort claim The first CI run of the #85 branch got far enough to resolve dependencies and revealed a second, unrelated blocker: views-pipeline-core -> viewser 6.6.4 pins pandas<2.0.0, and pandas 1.5.3 (the last 1.x) publishes wheels for cp38-cp311 only. On 3.12/3.13 the resolver must build pandas from source and fails with ModuleNotFoundError: No module named 'pkg_resources'. views-baseline therefore cannot be installed on Python 3.12 or 3.13 at all. This is not a regression from this epic — it has been true since the >=3.0.0 pipeline-core floor was adopted, and only became visible now that CI resolves far enough to hit it. pyproject.toml still declares requires-python = ">=3.11,<3.14" and 3.12/3.13 classifiers. That claim is false, and it is registered as C-47 rather than quietly narrowed: what a distribution claims to support is a release decision with downstream visibility, the constraint lives two hops upstream, and narrowing then re-widening requires-python across releases is worse than stating the gap once. The matrix is narrowed instead, so CI is honestly green rather than permanently two-thirds red — a permanently-red pipeline being the exact condition that let the retired-key outage survive five weeks unnoticed. Restore the full matrix when viewser drops the pandas<2 pin. Refs #85, #90. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PoczAeeAcC81UVMGMSiAvS
|
Update — the first real CI run found a second blocker, unrelated to the first. PR #94, run
So the The second blockerVerified against PyPI directly:
On 3.12/3.13 the resolver must therefore build pandas 1.5.3 from source, which fails immediately.
Decision (maintainer)Matrix narrowed to 3.11, with the full diagnosis inline in The reasoning for narrowing rather than tolerating red: a permanently two-thirds-red pipeline is Discharge for C-47: restore the full matrix when viewser drops the |
✅ CI green — first time since 2026-08-02Run 34333952461. Two S1–S6 of #85 are complete and verified. Ready for review. Two non-blocking observations from the run, noted not fixed — neither belongs in this PR:
🤖 Generated with Claude Code |
…value holes Review of #94 found that the rename silently disabled the guard that exists to catch exactly this class of bug, plus two validation holes the PR's own ADR-009 edit declares closed. 1. The C-10 forwarding guard stopped checking `targets` (finding 1). It skips any constructor param whose name is not a config key, and the config key had just been renamed, so `targets` fell out of the loop while the test stayed green — a test whose own failure message names "a silently dropped parameter (C-10 class)". Fixed with an explicit param->config-key map. Verified by mutation: dropping targets from a builder now fails the guard. 2. `regression_targets: []` passed the whole gate (finding 2). Presence yes, non-None yes — so every model was built with no targets and the run reported success having written no PredictionFrames. It is reachable in production precisely because check 4 is not: pipeline-core's `get_combined_config` rewrites `regression_targets: None` to `[]` before the manager sees it. The gate now rejects empty values for every required key; scalars have no length and are skipped. 3. `regression_targets: "lr_ged_sb"` was laundered into nine one-character targets (finding 11). The catalog now rejects a bare string. 4. Deriving targets in `__init__` broke `list_models()` (finding 3/A3), which ADR-004 declares Stable and the CIC declares side-effect-free — enumerating the catalog must not require a config carrying targets. It is a property now. 5. `match=re.escape(missing_key)` re-introduced the substring looseness this same commit removed 100 lines below: "steps" matches a message naming only "time_steps" (finding 5). Tightened to the exact list. 6. The conformance fixtures were not configs pipeline-core would accept (finding 7), falsifying the module docstring: `CoreConfigSniffer` rejected all seven for missing name/creator/rolling_origin_stride/deployment_status/metrics and skip_predictions_delivery. They are complete now, and a new test hands each to the sniffer so the claim is enforced rather than asserted in prose. 7. The retired-key guard never covered `MANAGER_BASE_CONFIG` (finding 9) — the fixture that actually caused #84. It does now. 8. `_KEYS_DEREFERENCED_ON_EVERY_RUN` claimed a uniform justification that is not uniform (finding 3, addendum A1): `prediction_format` is never dereferenced in this package and pipeline-core reads it as `.get(..., "dataframe")`, so its absence is a silent fallback, not a KeyError. Corrected, and `run_type`'s deliberate exclusion is now recorded there rather than left implicit (C-48). 295 passed (was 283), ruff clean. Refs #85, #94. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PoczAeeAcC81UVMGMSiAvS
… short The sweep in e406920 missed four contributor-facing files and left two register rows contradicting entry bodies edited in the same commit — the same incomplete- sweep failure mode this epic is about. - README.md:154 still told readers the input "must contain target columns specified in config['targets']" — on the file pyproject ships as the PyPI front page, advising a key that now makes combined_targets() raise. - publish_package.yml carried the third copy of the "views-pipeline-core is not yet on PyPI" claim; the same PR rewrote the other two to say the opposite and to instruct the releaser to exercise the install-back. - hardened_protocol_template.md told contributors `targets` is "a known gap documented in ADR-009", citing an ADR this PR rewrote to say it is closed. - The manager CIC's usage examples were executably false: they omitted the core keys, so they raise MissingHyperparameterError, not the documented ValueError. - ADR-012 still said "beyond `targets`". - The ReproducibilityGate CIC ships the downstream static check as `CORE_PARAMS - set(hp.keys())`. With `level` and `prediction_format` in the genome and living in config_meta.py, that now reports all 29 healthy models as broken. Corrected to validate the merged config. - Register K5/K7 cluster rows still said the gate omits level/targets and that C-38 is live and gating the suite — both contradicted by their own entry bodies. The workflow header's "REAL merge gate" claim is also removed: the jobs run and pass, but neither branch declares required status checks, so red still does not block a merge. Registered as C-49 rather than silently overstated. C-41's trigger is corrected: it claimed a clean install raises ModuleNotFoundError because pipeline-core declares no scipy. Directly true and misleading — views-baseline -> views-pipeline-core -> views-evaluation requires scipy>=1.11,<2, so scipy is installed on any real install (verified at 1.15.1). The residual defect is smaller and different: we import scipy directly while declaring nothing. New: C-48 (run_type read past the gate), C-49 (CI not a required check), C-50 (catalog reimplements the platform's target vocabulary — a recorded consequence of the ADR-013 decision, including that the new conformance guard cannot detect the failure it was built for), C-51 (unpinned ruff), C-52 (fixture keyed by algorithm). Refs #85, #94. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PoczAeeAcC81UVMGMSiAvS
…check `/review-diff` on this branch caught two of the four guards added in the previous commit doing nothing: `test_c14_does_not_claim_a_downstream_block_that_no_longer _exists` asserted "C-14 not in open ids", and the discharge-condition test asserted one exact phrase inside C-42. Both could only fail if those specific entries returned, which they will not. That is the K8 pattern this branch adds to the register — a guard written by the author of the thing guarded, encoding the author's model of failure — committed four hours after I flagged the identical defect as finding 13 of the #94 review. The two are replaced by one general guard: no entry under Open Concerns may carry a self-declared terminal marker (RESOLVED, DISCHARGED, WITHDRAWN). That subsumes both the C-38 case (body said RESOLVED while filed open) and the C-27 case (withdrawn in July, status hidden in the Tier cell) without naming either. Mutation-tested rather than assumed: injecting each of the three markers into an open entry fails the guard, and mutating the declared open count fails the header guard. A first attempt at this verification silently applied no mutations at all (`str.replace(old, new, '1')` — count passed as a string) and reported three false "caught" results; that run is discarded, not reported. What the guards deliberately do not attempt is deciding whether an entry's claim is still true of the world. C-14 was caught by running views-models' suite, not by a test, and pretending otherwise would be a third tombstone. Also preserves C-14's original trigger verbatim in its resolution — the wrong claim is that entry's whole value, and the compressed resolution format would otherwise delete it. Refs #85, #98. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PoczAeeAcC81UVMGMSiAvS
Stories S1–S6 of epic #85. Restores every baseline model to a runnable state and makes the class
of break that killed them detectable here rather than five weeks later in another repo.
Closes #84, #86, #87, #88, #89, #90, #91. Remaining in the epic: #92 (release 1.0.2) and
views-models#459 (floor bump + smoke-test fix), both of which depend on this landing.
The break
catalog.pyreadself.config["targets"]at 7 sites. views-pipeline-core retired that key on2026-08-02 (
507ae11, #380/#381, shipped in 3.0.0) and migrated its own 13 read sites; views-baselinewas never swept. Every
get_model()raisedKeyError: 'targets', so all 29 baseline models havebeen unrunnable since that date.
Why nothing caught it — three independent failures, all addressed here
conftest.pysupplied bothtargetsandregression_targets, so 248 tests passed against a config shape that cannot exist — and thatpipeline-core's
combined_targets()now actively raises on. Deleting it turns 34 tests redwith the production
KeyErrorbefore the catalog fix goes in. That red is the evidence.targets; we read it. pipeline-core registered that blindness itself (C-289) and itsconformance suite explicitly exempts views-baseline, so S3: Conformance test — build all 7 catalog models from a realistic merged config #88 puts the check on our side.
Commits
0840f33regression_targets(derived once, not at 7 sites);regression_targets+levelpromoted toCORE_GENOME; 48 config-key sites migrated6765b3a21878d2[tool.uv] environments— CI can resolve againe406920CI was red for a different reason than documented
The header note said CI would go green once pipeline-core published. It published 2026-08-03. CI
would still have been red. The real cause:
pipeline-core → viewser → docker<6 → pywin32==227,which has no wheels above cp39, failing uv's universal resolution for a platform CI never runs on.
Fixed with uv's own documented remedy;
uv locknow resolves 118 packages in 1.32s where it waspreviously unsatisfiable. This PR is the first test of whether CI is actually green.
Design notes
combined_targets(), which Every baseline model has been unrunnable since 2026-08-02 — catalog.py reads the retired config['targets'] key (27 models) #84 proposed:catalog.pyis undermodel/, which ADR-013forbids from importing pipeline-core at module level, and passing targets down from the manager
would leave one of seven config keys arriving by a different mechanism than the other six.
classification_targetsguard. 0 of 29 configs declare one, andZeroModel/LocfModelarevalid classification baselines — a catalog-wide veto would block models that already work.
CORE_GENOMEpromotion needs no views-models change: verified 29/29 declare both keys, unlikethe C-24
seedpromotion which required a coordinated pair.Things the work found that were not in the plan
CORE_GENOMEleft all 277 tests green. Closed with two guards that assert the keys by name —a test parametrized over
CORE_GENOMEcannot catch a key being removed from it.match="steps"exposed a lying fixture — a config that also omittedprediction_formatwhile its comment claimed onlystepswas missing.than quietly rewritten.
Verification
283 passed, 1 xfailed(was 248) ·ruff check .clean ·docs/validate_docs.shgreen ·reverting the catalog fix fails 15 of 29 conformance tests · 10 mutations run, 8 killed, 1 equivalent.
Out of scope and noted, not fixed: C-19, C-40, C-41 (undeclared
scipy), C-45.🤖 Generated with Claude Code
https://claude.ai/code/session_01PoczAeeAcC81UVMGMSiAvS