Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/CICs/UNFAOPostProcessorManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ manager._save()
- **Beige tests:** Missing ensemble name in config; None environment variables; empty forecast bucket; DataFrames with unexpected index structure
- **Red tests:** Corrupted parquet downloads; network timeouts during upload; DataFrames where all cells map to None (all-ocean input)

Currently: the manager cannot be instantiated without `views-pipeline-core`, so its stage logic is covered by **source-scan and replica tests** — `tests/test_validation.py`, `tests/test_launch_config.py` (the refusals), `tests/test_gaul_lookup_access.py` (one lookup read, threaded). `tests/test_append_metadata.py` was deleted in #149 with the method it mirrored. <!-- legacy-ok: retirement record --> A full end-to-end test against the live manager (C-03) still requires a production-like environment.
Currently: the manager cannot be instantiated without `views-pipeline-core`, so its stage logic is covered by **source-scan and seam tests** — `tests/test_validation.py` (the metadata null-gate, tested against `contract/historical.assert_metadata_complete` where it fires, plus pins that it has not drifted back into `_validate`), `tests/test_launch_config.py` (the refusals), `tests/test_gaul_lookup_access.py` (one lookup read, threaded). There is no longer a replica of any manager method: `tests/test_validation.py` held one until S4 (#185) and it had diverged from `_validate` since #149, while `tests/test_append_metadata.py` was deleted in #149 with the method it mirrored. <!-- legacy-ok: retirement record --> A full end-to-end test against the **live manager** still requires a production-like environment and is tracked as **#18**; þing-02 **D2** forbids integration tests against the production Appwrite project, no non-production one existing.

The input-integrity guards (S0–S6, epic #51) are representation-free invariants in `views_postprocessing/delivery/` that the manager **calls** (never inherits). Each has primitives unit tests — `tests/test_delivery_coverage.py` (S1/S4), `tests/test_delivery_observed_range.py` (S2), `tests/test_provenance.py` (S5), `tests/test_frame_extraction.py` (the seam), `tests/test_store_metadata.py` (store identity) — and `tests/test_input_integrity_e2e.py` drives the invariants on **primitives**, which is how the manager calls them. S3's forecast-identity rule moved to the wire layer (#150) and is covered by `tests/test_wire_source_selection.py`. The design contract (representation-free, called-not-inherited) is pinned by `tests/test_input_integrity_design_contract.py`.

Expand Down
55 changes: 34 additions & 21 deletions reports/technical_risk_register.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
| Owner | Dylan Pinheiro / PRIO MD&D Team |
| Last Updated | 2026-08-02 |
| Total Concerns | 74 |
| Open Concerns | 22 |
| Resolved Concerns | 52 |
| Open Concerns | 21 |
| Resolved Concerns | 53 |

---

Expand Down Expand Up @@ -113,24 +113,6 @@ that indexes only deleted code is noise.

## Open Concerns

### C-03: Test coverage gaps in manager validation and the enrich→validate path

| Field | Value |
|-------|-------|
| ID | C-03 |
| Tier | 3 |
| Source | `repo-assimilation` (2026-06-02), `test-review` (2026-06-02) |
| Trigger | When modifying the manager's `_validate()` or the enricher, verify that the test suite covers the changed behavior — end-to-end coverage across the enrich→validate path is still absent |
| Location | `tests/test_validation.py`, `views_postprocessing/unfao/managers/unfao.py` |

Initial state was zero test coverage. A 73-test suite was written (2026-06-02) covering the (now-deleted) mapper's core guarantees and the validation logic (missing columns, null rejection, error messages). Remaining gaps after the mapper removal: (1) the validation tests replicate `_validate()` logic in a standalone function because `views-pipeline-core` is unavailable in test environments — if the real `_validate()` diverges, tests pass while production fails; (2) no end-to-end test enriches through `GaulLookupEnricher` then validates through the manager.

Tier recalibrated from 2 to 3 during review-rr (2026-06-02): the gap is maintainability (test-code divergence, missing integration path), not structural fragility.

**Update 2026-06-24:** narrowed with the mapper deletion (C-39, PR #42). The mapper-coverage dimension is gone with the mapper (`tests/test_mapping.py` deleted; the determinism/cache/shapefile/`ThreadPoolExecutor` gaps no longer exist). Two manager-side gaps remain: the standalone `_validate()` replica and the missing enrich→validate end-to-end test.

---

### C-07: Undeclared direct runtime dependencies in pyproject.toml

| Field | Value |
Expand Down Expand Up @@ -711,6 +693,37 @@ See also C-40 (the inheritance/representation coupling this migration unwinds),
---

## Resolved Concerns

### C-03: Test coverage gaps in manager validation and the enrich→validate path — RESOLVED

| Field | Value |
|-------|-------|
| ID | C-03 |
| Resolved | 2026-08-02 |
| Resolution | **Closed by S4 (#185): one of its two residuals is gone, the other is relocated to a tracker that already exists.**

**Residual 1 — the `_validate()` replica — REMOVED.** `tests/test_validation.py` defined its own `validate_dataframe` and then tested *that*: forty-odd parametrised cases exercising a function declared twelve lines above them and **no production code at all**. A closed loop that could not fail while the delivery broke. Its header claimed *"the logic tested matches unfao.py:_validate() exactly"* — false since **#149**, which stopped `_validate` null-gating entirely (its docstring now says *"Neither payload is null-gated here"*). It also carried `REQUIRED_METADATA_COLS` as a nine-element literal, a hand copy of the contract **C-70** was resolved to make single-source.

Replaced by tests of `contract/historical.assert_metadata_complete` — the code that actually gates a delivery — parametrised over the **imported** `METADATA_COLS`, plus source-scan pins that the gate stays at build time and is still invoked. Verified 2026-08-02: `pytest -q tests/test_validation.py` → **14 passed**. Mutation-tested: narrowing the gate to a single column fails **9 of 14**; the old suite passed that mutation untouched, because it was not testing the gate.

**Residual 2 — the enrich→validate end-to-end test — RELOCATED to #18**, per the Register Conventions' relocation rule (a relocation is not complete until the destination exists and is cited by number). Every *leg* is now covered — enrichment (`test_enrichment.py`, 16), artifact build (`test_historical_builder.py`, 7), reader parity (`test_historical_parity.py`, 3), the invariants on primitives (`test_input_integrity_e2e.py`, 8), the wire end to end (`test_hop_b_sink_e2e.py`, 6), the null-gate (`test_validation.py`, 14). What remains uncovered is **the manager orchestrating them**, which needs views-pipeline-core and a production-like Appwrite environment — and þing-02 **D2** forbids integration tests against the production project, no non-production one existing.

That gap has **two standing trackers already**, which is why keeping a third here is noise rather than signal: issue **#18** (open since 2026-06-04) and `tests/test_falsification_campaign_3_5.py`, an `xfail(strict)` probe that **flips to XPASS the moment someone writes the test** — a self-surfacing tracker, which is more than this entry was doing. |
| Tier | 3 |
| Source | `repo-assimilation` (2026-06-02), `test-review` (2026-06-02) |
| Trigger | When modifying the manager's `_validate()` or the enricher, verify that the test suite covers the changed behavior — end-to-end coverage across the enrich→validate path is still absent |
| Location | `tests/test_validation.py`, `views_postprocessing/unfao/managers/unfao.py` |

Initial state was zero test coverage. A 73-test suite was written (2026-06-02) covering the (now-deleted) mapper's core guarantees and the validation logic (missing columns, null rejection, error messages). Remaining gaps after the mapper removal: (1) the validation tests replicate `_validate()` logic in a standalone function because `views-pipeline-core` is unavailable in test environments — if the real `_validate()` diverges, tests pass while production fails; (2) no end-to-end test enriches through `GaulLookupEnricher` then validates through the manager.

Tier recalibrated from 2 to 3 during review-rr (2026-06-02): the gap is maintainability (test-code divergence, missing integration path), not structural fragility.

**Update 2026-06-24:** narrowed with the mapper deletion (C-39, PR #42). The mapper-coverage dimension is gone with the mapper (`tests/test_mapping.py` deleted; the determinism/cache/shapefile/`ThreadPoolExecutor` gaps no longer exist). Two manager-side gaps remain: the standalone `_validate()` replica and the missing enrich→validate end-to-end test.

---

---

### C-43: ADR-011 enrichment swap shipped without its output-equivalence proof — and the proof is now unrecoverable — RESOLVED

| Field | Value |
Expand Down Expand Up @@ -760,7 +773,7 @@ This is a textbook instance of **C-42**'s registered hazard (acting on a mis-sta

**Residual (why this entry stays open):** the verification was a one-off session result, not a standing guarantee. Nothing in CI re-runs it, so a future lookup rebuild against a wrong or stale datafactory would ship silently exactly as before. **C-43 closes when `tests/test_gaul_lookup_fidelity.py` is committed and green** — the entry should then cite the test, not the session. Tracked as **Cluster K**; the same test discharges C-59 and C-61.

See also C-03 (the sibling enrich→validate test-coverage gap), C-22 (no post-delivery correction/recall process — **now acute: the consequence path is live**), C-39 / C-31 / C-23 (the resolved mapper-deletion cluster this emerged from), C-30 (coverage — discharged by the same run that left this standing), C-32 / C-34 (RESOLVED — the go-global scale risks that fired cleanly), D-08 (the swap-to-lookup-first decision whose verification debt this is), #131 (run-0 delivery-integrity verification).
See also C-03 (the sibling enrich→validate test-coverage gap — RESOLVED 2026-08-02; its manager-orchestration residual relocated to #18), C-22 (no post-delivery correction/recall process — **now acute: the consequence path is live**), C-39 / C-31 / C-23 (the resolved mapper-deletion cluster this emerged from), C-30 (coverage — discharged by the same run that left this standing), C-32 / C-34 (RESOLVED — the go-global scale risks that fired cleanly), D-08 (the swap-to-lookup-first decision whose verification debt this is), #131 (run-0 delivery-integrity verification).

---

Expand Down
23 changes: 10 additions & 13 deletions tests/test_falsification_campaign_3_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
Generated by falsification campaign on 2026-06-02

READ-ONLY CAMPAIGN: These stubs document test coverage gaps.

DISCHARGED 2026-08-02 (S4 / #185): `test_falsify_3_2_validation_uses_replica_not_real_code`
is removed — the replica it described is gone. `tests/test_validation.py` now tests
`contract/historical.assert_metadata_complete` directly, parametrised over the imported
`gaul_schema.METADATA_COLS`. Mutation-proven: narrowing the gate to one column fails 9 of
its 14 tests, where the replica-based suite passed that mutation untouched.

A probe written as `assert False` can never flip to XPASS, so the "surfaced for promotion"
mechanism below cannot reach it — a discharged probe has to be retired by hand, and leaving
one in place asserts a defect that no longer exists.
"""

import pytest
Expand All @@ -25,16 +35,3 @@ def test_falsify_3_2_wrong_type_columns_not_tested():
"""
assert False, "No test for wrong-type column values (e.g., string in numeric column)"


def test_falsify_3_2_validation_uses_replica_not_real_code():
"""
test_validation.py uses a standalone validate_dataframe() function that
replicates the logic from UNFAOPostProcessorManager._validate(). If the
real _validate() changes (e.g., adds a new column to the required list),
the replica won't be updated and tests will pass while production fails.

This is a structural test-code coupling gap, not a missing test case.

Severity: Soft falsification
"""
assert False, "Validation tests use replicated logic, not the real _validate() method — divergence risk"
42 changes: 17 additions & 25 deletions tests/test_falsification_campaign_4_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
Falsification test stubs for Campaign Claim 4.3:
"Schema change would fail a test"
Generated by falsification campaign on 2026-06-02

DISCHARGED 2026-08-02 (S4 / #185): `test_falsify_4_3_filter_cols_change_not_detected_by_validation_tests`
is removed. The hardcoded `REQUIRED_METADATA_COLS` replica it described is gone;
`tests/test_validation.py` parametrises over the imported `gaul_schema.METADATA_COLS`, so
adding a column to the declared contract extends the gate's coverage automatically — the
property register C-70 was resolved to give us.

`test_falsify_4_3_enrichment_test_checks_only_5_of_9_filter_cols` is removed for the same
reason, verified in the same pass. It described `test_enrichment` asserting 5 of the 9
contract columns. That file now imports `METADATA_COLS` and asserts
`list(lookup.columns) == METADATA_COLS` — exact equality on all nine — then iterates the
full set again for presence and for nulls. The four columns the probe said had "no schema
drift protection" are covered three times over.

**Claim 4.3 is therefore fully discharged and this module holds no live probe.** It is kept
rather than deleted because the campaign record is the point: it says what was checked on
2026-06-02, what was wrong, and when each finding was closed.
"""

import pytest
Expand All @@ -12,28 +29,3 @@
pytestmark = pytest.mark.xfail(reason="falsification probe — asserts a known-open finding by design (see reports/technical_risk_register.md, C-36); xfail(strict) keeps the suite green-when-healthy and surfaces any probe that starts passing", strict=True)


def test_falsify_4_3_enrichment_test_checks_only_5_of_9_filter_cols():
"""
test_enrichment_adds_expected_columns checks 5 columns:
country_iso_a3, admin1_gaul1_code, admin1_gaul1_name,
admin2_gaul2_code, admin2_gaul2_name.

But filter_cols in unfao.py has 9 columns (also: pg_xcoord, pg_ycoord,
admin1_gaul0_code, admin1_gaul0_name). Removing the missing 4 from
either side would not fail any test.

Severity: Soft falsification
"""
assert False, "Enrichment schema test checks 5 of 9 filter_cols — 4 columns have no schema drift protection"


def test_falsify_4_3_filter_cols_change_not_detected_by_validation_tests():
"""
The validation tests use a hardcoded REQUIRED_METADATA_COLS list that
replicates unfao.py's _necessary_metadata_cols. If someone adds or
removes a column from unfao.py's list, the test replica is not
automatically updated — the tests continue to pass against the old list.

Severity: Soft falsification
"""
assert False, "Validation test's column list is a replica — adding/removing a column in unfao.py is not caught"
Loading
Loading