Skip to content

S1 — Delete the retired delivery path; refuse an incomplete config #149

Description

@Polichinel

Epic: #148 · Closes C-63, #145 · Resolves C-29 · Unblocks S2–S8

Background

The frame-native contract path won on 2026-07-27 (run-0, global-land, FAO served since). The pandas path it replaced was kept behind a config fork "until run 0 proves the contract path live" (register C-40) and never removed. This story is that removal. It is not new work — it is the named post-run-0 follow-up, four days overdue, and D-11 warned that if it lingers the duplication becomes permanent.

Two independent dispatch axes, four theoretical modes, one used:

Axis Gate Declared by
Historical representation declared_data_format(queryset) == "feature_frame" (unfao.py:130) views-models config_queryset.py:62
Delivery protocol configs.get("wire_contract") (:233, :294, :323, :409, :520) views-models config_meta.py:26

Production declares both. Omitting either silently selects the retired half — that is the inference ADR-003 forbids.

Work

Delete the legacy leg at every fork:

  • _read_forecast_data :236-270 — legacy body; keep the contract call
  • _read_historical_data :134-146 — pandas leg; _read_historical_frame becomes the reader
  • _transform :288-298 — legacy leg (geography attaches at artifact build)
  • _validate :300-348 — both legacy legs; keep the contract assertion + _check_coverage
  • _check_coverage :414-418 — legacy forecast branch
  • _save :523-577 — body becomes return self._save_contract()
  • _append_metadata :276-286, _delivery_description :619-636 — legacy-only
  • LEGACY_FORECAST_FILTERS :29-34
  • _historical_coverage_source :579+ — collapse to the frame branch
  • imports :5, :7, :14read_dataframe, PGMDataset, pandas if unreferenced

Add the single refusal. One place, at the manager's entry, raising and naming the missing key — not .get() with a silent default. Follow the shape of appwrite_env.assert_env_declared (unfao/appwrite_env.py:41), which is this repo's existing fail-loud-on-missing-declaration idiom.

Reuse, do not rewrite: _ContractStorePort (:37-77) already carries the fail-loud upload guard that #145 asks for; _unfao_appwrite_config() (:499) already builds the config the deleted legacy body duplicated verbatim; _save_contract (:442) is the surviving path.

Do not touch unfao/enrichment.py or unfao/extraction.py in this story — S3 and S4 handle them once the callers are gone.

Acceptance criteria

  • unfao/managers/unfao.py < 450 lines (from 636)
  • Zero wire_contract / data_format branches outside the single refusal
  • A launch config missing either key raises, naming the key — new test asserts this
  • #145's silent uploads are gone: no dsm.upload_data(...) whose result is discarded
  • test_hop_b_sink_e2e.py byte-parity unchanged and green — proves the surviving path did not move

Testing / validation

  • ruff check .; pytest -q --deselect tests/test_datafactory_deploy_readiness.py — the 5 known local pyarrow byte-parity failures only, no new ones
  • Four test files couple to legacy-only symbols and need rework, not deletion: test_append_metadata.py (12 hits), test_selection_guard.py (3), test_input_integrity_e2e.py (3), test_falsification_fao_notes_relevance.py (1)
  • test_selection_guard.py pins LEGACY_FORECAST_FILTERS as an ADR-013 §11.4 golden string. Re-point it at the ADR's post-adoption record — do not drop the guarantee, the historical fact stays pinned after the code goes.

Risk

The legacy path has no end-to-end test, so it could already be broken and nothing would report it. That cuts both ways: it is also why deleting it is low-risk — unreachability is established by config (config_meta.py:26, config_queryset.py:62), not by test.

ADR-013 §11.4 is satisfied, not violated: it required a type-aware guard live before the first contract upload — both guards merged 2026-07-15, run-0 uploaded 2026-07-27. Deleting the legacy reader retires the selector §11.4 was protecting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    implementationCode implementation workstoryA single reviewable unit of an epic

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions