diff --git a/.github/workflows/run_pytest.yml b/.github/workflows/run_pytest.yml index 94773d6..7e7b354 100644 --- a/.github/workflows/run_pytest.yml +++ b/.github/workflows/run_pytest.yml @@ -67,14 +67,6 @@ jobs: path: _siblings/views-appwrite fetch-depth: 0 - - name: Checkout views-crafdapi (sibling) - uses: actions/checkout@v3 - with: - repository: views-platform/views-crafdapi - ref: main - path: _siblings/views-crafdapi - fetch-depth: 0 - - name: Set up Python uses: actions/setup-python@v4 with: @@ -97,7 +89,6 @@ jobs: - name: Run tests env: VIEWS_APPWRITE: ${{ github.workspace }}/_siblings/views-appwrite - VIEWS_CRAFDAPI: ${{ github.workspace }}/_siblings/views-crafdapi run: | set -e poetry run pytest tests/ diff --git a/docs/ADRs/016_ci_read_access_to_private_siblings.md b/docs/ADRs/016_ci_read_access_to_private_siblings.md index ee77a84..bb0cba9 100644 --- a/docs/ADRs/016_ci_read_access_to_private_siblings.md +++ b/docs/ADRs/016_ci_read_access_to_private_siblings.md @@ -115,7 +115,7 @@ Who is who, once and in one table: | repository | public? | fetched by CI? | why not, if not | |---|---|---|---| | **views-appwrite** | yes | **yes** | — carries the registry drift checks | -| **views-crafdapi** | yes | **yes**, temporarily | — retires when views-crafdapi#53 lands, not before (ADR-017 §5 is per partner) | +| **views-crafdapi** | yes | **no**, since 2026-08-12 | it was fetched for one test; that check read the consumer's source, broke twice in a day when both consumers improved theirs, and was deleted rather than repaired (ADR-017 §7 and its §5 erratum) | | **views-datafactory** | yes | no | its checks need raw data that is not in its git repository; fetching it turns an honest skip into a crash | | **views-faoapi** | **no** | no | private — no amount of workflow configuration reaches it. That case is [ADR-017](017_facts_across_a_private_boundary.md) | @@ -148,12 +148,14 @@ Each rule is a plain function, so each is also run against a deliberately broken to prove it objects. A rule only ever tried against a correct file is a rule nobody has watched fail. -**One of these downloads is temporary, and it is worth knowing which.** `views-crafdapi` is -fetched for exactly one test. [ADR-017](017_facts_across_a_private_boundary.md) §7 replaces -that test with one that reads a public declaration instead — at which point this download -buys nothing and goes. That has been decided and is not yet done; it waits on the -declaration landing in the registry. That is a decision this document cannot make on its own, which -is why it is recorded there and cross-referenced here. +**One of these downloads was temporary, and it is worth knowing what became of it.** +`views-crafdapi` was fetched for exactly one test — a check that read the consumer's own +source. [ADR-017](017_facts_across_a_private_boundary.md) §7 said this repository was never +entitled to depend on another's file layout, and on 11 and 12 August 2026 both consumers +proved it by refactoring a literal into a named constant and reddening this repository +twice in a day. The check was deleted rather than repaired, and the download went with it. +What that costs is register C-92: nothing here verifies that a consumer's query uses the +name it declares. ### §6 A repository CI expects but cannot find turns the build red @@ -311,8 +313,12 @@ the record that owns it. **Update 2026-08-11.** The declaration now exists (views-appwrite#75), views-faoapi checks itself against it (their #379), and this repository's check reads the registry rather than that repository's source. So the FAO half is no longer laptop-only, and no -credential was issued. What remains is CRAF'd's consumer-side check (views-crafdapi#53); -until it lands, that partner's source-read stays, and with it that partner's fetch. +credential was issued. CRAF'd's consumer-side check landed the next day (views-crafdapi#53, closed 2026-08-12). + +**Amended 2026-08-12.** Both source-reads are now gone, and the crafd fetch with them — +but not because those gates opened. The reads broke twice in twenty-four hours, each time +because a consumer improved its own code, so the mechanism was abandoned rather than +sequenced out. ADR-017 §5 carries the erratum; the residual is C-92. --- diff --git a/docs/ADRs/017_facts_across_a_private_boundary.md b/docs/ADRs/017_facts_across_a_private_boundary.md index d327e68..2710c15 100644 --- a/docs/ADRs/017_facts_across_a_private_boundary.md +++ b/docs/ADRs/017_facts_across_a_private_boundary.md @@ -161,13 +161,43 @@ machine for the private one. Slightly redundant for a while, and redundancy is t price for not having a window where the only thing verifying a delivery label is our own typing. +**Erratum, 2026-08-12. The sequence above was followed, and the check was still the wrong +mechanism. Both facts belong here, and an earlier draft of this erratum got the first one +backwards.** + +Both partners' step 3 landed. views-faoapi#379 closed on 11 August (commit `8615574`); +views-crafdapi#53 at 12:15 on the 12th (commit `0c493ae`). So the constraint stated above was **satisfied**, not +bypassed — and the first version of this erratum claimed the opposite, that "neither +partner's step 3 is what removed them". That was false when written, six hours after the +second one landed. It is corrected here rather than quietly, because an ADR that repeals +its own rule on a false claim about another repository is §3's failure inside §5's text. + +**What actually happened is more useful than what that draft said, and it is checked +rather than asserted.** In both repositories the commit that satisfied step 3 was *the +same commit* that broke our source-reading check — verified: `8615574` and `0c493ae` each +introduce the named constant and add that repository's registry-binding test: each consumer, while binding its served name to the registry, tidied +`APIPathManager("literal")` into `APIPathManager(CONSUMER_DOCUMENT_NAME)`. The improvement +and the breakage were one edit. Twice, a day apart. + +That is the argument for removing the check, and it is **§7's argument, not §5's**: we were +never entitled to depend on another repository's file layout, and a mechanism that a +consumer breaks by improving itself will keep breaking. The ordering rule above did its +job; the thing it was ordering was not worth having. + +So the ordering constraint is not repealed — it is **discharged**, and superseded for this +class by a plainer rule: **a check that reads another repository's source is not a check +this repository builds.** The residual window is now permanent rather than temporary, +registered as **C-92**, and what closes it is each consumer proving its query uses the name +it declares — asked for in views-faoapi#390 and views-crafdapi#55, because that is a fact +only the consumer can hold. + **One assumption worth stating rather than relying on.** The registry is versioned, so the two sides could in principle read it at different editions and both pass while disagreeing. That is not a live risk here because the label is contract-immutable — changing it is an amendment, which produces a new edition both sides re-pin to, and our pin's reachability is already checked. Recorded because an unstated assumption carrying a silent-failure mode is -what §1 is about. Today the check still reads the consumer's source -and still skips in CI. The order the three land in, and what is blocked on what, is +what §1 is about. *(Until 2026-08-12 the check also read the consumer's source and +skipped in CI; see the erratum above — that half is gone and its residual is C-92.)* The order the three land in, and what is blocked on what, is Appendix B. Said here because a decision record that reads as a description of the code is how this repository has repeatedly ended up believing work was done. @@ -377,21 +407,31 @@ visibility can be inferred for future APIs. Corrected: **retirement is per partner, and follows that partner's step 3.** For FAO that is now met (views-faoapi#379 merged 2026-08-11), so the FAO source-read goes. For - CRAF'd it is not (views-crafdapi#53 open), so the crafd source-read stays — and with - it the crafd fetch, whose `note` in `tests/conftest.py` already records that it lives - or dies with that one check. + CRAF'd it was not yet, so the crafd source-read stayed — and with it the crafd fetch. + + *(Superseded 2026-08-12. views-crafdapi#53 closed at 12:15 that day, and the crafd + source-read was deleted anyway — not because the gate opened, but because the mechanism + was wrong. Both fetch and check are gone; `tests/conftest.py`'s note records it. See the + §5 erratum.)* 3. **views-faoapi** — verify its own served label against the declaration; §8's other half. Filed as views-faoapi#379, and the consumer-side maintainer has accepted it. -4. **views-crafdapi** — the same self-check for the other partner. **Not yet filed at the - time of writing, and that gap matters**: §7 retires the crafdapi fetch, so without this - leg the CRAF'd label would end up checked against a registry row that nobody checks - against CRAF'd's actual code — no verification against reality at all, which is worse - than the status quo. §7 argues the rule must apply uniformly; uniform application means - uniformly filing the third leg. Sequenced after that partner's data contract settles, - per step 1. - -**Step 3 is a precondition for removing the old check**, though not for adding the new one -— see the sequencing note in §5. The order is safe to interrupt at any point: until step 3 -lands for a partner, that partner's source-reading check stays, so no window exists in which -the label is verified only against our own typing. +4. **views-crafdapi** — the same self-check for the other partner. Landed as + views-crafdapi#53 (closed 2026-08-12), with the *query*-side ask filed the same day as + views-crafdapi#55 alongside views-faoapi#390. + + The reasoning that made this leg necessary still stands: §7 retires the crafdapi fetch, + so without it the CRAF'd label would be checked against a registry row that nobody + checks against CRAF'd's actual code — no verification against reality at all, which is + worse than the status quo. §7 argues the rule must apply uniformly; uniform application + means uniformly filing the third leg. + +**Step 3 was a precondition for removing the old check**, though never for adding the new +one — see the sequencing note in §5. The order was safe to interrupt at any point: until +step 3 landed for a partner, that partner's source-reading check stayed, so no window +existed in which the label was verified only against our own typing. + +**That last sentence stopped being true on 2026-08-12, deliberately.** Both source reads +were deleted once the mechanism — not the ordering — was found wanting, so the window is +now permanent and carried as **C-92**. See the §5 erratum for why the constraint was +discharged rather than repealed. diff --git a/reports/technical_risk_register.md b/reports/technical_risk_register.md index 504a11c..8071d55 100644 --- a/reports/technical_risk_register.md +++ b/reports/technical_risk_register.md @@ -161,7 +161,7 @@ that indexes only deleted code is noise. ## Open Concerns -### C-97: Thirty-three coordinate values sit in docstrings and comments, where the scan deliberately does not look +### C-97: Coordinate values sit in docstrings and comments, where the scan deliberately does not look | Field | Value | |-------|-------| @@ -170,7 +170,7 @@ that indexes only deleted code is noise. | Source | `/code-review max` on #242, 2026-08-12; count reproduced here | | Trigger | **Either.** (a) A check whose failing function contains one of these values starts firing in CI. (b) Someone proposes widening the no-copy scan to docstrings — at which point this entry is the measurement that says what that would cost. | | Owner | This repository. | -| Location | Measured across `git ls-files '*.py'`: 33 standalone occurrences in 9 files, including `views_postprocessing/{unfao,crafd}/managers/`, `contract/store_metadata.py`, `contract/wire/sink.py`. | +| Location | Measured across `git ls-files '*.py'`, **25 standalone occurrences in 8 files** (2026-08-12, down from 33 in 9 when filed — #242 cleared the no-copy scan's own docstring and #248 cleared `tests/test_product.py`). Includes `views_postprocessing/{unfao,crafd}/managers/`, `contract/store_metadata.py`, `contract/wire/sink.py`. **The count moves whenever prose is edited; re-measure rather than cite it.** | The no-copy scan compares string **constants** and excludes docstrings outright. That exclusion is C-57's recorded lesson: an early draft fired on refusal labels and on docstrings naming which store a function serves, and *"a guard that fails on `def file_metadata(record)` gets deleted — after which the real rule is unguarded"* (ADR-014 §3). @@ -388,16 +388,16 @@ Cross-refs: **C-90** (the same module's untested core), **C-88** (why the module --- -### C-92: This repository lost its only check that the FAO consumer still SELECTS by the delivery label +### C-92: Nothing here checks that a consumer SELECTS by the delivery label — for either partner | Field | Value | |-------|-------| | ID | C-92 | | Tier | 2 — the failure mode is invisible by construction and is on the live FAO path. Upload succeeds, storage is billed, the consumer's endpoint returns empty, nothing raises anywhere. | | Source | `/code-review max` on PR #239 post-merge, 2026-08-11 | -| Trigger | views-faoapi changes how `managers/prediction/manager.py` selects — to a category, a metadata field, a query builder — without touching its served-name constant. | +| Trigger | **Either consumer** changes how it selects — to a category, a metadata field, a query builder — without touching its served-name constant. Also live: `manager.py:117` in both drops the name filter entirely when `model_name` is falsy. | | Owner | Shared: views-faoapi owns the check; this repository owns noticing it does not exist. | -| Location | `tests/test_product.py:63` (`_CONSUMER_FILTER`), `:274` (`_CONSUMER_SELF_CHECK_PENDING`), `:344`; `tests/conftest.py:159-167`; ADR-017 §5. | +| Location | `views_postprocessing//product.py::CONSUMER_DOCUMENT_NAME`; `tests/test_product.py::test_the_declared_consumer_name_matches_the_registry` (what remains); ADR-017 §5/§7. Function names, not line numbers. | PR #239 retired the FAO half of the source-reading check on the strength of views-faoapi#379. That was the right sequencing — §5 requires the consumer-side check to land first, and it had. **But the two checks are not the same check.** @@ -407,9 +407,28 @@ views-faoapi#379 binds their *served-name constant* to the registry row. The ass **C-87 is not this.** C-87 records that we verify our copy against the declaration rather than the consumer's code against it. This is narrower and worse: for one partner we briefly had the second check and gave it up for something that does not cover the same failure. -**A second, structural half.** `_CONSUMER_SELF_CHECK_PENDING` is asserted non-empty and asserted to name only real partners — never asserted to *cover* them. Every other partner map here is two-sided against `PARTNER_PACKAGES`; this one is not, so a third partner is silently exempt from the source read the day it lands. And `SIBLINGS["views-crafdapi"].note` says the fetch "buys nothing and should be removed" once the registry read exists — which PR #239 landed — while ADR-017 §5 forbids retiring that partner's source read until views-crafdapi#53 lands. A maintainer following the note does the thing the ADR forbids, and `test_the_pending_list_is_not_empty` does not object because the map stays non-empty. +**~~A second, structural half.~~ MOOT 2026-08-12 (#248)** — `_CONSUMER_SELF_CHECK_PENDING`, `test_the_pending_list_is_not_empty` and the `SIBLINGS` note it describes were all deleted with the mechanism. Left visible because the reasoning still applies to any future map of this shape: `_CONSUMER_SELF_CHECK_PENDING` is asserted non-empty and asserted to name only real partners — never asserted to *cover* them. Every other partner map here is two-sided against `PARTNER_PACKAGES`; this one is not, so a third partner is silently exempt from the source read the day it lands. And `SIBLINGS["views-crafdapi"].note` says the fetch "buys nothing and should be removed" once the registry read exists — which PR #239 landed — while ADR-017 §5 forbids retiring that partner's source read until views-crafdapi#53 lands. A maintainer following the note does the thing the ADR forbids, and `test_the_pending_list_is_not_empty` does not object because the map stays non-empty. -Cross-refs: **C-87** (the broader residual this sharpens, not duplicates), ADR-017 §5/§8/Appendix B, views-faoapi#379, views-crafdapi#53. +**Resolved as far as it can be here, 2026-08-12 (#248) — the check is gone, the gap is permanent, and the ask is filed.** + +Both source reads are deleted. Not because the sequencing constraint was satisfied — it **dissolved**: the reads broke twice in twenty-four hours, views-faoapi on 11 August and views-crafdapi on the 12th, each time because that repository refactored a literal argument into a named constant. Their code got better and our test went red. ADR-017 §7 said we were never entitled to depend on another repository's file layout; two breakages in a day is the evidence, and repairing the regex a third time would have been repairing the wrong thing. ADR-017 §5 carries the erratum. + +**The gap is now permanent and unguarded here, by choice.** Nothing in this repository verifies that a consumer's query uses the name it declares. The chain reads: + +| link | owner | held by | +|---|---|---| +| we upload with name N | us | construction | +| N == the registry row | us | `test_product.py::test_the_declared_consumer_name_matches_the_registry` | +| their constant == the registry row | them | views-faoapi's `tests/test_seam_contract_binding.py`; views-crafdapi's equivalent | +| **their query == their constant** | **them** | **nothing** | + +**Filed where the fact lives:** views-faoapi#390 (under their seam-verification epic #383, whose flagship this is) and views-crafdapi#55. Both carry `file:line` evidence, both note that `manager.py:117` applies the name filter *conditionally* so a falsy name broadens the query rather than failing, and both say plainly that we are not prescribing their internals. + +**Verified intact at the time of writing** — the constant reaches the path manager and the manager still filters on it, in both consumers. This is a risk, not an incident. + +**Trigger** is now theirs to clear and ours to notice: when either issue lands, this entry closes for that partner. Until then the honest statement is that a delivery is verified by two values this platform authored agreeing with each other, plus the consumer's own word. + +Cross-refs: **C-87** (the broader residual this sharpens, not duplicates), **C-94** (the producer-side preflight that would close it without asking anyone), ADR-017 §5/§7/§8/Appendix B, views-faoapi#390, views-crafdapi#55. --- @@ -586,7 +605,7 @@ would fix it, and its trigger is below. | ID | C-87 | | Tier | 2 — the failure mode is invisible by construction. The upload succeeds, the storage is paid for, the consumer's endpoint returns empty, and nothing anywhere raises. That is the shape ADR-013 §4.1a calls *"invisible to the consumer, not merely degraded"*. | | Source | ADR-017 §8, sharpened by external review (#232, #234), 2026-08-10 | -| Trigger | **Either half going missing.** (a) `views-faoapi#379` or `views-crafdapi#53` is closed without the check being written. (b) A private API operated by a **third party** becomes a consumer — at which point the second half cannot be required at all and this becomes permanent. | +| Trigger | **Either half going missing.** (a) ~~`views-faoapi#379` or `views-crafdapi#53` is closed without the check being written.~~ **Both closed WITH the check written** (2026-08-11, 2026-08-12). The live trigger is now (a') either consumer's *query* stops using its declared constant — see **C-92**, and views-faoapi#390 / views-crafdapi#55. (b) A private API operated by a **third party** becomes a consumer — at which point the second half cannot be required at all and this becomes permanent. | | Owner | The consumer-side seats own the check; this repository owns noticing that it exists. | | Location | `views_postprocessing//product.py::CONSUMER_DOCUMENT_NAME`; the check in `tests/test_product.py`; ADR-017 §5, §8, Appendix B. | @@ -594,9 +613,9 @@ ADR-017 decides that the delivery label is declared in the public coordinate reg Its residual is stated plainly in §8 and belongs here rather than only in a document: **we will verify our copy against the declaration, not the consumer's code against it.** If a consumer quietly starts filtering on something else, our check passes and the delivery is invisible exactly as before. -**Why this is a risk and not merely a note.** The second half is real work in repositories this project does not control. `views-faoapi#379` has a willing owner. `views-crafdapi#53` is blocked on that partner's data contract and could sit for a long time. Until both land, the label's agreement with reality rests on the source-reading check — which ADR-017's sequencing deliberately keeps alive for exactly this reason, and which someone could remove believing the registry check replaced it. +**Why this is a risk and not merely a note.** The second half is real work in repositories this project does not control. `views-faoapi#379` has a willing owner. `views-crafdapi#53` is blocked on that partner's data contract and could sit for a long time. Both have now landed. The label's agreement with reality **no longer rests on any source-reading check**: both were deleted on 2026-08-12, not because those gates opened but because a consumer improving its own code broke the mechanism twice in a day (ADR-017 §5 erratum). What remains is C-92 — a consumer's word that its query uses the name it declares. -**What was already prevented.** A reviewer caught that the obvious sequence created a window where the source-reading check was deleted before the consumer-side check existed, leaving a green build proving only that two values this platform authored agreed with each other. ADR-017 now forbids that ordering. This entry exists so the ordering constraint has a home outside the document that states it. +**What was already prevented, and then accepted.** A reviewer caught that the obvious sequence created a window where the source-reading check was deleted before the consumer-side check existed, leaving a green build proving only that two values this platform authored agreed with each other. ADR-017 forbade that ordering, and the ordering was in fact honoured — both partners' consumer-side checks landed before their source-read was removed. The window exists anyway, permanently, because the source-reads were then deleted on their own demerits. This entry exists so that history has a home outside the document that states it. Cross-refs: **C-77** (the same field's producer-side half, resolved), ADR-013 §4.1a, ADR-017 §5/§8/Appendix B, views-appwrite#75, views-faoapi#379, views-crafdapi#53. @@ -960,7 +979,7 @@ Cross-refs: **C-62** (the transitive dependency drag; the other 31 alerts), **C- Nine of the seventeen are **new in this arc**, including both registry-drift detectors (pinned edition, commit-reachable-from-`main`) for both partners. Those detectors have a demonstrated drift rate: they fired **twice on 2026-08-03**, hours apart. A detector for a fault that recurs twice in a day, running only on one machine, is most of the way to not existing. Where each sibling stands, after trying them: -- **views-crafdapi** — public, its check reads source text. **Now checked out in CI**, recovering **one** test: the cross-seam consumer-document-name pin for CRAF'd. +- **views-crafdapi** — public; its check read source text. Checked out in CI from 2026-08-10, recovering **one** test; **that checkout was removed on 2026-08-12** when the check it served was deleted (ADR-017 §5 erratum, C-92): the cross-seam consumer-document-name pin for CRAF'd. - **views-datafactory** — public, but its eight tests need the producer's raw GAUL parquets, which are **not in its git repository**. Checking it out converts an honest skip into a `FileNotFoundError`; tried and reverted. - **views-appwrite** — was private when this was written; **made public 2026-08-08** (`views-appwrite@9d80b75`) and **now checked out in CI**, recovering **seven** tests including both registry-drift detectors. No credential was needed for any of them, and none was ever the obstacle after 2026-08-08 — the obstacle was that this line went on saying "private" for two days after it stopped being true. - **views-faoapi** — **private**, and the only one. Its single check is dark. Closing it needs either a credential or FAO's consent to make that repository public; the second is being pursued, and ADR-016 §8 carries the trigger for falling back to the first. diff --git a/tests/conftest.py b/tests/conftest.py index 839acaa..1943321 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -158,12 +158,16 @@ class Sibling: ), "views-crafdapi": Sibling( env="VIEWS_CRAFDAPI", - ci_checkout=True, + ci_checkout=False, note=( - "PUBLIC, and fetched **temporarily**. Measured: this fetch serves exactly one " - "test, the consumer-name pin. ADR-017 §7 replaces that test with a read of " - "the public registry, at which point this fetch buys nothing and should be " - "removed along with it. Until then it is real coverage, not decoration." + "PUBLIC, and no longer fetched. It served exactly one test — the check that " + "read this consumer's source for its query filter — and #248 deleted that " + "check rather than repairing it: the same read broke twice in 24 hours " + "because both consumers refactored a literal into a named constant, which " + "ADR-017 §7 predicted. The declaration stays because CONSUMER_REPO still " + "names this repository; only the fetch is gone. What the source read used to " + "cover is register C-92, and views-crafdapi#55 is the ask that would close it " + "where the fact lives." ), ), } @@ -175,10 +179,12 @@ class Sibling: #: the second one ``un-crafdapi`` while the repository on disk is ``views-crafdapi`` — #: exactly the kind of near-miss that makes guessing expensive. #: -#: This exists so the consumer-document-name pin can be checked **across the seam** -#: rather than asserted locally. A name this repo declares and the consumer filters on -#: is a fact this repo does not own; declaring it here is right, but only the sibling -#: checkout can confirm it still matches (ADR-014 §1 — the guarantee needs a check). +#: It exists so this repository records **who receives each delivery**. It used to also +#: locate a sibling checkout so the consumer-document-name pin could be read from that +#: consumer's source; #248 deleted that read (ADR-017 §7 — we were never entitled to +#: depend on another repository's file layout, and two consumers proved it in a day by +#: improving theirs). What the map is for now is addressing: it is who register C-92's +#: cross-repo asks are sent to. CONSUMER_REPO = { "unfao": "views-faoapi", "crafd": "views-crafdapi", diff --git a/tests/test_ci_sibling_coverage.py b/tests/test_ci_sibling_coverage.py index 0572e2e..ee0238a 100644 --- a/tests/test_ci_sibling_coverage.py +++ b/tests/test_ci_sibling_coverage.py @@ -181,8 +181,9 @@ def _g4_every_note_names_a_record( **Widened 2026-08-10 after external review.** The rule used to look only at siblings with ``ci_checkout=False``, which left a hole in exactly the place ADR-016 calls out: - `views-crafdapi` is fetched *temporarily*, until the check it serves moves to a - registry read, and that fact lived in a note **no rule examined**. A claim about + `views-crafdapi` was fetched *temporarily*, until the check it served moved to a + registry read, and that fact lived in a note **no rule examined**. (That fetch is gone + as of #248; the rule this incident produced is not.) A claim about another repository, in prose, that nothing can check — which is the failure ADR-016 §3 diagnoses, reintroduced for the one download the document itself calls temporary. diff --git a/tests/test_product.py b/tests/test_product.py index 633f0e1..e4b461d 100644 --- a/tests/test_product.py +++ b/tests/test_product.py @@ -8,9 +8,11 @@ The consumer-name pin is the one that matters most and the one a partner-scoped test could never catch: a document written under a name the consumer does not filter for is -delivered, stored, billed, and invisible. That is not hypothetical here — six -`orange_ensemble` forecast documents sat in `unfao_bucket` for months while FAO's -forecast serving read empty (ADR-013 §11.4 post-adoption record, register C-01). +delivered, stored, billed, and invisible. That is not hypothetical here — six forecast +documents sat in the UNFAO bucket for months, under a name nothing was looking for, while +FAO's forecast serving read empty (ADR-013 §11.4 post-adoption record, register C-01). +*(Named, not quoted: the bucket's declared value is a coordinate, and this docstring is +printed by pytest whenever anything in this file fails — register C-97.)* Parametrised over `tests/conftest.PARTNER_PACKAGES`, so a third partner is caught by `test_clone_readiness.py::test_the_declared_partner_list_is_the_real_one` rather than @@ -35,49 +37,32 @@ #: passes for every possible value, which is the shape of the replica defect epic #181 #: spent a story retiring. So the expected value is written out. #: -#: **But a literal transcribed from another repository is a guarantee, and ADR-014 §1 -#: says a guarantee needs a check.** On its own this pin catches drift authored *here* — -#: which was never the danger. If views-crafdapi changed its filter tomorrow, this file -#: would stay green, the delivery would upload, and the document would be invisible: -#: ADR-013 §4.1a, the defect that stranded six `orange_ensemble` documents in -#: `unfao_bucket` while FAO's forecast serving read empty for months. +#: **A literal transcribed from another repository is a guarantee, and ADR-014 §1 says a +#: guarantee needs a check.** On its own this pin catches drift authored *here*, which was +#: never the danger: if a consumer changed its filter tomorrow this file would stay green, +#: the delivery would upload, and the document would be invisible (ADR-013 §4.1a). #: #: ``test_the_declared_consumer_name_matches_the_registry`` closes that half against the #: **public coordinate registry** rather than the consumer's source (ADR-017 §5). It runs #: for every partner and needs no credential, because the registry is public even when the #: consumer is not — which is why it reaches the FAO partner and its predecessor could not. #: -#: One partner is still ALSO read at the source, and only one: -#: ``_CONSUMER_SELF_CHECK_PENDING`` below names it and the issue that retires it. +#: What it does **not** close is whether the consumer's query uses the name it declares. +#: This repository used to read their source for that; the check broke twice in 24 hours +#: because both consumers refactored a literal into a named constant, and #248 deleted it +#: rather than repairing it. Register **C-92** carries the gap; views-faoapi#390 and +#: views-crafdapi#55 are the asks that would close it where the fact lives. _CONSUMER_DOCUMENT_NAME = { "unfao": "un_fao", "crafd": "un_crafd", } -#: Where the consumer's name lives, and the mechanism that consumes it. Both are -#: pinned: a consumer that kept the string but started filtering on a different field -#: would strand a delivery just as thoroughly as one that renamed it. _PKG = Path(__file__).resolve().parent.parent / "views_postprocessing" -_CONSUMER_PATH_MANAGER = re.compile(r'APIPathManager\(\s*"([a-z0-9_]+)"') -_CONSUMER_FILTER = 'filters["name"] = self.model_path.model_name' - - def _product(partner: str): return importlib.import_module(f"views_postprocessing.{partner}.product") -def _consumer_package(repo: Path) -> Path: - """``src/views_api`` inside a consumer checkout.""" - candidates = sorted((repo / "src").glob("views_*api")) - assert len(candidates) == 1, ( - f"expected exactly one package under {repo}/src, found {candidates}. The " - "consumer's layout changed; this test's assumption about where to look is " - "part of what it asserts." - ) - return candidates[0] - - def test_every_partner_has_its_consumer_name_pinned(): """Assert this file's declared scope is the real one (ADR-014 §2).""" assert set(_CONSUMER_DOCUMENT_NAME) == set(PARTNER_PACKAGES), ( @@ -114,7 +99,6 @@ def test_upload_interlock_defaults_off(partner): assert _product(partner).UPLOAD_ENABLED is False - @pytest.mark.parametrize("partner", PARTNER_PACKAGES) def test_both_delivery_legs_name_the_document_from_the_declaration(partner): """Register C-77 — the forecast and historical legs must agree, by construction. @@ -164,21 +148,28 @@ def test_both_delivery_legs_name_the_document_from_the_declaration(partner): "checked against the consumer's filter." ) -# ── across the seam: the pin above, checked against the repo that owns the fact ── +# ── across the seam: the pin above, checked against the registry that owns the fact ── + +def test_every_partner_names_the_repository_that_consumes_its_delivery(): + """Every partner must name its consumer, and that name must be a declared sibling. -def test_every_partner_has_a_declared_consumer_repository(): - """The gated check below iterates this map; assert it is real (ADR-014 §2).""" + **No test reads a consumer's checkout any more** (#248), so this no longer guards a + call site. It guards the *addressing*: `CONSUMER_REPO` is how this repository says who + receives each delivery, and it is what the cross-repo asks in register C-92 are + addressed to. A partner with no consumer named is a delivery with no recorded + recipient; a consumer named but absent from `SIBLINGS` is a name nothing else in this + repository can resolve. + """ assert set(CONSUMER_REPO) == set(PARTNER_PACKAGES), ( f"partners with no declared consumer repository: " - f"{sorted(set(PARTNER_PACKAGES) - set(CONSUMER_REPO))}. Without one, that " - "partner's consumer-name pin is never checked against the consumer." + f"{sorted(set(PARTNER_PACKAGES) - set(CONSUMER_REPO))}. Without one, nothing " + "records who receives that partner's delivery." ) undeclared = sorted(r for r in CONSUMER_REPO.values() if r not in SIBLINGS) assert not undeclared, ( - f"consumer repositories with no SIBLINGS entry: {undeclared}. " - "require_sibling() raises KeyError rather than skipping for those, so the " - "check would fail confusingly instead of skipping cleanly." + f"consumer repositories with no SIBLINGS entry: {undeclared}. Every repository " + "this one names must be resolvable from one declaration, or the two lists drift." ) @@ -233,8 +224,9 @@ def test_the_declared_consumer_name_matches_the_registry(partner): which is public, so this needs no credential even for the private partner — that is the whole of ADR-017 §5 in one assertion. - *"Neither reads the other" is the end state, not yet the present one:* CRAF'd's source - is still read by the check below, until views-crafdapi#53 lands. + *"Neither reads the other" is now the present state, not just the end state* — #248 + deleted the last source read. What that costs is register C-92: nothing here verifies + that a consumer's query uses the name it declares. The value it guards is the one whose failure is silent: a delivery filed under a name the consumer does not ask for is uploaded, stored, billed, and invisible — no error @@ -264,85 +256,3 @@ def test_the_declared_consumer_name_matches_the_registry(partner): f"[{partner}] the pin in this file disagrees with the registry — the local " "always-on test above should have caught a mismatch with the module first." ) - - -#: Partners whose consumer-side self-check has NOT landed yet, so this repository still -#: reads their source as well as the registry (ADR-017 §5's sequencing constraint). -#: -#: Declared rather than inferred, and deliberately a *shrinking* list: an entry leaves it -#: when that partner's own check lands, and the day it empties this whole mechanism goes. -_CONSUMER_SELF_CHECK_PENDING = { - "crafd": "views-crafdapi#53", -} - - -def test_the_pending_list_is_not_empty_and_names_only_real_partners(): - """An empty map must be a prompt to delete code, not a silent skip. - - ``parametrize`` over an empty collection is a **skip** under pytest's default - ``empty_parameter_set_mark``, and this repository declares no pytest configuration at - all. So emptying this map — by a premature cleanup, or an edit made before - views-crafdapi#53 lands — would retire the only check that reads what a consumer - actually does, and report ``1 skipped`` on a green build. The registry check would - then be comparing two values this platform authored to each other, which is the exact - failure the retained test's docstring warns about. - - So the map is asserted non-empty. When it legitimately empties, this assertion is - what tells you to delete the test, its regexes, and the sibling fetch that serves it. - """ - unknown = sorted(set(_CONSUMER_SELF_CHECK_PENDING) - set(PARTNER_PACKAGES)) - assert not unknown, f"not partners: {unknown}" - assert _CONSUMER_SELF_CHECK_PENDING, ( - "_CONSUMER_SELF_CHECK_PENDING is empty, which means every consumer now checks " - "itself against the registry. That is the end state ADR-017 §5 describes — so " - "finish it: delete test_the_consumer_still_filters_on_the_name_until_it_checks_" - "itself, the _CONSUMER_PATH_MANAGER and _CONSUMER_FILTER patterns, and the " - "views-crafdapi entry in tests/conftest.py::SIBLINGS whose note says that fetch " - "serves exactly this one test. Leaving them costs a clone on every pull request " - "and asserts nothing." - ) - - -@pytest.mark.parametrize("partner", sorted(_CONSUMER_SELF_CHECK_PENDING)) -def test_the_consumer_still_filters_on_the_name_until_it_checks_itself(partner): - """The source-read, kept ONLY for partners whose own check does not exist yet. - - ADR-017 §5 constrains the order: this repository stops reading a consumer's source - when *that consumer* starts checking itself against the registry. Otherwise there is - a window where the registry row is a string a human typed, our check compares our - copy to it, and nothing anywhere consults what the consumer actually does — a green - build proving only that two values this platform authored agree. - - views-faoapi#379 landed on 2026-08-11, so the FAO half of this check is gone. - views-crafdapi#53 has not, so CRAF'd's stays — and with it the crafd sibling fetch, - whose ``note`` in ``tests/conftest.py`` records that it lives or dies with this one - check. - - **This test is meant to be deleted.** When #53 lands, remove that partner from - ``_CONSUMER_SELF_CHECK_PENDING``; when the map empties, remove this test, the regexes - it uses, and the fetch. - """ - repo = require_sibling(CONSUMER_REPO[partner]) - pkg = _consumer_package(repo) - - api = (pkg / "managers" / "api.py").read_text() - found = _CONSUMER_PATH_MANAGER.findall(api) - assert len(found) == 1, ( - f"expected exactly one APIPathManager(...) construction in " - f"{CONSUMER_REPO[partner]}'s managers/api.py, found {found}. This is the fragile " - "half ADR-017 retires — a layout change there breaks a check here, which is what " - "happened to the FAO half on 2026-08-11. It survives only until " - f"{_CONSUMER_SELF_CHECK_PENDING[partner]} lands." - ) - assert found[0] == _CONSUMER_DOCUMENT_NAME[partner], ( - f"{CONSUMER_REPO[partner]} filters on {found[0]!r}; this repository declares " - f"{_CONSUMER_DOCUMENT_NAME[partner]!r}. A delivery under a name the consumer does " - "not ask for is uploaded, stored, and invisible (ADR-013 §4.1a)." - ) - - manager = (pkg / "managers" / "prediction" / "manager.py").read_text() - assert _CONSUMER_FILTER in manager, ( - f"{CONSUMER_REPO[partner]} no longer selects by {_CONSUMER_FILTER!r}. The name " - "may still match while the consumer filters on something else entirely — same " - "invisibility, different cause." - )