diff --git a/docs/ADRs/013_sampled_forecast_wire_contract.md b/docs/ADRs/013_sampled_forecast_wire_contract.md index 769007e..09e097d 100644 --- a/docs/ADRs/013_sampled_forecast_wire_contract.md +++ b/docs/ADRs/013_sampled_forecast_wire_contract.md @@ -358,6 +358,49 @@ yet) — consumers must not treat it as authoritative before then. The lift of t caveat is tracked as a reminder in pipeline-core: their issue #279 (filed 2026-07-19) fires when the first real release ships. +**§2.2a Amendment A2 — run maturity, drafted 2026-08-05, NOT YET IN FORCE.** + +Requested by views-postprocessing #133 on behalf of views-faoapi (its ADR-033, epic +#244) so that a consumer can tell a production run from a test one. This clause defines +the field and the cost of adopting it. **It is not adopted, and `contract_version` +remains 1.5.** Nothing in this section describes bytes that ship today. + +*The field.* A fourth key inside `provenance`: + +| key | type | meaning | +|---|---|---| +| `status` | string | the producing run's maturity in views-models ADR-017's vocabulary — `graduate`, `candidate`, and the rest. Never inferred; absent is not `graduate`. | + +*Why this is an amendment and not a free addition.* §2.1 clause 1 opens the header to +new **top-level** keys and closes the three sub-objects. `provenance` is one of them, so +a fourth key is a contract change by this document's own rule. It is purely additive, so +it is a **MINOR** bump — 1.5 → 1.6 — and a reader built for 1.5 still accepts a 1.6 +artifact. + +*What adoption actually costs, which is the reason it is deferred.* `contract_version` +is written **inside the header bytes**, and §10 pins those bytes. So a MINOR bump is not +a free-standing edit: it rebuilds the golden fixture, and §10 requires all three +implementing repositories to re-vendor the new bytes and re-pin their root hashes +together. The full coordination cost is paid for one optional field. + +*Who can stamp it, and it is not this repository.* Hop-B shard headers are the Hop-A +headers **re-embedded untouched** (`contract/wire/sink.py`). This repository forwards +provenance; it does not compose it, and it does not know a run's maturity. Synthesising +one here would be a producer guessing at a fact it was not told — precisely the +inference this platform's declare-don't-infer rule exists to forbid. **The stamp belongs +to the Hop-A producer (views-models),** and this repository's part is to forward it, +which requires no code change once the key is admitted. + +*Named trigger for adoption (ADR-014 §4).* Adopt A2 **when the next re-vendor of the +§10 fixture happens for another reason** — the pyarrow fix tracked as register C-72 / +issue #174 is the one currently expected, since it also changes delivered bytes and also +requires all three repos to move together. Riding along costs nothing extra; going alone +costs a three-repo coordination for a field the consumer has stated it only surfaces. +**Owner:** whoever executes that re-vendor. If views-faoapi ever promotes `status` from a +surfaced label to a serving gate, that supersedes this trigger and A2 should be adopted +on its own. + + **§2.3 Governance hook.** Changing `sample_count` (wire thinning) or `dtype` on the **FAO delivery** changes the published HDI/MAP numbers, and is therefore a **re-baseline event**: it requires sign-off under views-faoapi **ADR-023 @@ -890,6 +933,18 @@ runs can therefore never touch the live bucket by accident. Dated events after adoption. Errata correct errors in this document; other entries record execution progress against it. +- **2026-08-05 — Amendment A2 drafted, not in force (§2.2a; `contract_version` stays + 1.5):** views-postprocessing #133 asked for three declared fields on the run manifest — + `maturity`, `source`, and a required schema version. Measured against what is actually + delivered and actually read, **two of the three already ship**, and the third is not + ours to stamp. `source` is `provenance.ensemble`, forwarded from the Hop-A header and + read by views-faoapi as exactly that. The schema version is `contract_version`, present + on the run manifest and in every shard header. Only maturity is missing, it belongs in + `provenance`, and `provenance` is a closed sub-object — hence A2 rather than a silent + addition. The request was written against the manifest; the consumer reads the shard + header. Recorded because an accepted ask nobody re-measured would have produced a + three-repo fixture re-vendor to add two fields that were already there. + - **2026-08-02 — Erratum E2 (§7d links; `contract_version` stays 1.5):** §7d's first link was **broken and had never resolved**. Issue #158 renamed the cross-repo contract from `PLATFORM-001` to *the Appwrite Seam Contract* and the rename was applied as a diff --git a/reports/technical_risk_register.md b/reports/technical_risk_register.md index 382b040..a1e6879 100644 --- a/reports/technical_risk_register.md +++ b/reports/technical_risk_register.md @@ -5,8 +5,8 @@ | Project | views-postprocessing | | Owner | Dylan Pinheiro / PRIO MD&D Team | | Last Updated | 2026-08-05 | -| Total Concerns | 84 | -| Open Concerns | 11 | +| Total Concerns | 85 | +| Open Concerns | 12 | | Resolved Concerns | 73 | --- @@ -161,6 +161,36 @@ that indexes only deleted code is noise. ## Open Concerns +### C-85: A cross-repo ask is adopted on its stated terms without anyone measuring the current state + +| Field | Value | +|-------|-------| +| ID | C-85 | +| Tier | 3 — no delivery is affected and no wrong data ships. What is at risk is spending a coordinated three-repository change on work that is already done, which is expensive in exactly the currency this platform has least of. | +| Source | #133 execution (2026-08-05) — the ask was re-measured before implementing it | +| Trigger | The next time an issue filed by another repository's seat is picked up for implementation. Before writing code, check what this repository already delivers and what the consumer already reads — the two are stated in the issue and were both wrong last time. | +| Owner | Whoever implements a cross-repo issue. This is a habit, not a mechanism; see below for why no guard is proposed. | +| Location | Not a code defect. #133; ADR-013 §2.2a. | + +#133 asked for three declared fields — `maturity`, `source`, a required schema version — and said `_save_contract` ships the forecast run only. It was filed in good faith by the views-faoapi seat, accepted, and carried on the backlog for weeks. Measured on 2026-08-05, before writing anything: + +| the ask | the measured state | +|---|---| +| stamp `source`, *"`source="unknown"` is what's live today"* | **already delivered** as `provenance.ensemble`, and views-faoapi reads exactly that key | +| declare a required schema version | **already delivered** as `contract_version` — on the run manifest and in every shard header | +| stamp `maturity` on the run manifest | genuinely missing — but it belongs in the **shard header**, which is where the consumer reads it, and it is **not this repository's to stamp** | +| *"the global historical is not uploaded"* → decide whether FAO stops receiving it | **false.** `_save_contract` uploads it, `category="historical"`, under the same interlock as the forecast. There was no decision to make | + +**Two of three fields already shipped, and the decision had no premise.** Had the issue been implemented as written, the cost would have been a `contract_version` bump — which is written *inside* the header bytes that §10 pins — and therefore a rebuild of the golden fixture and a coordinated re-vendor across all three implementing repositories, in order to add two fields that were already there. + +**Why the request was wrong is more useful than that it was wrong.** It was not careless. It was written against the **run manifest**, which is the artifact whose name suggests it carries run-level facts. The consumer reads them from the **shard header's `provenance`**, because that is where the producing pipeline's identity travels. Both seats were describing a real need and neither was describing the same object. A cross-repo ask names an artifact in the other repo's vocabulary, and vocabulary is exactly what does not survive the trip. + +**No guard is proposed, deliberately.** There is no mechanical check for "is this request still true", and inventing a ceremony — a template, a checklist field — would be process theatre that decays into an unread heading. What made the difference here was reading the consumer's source before writing any, and that is a habit worth writing down rather than automating. Registered so the next person has the worked example instead of the rule. + +Cross-refs: **C-72** (the re-vendor this would have triggered, and the trigger A2 now rides on), **C-77** (the historical leg whose correctness is what makes the co-delivery premise false), ADR-013 §2.2a, ADR-014 §4 (the deferral's named trigger), #133, views-faoapi ADR-033 and its register C-169. + +--- + ### C-84: Every identity this repo delivers under dies on 2026-11-17, within 3h35m of the other | Field | Value | diff --git a/tests/test_falsify_adr013_s2.py b/tests/test_falsify_adr013_s2.py index 92e11b2..7a751d2 100644 --- a/tests/test_falsify_adr013_s2.py +++ b/tests/test_falsify_adr013_s2.py @@ -8,6 +8,8 @@ import re from pathlib import Path +import pytest + _ADR = Path(__file__).resolve().parent.parent / "docs" / "ADRs" / "013_sampled_forecast_wire_contract.md" @@ -43,3 +45,37 @@ def test_s2_has_no_unexplained_gid_epic(): if "gid/id epic" in s2: # keeping the phrase requires an inline explanation of what that episode was assert re.search(r"gid/id epic[^.]{0,200}(identifier|meant|ambigu)", s2) + + +def test_s2_2a_is_not_secretly_in_force(): + """§2.2a defines a field that does NOT ship. Guard the "does not" (ADR-014 §1). + + Amendment A2 admits `provenance.status` and says plainly that it is not adopted and + `contract_version` stays 1.5. That is a claim about the code, written in a document, + and this repository has been caught three times this epic by prose describing work it + had not done — here the risk runs the other way, which is worse: someone implements + A2, ships 1.6, and leaves §2.2a still saying it is not in force. A reader planning + the three-repo re-vendor would then believe it was still owed. + + So the guard is bidirectional. Either §2.2a says A2 is unadopted and the code agrees, + or the section was rewritten on adoption and this test must be rewritten with it. + """ + from views_postprocessing.contract.wire.header import _PROVENANCE_KEYS, CONTRACT_VERSION + + s2 = _s2() + if "NOT YET IN FORCE" not in s2: + pytest.skip( + "§2.2a no longer declares itself unadopted — A2 was presumably adopted. " + "Rewrite this test against whatever the section now claims; do not delete it." + ) + + assert CONTRACT_VERSION == "1.5", ( + f"§2.2a says `contract_version` remains 1.5; the header declares " + f"{CONTRACT_VERSION}. If A2 (or anything else) was adopted, the ADR is now " + "describing a contract that is not the one shipping." + ) + assert "status" not in _PROVENANCE_KEYS, ( + "§2.2a says A2 is not in force, but `status` is in the closed provenance keyset. " + "The field shipped and the amendment was never marked adopted — which leaves the " + "§10 golden fixture, three repos' vendored copies, and this document disagreeing." + )