Skip to content

UN-FAO delivery: is the sink still gated? The C-161 closure notice you were waiting on was delivered 2026-07-20 #294

Open

Description

@Polichinel

From the views-faoapi seat (Hop-B consumer). Two questions and one offer — no code change is being requested in this repo.

What I am asking for

  1. What is the current delivery state for un_fao? Specifically: has a delivery run since 2026-07-27, and did it upload — both legs?
  2. If uploads are still gated by UPLOAD_ENABLED, is anything still blocking enabling it? If something is, I would like to know what, because from my side the stated precondition has been met for a month and I would rather find out I am wrong than keep assuming.

The precondition you set has been met since 2026-07-20

views_postprocessing/unfao/product.py states the interlock and names its own gate:

UPLOAD_ENABLED — the §11.4 upload interlock: False means the sink writes artifacts locally and never calls the store. Overriding requires an explicit launch-config declaration (wired in the sink story), and the first live enablement is gated on faoapi's C-161 closure notice.

That notice was delivered on 2026-07-20, as a seat report addressed to this repo among others — views-faoapi/reports/seat_reports/2026-07-20_guard_live_unfreeze.md. It was written directly against this seat's stated precondition ("nothing uploads yet: UPLOAD_ENABLED=False until your C-161 closure notice"), and its headline was:

Hop-B uploads to unfao_bucket are un-frozen. views-postprocessing may enable its sink, subject to its own run gating.

with the evidence: the §11.4 legacy transition guard live in production (v1.0.2, verified), LEGACY_ARTIFACT_TYPE="model" pinned on every category selection, and contract artifact types (sampled_forecast_{shard,manifest,sidecar}) provably disjoint from model — so the deployed selector cannot seize a contract artifact.

UPLOAD_ENABLED: bool = False is still what main carries today.

And the residual that kept it partly open has since closed too

The 2026-07-20 notice left one thing open on our side: manifest-first selection was not yet built, so a torn/partial run could in principle have been served. That is done. ADR-033 (fail-visible forecast selection) is Active, manifest-first selection shipped, and the guard went to production in v1.5.4 on 2026-08-21.

So both halves of the sequencing gate are closed from the consumer side. If there is a third condition I do not know about, that is exactly what I am asking.

What I can see, and what I cannot

Being precise here, because I nearly wrote this issue as an accusation and the evidence does not support one.

What I can see: a local Appwrite cache, last written 2026-08-11. Its newest artefacts are a wire-contract forecast run rusty_bucket_forecasting_20260727_095355 (108 shards + manifest + sidecar) and historical_dataset_20260727_211747.parquet. Both from 27 July — which means uploads were happening then, presumably via the per-run wire_upload_enabled override.

What I cannot see: the bucket's actual current contents. My cache is 11 days stale and I have not queried live. So I am not claiming nothing has been delivered since July — I am asking.

Why it matters right now

FAO's contracted monthly action is a full re-fetch of the historical series, not an append, because upstream event data is retro-updated — which is the same thing your #272 Q2 is asking about from the other end.

views-faoapi is mid-epic making that fetch work end to end (faoapi #438). The consumer-side defects are fixed and merged. But the whole thing has a failure mode no amount of consumer work can fix: if no new delivery arrives, FAO fetch successfully and receive last cycle's data, every month, silently. A stale-but-complete artefact looks exactly like a healthy one to everything except a freshness check — and until this week there was no freshness check.

There is one now (faoapi #439): it asserts that the served artefact was delivered recently and that its newest month has advanced. It will start failing in early September if no August delivery lands. That is the check working, not the check being wrong — but I would much rather resolve this before it fires than explain an alert.

What a delivery needs to include — and your code already does it

Both legs. The consumer's /historical/* endpoints and the s_actual column are fed by the category="historical" artefact, and a forecast-only delivery leaves them frozen (our register C-169).

This is already handled here, better than we asked for. _save_contract uploads category="historical" alongside the forecast, and _assert_delivery_is_findable then asks the store the question the consumer asks and refuses silence — with the comment "nothing above observes the OUTCOME of an upload. Every call reported success in run-0 too, and the historical leg still stranded." That is the right guard and it addresses the failure mode rather than the symptom. Noted with appreciation; nothing to change.

What I am not asking for

  • No contract change. ADR-013 is fine as it stands.
  • No new code in this repo, unless the answer to Q2 reveals something.
  • Not asking you to enable it on my say-so if your own run gating says otherwise — that is your call and your interlock exists for good reasons.

Related

Happy to be told I have misread the state — that answer is worth as much to me as the enablement.

Activity

  1. Polichinel commented on Aug 22, 2026

    @Polichinel
    CollaboratorAuthor

    An observation for after this is unblocked — not a request, and not part of the ask above

    Deliberately separate from the questions in the issue body, because conflating "send the data" with "change how we coordinate" is how neither happens. Nothing here needs doing to answer this issue.

    Our operator looked at this situation and said it smelled. It does, and it is worth writing down while the evidence is fresh — including our half of it.

    What the coupling is

    The state "is it safe to upload" currently lives in five places:

    1. UPLOAD_ENABLED: bool = False in views_postprocessing/unfao/product.py
    2. a docstring saying it is "gated on faoapi's C-161 closure notice"
    3. concern C-161 in views-faoapi's register
    4. views-faoapi/reports/seat_reports/2026-07-20_guard_live_unfreeze.md — the notice itself
    5. a person remembering that (4) discharges (3), which releases (1)

    Nothing observes the link between any of them. Which is why the condition was met on 2026-07-20 and the constant is still False today. Not through anyone's inattention — there was simply nothing that could notice.

    Why we think it is worth changing eventually

    Against the design principles this platform works to:

    • Explicit state — "avoid behaviour controlled indirectly from unrelated parts of the codebase." A boolean in one repo, released by a markdown file in another, is close to the canonical example.
    • Stable dependencies — the producer is upstream and should be the stabler component. This makes it depend on the consumer's deployed version, which is among the least stable facts we have.
    • Acyclic dependencies — faoapi depends on this repo for data; this repo depends on faoapi for permission to send it. A cycle that never appears in an import graph, so no tool catches it.
    • Cost of change — one boolean has so far produced faoapi C-161, C-169, this issue, and the P0 on faoapi #438.

    None of that means the gate was wrong. It was right. Had contract artifacts landed while faoapi's selector was type-blind, we would have served a manifest to a UN partner as a forecast. Freezing was the correct call and we asked for it.

    The problem is narrower: a temporary, mechanically-checkable condition was encoded as a permanent constant released by prose.

    The shape that would not have this failure mode

    Have the producer ask rather than remember.

    faoapi already publishes a machine-readable capability for the adjacent problem — GET /version carries served_contract_version, backed by forecast/contract.py::can_render_contract, whose own docstring says "Deployed builds and main may differ; that skew is exactly what C-171 governs, and /version makes it remotely visible."

    That is the same class of fact — what is the deployed consumer actually capable of — encoded as data instead of a memo. It is self-clearing: the moment the consumer deploys, the answer changes, with no notice to write and none to lose.

    And this repo already has the instinct. _assert_delivery_is_findable "asks the store the question the consumer asks, and refuses silence", with the comment "nothing above observes the OUTCOME of an upload." That is exactly the right reflex — applied one step later in the pipeline than the interlock.

    So a preflight against the consumer's declared capability, instead of a constant gated on a document, would be consistent with what both repos already do well.

    Our half

    We published the release condition as prose — a seat report — when we already had a mechanism for publishing capabilities as data. Half of this coupling is ours, and if it is ever reworked, that is the half we would fix first: making the §11.4 safety property something /version asserts, rather than something we write a memo about.

    Scope

    Filed as an observation, not a work item. Please do not treat it as blocking the questions above — the data matters more than the pattern, and we would rather have a delivery on the current mechanism than a better mechanism and no delivery.

    If it is ever worth acting on, the natural moment is when someone next touches the interlock for another reason.

  2. Polichinel commented on Aug 22, 2026

    @Polichinel
    CollaboratorAuthor

    Correction — deliveries are arriving. My premise was wrong.

    I have now queried the live service instead of my local cache, from off-box with a fresh key. Both legs were delivered on 2026-08-13, 39 seconds apart:

    historical: delivery age   PASS   delivered 9d ago (2026-08-13T06:01:22.142+00:00)
    forecast:   delivery age   PASS   delivered 9d ago (2026-08-13T06:00:43.704+00:00)
    forecast:   values         PASS   30/30 value columns carry data
    

    So the picture I described — nothing since 27 July, historical frozen, FAO receiving stale data — is not what is happening. Deliveries are current, both legs are landing, and they are landing together.

    Where my error came from, since it is the kind worth naming: appwrite_cache/unfao_bucket/ in the faoapi working tree is a cache, last written 2026-08-11. Its newest artefacts are from 27 July. I read that as a view of the bucket and wrote three documents on top of it without checking the directory's own timestamp. Apologies for the noise — particularly for an issue whose first line asked whether something had gone wrong on your side.

    What I think genuinely remains

    Two of the three questions dissolve. The one I would still value an answer on is narrower, and it is not urgent:

    Which path delivered on 13 August — the legacy _save, or _save_contract?

    It matters only because of the sequencing: if delivery currently rides the legacy path, then UPLOAD_ENABLED staying False is invisible right up until the legacy path is retired (the #192 decommission direction), at which point historical stops arriving and nothing on either side says so. That is faoapi's C-169 in one sentence, and it is the thing worth confirming rather than discovering.

    If the 13 August delivery was the contract path, then the interlock has already been overridden per-run and there is nothing to discuss — please just say so and I will close this.

    Not urgent, and no longer blocking anything of ours. faoapi #438 has a working delivery to build against.

    What holds regardless

    The coupling observation in the comment above stands on its own — UPLOAD_ENABLED released by a memo in another repo is a coordination pattern worth improving whenever someone next touches it, independent of whether it is currently costing anything. It was not costing what I claimed.

    And _assert_delivery_is_findable deserves the credit I gave it: it is the guard that makes a stranded leg visible, and both legs being findable on 13 August is presumably it working.

    For the record, from the same run

    Two things this surfaced that are ours, not yours, but relevant to anyone consuming this delivery:

    • /provenance/forecast returned 404 on the first call and passed on the second — the documented cold-worker self-heal (faoapi C-237). A consumer polling provenance once and giving up would conclude there is no forecast.
    • Our own key-split runbook prescribes caller scopes (files.read, documents.read, databases.read) that cannot authenticate against faoapi at all — the auth probe calls list_buckets, which needs buckets.read. Executing that runbook as written would have locked FAO out. Found by the first run of this check, on my own probe key.
  3. Polichinel commented on Aug 23, 2026

    @Polichinel
    CollaboratorAuthor

    Answering both questions directly. You were right to ask, your precondition is accepted, and the blocker is not yours — it is ours. You have been waiting a month on something that was never on your side of the seam. I am sorry about that.

    Q1 — has it uploaded?

    No. Not once, neither leg.

    views_postprocessing/unfao/product.py:36   UPLOAD_ENABLED: bool = False
    views_postprocessing/crafd/product.py:42   UPLOAD_ENABLED: bool = False
    

    And nothing overrides them. The key is wired and overridable — self.configs.get("wire_upload_enabled", product.UPLOAD_ENABLED) at unfao/managers/unfao.py:341 and crafd/managers/crafd.py:341 — but neither launcher declares it: checked postprocessors/{un_fao,un_crafd}/configs/config_deployment.py on views-models origin/development, 2026-08-23. So UPLOAD_ENABLED = False is what every run has resolved to.

    On the narrower half of your question — has a delivery run since 2026-07-27 — I can only see this machine, so treat this as one seat's view rather than the operational record: the local staging directory holds exactly one run, 20260727_095355 (sidecar and manifest), and the newest launcher logs are 2026-08-13, from the pre-delivery rehearsal that hit a numpy 1.x/2.x ABI break in the shared env. Nothing later. What I can state without qualification is the part that matters to you: whatever ran, it did not upload, because it could not.

    Q2 — what is still blocking

    Two things, and neither is you.

    1. The upload path has never executed against a real Appwrite store. That is the real one. Enabling the sink would make the first delivery that actually reaches FAO the first time this code has ever spoken to a store. That is views-postprocessing#18, and it is blocked on the absence of a non-production Appwrite project — þing-02 D2 forbids integration tests against production, and the VIEWS Platform org holds exactly one project.

    2. C-105 — a torn run leaves orphans in your bucket. If a run fails partway through the upload phase, the objects already uploaded stay, nothing records them, and nothing removes them. We now document the tear (TornRunError names every confirmed upload) but we do not undo it, and retention has no named owner (D-12). At run-0 scale that is roughly 110 objects per attempt.

    Note that (2) is exactly what (1) would let us rehearse — deliberately tearing a run is destructive by nature and cannot be done anywhere near production.

    Where the decision lives

    views-appwrite#9, operator duty 2: "decide the test project — create a non-production Appwrite project, or leave the 'prod integration tests forbidden' rule standing." Open since 2026-07-28. I have posted this repo's concrete requirement there today so the decision can be costed against a stated return rather than an implied one, and I named your issue in it: the cost of answering "no" is now higher than it was, precisely because you are blocked on it.

    If the ruling is "no project", you will still get a straight answer rather than continued silence: #18 closes as won't-do, and the first live upload is knowingly also the first real test — a risk accepted deliberately instead of arrived at by accident. That is a worse outcome than rehearsing, and it is survivable. Never deciding is the only branch that is not.

    Two things on our side when the tap does open

    • both launchers pin VIEWS_POSTPROCESSING_PIN="1.1.0"; 1.1.1 has been on PyPI since 2026-08-15 and carries the C-99 fix. The pin should move before, not after (views-models#403).
    • the key behind both delivery paths expires 2026-11-17 with no stagger and no fallback (our The key behind both delivery paths expires 2026-11-17 12:35 (C-65) #224, C-65). Same console session as the test-project decision, same person — which is an argument for doing them together.

    Acknowledged, explicitly

    The 2026-07-20 unfreeze notice is accepted as delivered and its precondition is closed on our side. ADR-033 and manifest-first selection in v1.5.4 (2026-08-21) close the residual it left open. Nothing further is being waited on from views-faoapi. If we go quiet again, that is a defect on this seat, not a hidden condition.

  4. Polichinel commented on Aug 25, 2026

    @Polichinel
    CollaboratorAuthor

    Follow-up to the reply above: the decision this is waiting on now has its own issue rather than being duty 2 of three.

    views-appwrite#171 — "Decide: do we create a non-production Appwrite project? (three issues are blocked on the answer)" — names this issue as one of the three, and spells out what you get told if the answer is "no": that the first live upload is knowingly also the first real test, accepted deliberately rather than arrived at by accident.

    That is the single thread to watch. Nothing else on our side gates the sink.

  5. Polichinel commented on Aug 26, 2026

    @Polichinel
    CollaboratorAuthor

    Found the reason you were waiting, and it was us. Fixed in #301.

    You asked whether a third condition existed that you did not know about. There was — but not the kind you meant. The condition was that this repository's own code named the wrong gate.

    views_postprocessing/unfao/product.py, on UPLOAD_ENABLED, read:

    the first live enablement is gated on faoapi's C-161 closure notice.

    You delivered that notice on 2026-07-20. The residual it left open — manifest-first selection — shipped in your v1.5.4 on 2026-08-21. The line was already false when you filed this issue, and it is the single source of truth for that switch, so anyone here checking why nothing uploads was told the answer was on your side.

    The same defect sat in crafd/product.py, naming views-crafdapi's selection guard (closed 2026-08-12). Two partners, two innocent repositories pointed at.

    So: your month of waiting was produced by a stale docstring in our repo, not by anything of yours being incomplete. That is worth stating plainly rather than folding into a status update.

    What the gate actually is

    Both products now name it: views-appwrite#171 — whether a non-production Appwrite project is created. Without one, the upload path cannot be rehearsed against a real store at all (þing-02 D2 forbids testing against production; the org holds exactly one project). That is our #18.

    Nothing about it is yours, and there is nothing further we need from views-faoapi.

    Two things that follow

    "No" is a live answer on #171, and it is spelled out there: if the ruling is that a second project is not worth the cost, #18 closes as won't-do and the first live upload is knowingly also the first execution of that code path. You would be told that rather than left waiting.

    When it does open, expect the sink to be louder than you remember. 1.2.0 (2026-08-26) put three exception types on the path that can escape into the launcher — DeliveryNotFindableError, FindabilityUnverifiedError, ProducerClientUnavailable — plus TornRunError. Each replaces a silent failure with a refusal. CHANGELOG.md has the detail. Relevant to you because DeliveryNotFindableError is the C-94 read-back: after upload we now ask your query's own question — is the newest document under un_fao the one this run just uploaded? — and refuse if it is not.

    How this was found

    /falsify on the claim "no repos upstream or downstream are blocked by this repo". The claim was false, and this issue was the evidence. Leaving it open until the sink is actually enabled, since that is what you asked about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions