Skip to content

release: 1.2.0 to main - #300

Merged
Polichinel merged 12 commits into
mainfrom
development
Aug 26, 2026
Merged

release: 1.2.0 to main#300
Polichinel merged 12 commits into
mainfrom
development

Conversation

@Polichinel

Copy link
Copy Markdown
Collaborator

Promotes development to main for the 1.2.0 release.

What ships

C-94 delivery findability preflight — an invisible delivery now raises instead of reporting success
C-103 ProducerClientUnavailable — a broken producer client refuses rather than degrading open
C-105 TornRunError — a torn upload names every object it left behind
#297 observed_through stamped into delivery provenance, in both partners
#278 ADR-013 §5.1a — nullable int64 considered and rejected, with the measurement
C-111 CHANGELOG.md, first one this repo has had

Consumer impact

Three exception types can now escape into a launcher, all deliberate, each replacing a silent failure with a loud one. CHANGELOG.md states them; nothing in a launcher needs changing. If one fires after upgrading, it is reporting a pre-existing problem rather than creating a new one.

Release audit

/falsify on the readiness claim returned FALSIFIED on one hard finding — no consumer-facing notes — which this PR fixes. Two soft findings remain open and are about the release path, not the package:

  • tag push does not publish — the workflow fires on release: published. Tags 1.0.0 and 1.1.0 exist with no Release and were never published this way. The GitHub Release is the publishing step.
  • the publish job runs no tests — it validates only that the version exceeds PyPI's.

Also noted: from 2026-10-18 the credential-expiry tripwire reddens the required test check and no release can be cut until ACKNOWLEDGED_UNTIL is set (C-110). 53 days out; does not affect this release.

Verification

507 passed, 40 xfailed, 3 skipped. ruff clean. Register integrity green.

🤖 Generated with Claude Code

Polichinel and others added 12 commits August 21, 2026 12:33
…machinery (#284, #285)

#285's census records this repo as "enforcement test: none". It has seven.
`tests/test_clone_readiness.py` proves both VERTICAL arrows of ADR-002 by
importing packages in a subprocess with the others blocked — and its own
docstring says a regex was tried first and rejected, because it missed
`from ..contract import x` and `from views_postprocessing import contract`.
There is even a mutation proof of the detector. That is stronger than a static
contract: it proves the modules import in isolation, not merely that no import
statement mentions them.

What nothing proved is the HORIZONTAL arrow. Nothing stopped `crafd` and `unfao`
importing each other, and that is the arrow keeping a partner liftable: the two
are deliberate clones (C-33), so the realistic violation is a copy-paste leaving
a sibling's import behind. `test_the_machinery_imports_without_any_partner`
cannot see it — that test imports the machinery, and this is partner-to-partner.

Two halves, matching the split this file already documents: the subprocess is
load-bearing and sees transitive arrivals; the source scan is the supplement and
covers `managers/`, which the subprocess deliberately skips because importing a
manager needs views-pipeline-core and a purity check should not be contingent on
a heavy framework being installed (C-40 (a)).

Mutation-proven in both halves: a sibling import added to `crafd/product.py`
fails the subprocess half; a sibling named in `crafd/managers/crafd.py` fails the
source half.

WHY NOT import-linter, as #284 proposes. It would add a dev dependency, a CI step
and a config block to assert three things — of which two are already covered
here, and covered more strongly. The one it would add is this test. #285 itself
points approvingly at views-datafactory doing the same thing in two assertions in
an existing file, with no new dependency and no graph library; that is the
argument, and it applies here. If the platform later standardises on
import-linter, adopting it is a one-line pyproject block and this test can stay
or go — nothing here forecloses it.

Suite: 470 passed, 3 skipped, 37 xfailed. ruff clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…view-diff

The source half scanned the manager's whole text for the sibling's module path.
This repository's comments cite module paths constantly — C-33's own text points
at `unfao/product.py` — so a documentation comment naming the sibling would have
failed the test for a prose reason. That is the false alarm ADR-014 §3 says gets
a guard deleted, and it would have been deleted for being right about nothing.

Now walks the AST and looks at `Import` / `ImportFrom` targets only.

Re-mutation-proven, both directions:
  a real `from views_postprocessing.unfao import product` -> caught
  a comment naming views_postprocessing.unfao            -> ignored

Suite: 470 passed, 3 skipped, 37 xfailed. ruff clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e-review high

Six findings. The first two are the same class this file's own docstring says
defeated the previous regex — reintroduced by me while "tightening" a substring
scan into an AST one.

1. MEDIUM — `and not node.level` skipped every relative import, so
   `from ...unfao import product` in a manager passed. Lines 155-158 of this
   module name `from ..contract import gaul_schema` as precisely the miss that
   made the regex insufficient, and `from ..crafd import product` is the form
   once used to demonstrate a real gap in `contract/enrichment.py`. `level` is
   now resolved against the file's own package.

   Note on the review's example: `from ..unfao import product` inside
   `crafd/managers/` resolves to `views_postprocessing.crafd.unfao`, which is not
   the sibling — so ignoring it is correct. From `crafd/product.py` the same
   statement does reach the sibling, and is caught. The resolution is
   depth-correct, verified at both depths.

2. MEDIUM — `from views_postprocessing import unfao` was invisible: the sibling's
   name is on the alias, not the module. Each alias is now joined onto the
   resolved prefix.

3. MEDIUM — the scan read only `managers/<partner>.py`, while the subprocess half
   drops the whole `managers` package. `managers/__init__.py` carries a real
   import today and was covered by neither. Now globs `managers/**/*.py`, which
   also removes a FileNotFoundError traceback for a manager not named after its
   partner.

4. LOW — `".managers" not in m` is a substring test: a module named
   `managers_shared.py` would be dropped from the subprocess half while sitting
   outside the AST half, exempt from the guard with no signal. Matches on the
   package segment now.

5. LOW — `not m.endswith(".__init__")` was dead; `_modules_on_disk` already
   filters those. Removed, with a note saying where inits are covered instead.

6. LOW — the fixture README insertion split the sentence it interrupted, leaving
   the pyarrow explanation stranded after a views_frames digression. Restored,
   new material in its own paragraph.

Mutation-proven against the real files, every form: 3-dot relative, the package
form, the dotted import, and a sibling import in `managers/__init__.py` — each
fails; an innocent `contract` import and a comment naming the sibling do not.

Suite: 470 passed, 3 skipped, 37 xfailed. ruff clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mes-2

#284/#285: partner independence — and #286 is blocked upstream, with the byte question settled
No source changes. The register, plus two xfail stubs from the release-readiness
audit.

THREE NEW ENTRIES.

C-110 (Tier 3) — the release path will block itself from 2026-10-18 and nothing
tells the person it blocks. Measured against the live ruleset: protect_main is
active, `test` is a REQUIRED check, bypass_actors is empty. The expiry tripwire
added on 2026-08-19 reddens that check 30 days before 2026-11-17, so no PR merges
and no release can be tagged. The escape is real and was verified — a PR setting
ACKNOWLEDGED_UNTIL is green on its own branch — which is why it is Tier 3 rather
than C-86's Tier 2. This entry exists because the interaction falls between two
records and is in neither: C-84 never mentions the release path, C-86 never
mentions the tripwire. The ruleset was not queried when the tripwire was added.

C-111 (Tier 3) — a release can change whether a delivery fails, and the version
number is the only thing that says so. Three exception types can now escape into
a launcher, and DeliveryNotFindableError fails a delivery that previously
succeeded silently. That is C-94 working as designed. There is no changelog, so
views-models would take it with no notice.

C-112 (Tier 3) — nothing here can see what production actually runs. Measured:
both launchers pin 1.1.0; the newest tag is 1.1.1, eight days old, and 1.1.0
carries the C-99 fail-open that killed the first CRAF'd delivery. Meanwhile main
holds 29 unreleased commits. The obvious fix — check the launchers' pin in CI —
would add a fifth repo that can redden this build, which is C-86 with no bypass
actors, so it is recorded as a decision rather than proposed.

FROM review-rr TRIAGE AND STRATEGIC.

- Cluster I closed: its own prescription landed. It asked for a
  test_register_integrity.py checking header counts, no RESOLVED under Open, and
  reference resolution. That file exists, does exactly those, and caught two real
  errors this week. The residual is recorded: the guard covers entries, not the
  cluster section — which is how Cluster I sat fully resolved and unmarked.
- Cluster J: dropped a stale "(acute)" from a resolved C-22.
- C-109 cross-linked to C-95, its nearest sibling.
- C-81 re-measured. Its headline said CI verifies 8 fewer tests than local; it is
  2 (468/5 skipped vs 470/3). PR #280 moved four checks into the gate four days
  ago and the entry — whose whole job is measuring that distance — kept quoting
  the old number. Trigger rewritten to the gap that remains: the checks needing
  views-datafactory artifacts that are not in its git repository.
- Cluster N added, "This repository cannot see itself": C-109, C-107, C-95,
  C-110, C-111, C-112 and C-81. Six arrived in one sprint, each filed as
  unrelated. Its fix strategy is deliberately NOT more guards — C-109 records why
  the mechanical version fails; four of the seven were found by reading.
- C-106 tagged [backlog].

THE STUBS. tests/test_falsification_release_readiness.py holds the two soft
falsifications as xfail(strict=True), so the suite colour is unchanged. They are
xfail rather than plain failures deliberately: `test` is a required check with
zero bypass actors, so committing red tests would block their own fix — which is
C-110.

Register: 109 -> 112 total, 29 -> 32 open. Integrity guards green (40 tests),
ruff clean, suite unchanged at the C-104 baseline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd-falsification-stubs

Register: C-110–C-112, Cluster N, and the release-readiness stubs
Running `poetry install` took the suite from 26 failed/470 passed to
6 failed/490 passed (pipeline-core 2.3.0 -> 3.0.1), then died building
levenshtein 0.20.9 from source. What remains is one drifted package,
pyarrow 23.0.1 against 16.1.0 locked, and all six failures are that
cause: five C-72 byte-parity checks plus the lock check reporting it.

Wheel availability, measured against the index: pyarrow 16.1.0 and
levenshtein 0.20.9 both ship cp311; levenshtein stops at cp311, pyarrow
at cp312. levenshtein is capped >=0.20,<0.21 by ingester3, so the
ceiling is upstream. poetry.lock installs on cp311 alone. CI runs 3.11
and the delivery's conda prefix is 3.11.15 with pyarrow 16.1.0 — both
match the lock. Only the developer venv (3.13.7) is off, and the
command everyone is told to run cannot bring it back.

That makes the entry's own mitigation half wrong in the way it names as
a failure: test_locked_environment rules out printing a remedy that
cannot work, and on 3.12+ its "Run `poetry install`" line does exactly
that. Trigger and Location updated; what closes the entry is now a
rebuild on 3.11, not a reinstall.

The second half — pyproject declaring >=3.11,<3.15 when only 3.11
resolves — is filed as #295, being a declaration defect rather than an
environment one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n-311-only

docs(register): C-104 — the lock installs on cp311 only
CRAF'd received July 2026 as history. #297 established the month was real but
~1% reported — six cells of 64,742, zero in `ged_sb` but non-zero in `ged_ns`
and `ged_os` — so the producer's inferred boundary declared it observed and the
clip kept it, correctly, by its own contract.

Establishing that took a day, because the artifact could not answer the question
a partner asks afterwards: observed through when, and decided against what? The
boundary was recoverable only because those six cells happened to land in the
two columns that leave a trace. In `ged_sb` the data would have been mute.

`build_provenance` now requires `observed_through` and always emits it. Required
rather than optional because a caller that forgets it is the failure being fixed;
always emitted because an absent key is indistinguishable from an artifact built
before the field existed. An explicit null carries the degrade-open case — the
boundary could not be read, so this delivery was NOT clipped — which is the case
that most needs recording and exactly the one an omit-when-absent field drops.

A third state is kept distinct: `UNREAD`, the managers' initial value, refuses at
build time. Letting it collapse to null would report "clip skipped" for a run
whose clip in fact ran — the C-103 conflation, one layer down.

`observed_through` joins the essential set in `compact_description`, or the
255-char fallback would drop it precisely when descriptions are long, and joins
the redaction guard's declared keyset deliberately: a month_id integer, no PII,
no credential, and what the partner needs to tell a sparse month from a
fabricated one.

Both partners, not just CRAF'd. The managers are deliberate clones and the
UN-FAO side has the external partner; a fix in one is half a fix. A test asserts
they do not diverge.

Deferred with a trigger: the producer also publishes a per-source
`last_valid_month_ids` map. `datafactory_query.defaults` exposes only the scalar,
and a multi-source map would not fit the 255-char carrier. When C-15's structured
metadata field lands upstream and the ceiling goes, stamp the map too.

12 tests: the rule on primitives, and the wiring as declaration checks. One
asserts the assignment precedes the degrade-open early return — placed after it,
the path that most needs recording would raise at provenance time instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ange-boundary

feat(delivery): stamp the observed-range boundary into provenance (#297)
Bumps 1.1.1 -> 1.2.0. MINOR rather than PATCH: the delta adds a new module
(delivery/findability.py), three exception types, a changed return value on
store_port.upload(), and a required keyword on build_provenance.

Adds CHANGELOG.md, which this repository has never had. C-111's trigger was
"the next version is cut — write what changed for a consumer, or record why the
number alone is enough", and this is that release. The entry leads with the
three exceptions that can now escape into a launcher, because the sharp one is
C-94: a delivery whose artifacts land where the consumer cannot see them
previously reported success and now raises. That is the intended behaviour and
still a change views-models would otherwise learn about only as an integer.

Venue chosen deliberately: the root CHANGELOG.md rather than the GitHub release
body, so a consumer reading the source tree at a pinned version sees it without
leaving the checkout. C-111 left the venue open; this is the choice, recorded.

Two falsification probes asserted this gap and both retire here — S2 from the
2026-08-21 audit and H1 from the 2026-08-25 one. They are one finding located
twice, because the second audit designed probes without reading the first's
stubs. Noted in the test module rather than the register: it is a lesson about
the audit procedure, not about the concern.

Three probes stay open and xfail: the expiry tripwire blocking releases from
2026-10-18 (S1/C-110), tag-push not publishing (S3), and the publish job
shipping without running tests (S4). None blocks this release; all three are
about the release path rather than the package.

Register: C-111 moved to Resolved, counts 32->31 open, 80->81 resolved,
verified by tests/test_register_integrity.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
release: 1.2.0 — and tell consumers what changed about failing (C-111)
@Polichinel
Polichinel merged commit c65508d into main Aug 26, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant