Phase A ship blockers #2

Merged
pdurlej merged 5 commits from feat/phase-a-ship-blockers into main 2026-05-04 22:42:57 +02:00
Collaborator

Summary

Implements Phase A ship blockers as five atomic commits:

  1. A1: unify MCP classification namespace and add drift detection tests.
  2. A2: validate baseline JSON shape on load and raise ConfigError for malformed baselines.
  3. A3: fix Forgejo CI runner image assumptions and add Forgejo self-CI workflow.
  4. A4: normalize FastMCP dataclass test responses so MCP tests pass on Python 3.11.
  5. A5: prepare alpha release versions/artifacts for pyfallow 0.3.0a2 and pyfallow-mcp 0.1.0a2.

Verification

  • Post-A1/A2/A3/A4/A5 local gates passed.
  • Full final gate passed:
    • PATH=/tmp/pyfallow-release-venv/bin:$PATH PYTHONPATH=src:mcp/src pytest -q
    • PATH=/tmp/pyfallow-release-venv/bin:$PATH PYTHONPATH=src pyfallow analyze --root . --fail-on warning --min-confidence medium
    • /tmp/pyfallow-release-venv/bin/python -m compileall -q src tests mcp/src mcp/tests
  • Python 3.11 cross-check passed:
    • PYTHONPATH=src:mcp/src /tmp/pyfallow-py311/bin/python -m pytest -q tests mcp/tests
    • PYTHONPATH=src /tmp/pyfallow-py311/bin/python -m pyfallow analyze --root . --fail-on warning --min-confidence medium
  • Built local artifacts:
    • dist/pyfallow-0.3.0a2-py3-none-any.whl
    • dist/pyfallow-0.3.0a2.tar.gz
    • mcp/dist/pyfallow_mcp-0.1.0a2-py3-none-any.whl
    • mcp/dist/pyfallow_mcp-0.1.0a2.tar.gz
  • twine check passed for both package artifact sets.
  • Local fresh-venv wheel install smoke passed: pyfallow --version, pyfallow analyze, and pyfallow-mcp --help.

Blockers

TestPyPI upload is still pending because no TestPyPI token has been provided to this Codex session yet. Package names appeared unclaimed on PyPI and TestPyPI during local checks.

Do not merge until Piotr reviews Phase A and CI is green.

## Summary Implements Phase A ship blockers as five atomic commits: 1. A1: unify MCP classification namespace and add drift detection tests. 2. A2: validate baseline JSON shape on load and raise ConfigError for malformed baselines. 3. A3: fix Forgejo CI runner image assumptions and add Forgejo self-CI workflow. 4. A4: normalize FastMCP dataclass test responses so MCP tests pass on Python 3.11. 5. A5: prepare alpha release versions/artifacts for pyfallow 0.3.0a2 and pyfallow-mcp 0.1.0a2. ## Verification - Post-A1/A2/A3/A4/A5 local gates passed. - Full final gate passed: - `PATH=/tmp/pyfallow-release-venv/bin:$PATH PYTHONPATH=src:mcp/src pytest -q` - `PATH=/tmp/pyfallow-release-venv/bin:$PATH PYTHONPATH=src pyfallow analyze --root . --fail-on warning --min-confidence medium` - `/tmp/pyfallow-release-venv/bin/python -m compileall -q src tests mcp/src mcp/tests` - Python 3.11 cross-check passed: - `PYTHONPATH=src:mcp/src /tmp/pyfallow-py311/bin/python -m pytest -q tests mcp/tests` - `PYTHONPATH=src /tmp/pyfallow-py311/bin/python -m pyfallow analyze --root . --fail-on warning --min-confidence medium` - Built local artifacts: - `dist/pyfallow-0.3.0a2-py3-none-any.whl` - `dist/pyfallow-0.3.0a2.tar.gz` - `mcp/dist/pyfallow_mcp-0.1.0a2-py3-none-any.whl` - `mcp/dist/pyfallow_mcp-0.1.0a2.tar.gz` - `twine check` passed for both package artifact sets. - Local fresh-venv wheel install smoke passed: `pyfallow --version`, `pyfallow analyze`, and `pyfallow-mcp --help`. ## Blockers TestPyPI upload is still pending because no TestPyPI token has been provided to this Codex session yet. Package names appeared unclaimed on PyPI and TestPyPI during local checks. Do not merge until Piotr reviews Phase A and CI is green.
MCP safe_to_remove used hyphenated decisions while the core classifier and the rest of the MCP response used underscore labels. That was a silent API drift: clients could see safe-auto from one tool and auto_safe from another.

This makes MCP schema literals render from pyfallow.classify.CLASSIFICATION_GROUPS and updates safe_to_remove to return the core namespace. The new classification namespace test suite catches both schema drift and safety-output drift, with a canary for high-confidence auto_safe findings.

Verified:
- TDD red: mcp/tests/test_classification_namespace.py failed on Classification.decision drift and safe_classification returning safe-auto
- Negative regression: changing safety.py back to safe-auto made test_safe_classification_canary_returns_auto_safe_for_clean_high_confidence fail
- Negative regression: changing ClassificationDecision back to hyphen labels made test_classification_decision_mirrors_core_groups fail
- PYTHONPATH=src:mcp/src /tmp/pyfallow-release-venv/bin/python -m pytest -q mcp/tests: 17 passed
- PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src:mcp/src pytest -q && PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src pyfallow analyze --root . --fail-on warning --min-confidence medium && /tmp/pyfallow-release-venv/bin/python -m compileall -q src tests mcp/src mcp/tests: passed
read_baseline previously returned raw JSON and let malformed baseline data fail later inside comparison. Integer fingerprints or missing contract fields produced cryptic downstream errors instead of telling the user the baseline file was malformed.

This adds ConfigError as the user-input error type and validates baseline shape during load. The canonical contract is version plus issues with string fingerprints; legacy fingerprints lists are still accepted and normalized into issues so older/simple baseline files fail cleanly or keep working.

Verified:
- TDD red: tests/test_baseline.py had 5 failing validation cases before read_baseline validation
- Negative regression: removing _validate_baseline_shape made integer-fingerprint and missing-version tests fail again
- PYTHONPATH=src /tmp/pyfallow-release-venv/bin/python -m pytest -q tests/test_baseline.py: 6 passed
- CLI repro with /tmp/bad-baseline.json containing integer fingerprints returns ConfigError message and exit code 2, not TypeError
- PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src:mcp/src pytest -q && PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src pyfallow analyze --root . --fail-on warning --min-confidence medium && /tmp/pyfallow-release-venv/bin/python -m compileall -q src tests mcp/src mcp/tests: passed
The Forgejo template used a python:3.12 container, which breaks actions/checkout@v4 because the image does not include Node.js. The template now uses ubuntu-latest as the runner label and actions/setup-python@v5 to select Python explicitly, matching the GitHub workflow pattern.

Adds pyfallow's own .forgejo/workflows/ci.yml with the same Python 3.11/3.12/3.13 matrix and steps as GitHub CI. The checked-in CI template bundle is regenerated so users do not download the stale container-based Forgejo workflow.

Verified:
- yamllint -d '{extends: relaxed, rules: {line-length: disable}}' examples/ci/forgejo-actions.yml .forgejo/workflows/ci.yml: passed
- tests/test_pyfallow.py now asserts Forgejo uses ubuntu-latest, has setup-python, and has no container directive
- PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src:mcp/src pytest -q && PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src pyfallow analyze --root . --fail-on warning --min-confidence medium && /tmp/pyfallow-release-venv/bin/python -m compileall -q src tests mcp/src mcp/tests: passed
- Live Forgejo runner verification is pending the Phase A PR after A5, per branch push protocol
Verified:
- Reproduced Python 3.11 failure before fix: PYTHONPATH=src:mcp/src /tmp/pyfallow-py311/bin/python -m pytest -q mcp/tests/test_mcp.py -> 9 failed, 4 passed.
- Python 3.11 green after fix: PYTHONPATH=src:mcp/src /tmp/pyfallow-py311/bin/python -m pytest -q mcp/tests/test_mcp.py -> passed.
- Current release venv green after fix: PYTHONPATH=src:mcp/src /tmp/pyfallow-release-venv/bin/python -m pytest -q mcp/tests/test_mcp.py -> passed.
- Negative regression verified by temporarily removing the dataclass branch: Python 3.11 returned 9 failed, 4 passed again.
- Full gate passed: PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src:mcp/src pytest -q && PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src pyfallow analyze --root . --fail-on warning --min-confidence medium && /tmp/pyfallow-release-venv/bin/python -m compileall -q src tests mcp/src mcp/tests.
Prepare alpha release artifacts
All checks were successful
CI / Python 3.11 (push) Successful in 54s
CI / Python 3.12 (push) Successful in 55s
CI / Python 3.13 (push) Successful in 55s
CI / Python 3.11 (pull_request) Successful in 52s
CI / Python 3.12 (pull_request) Successful in 53s
CI / Python 3.13 (pull_request) Successful in 52s
11c0a31b51
Verified:
- Version strategy used Phase A default alpha incremental: pyfallow 0.3.0a2 and pyfallow-mcp 0.1.0a2.
- python3 -m pip index versions pyfallow -> no matching distribution; python3 -m pip index versions pyfallow-mcp -> no matching distribution.
- TestPyPI name checks also returned no matching distribution for both packages.
- https://pypi.org/user/pdurlej/ returned HTTP 200.
- Bitwarden status is locked and no TWINE/PYPI token environment variables are present; TestPyPI upload and TestPyPI fresh-install smoke are blocked and were not run.
- PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src pytest -q tests/test_pyfallow.py::test_release_metadata_version_schema_and_readme_examples -> passed.
- Built core artifacts: dist/pyfallow-0.3.0a2-py3-none-any.whl and dist/pyfallow-0.3.0a2.tar.gz.
- Built MCP artifacts: mcp/dist/pyfallow_mcp-0.1.0a2-py3-none-any.whl and mcp/dist/pyfallow_mcp-0.1.0a2.tar.gz.
- /tmp/pyfallow-release-venv/bin/python -m twine check dist/* and mcp/dist/* -> PASSED.
- Local fresh-venv wheel install smoke passed: pyfallow --version -> pyfallow 0.3.0a2; pyfallow analyze ran; pyfallow-mcp --help worked.
- Python 3.11 cross-check passed: PYTHONPATH=src:mcp/src /tmp/pyfallow-py311/bin/python -m pytest -q tests mcp/tests and PYTHONPATH=src /tmp/pyfallow-py311/bin/python -m pyfallow analyze --root . --fail-on warning --min-confidence medium.
- Full gate passed: PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src:mcp/src pytest -q && PATH=/tmp/pyfallow-release-venv/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Users/pd/.codex/tmp/arg0/codex-arg0krhtjs:/Users/pd/.local/bin:/Library/Frameworks/Python.framework/Versions/3.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Applications/Little Snitch.app/Contents/Components:/Applications/Obsidian.app/Contents/MacOS:/Users/pd/.lmstudio/bin:/Applications/Codex.app/Contents/Resources PYTHONPATH=src pyfallow analyze --root . --fail-on warning --min-confidence medium && /tmp/pyfallow-release-venv/bin/python -m compileall -q src tests mcp/src mcp/tests.
Collaborator

TestPyPI upload + smoke verification

Posted by Claude Opus 4.7 (orchestrator) on 2026-05-04 night shift, after Codex (executor) completed Phase A.

Uploaded artifacts

twine upload --repository testpypi succeeded for all 4 artifacts. twine check passed pre-upload.

Fresh-venv install smoke (Python 3.12.12)

$ pyfallow --version
pyfallow 0.3.0a2

$ pyfallow analyze --root . --format text
pyfallow 0.3.0a2 - 1 issues (0 error, 0 warning, 1 info)
Analyzed 28 modules / 28 files.
src/pyfallow/predict.py:109: PY031 info low unused-symbol verify_imports

$ pyfallow-mcp --help
usage: pyfallow-mcp [-h] [--root ROOT] [--version]

A1 invariant verified live on installed package

>>> from pyfallow.classify import CLASSIFICATION_GROUPS
('auto_safe', 'review_needed', 'blocking', 'manual_only')
>>> from pyfallow_mcp.schemas import Classification
>>> Classification.model_fields['decision'].annotation
typing.Literal['auto_safe', 'review_needed', 'blocking', 'manual_only']

Single source of truth respected end-to-end on a fresh install pulled from TestPyPI.

Production PyPI upload remains a deliberate manual step. Ready for your review + merge decision.

— Claude Opus 4.7 (orchestrator), nightly continuation

## TestPyPI upload + smoke verification ✅ Posted by Claude Opus 4.7 (orchestrator) on 2026-05-04 night shift, after Codex (executor) completed Phase A. ### Uploaded artifacts - https://test.pypi.org/project/pyfallow/0.3.0a2/ - https://test.pypi.org/project/pyfallow-mcp/0.1.0a2/ `twine upload --repository testpypi` succeeded for all 4 artifacts. `twine check` passed pre-upload. ### Fresh-venv install smoke (Python 3.12.12) ``` $ pyfallow --version pyfallow 0.3.0a2 $ pyfallow analyze --root . --format text pyfallow 0.3.0a2 - 1 issues (0 error, 0 warning, 1 info) Analyzed 28 modules / 28 files. src/pyfallow/predict.py:109: PY031 info low unused-symbol verify_imports $ pyfallow-mcp --help usage: pyfallow-mcp [-h] [--root ROOT] [--version] ``` ### A1 invariant verified live on installed package ```python >>> from pyfallow.classify import CLASSIFICATION_GROUPS ('auto_safe', 'review_needed', 'blocking', 'manual_only') >>> from pyfallow_mcp.schemas import Classification >>> Classification.model_fields['decision'].annotation typing.Literal['auto_safe', 'review_needed', 'blocking', 'manual_only'] ``` Single source of truth respected end-to-end on a fresh install pulled from TestPyPI. Production PyPI upload remains a deliberate manual step. Ready for your review + merge decision. — Claude Opus 4.7 (orchestrator), nightly continuation
Sign in to join this conversation.
No description provided.