epic-5/story-1: Establish Maintainer Release and Parity Validation Workflow - #43
Conversation
There was a problem hiding this comment.
Code Review
This pull request establishes the maintainer release and parity-validation workflow, introducing release process documentation, CI quality gates (Ruff, mypy, coverage, and Towncrier), helper scripts for release verification and notes extraction, and direct unit tests to meet the 95% coverage floor on evaluation modules. Feedback from the reviewer suggests running development tools via the current Python interpreter in the release verification script to prevent environment mismatches, and simplifying redundant ternary operations and explicit boolean checks in the evaluation rules and segments logic to be more Pythonic.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| runner.run("Ruff lint", ["ruff", "check", "src", "tests", "scripts"]) | ||
| runner.run("mypy --strict", ["mypy", "--strict"]) |
There was a problem hiding this comment.
Invoking ruff and mypy directly as global shell commands can fail if they are not in the system PATH, or it might run a globally installed version instead of the one pinned in the active virtual environment. Running them via sys.executable -m ensures they are executed using the current Python interpreter's environment.
| runner.run("Ruff lint", ["ruff", "check", "src", "tests", "scripts"]) | |
| runner.run("mypy --strict", ["mypy", "--strict"]) | |
| runner.run("Ruff lint", [sys.executable, "-m", "ruff", "check", "src", "tests", "scripts"]) | |
| runner.run("mypy --strict", [sys.executable, "-m", "mypy", "--strict"]) |
| runner.run( | ||
| "towncrier draft", | ||
| ["towncrier", "build", "--draft", "--version", args.version], | ||
| ) |
There was a problem hiding this comment.
Similarly, invoking towncrier directly as a global command can lead to environment mismatch or command-not-found errors. Running it via sys.executable -m towncrier ensures the correct environment-specific package is used.
| runner.run( | |
| "towncrier draft", | |
| ["towncrier", "build", "--draft", "--version", args.version], | |
| ) | |
| runner.run( | |
| "towncrier draft", | |
| [sys.executable, "-m", "towncrier", "build", "--draft", "--version", args.version], | |
| ) |
| this_matched = True | ||
| else: | ||
| this_matched = is_rule_matched(segment_rule, rules) | ||
| this_matched = True if not rules else is_rule_matched(segment_rule, rules) |
There was a problem hiding this comment.
The ternary expression True if not rules else is_rule_matched(segment_rule, rules) is redundant. Simplifying it to a direct boolean expression is more Pythonic and readable.
| this_matched = True if not rules else is_rule_matched(segment_rule, rules) | |
| this_matched = not rules or is_rule_matched(segment_rule, rules) |
| if _process_rule_item(data, item) is True: | ||
| return True | ||
| return False | ||
| return any(_process_rule_item(data, item) is True for item in items) |
There was a problem hiding this comment.
Since _process_rule_item is annotated to return a bool, the explicit is True check is redundant. Simplifying it to a direct boolean evaluation is more idiomatic.
| return any(_process_rule_item(data, item) is True for item in items) | |
| return any(_process_rule_item(data, item) for item in items) |
| if _process_or_when(data, block) is not True: | ||
| return False | ||
| return True | ||
| return all(_process_or_when(data, block) is True for block in blocks) |
| if _process_and(data, branch) is True: | ||
| return True | ||
| return False | ||
| return any(_process_and(data, branch) is True for branch in or_branches) |
65e82a8 to
16fccf1
Compare
13f7556 to
87786af
Compare
16fccf1 to
cc3c404
Compare
Beads: ai-driven-product-dev-kwat Agent: fullstack-sdk-dev Status: completed Added [tool.ruff]/[tool.mypy]/[tool.coverage.*]/[tool.towncrier] config, pinned pytest to 8.4.x, added pytest-cov/ruff/mypy/towncrier dev-deps. Bounded gate-traceable src/ fixes: 26 mypy-strict errors -> 0 (generic type args, no-any-return casts, __exit__ -> None on Transport protocol + impls, generic _diagnose TypeVar). Ruff E/W/F/B/SIM/RUF green on src (line-length=100 matching codebase; tests/scripts scoped per-file-ignores). 687 tests green, 0 regression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-kwat Ruff --fix applied F401 unused-import and SIM collapsible cleanups to test files as part of standing up the lint gate. No behavior change; 687 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-h0df Agent: sdk-test-writer Status: completed 45 direct unit tests for evaluation/ internal helpers (bucketing surrogate pairs, experiences bucket-building + miss paths, features _cast_value/ _feature_change_for/_variable_types, rules comparators, segments id-skip branches). evaluation/ coverage 89% -> 97% (clears qs-03 95% floor). No production code changed. 687 -> 732 tests, 0 regression. overall cov 96.81%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-sds5 Agent: fullstack-sdk-dev Status: completed CHANGELOG.md with towncrier marker, changes/README.md documenting the fragment-per-PR convention + categories, and an initial issue-less fragment for the release-workflow standup. 'towncrier build --draft --version 0.1.0' renders a valid 0.1.0 draft. qs-10. 732 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bounds override Beads: ai-driven-product-dev-siy9 Agent: fullstack-sdk-dev Status: completed scripts/verify_release.py runs all 7 gates (ruff, mypy strict, pytest+85% floor, evaluation/ 95% floor, parity suite, towncrier draft, uv build) and exits 0 locally — the maintainer green bar. scripts/extract_release_notes.py pulls a version section from CHANGELOG.md for gh release --notes-file (handles v-prefix). ci/lower-bounds-overrides.txt pins httpx==0.28.0 (sole runtime dep). qs-09/qs-03. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-d2r2
Agent: fullstack-sdk-dev
Status: completed
ci.yml: lint -> type-check (fail-fast) -> 15-cell test matrix (3.9-3.13 x
{ubuntu,macos,windows}) with project 85% + evaluation 95% coverage floors,
bounds-check (lower httpx==0.28.0 / upper newest), PR-only towncrier-fragment
gate, build (wheel+sdist artifact). workflow_call-able. release.yml: v* tag
trigger, reuses ci.yml as gate, version-match, towncrier build --yes, uv build,
OIDC publish via pypa/gh-action-pypi-publish (id-token: write, no tokens),
GitHub Release with extracted notes (--prerelease for a/b/rc/dev). Both YAML
parse + structure verified against qs-02/qs-11 (actionlint unavailable locally).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Beads: ai-driven-product-dev-dh6r Agent: fullstack-sdk-dev Status: completed docs/release-process.md: full maintainer release flow (gates table, coverage+ parity release gates, parity-fixture regeneration via generate_parity_fixtures, dependency-bounds policy, towncrier discipline, cut-a-release steps, one-time PyPI Trusted Publisher setup [Task 4.2 documented-not-performed], v0.0.1-test fork verification [Task 5.1 documented-not-performed], troubleshooting). README Development gates + Releasing section. docs/index.md cross-link. 732 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CI matrix introduced in this story (qs-02: 3.9-3.13 × {ubuntu,macos,windows})
exposed two pre-existing test-portability defects, red on all 5 Windows cells:
- tests/integration/test_queue_lifecycle.py: the generated subprocess script
was written with the platform-default encoding. On Windows (cp1252) the
em-dashes in its comments became byte 0x97, which the child interpreter
cannot parse as UTF-8 source (SyntaxError: Non-UTF-8 code). Write the script,
read the output file, and write the child's JSONL with explicit encoding="utf-8".
- tests/test_layering.py: the import-boundary allow-list uses POSIX-style paths
but compared against str(path.relative_to(_SRC)), which yields backslash
separators on Windows -> false-positive contract violation. Normalize with
Path.as_posix() at both comparison/report sites.
Product code unchanged; the SDK was already portable. 732 tests pass locally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Windows has no POSIX SIGTERM delivery to a Python handler: os.kill(pid, SIGTERM) maps to TerminateProcess and terminates the process unconditionally, so the registered graceful-flush handler never runs and the child exits non-zero. SIGTERM-based graceful shutdown is a POSIX concept; the SDK's Windows graceful-shutdown path is the atexit hook, which is exercised by test_atexit_scenario_attempts_final_delivery (passing on all platforms). Marks the SIGTERM scenario skipif(sys.platform == "win32"). This is the last of the Windows-matrix failures surfaced by the qs-02 CI matrix; the prior commit fixed the UTF-8-encoding and path-separator portability bugs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… to UTF-8 hashing — F-063 propagation The qs-03 coverage tests in tests/test_evaluation_internals.py exercised the deleted `_utf16_code_units` helper and asserted UTF-16 surrogate-pair semantics. Replaced the 3 affected tests with `murmurhash3_32` tests covering the same BMP/ASCII, astral (4-byte UTF-8), and mixed-string branches against real-npm oracle values (😀 U+1F600 @9999 = 689720682). All other internal-coverage tests unchanged. Gates green on this branch (ruff/mypy come online here): full suite 740 passed, parity 131 passed, ruff clean, mypy --strict clean (43 files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
87786af to
331edf0
Compare
F-066 propagation (rebase onto remediated 3-3 — conflict resolved)Rebased onto the remediated stack (3-3
|
|
Superseded — all commits already in main (bc76b64). Closing without merge as part of post-sprint cleanup. |
Story 5.1 — Establish Maintainer Release and Parity Validation Workflow
Part of sprint
sprint/2026-04-06-convert-python-sdk. Stacked on epic-4/story-5 (#42 → #41 → #40 → #39 → #38 → epic-3 chain).What was built (genuine BUILD story — the story file's stale "shipped on dev-branch" Codex DAR was verified false against the live branch and treated only as a blueprint)
.github/workflows/ci.yml(qs-02): Ruff lint → mypy--strict→ 15-cell pytest matrix (Python 3.9–3.13 × ubuntu/macos/windows) → dependency-bounds check → towncrier-fragment gate (PR-only) →uv build.workflow_callable so release reuses it..github/workflows/release.yml(qs-11):v*tag-triggered, reuses ci.yml as the gate, version/tag match check,towncrier build,uv build, OIDC Trusted Publishing viapypa/gh-action-pypi-publish(id-token: write, no long-lived tokens), GitHub Release with compiled notes viascripts/extract_release_notes.py.evaluation/floor, both fail-not-warn. Backfilledevaluation/89% → 97% via newtests/test_evaluation_internals.py(45 direct unit tests, no production code changes).ci/lower-bounds-overrides.txt(httpx lower-bound install + smoke; httpx is the only runtime dep).pyproject.toml(changes/fragments, CHANGELOG.md marker),changes/README.mdconvention doc, PR fragment gate.scripts/verify_release.pyreproduces all gates locally;docs/release-process.md+ README "Releasing" section.tests/parity/infra +scripts/generate_parity_fixtures.pyas a release-blocking job (cross-SDK-critical fields per Story 4.3). No duplication.Quality
scripts/verify_release.pyruns green locally — all 7 gates pass (Ruff, mypy --strict 26→0, pytest+85% floor, evaluation/ ≥95%, parity suite, towncrier draft,uv build). Overall coverage 96.81%.src/edits gate-traceable and behavior-preserving (mypy/ruff fixes only; no opportunistic refactors).ai-driven-product-dev-08y2; tasks-kwat/-h0df/-sds5/-siy9/-d2r2/-dh6r— closed.Warnings / follow-ups
ci.yml/release.ymlparse cleanly and match qs-02/qs-11 — to be confirmed on the first real CI run.docs/release-process.md): Task 4.2 (PyPI Trusted Publisher one-time manual setup on pypi.org) and Task 5.1 (pushv0.0.1-testto a fork to verify the publish job is reached).🤖 Generated with Claude Code