You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Manifest must declare {maturity, source, required-schema-version} + stamp them — faoapi fail-visible selection contract (faoapi ADR-033 / #244) #133
Filed by the faoapi seat. Cross-repo contract needed to unblock the FAO global delivery (run-0 incident, faoapi #243).
Why
faoapi is adding fail-visible selection gates (faoapi ADR-033, epic #244): it will refuse to serve a run to FAO unless the run manifest declares the run is (a) production-grade, (b) stamped with a real source, and (c) renderable by the deployed schema — surfacing the reason in /health + /provenance rather than silently serving stale/test data (which is exactly what happened with run-0: a 139-day test artifact served behind 200 OK). This is faoapi's consumer-side enforcement of views-models ADR-017 §5 tier-rule.
For a delivered run to actually reach FAO, the producer (this repo — the un_fao manager + the vpp ADR-013 wire manifest) must declare that metadata on the manifest.
source / provenance — the producing ensemble/pipeline, not "unknown". faoapi refuses unstamped provenance (source="unknown" is what's live today).
Required schema/contract version — extend the existing manifest contract_version with the served-schema version faoapi must be able to render; faoapi refuses a run it can only render degraded.
Work
Amend vpp ADR-013 (the wire contract) to add these declared fields to the run manifest.
Stamp them in the un_fao manager / wire sink (views_postprocessing/unfao/managers/unfao.py) when building the manifest.
Related decision on this repo's side — C-169 (historical co-delivery)
In contract mode, _save_contract ships the forecast run only; the global historical is coverage-validated (_check_coverage) but not uploaded. So faoapi's /historical/* + bulk s_actual go stale under a contract cutover (faoapi register C-169, ADR-028 pointer). Decide: add historical delivery to the contract path, or record that FAO stops receiving historical.
Reframed (2026-07-28) — faoapi does not gate on maturity; eligibility stays upstream
Following the views-models seat's ADR-017 reframe: faoapi is the delivered half of ADR-017's "in production = graduate AND delivered," so it must not re-judge eligibility. faoapi serves the newest complete manifested run for its line and reads no deployment/maturity label. The manifest fields are still wanted — but faoapi's use of each changes:
source — faoapi verifies present (refuses an unidentifiable run — audit, not eligibility) and surfaces it.
required schema version — faoapi verifies renderable (capability gate).
status/maturity — faoapi surfaces it (informational, in /provenance + a response flag) so an interim/shadow run isn't mistaken for graduated production; it is not a faoapi gate. It's carried for provenance/audit and for ADR-017's upstream governance.
So the eligibility decision (what may reach the bucket) stays here / at the delivery boundary, exactly as ADR-017 intends. The C-169 historical co-delivery decision is unchanged. Ref faoapi ADR-033 §2/§8 (revised).
Measured before implementing, because implementing this as written costs a contract_version bump — which lives inside the header bytes §10 pins — and therefore a golden-fixture rebuild plus a re-vendor across all three implementing repos.
Two of the three fields already ship, and the fourth claim is false.
the ask
measured 2026-08-05
stamp source; "source="unknown" is what's live today"
already delivered as provenance.ensemble. views-faoapi reads exactly that key — dataset_service.py:793, wire_prov.get("ensemble", "unknown"). The "unknown" seen live is the legacy path, not the wire path
required schema version
already delivered as contract_version — on the run manifest and in every shard header. faoapi already reads manifest.contract_version
stamp maturity
genuinely missing — see below
"_save_contract ships the forecast run only; the global historical is not uploaded"
false. It uploads it, category="historical", under the same upload_enabled interlock as the forecast. /historical/* does not go stale under a contract cutover
On maturity — it is real, and it is not this repo's to stamp
Two facts, both checkable:
It belongs in provenance, which §2.1 clause 1 closes. Adding a key there is a contract amendment, not a free addition. Purely additive → MINOR, 1.5 → 1.6.
Hop-B shard headers are the Hop-A headers re-embedded untouched (contract/wire/sink.py:110). This repo 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 nobody told it. The stamp belongs to the Hop-A producer — views-models. Our part is to forward it, which needs no code change once the key is admitted.
Note also that the request is written against the run manifest, but faoapi reads these from the shard header's provenance. That mismatch is most of why the ask looked bigger than it is.
What landed
ADR-013 §2.2a — Amendment A2, drafted and not in force (contract_version stays 1.5). It defines provenance.status, states why it is an amendment, what adoption costs, who can stamp it, and a named trigger: adopt when the next §10 fixture re-vendor happens for another reason — C-72/#174 (pyarrow) being the one expected, since it also changes delivered bytes and also needs all three repos to move together. Going alone costs a full three-repo coordination for one optional field that faoapi explicitly surfaces rather than gates on (dataset_service.py:788).
Guarded bidirectionally by test_s2_2a_is_not_secretly_in_force — the worse failure is A2 shipping while §2.2a still says it has not, leaving three repos believing a re-vendor is still owed.
**§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
Acceptance criteria
ADR-013 amended — §2.2a Amendment A2, drafted with an explicit adoption trigger.
un_fao stamps all three — source and the schema version already ship; maturity is not ours to stamp and the amendment says so and names who it belongs to.
C-169 (historical co-delivery) decided and recorded — the premise is false, so there was no decision. Historical already co-delivers. views-faoapi should close its C-169 or re-scope it, and /historical/* staleness is not a contract-cutover risk.
One thing for the faoapi seat, free
faoapi's freshness signal reads provenance.created_at and falls back to the store's $createdAt. We already ship generated_at as a top-level header key — and §2.1 leaves top-level keys open, so faoapi can read the producer's own timestamp today at zero contract cost, no amendment required.
Registered here as C-85 (Tier 3): a cross-repo ask adopted on its stated terms without anyone re-measuring. Filed in good faith and wrong for an instructive reason — it named the artifact in this repo's vocabulary while the consumer reads a different one. Not a criticism of the filing; a note that the trip between repos is where vocabulary gets lost.
Leaving this issue open for the maintainer to route the views-models half.
Closing — two of three fields shipped before this was filed, the premise was false, and the third belongs to views-models.
Verified against the tree rather than the issue:
field
state
source
ships as provenance.ensemble — contract/wire/header.py, enforced as a closed sub-object
required schema version
ships as contract_version in both artefacts — the header and the run manifest
maturity
genuinely absent
The C-169 premise is false. It said /historical/* goes stale; the historical leg uploads with category="historical" inside _save_contract, so it does not.
And faoapi withdrew the gate. Its 2026-07-28 comment states faoapi does not gate on maturity.
maturity is recorded as ADR-013 §2.2a Amendment A2, explicitly NOT IN FORCE, guarded bidirectionally by tests/test_falsify_adr013_s2.py::test_s2_2a_is_not_secretly_in_force. Its named trigger is the next §10 re-vendor — which is #174, not this issue. Register C-85 exists solely to record that this ask was adopted without anyone re-measuring first.
Nothing is owed here. Leaving it open reads as owed work that is not.
Activity
Polichinel commentedon Jul 28, 2026
Reframed (2026-07-28) — faoapi does not gate on maturity; eligibility stays upstream
Following the views-models seat's ADR-017 reframe: faoapi is the delivered half of ADR-017's "in production = graduate AND delivered," so it must not re-judge eligibility. faoapi serves the newest complete manifested run for its line and reads no deployment/maturity label. The manifest fields are still wanted — but faoapi's use of each changes:
source— faoapi verifies present (refuses an unidentifiable run — audit, not eligibility) and surfaces it.status/maturity— faoapi surfaces it (informational, in/provenance+ a response flag) so an interim/shadow run isn't mistaken for graduated production; it is not a faoapi gate. It's carried for provenance/audit and for ADR-017's upstream governance.So the eligibility decision (what may reach the bucket) stays here / at the delivery boundary, exactly as ADR-017 intends. The C-169 historical co-delivery decision is unchanged. Ref faoapi ADR-033 §2/§8 (revised).
Merge pull request #226 from views-platform/docs/adr013-provenance-ma…
Polichinel commentedon Aug 5, 2026
Measured before implementing, because implementing this as written costs a
contract_versionbump — which lives inside the header bytes §10 pins — and therefore a golden-fixture rebuild plus a re-vendor across all three implementing repos.Two of the three fields already ship, and the fourth claim is false.
source; "source="unknown"is what's live today"provenance.ensemble. views-faoapi reads exactly that key —dataset_service.py:793,wire_prov.get("ensemble", "unknown"). The"unknown"seen live is the legacy path, not the wire pathcontract_version— on the run manifest and in every shard header. faoapi already readsmanifest.contract_versionmaturity_save_contractships the forecast run only; the global historical is not uploaded"category="historical", under the sameupload_enabledinterlock as the forecast./historical/*does not go stale under a contract cutoverOn maturity — it is real, and it is not this repo's to stamp
Two facts, both checkable:
provenance, which §2.1 clause 1 closes. Adding a key there is a contract amendment, not a free addition. Purely additive → MINOR, 1.5 → 1.6.contract/wire/sink.py:110). This repo 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 nobody told it. The stamp belongs to the Hop-A producer — views-models. Our part is to forward it, which needs no code change once the key is admitted.Note also that the request is written against the run manifest, but faoapi reads these from the shard header's provenance. That mismatch is most of why the ask looked bigger than it is.
What landed
ADR-013 §2.2a — Amendment A2, drafted and not in force (
contract_versionstays 1.5). It definesprovenance.status, states why it is an amendment, what adoption costs, who can stamp it, and a named trigger: adopt when the next §10 fixture re-vendor happens for another reason — C-72/#174 (pyarrow) being the one expected, since it also changes delivered bytes and also needs all three repos to move together. Going alone costs a full three-repo coordination for one optional field that faoapi explicitly surfaces rather than gates on (dataset_service.py:788).Guarded bidirectionally by
test_s2_2a_is_not_secretly_in_force— the worse failure is A2 shipping while §2.2a still says it has not, leaving three repos believing a re-vendor is still owed.views-postprocessing/docs/ADRs/013_sampled_forecast_wire_contract.md
Lines 361 to 400 in 8db5602
Acceptance criteria
un_faostamps all three —sourceand the schema version already ship; maturity is not ours to stamp and the amendment says so and names who it belongs to./historical/*staleness is not a contract-cutover risk.One thing for the faoapi seat, free
faoapi's freshness signal reads
provenance.created_atand falls back to the store's$createdAt. We already shipgenerated_atas a top-level header key — and §2.1 leaves top-level keys open, so faoapi can read the producer's own timestamp today at zero contract cost, no amendment required.Registered here as C-85 (Tier 3): a cross-repo ask adopted on its stated terms without anyone re-measuring. Filed in good faith and wrong for an instructive reason — it named the artifact in this repo's vocabulary while the consumer reads a different one. Not a criticism of the filing; a note that the trip between repos is where vocabulary gets lost.
Leaving this issue open for the maintainer to route the views-models half.
Polichinel commentedon Aug 13, 2026
Closing — two of three fields shipped before this was filed, the premise was false, and the third belongs to views-models.
Verified against the tree rather than the issue:
sourceprovenance.ensemble—contract/wire/header.py, enforced as a closed sub-objectcontract_versionin both artefacts — the header and the run manifestmaturityThe C-169 premise is false. It said
/historical/*goes stale; the historical leg uploads withcategory="historical"inside_save_contract, so it does not.And faoapi withdrew the gate. Its 2026-07-28 comment states faoapi does not gate on maturity.
maturityis recorded as ADR-013 §2.2a Amendment A2, explicitly NOT IN FORCE, guarded bidirectionally bytests/test_falsify_adr013_s2.py::test_s2_2a_is_not_secretly_in_force. Its named trigger is the next §10 re-vendor — which is #174, not this issue. Register C-85 exists solely to record that this ask was adopted without anyone re-measuring first.Nothing is owed here. Leaving it open reads as owed work that is not.