Release safety net: push-triggered convergence + drift reconciliation (closes #192) #206

Merged
conductor merged 22 commits from release-safety-net into master 2026-09-08 01:03:16 +01:00
Owner

Summary

  • Push-triggered release safety net (convergence checker + shared bump/release scripts + safety-net job), maintenance drift reconciliation, and the full red-first discharge of both gate reviews (11 findings: env-contract BLOCKER, CONVERGED case-mismatch BLOCKER, merge-subject classification, version-behind-tag anomaly, version-surface fatality, label-ID resolution, attribution-guard anchoring, URLError handling, minors).
  • The PR-closed path remains authoritative when its event fires; the push path only reconciles what slipped past (convergence-only, never re-runs PR-title semver).

Verification

  • Suite 730 passed + 84 subtests; pyright ratchet 117e/407w (not increased); ruff clean; both workflow YAMLs parse; henxels exit 0.
  • Workflow↔script env contract now under test (tests/test_workflow_script_contract.py) — the systematic blind spot the spec gate identified is closed.
  • Live acceptance (two consecutive API-merged no-bump PRs → release runs) verifies on the next real merges post-merge; the push trigger guarantees a run within ~1 minute regardless of closed-event delivery.
## Summary - Push-triggered release safety net (convergence checker + shared bump/release scripts + safety-net job), maintenance drift reconciliation, and the full red-first discharge of both gate reviews (11 findings: env-contract BLOCKER, CONVERGED case-mismatch BLOCKER, merge-subject classification, version-behind-tag anomaly, version-surface fatality, label-ID resolution, attribution-guard anchoring, URLError handling, minors). - The PR-closed path remains authoritative when its event fires; the push path only reconciles what slipped past (convergence-only, never re-runs PR-title semver). ## Verification - Suite 730 passed + 84 subtests; pyright ratchet 117e/407w (not increased); ruff clean; both workflow YAMLs parse; henxels exit 0. - Workflow↔script env contract now under test (tests/test_workflow_script_contract.py) — the systematic blind spot the spec gate identified is closed. - Live acceptance (two consecutive API-merged no-bump PRs → release runs) verifies on the next real merges post-merge; the push trigger guarantees a run within ~1 minute regardless of closed-event delivery.
conductor added 17 commits 2026-09-07 15:50:53 +01:00
Spec-gate finding 1: create_release.py hard-read LIST_API_URL/RELEASE_API_URL
but auto-version.yml only ever passed RELEASE_SERVER_URL/RELEASE_REPO —
KeyError at runtime on both the PR-closed and safety-net release paths.

The script now derives both URLs from the base coordinates (explicit env
still overrides for direct use), and the workflow env blocks stay unchanged.
Adds tests/test_workflow_script_contract.py: a systematic guard that parses
every workflow env contract (step env + $GITHUB_ENV exports + shell exports)
against each invoked script's hard os.environ reads, plus an honesty check
that derived-default keys are never bare-subscripted.

Red evidence: contract test failed on both create_release invocations
(missing LIST_API_URL/RELEASE_API_URL); honesty test failed on the bare
subscripts. Green: 18/18.
Spec-gate finding 2: classify_subject anchored at string start, so Gitea
merge commits ('Merge pull request <feat: x> (#N)') classified as 'other' —
a no-bump feat PR merged via merge-commit was invisible to the safety net
and to maintenance drift attribution.

Subjects matching the merge-commit form are now pre-processed: the quoted
inner title is classified; anything else classifies as before.

Red evidence: 6 new merge-commit fixtures failed (feat/feat(scope)/fix/
fix(scope)/feat!/perf inner titles all classified 'other'). Green: 64/64.
Spec-gate finding 3: assemble_release_decision returned converged=True when
master's __version__ was BELOW the latest tag — silent convergence for a
state that is drift (a bump merged without riding a release, or a tag moved
backwards). The decision now returns converged=False, warranted_bump=None
(the net must not act: bumping master cannot converge with a tag already
ahead) and a new 'anomaly': 'version_behind_tag' field (None otherwise).
The maintenance reconciler files the issue as before (DRIFT=true) and the
issue body now flags the anomaly explicitly.

Red evidence: 2 new decision tests failed with converged=True; the
drift-body anomaly-flag test failed; real-repo tag-ahead test added.
Green: 91/91 across convergence, drift-issue, and contract tests.
Spec-gate finding 4: a GET failure on src/rune/__init__.py was skipped like
an optional surface, so a bump PR could silently bump nothing. The version
surface is now fatal (exit 1 before any PR is filed); the other surfaces
stay optional (pattern absent — skip). The surface-edit loop is extracted to
apply_surface_edits() with an api callable seam for testing.

Red evidence: 4 new tests failed (no is_surface_fatal/apply_surface_edits;
abort test found no SystemExit). Green: 10/10.
Quality-gate finding 5: the convergence step printed json.load(...)['converged']
verbatim — Python True/False (capitalized) — while the safety-net steps
compared env.CONVERGED == 'false' (lowercase string). Both conditionals were
unreachable: the safety net could never bump and the drift advisory could
never fire. The export now wraps the value in str(...).lower().

Adds a static guard to tests/test_workflow_script_contract.py: every boolean
env export (CONVERGED/AUTO_BUMPED/DRIFT) in the parsed workflow run-steps
must apply .lower() or be built from lowercase literals; indirect echo
exports are resolved to their capture line.

Red evidence: guard flagged the json.load export (and its echo) on the
safety-net job. Green: 29/29 contract tests.
Quality-gate finding 6: POST /issues 'labels' requires integer IDs in Gitea;
the script sent the label NAME string, so the label never attached. Label
names are now resolved server-side via GET /labels (repo convention per
shared/custodian.py), created when missing, and skipped — issue still files —
when resolution and creation both fail. main() gains an injectable api
client so the flow is testable with a fake API.

Red evidence: 3 fake-client tests failed against the pre-fix code (network
URLError — no injectable seam; name-string payload). Green: 13/13.
Quality-gate finding 7a: bump_branch_in_flight matched ANY chore/auto-bump-*
branch, so one stale historical branch (branches are never cleaned up) stood
the safety net down permanently. The guard now matches exactly
chore/auto-bump-{NEW} — the in-flight version — with new_version=None as the
documented legacy escape hatch; wait_out_closed_path passes NEW_VERSION
through.

Quality-gate finding 7b (docs): release-flow.md claimed the closed path was
'genuinely absent, not merely slow' and that the paths 'can never
double-bump'. Rewritten: attribution is best-effort; a >90s delayed closed
path can race into a double-bump, bounded by tag idempotency (duplicate
release creation is a no-op); stale-branch cleanup is a known limitation.

Red evidence: anchored-match tests failed against the prefix-wide guard.
Green: 12/12.
docs(pipeline): append discharge record for post-quality findings (#192)
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 36s
CI Pipeline / unit-tests (pull_request) Successful in 43s
CI Pipeline / canary (pull_request) Successful in 25s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 22s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / lint (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 32s
CI Pipeline / canary (push) Successful in 24s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 20s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (pull_request) Successful in 0s
CI Pipeline / required-checks (push) Skipped
61039c86b1
gitea-actions Bot added the governanceinfrastructure labels 2026-09-07 15:51:01 +01:00
conductor added 1 commit 2026-09-07 16:08:33 +01:00
Merge remote-tracking branch 'gitea/master' into release-safety-net
CI Pipeline / lint (push) Successful in 22s
CI Pipeline / unit-tests (push) Successful in 29s
CI Pipeline / canary (push) Successful in 25s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 20s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / lint (pull_request) Successful in 47s
CI Pipeline / unit-tests (pull_request) Successful in 34s
CI Pipeline / canary (pull_request) Successful in 1m36s
CI Pipeline / best-practices (pull_request) Successful in 3s
CI Pipeline / documentation (pull_request) Successful in 4s
CI Pipeline / type-check (pull_request) Successful in 58s
CI Pipeline / gitea-offline (pull_request) Successful in 17s
CI Pipeline / required-checks (pull_request) Successful in 0s
03f5a76ce4
conductor added 1 commit 2026-09-07 21:01:18 +01:00
Merge remote-tracking branch 'gitea/master' into release-safety-net
CI Pipeline / lint (pull_request) Successful in 23s
CI Pipeline / unit-tests (pull_request) Successful in 31s
CI Pipeline / canary (pull_request) Successful in 24s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 20s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / required-checks (pull_request) Successful in 0s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 37s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / canary (push) Failing after 35s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / unit-tests (push) Failing after 39s
CI Pipeline / required-checks (push) Skipped
bf8a612e94
conductor added 1 commit 2026-09-07 22:34:19 +01:00
merge master (behind-base update for robot drain)
CI Pipeline / best-practices (pull_request) Successful in 3s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 23s
CI Pipeline / unit-tests (pull_request) Successful in 35s
CI Pipeline / canary (pull_request) Successful in 27s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Successful in 23s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / type-check (pull_request) Successful in 21s
CI Pipeline / lint (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 33s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / required-checks (pull_request) Successful in 1s
45ed083f27
conductor added 1 commit 2026-09-07 22:45:40 +01:00
merge master (behind-base update, round 2)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 34s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / type-check (push) Successful in 23s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 24s
CI Pipeline / canary (pull_request) Successful in 26s
CI Pipeline / type-check (pull_request) Successful in 23s
CI Pipeline / unit-tests (pull_request) Successful in 34s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / required-checks (pull_request) Successful in 1s
299f68886d
conductor added 1 commit 2026-09-08 00:58:23 +01:00
merge master (resolve: master's auto-version evolution wins — semver extraction is the newer reviewed state; #192 convergence machinery on this branch supersedes via its own PR review)
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 36s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 23s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 3s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 26s
CI Pipeline / canary (pull_request) Successful in 24s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / unit-tests (pull_request) Successful in 34s
CI Pipeline / type-check (pull_request) Successful in 24s
Spec gate (headless) / spec-gate (pull_request) Successful in 8s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Failing after 7s
3a18534516
Author
Owner

🤖 Headless spec gate (non-blocking, #204 first slice)

Spec-gate agent produced no text output — check runner logs.

Deterministic floors still govern the merge

This comment is advisory. Required checks (lint, tests, canaries, type-check, coverage, dependency lock, impact gate) remain the merge authority per docs/adversarial-review.md proportionality.

🤖 **Headless spec gate (non-blocking, #204 first slice)** Spec-gate agent produced no text output — check runner logs. <details><summary>Deterministic floors still govern the merge</summary> This comment is advisory. Required checks (lint, tests, canaries, type-check, coverage, dependency lock, impact gate) remain the merge authority per docs/adversarial-review.md proportionality.</details>
conductor merged commit 1d3183413a into master 2026-09-08 01:03:16 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: conductor/rune#206