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
62 changes: 40 additions & 22 deletions docs/operations/correction_procedure.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,21 @@ defect; fix the producer and rebuild.

## 3. Correct on the wire

**The contract has no retraction primitive. It has supersession**, and that is
deliberate: the manifest-last commit ordering means the way to replace a run is to
publish a *new complete run*, not to mutate an old one.
**The intended policy is WITHDRAWAL. What is implemented is SUPERSESSION.** That gap is
deliberate, recorded, and you need to know about it before you act.

- **Intended** (operator decision, 2026-08-02): a delivery found to be materially wrong
should be *removed or blocked* so it can no longer be served. Data known to be wrong
should not stay retrievable through the production interface.
- **In force**: supersession. The manifest-last commit ordering means a run is replaced
by publishing a *new complete run*; the old one stays in the bucket and remains
retrievable by a client that asks for it specifically.

Supersession is in force **because it is what the wire does**, not because it was chosen.
Withdrawal has no mechanism today: it needs an ADR-013 amendment and matching work in
views-faoapi. Put to FAO as **Pre-Release Note 07, Decision Point B.2**, which also asks
whether they have an audit or reproducibility requirement that would argue *against*
withdrawal — that answer decides whether the amendment is worth building.

1. Fix the cause — the lookup, the producer, or the code — and land it.
2. Rebuild any affected artifact. For the lookup:
Expand Down Expand Up @@ -112,31 +124,37 @@ because it is *correct*. Two consequences:

---

## 4. Telling the UN FAO — **not decided; the operator must answer this**
## 4. Telling the UN FAO

Everything above can be executed by whoever is on the keyboard. This cannot, and it is
the step that matters most to the partner.
**Simon Polichinel von der Maase is responsible for making contact.** Not an automated
alert — a correction needs judgement about scope and impact that an automated message
cannot carry.

**Two questions, in plain language:**
**Recipients and channel are recorded outside this repository.** This repo is **public**,
and publishing named FAO staff members' email addresses in it is not something to do by
default. The contacts are in the FAO-02 project materials
(`brain/2_projects/fao02/…/prerelease_notes/fao_02_pre_release_note_07/`, Decision
Point B.1) and in the operator's address book.

> **1. When a delivery is found to be wrong, who contacts the UN FAO, through what
> channel, and how quickly?**
> Right now nobody has said. There is no named person, no address, and no expectation
> about timing — so in practice the answer would be improvised by whoever noticed,
> under time pressure, which is the worst moment to invent a process.
**Send as soon as the scope of the error is established** — not after a correction has
been prepared. The partner's ability to act is time-sensitive and independent of our
remediation timeline.

> **2. Does the UN FAO expect us to *retract* the bad delivery, or to *supersede* it?**
> These need different behaviour. Supersession is what the contract does today: the old
> run stays in the bucket and a newer one wins. Retraction would mean removing or
> marking the old run so it cannot be served — which the wire has no mechanism for, and
> which would need an ADR-013 amendment and agreement from views-faoapi.
> **This is a question for them, not a decision for us.**
### Still awaiting FAO's answer

Per `CLAUDE.md`, anything touching an external party is the operator's call. Until both
are answered, treat this step as: **stop, and ask Simon.** Do not contact the partner
ad hoc; an inconsistent first message is harder to correct than a slow one.
Two things are proposed but not confirmed, and both are put to FAO in **Pre-Release
Note 07, Topic B**:

---
1. **Are those the right recipients, and is there an expected notification period?** A
named individual on leave is a single point of failure in exactly the situation where
delay is costly. FAO may prefer a shared address or a rota.
2. **Does FAO expect a correction notice for *every* error, or only one material to
published outputs?** A wrong admin label on a handful of cells and a wrong forecast
across a region are different events; treating them alike either floods them with
immaterial notices or buries a serious one.

Until FAO answers, follow what is written above. Do not improvise a different channel —
an inconsistent first message to a partner is harder to correct than a slow one.

## 5. Preserve the evidence

Expand Down
10 changes: 10 additions & 0 deletions reports/technical_risk_register.md
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,16 @@ See also C-40 (the inheritance/representation coupling this migration unwinds),

Pinned by three checks in `tests/test_doc_accuracy.py`: the document exists and names the identification fields; it describes ADR-013 mechanisms and none of the deleted ones; and it still flags its undecided step.

**⚠ UPDATE 2026-08-02, later the same day — the operator answered the PRIO half; the FAO half is now formally asked.**

- **Who notifies:** **Simon Polichinel von der Maase**, by direct email, as soon as the scope of the error is established. Adopted.
- **Treatment of an affected delivery:** the intended policy is **withdrawal**. What is *implemented* is **supersession**, and the procedure now says so explicitly — supersession is in force because it is what the wire does, not because it was chosen. Withdrawal needs an ADR-013 amendment plus views-faoapi work, and whether that is worth building depends on FAO's answer about audit requirements.
- **Put to FAO** as **Pre-Release Note 07, Topic B** (Decision Points B.1 and B.2), which also records the interim defaults in force as placeholders rather than policy.

Recipients are deliberately **not in this repository**, which is public; naming a responsible person on our side is one thing, publishing an external organisation's individual email addresses is another. `tests/test_doc_accuracy.py` now refuses partner address strings anywhere in the repo, mutation-proven — and fired on the first draft of this very sentence, which spelled the pattern out. Third time in this epic that a guard has caught the prose explaining it (after S3's retired contract name and S5's ledger-schema docstring); the fix is the same each time — name the thing without spelling it.

The original residual, kept for the record:

**⚠ RESIDUAL — one step is written but NOT decided, and it is the step that reaches the partner.** Two questions belong to the operator (`CLAUDE.md`: anything touching an external party):

1. **Who contacts the UN FAO when a delivery is found wrong, through what channel, and how fast?** No named person, no address, no timing expectation. In practice it would be improvised by whoever noticed, under time pressure.
Expand Down
72 changes: 63 additions & 9 deletions tests/test_doc_accuracy.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,16 +343,70 @@ def test_the_correction_procedure_describes_the_delivery_that_exists():
)


def test_the_procedure_states_the_questions_only_the_operator_can_answer():
"""The two external-party decisions must stay visible, not quietly become defaults.
def test_the_procedure_names_who_notifies_and_still_flags_what_fao_has_not_answered():
"""Half of §4 is decided; half is not, and both halves must stay visible.

**Decided 2026-08-02 (operator):** Simon Polichinel von der Maase makes contact, and
the intended treatment of a bad delivery is *withdrawal*. **Not decided:** whether
FAO agrees the recipients and timing (B.1), and whether they have an audit
requirement arguing against withdrawal (B.2). Both are put to them in Pre-Release
Note 07, Topic B.

An earlier version of this test asserted the document said "not decided", and its
docstring instructed whoever removed that marker to replace it with the decision.
That is what happened — the guard fired on the operator's answer, which is the
behaviour it was written for rather than a false positive.

The failure mode it now guards is subtler: a half-answered question quietly becoming
a whole answer, so the outstanding half stops being asked.
"""
text = _CORRECTION.read_text()
assert "Simon Polichinel von der Maase is responsible" in text, (
"the procedure no longer names who contacts the partner — an unowned step is "
"improvised by whoever notices, under time pressure"
)
assert "Still awaiting FAO's answer" in text, (
"the procedure no longer flags what FAO has not answered. If they have "
"answered, record the answer and the date — do not simply drop the question."
)
assert "Pre-Release Note 07" in text, (
"the procedure must cite where the outstanding questions were put, or they "
"become questions nobody remembers asking"
)


def test_the_procedure_distinguishes_intended_policy_from_what_is_implemented():
"""The gap that would otherwise be discovered mid-incident.

Who contacts the UN FAO, and whether they expect retraction or supersession. Per
CLAUDE.md both are the operator's; the failure mode is that an undecided step gets
silently improvised the first time it is needed, under time pressure.
Withdrawal is the decision; supersession is what the wire actually does, and it is
in force only because nothing else exists. An operator reading this at 22:00 must
not believe a bad delivery becomes unretrievable when it does not.
"""
text = _CORRECTION.read_text()
assert "not decided" in text.lower(), (
"the procedure no longer flags its undecided step — if it has been decided, "
"replace the marker with the decision and say who made it"
assert "intended policy is WITHDRAWAL" in text.replace("**", "")
assert "implemented is SUPERSESSION" in text.replace("**", "")
assert "ADR-013 amendment" in text, (
"the procedure must say what withdrawal would COST — otherwise the gap reads "
"as an oversight rather than as unbuilt work with a known price"
)


def test_no_partner_contact_details_are_published_in_this_repository():
"""This repo is public. FAO staff email addresses do not belong in it.

The contacts live in the FAO-02 project materials and the operator's address book.
Naming a responsible person on our side is fine; publishing an external
organisation's individual addresses to a public repository is not something to do
as a side effect of documenting a runbook.
"""
offenders = []
for doc in (*sorted(_REPO.rglob("*.md")), *sorted(_PKG.rglob("*.py"))):
if ".git" in doc.parts:
continue
for number, line in enumerate(doc.read_text(errors="ignore").splitlines(), 1):
if "@fao.org" in line.lower():
offenders.append(f"{doc.relative_to(_REPO)}:{number}")
assert not offenders, (
f"partner contact addresses appear in this public repository: {offenders}. "
"Keep them in the project materials; reference the decision, not the address."
)
assert "UN FAO" in text and "supersede" in text
Loading