Skip to content

v0.23.0 — The release that reached nobody, and the toolchain consolidation

Choose a tag to compare

@github-actions github-actions released this 08 Aug 00:23
· 4 commits to main since this release
0a01c40

v0.23.0 — The release that reached nobody, and the toolchain consolidation

Release date: 2026-08-07

v0.x.0 bump carrying full MAJOR paperwork under the newly-added
docs/RELEASING.md §2.1.

Read this first if you skip everything else

v0.22.0 did not reach anyone.

Every adopter-facing copier copy example omitted --vcs-ref. Copier
resolves an unpinned git source to the highest-sorting tag, and this
repo carries the frozen v1.0.0v1.12.0 audit snapshots (ADR-014)
alongside the active v0.x line:

$ git tag --sort=-v:refname | head -1
v1.12.0

v1.12.0 sorts above every v0.x tag. The documented command served the
April 2026 snapshot:

Command Result
copier copy <url> Svc 435 files, no .copier-answers.yml
copier copy --vcs-ref=v0.22.0 <url> Svc 626 files, correct answers file

Nothing errored. The scaffold was complete and plausible — just three
months stale. Which is why it survived the entire life of the v0.x line.

Use --vcs-ref=v0.23.0. The docs now say so in all four places, and
scripts/check_adopter_scaffold_ref.py fails the build if they ever drift
from VERSION again.

The v1.x tags were not deleted or renamed. agentic/rules/18 and
ADR-014 both declare them immutable; changing that is a governance
decision needing its own ADR, not a side effect of a docs fix. The pin is
correct under every long-term option, so it lands now regardless.

Breaking for adopters

See MIGRATION.md §"v0.22.0 → v0.23.0" for the actions.

Change Why it breaks
black/isort/flake8 hooks removed, ruff-check/ruff-format added §1.3 classifies a pre-commit hook-set change as MAJOR
ruff formatblack output one-time reflow of your Python tree
scaffold command requires --vcs-ref without it you get a v1.x snapshot

A governance gap this release forced open

§1.3 says a pre-commit hook change requires MAJOR. §2 reserves v1.0.0
for cloud E2E evidence. So a MAJOR-class change had nowhere to go
the policy simply did not cover it.

New §2.1: on the v0.x channel a §1.3 MAJOR-class change ships as
v0.x.0 but carries the full MAJOR paperwork — a Breaking for adopters
block plus a MIGRATION.md section. The bump number is smaller; the
obligations are identical. Nothing about being pre-GA reduces what an
adopter is owed when a contract breaks.

Ruff consolidation (ADR-044)

The speed argument did not survive measurement and is not claimed. The
previous suite already ran --all-files in 2.62 s, inside the config
header's own < 5 s target.

What actually justified it:

  1. Six copies of one exclude list — three tools × two pre-commit
    configs, plus [tool.black]/[tool.isort] in two pyprojects. Same
    defect class as the gitleaks drift closed in v0.22.0: one truth,
    several declarations, silent divergence.
  2. A lint coverage hole. flake8's files: was
    ^(templates/service/|examples/). scripts/ and templates/tests/
    were type-checked and security-linted but never style-linted.

That hole was hiding a real defect: test_different_cache_keys_isolated
created ctx1, never read it, and asserted only on ctx2. The test
named for an isolation property never asserted it
— it would have passed
with caching absent entirely. Fixed by adding the missing assertion, not
by deleting the variable as the linter suggested.

Scope is deliberately parity-only (E,W,F,I). Ruff's UP/B/S
rulesets measured at 90 additional findings and are not enabled:
mixing them in would turn a toolchain swap into a code change, and a
reviewer could not tell which line moved for which reason. Ruff does not
replace mypy and only partially overlaps bandit; both retained.

The 55-file formatter reflow is isolated in its own commit and registered
in .git-blame-ignore-revs. Equivalence verified by running the
collectible test suite before and after and confirming an identical result.

Generated services no longer carry unresolvable ADR references

The render root cites 39 template ADRs and vendors 6; the other 33
were dangling, and a consuming repo's reference checker flagged them.

Renaming to template-ADR-NNN is structurally blocked:
check_vendored_runtime_drift.py holds templates/service/agentic, the
shipped ADR files, and the config schemas byte-identical to their root
counterparts. Rewriting identifiers would break that gate or fork the
generated service from upstream — making every future copier update a
conflict. A pointer costs nothing; a fork costs every future update.

Shipped a resolution layer instead:
templates/service/docs/decisions/README.md, enforced by
scripts/check_service_adr_references.py.

Known follow-ons

  • Clock-isolation allowlist is keyed by file:line and any reformat
    invalidates it. It did; entries were remapped 1:1 after verifying same
    call count, same APIs, same order. Keying on the enclosing test name
    would survive that churn.
  • Ruff UP/B/S — 90 findings, own ADR.
  • mypy → pyright — considered in ADR-044, not ruled out.
  • Shadow-lane precision data — the ADR-019 lane fires now but has
    classified nothing. Phase 2 still needs 14 days of real data.

Verification

All contract tests green on main at the tagged commit. Execution
evidence in VALIDATION_LOG.md Entry 017.