ADR-338: Wire mypy --strict static analysis into ml/ build - #251
ADR-338: Wire mypy --strict static analysis into ml/ build#251jodavis-claude wants to merge 2 commits into
Conversation
Adds mypy 2.3.0 to ml/requirements.txt, a strict [tool.mypy] config in the new ml/pyproject.toml scoped to ml/pipeline/ and ml/test/, and a new scripts/validate-ml-build.sh/.cmd gate pair matching the validate-build.sh/.cmd naming convention. scripts/validate.sh/.cmd now run validate-ml-build after validate-build, and CLAUDE.md's Quality Gates table lists the new gate. Creates ml/pipeline/__init__.py and ml/test/__init__.py as the initial package roots so the empty tree satisfies the strict mypy pass (mypy --strict exits non-zero on a directory with no .py files at all).
Automated first-pass review notes — ADR-338(Posted as informational analysis, not a review verdict — PR already has a human-submitted approval.) Exit criteria — all 6 checked
Issues foundNone at Priority 1–4 (requirements, correctness/fault tolerance, security, performance). No inline comments warranted. One non-blocking, low-priority observation (not on a file this PR touches):
Verified non-issues
|
scripts/validate.sh invokes the script directly (not via bash), so committing it as mode 100644 caused CI to fail with 'Permission denied' (exit 126) in the devcontainer-validate job. Restore the executable bit to match validate-build.sh/validate-tests.sh.
jodavis-claude
left a comment
There was a problem hiding this comment.
Sign-off review for ADR-338 (PR #251)
Prior review threads: None exist on the PR (confirmed via GraphQL reviewThreads), consistent with the context file's note that no first-pass review was ever actually posted to GitHub — only recorded in the workflow context.
Blocking issue from the recorded sign-off analysis: scripts/validate-ml-build.sh was committed without the executable bit (100644), causing the devcontainer-validate CI job to fail with Permission denied (exit 126) since scripts/validate.sh invokes it directly rather than via bash.
Verified fixed: commit a570da3 is a mode-only change (100644 → 100755, zero content diff, confirmed via git diff --summary). Re-ran the PR's CI after the push — devcontainer-validate, build-and-test, Test Results, and gate all now pass.
Re-scan of the diff: confirmed the actual GitHub PR diff (gh pr diff --name-only) matches exactly what was reviewed previously — CLAUDE.md, ml/pipeline/__init__.py, ml/pyproject.toml, ml/requirements.txt, ml/test/__init__.py, scripts/validate-ml-build.sh/.cmd, scripts/validate.sh/.cmd. No content changed since the prior review pass beyond the mode fix, so no new files needed re-review. Spot-checked each against the task brief and CONTRIBUTING.md/CLAUDE.md conventions:
ml/pyproject.toml—[tool.mypy] strict = true,files = ["pipeline", "test"]scoped correctly relative toml/.scripts/validate-ml-build.sh/.cmd— matchvalidate-build.sh/validate-tests.cmdstructural patterns;.shnow executable,.cmdfiles are consistently non-executable across the repo (100644), so no issue there.scripts/validate.sh/.cmd—validate-ml-buildcorrectly wired betweenvalidate-buildandvalidate-tests.CLAUDE.mdQuality Gates — new entry correctly listed alongsidevalidate-build/validate-tests.ml/requirements.txt—mypy==2.3.0pin matches the verified-installed version.
All six exit criteria are met and CI is green. No new Priority 1–4 issues found.
Verdict
Approved.
Work item
ADR-338: Wire
mypy --strictstatic analysis into theml/build so every subsequent OOP pipeline task (ADR-339 onward) is written strict-clean from the start, instead of needing a later cleanup pass across the whole tree.Changes
ml/requirements.txt— addedmypy==2.3.0, matching the version already installed and verified in the container's/opt/ml-envvenv.ml/pyproject.toml(new) —[tool.mypy]section withstrict = trueandfiles = ["pipeline", "test"], scoped toml/pipeline/andml/test/.scripts/validate-ml-build.sh(new, executable) — followsvalidate-build.sh'sSCRIPT_DIRresolution pattern,cds intoml/, and runsmypy --strict pipeline test.scripts/validate-ml-build.cmd(new) — Windows equivalent, followingvalidate-tests.cmd's structure.scripts/validate.sh/scripts/validate.cmd(modified) — wiredvalidate-ml-buildin between the existingvalidate-buildandvalidate-testscalls.CLAUDE.md(modified) — added a new Quality Gates entry forscripts/validate-ml-buildbetweenvalidate-buildandvalidate-tests.ml/pipeline/__init__.pyandml/test/__init__.py(new, empty) — package roots required formypy --strictto have anything to check; without at least one.pyfile mypy exits 2 rather than passing.Design decisions
mypy==2.3.0(not a newer/latest version) since that's the version already installed and verified working in this container's/opt/ml-envvenv, and the spec gives no other version guidance.ml/pipeline/__init__.py/ml/test/__init__.pypackage roots — confirmed live thatmypy --strictexits non-zero against nonexistent or truly empty directories, so this was necessary (not optional scaffolding) to satisfy the "script passes trivially" exit criterion. These will become real package roots once ADR-339 populatesml/pipeline/core/andml/test/pipeline/core/.check_tool mypyline toscripts/validate.sh/.cmd's tool-availability checks —mypyis a Python dependency restored viaml/requirements.txt, analogous to how NuGet-restored packages aren't individually pre-checked..mypy_cache/to.gitignore— flagged as an adjacent, out-of-scope risk (not called out in exit criteria); manually cleaned up any generated cache before committing..github/workflows/build-and-test.yml— it doesn't invokescripts/validate.shtoday, so this gate isn't yet enforced in CI; out of this task's stated exit criteria.Testing completed
No unit tests: this task is pure build/CI tooling (config files and shell/batch scripts) with no classified component in scope. Verified functionally instead: ran
scripts/validate-ml-build.shdirectly and confirmed it exits 0 against the emptyml/pipeline//ml/test/package roots ("Success: no issues found in 2 source files"), then temporarily added a type-error scratch file and confirmed the script correctly exits 1 with amypyarg-typeerror (proving--strictis genuinely active), before removing the scratch file and re-confirming a clean pass.