Skip to content

[EPIC] ADR-013 Hop-B sink leg: frame-native FAO delivery (shards + sidecar + run manifest + gates) #105

Closed

Description

@Polichinel

Problem

The Hop-B half of ADR-013 does not exist. Today's outbound _save (unfao/managers/unfao.py:283-329) uploads a pandas point-estimate parquet under the ensemble's document name — the confirmed F1 invisibility: faoapi's unconditional name filter has never been able to see a forecast uploaded this way. Meanwhile everything upstream is built: pipeline-core's Hop-A publish leg ships Track-A archives, vpp's track_a_source.py verifies and assembles them, the §6 draws gate is merged (but wired nowhere), and the §10 golden fixture pins every byte the sink must produce.

Why now

The wire exists end-to-end in code from PFE to the internal store. This epic is the missing middle. The C-161 constraint (faoapi's Hop-B guard must reach production before any contract upload) orders the finish, not the building: the sink ships upload-disabled by default (ADR-013 §11.4 interlock) and cannot touch the live bucket until explicitly enabled after C-161 closes.

End state

A composed, fixture-proven Hop-B sink: contract-mode inbound (Hop-A manifest selection, await-all-configured-targets, verified assembly) → §6 no-collapse gate → per-(target,month) arrow shards → §5 sidecar (+ gid parity) → ONE run manifest uploaded LAST → all documents under name="un_fao" with §4.1a fields — with the e2e proof that fixture Track-A artifacts in produce byte-identical fixture Hop-B artifacts out. UPLOAD_ENABLED=False throughout the epic.

Layout (maintainer-approved 2026-07-20)

New subpackage views_postprocessing/unfao/wire/ (naming, header, shard, sidecar, run_manifest, source_selection, sink) + unfao/product.py (the declared FAO product: targets, S_MIN, consumer document name, upload interlock) + delivery/parity.py (gid-set parity invariant, pandas-free). Manager gains a thin declared contract-mode branch only. Principles: wire/ = one closure (CCP/CRP, screams the contract); product separate (SRP — changes with the partner product); delivery/ ← seams ← wire/ ← manager (SDP/ADP); sink composed with an injected store port (DIP).

In scope

Stories S1–S8 (linked below): product+naming, header, shard writer, sidecar+parity, run manifest, source selection, sink+wiring+e2e, closeout.

Out of scope

Legacy-path deletion (later story); S7/#92 historical inbound; partner generalization (#97 — WET stands); repo rename (#96 / D-12); track_a_source.py relocation into wire/ (future mechanical move); live upload enablement (gated on C-161).

Epic acceptance criteria

Supersedes #91 (S6 outbound) and #88 (S3 convert-at-the-door); resolves #45. Contract: docs/ADRs/013_sampled_forecast_wire_contract.md.

🤖 Generated with Claude Code

Activity

  1. added
    views-frames-adoptionAdopt the views-frames v1.0.0 data-contract
    epicA large capability spanning multiple stories
    on Jul 19, 2026
  2. added a commit that references this issue on Jul 20, 2026
  3. Polichinel commented on Jul 20, 2026

    @Polichinel
    CollaboratorAuthor

    Epic complete (2026-07-20). All 8 stories shipped (PRs #115–#122): the Hop-B sink exists, fixture-proven byte-for-byte, upload-disabled behind the §11.4 interlock. See the tracking issue #114 (all boxes checked) and ADR-013's Post-adoption record for the shipped-state entry. Live enablement awaits faoapi C-161.

    🤖 Generated with Claude Code

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

    epicA large capability spanning multiple storiesviews-frames-adoptionAdopt the views-frames v1.0.0 data-contract

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions