Skip to content

fix: harden cortex-score for first PyPI release#1

Merged
madhavcodez merged 1 commit into
mainfrom
fix/pre-release-hardening
Jun 11, 2026
Merged

fix: harden cortex-score for first PyPI release#1
madhavcodez merged 1 commit into
mainfrom
fix/pre-release-hardening

Conversation

@madhavcodez

Copy link
Copy Markdown
Owner

Summary

Pre-release hardening pass driven by a multi-agent code review (10 specialist reviewers + adversarial verification). Fixes real correctness bugs, finishes the version-source migration, tightens the published output contract, and makes the package PyPI-publishable.

Verified green: 137 tests pass (~90% coverage, up from 125 / 88%); ruff + ruff format --check + mypy --strict clean; python -m build + twine check pass; the built wheel installs and scores in a clean, torch-free environment.

Correctness fixes

  • result_id audit hash now verifies. It was computed from a hand-built parallel dict using created_at.isoformat() (+00:00) while the serialized JSON uses Z, so recomputing the hash from the artifact never matched the documented contract. Now hashed from the result's own canonical serialization — proven to round-trip from the JSON alone.
  • MZ3 export now produces NiiVue-readable files. The ported writer used a <I 0x4D5A0003 magic (reads as 3, not 23117), attr=1 (=isFACE, not isSCALAR=8), an 18-byte header, and raw zlib — NiiVue rejects all of that at the magic check. Rewritten to the verified spec (<HHIII, magic 0x5A4D, attr=8, gzip, deterministic) with a test that validates the real bytes.
  • Boundary errors instead of tracebacks/garbage: 1-D/non-finite inputs and unsupported meshes now raise clear errors (UnsupportedMeshError); the CLI exits 1 cleanly on missing files / bad input; out-of-range Yeo indices raise AtlasMismatchError instead of silently zero-filling a network; previously-silent TRIBE segment-parse failures now emit warnings.

Version source

Completed the half-finished importlib.metadata migration (the previous tree had an untracked version.py that imports would have broken on commit): tracks version.py, drops the dead hatch-vcs _version.py hook + its ruff/coverage refs, and stops shipping the generated artifact. Installed wheels resolve the real version into provenance; uninstalled source trees report an honest 0.0.0+unknown sentinel.

Contract / packaging / CI

  • PEP 639 SPDX license metadata (license = "MIT" + license-files), hatchling>=1.27, Python 3.13 classifier.
  • sha256 fields constrained to hex; audit-identity strings constrained non-empty; cached atlas arrays made read-only.
  • CI: Python 3.13, least-privilege permissions, SHA-pinned actions, a built-wheel install-smoke gate, packaging tests wired in.
  • release.yml: pinned publish action, pre-publish wheel smoke, attestations enabled.

Docs / cleanup

Added docs/install-gpu.md (was referenced but missing); fixed the README "not on PyPI yet" line and result_id copy; added a 0.1.0 CHANGELOG entry and CITATION version/date-released; made the cache wording honest (it's v0.1.1 plumbing, not yet wired into scoring); removed the no-op score --no-cache flag and dead code.

Decisions

  • Cache: kept the tested two-tier cache module as clearly-labeled v0.1.1 plumbing (the scoring path doesn't read/write it yet); only the misleading no-op --no-cache flag was removed.
  • Version: completed the importlib.metadata switch rather than reverting (modern standard; makes source checkouts importable).

Before publishing (manual, can't be done in this PR)

  1. Configure PyPI Trusted Publishing — the repo has no pypi GitHub environment and no PyPI pending publisher yet. Add a pending publisher on PyPI (repo madhavcodez/cortex-score, workflow release.yml, environment pypi) and create a pypi GitHub environment. The name cortex-score is available.
  2. Tag v0.1.0 after merge — version is git-tag-driven; the tag triggers release.yml.
  3. (Recommended) Rehearse on TestPyPI first.

Test plan

  • pytest -m "not gpu" — 137 passed, ~90% coverage
  • ruff check / ruff format --check / mypy --strict — clean
  • python -m build + twine check — pass (Metadata 2.4, License-Expression: MIT)
  • Built wheel installs + CPU-scores in a fresh venv with no torch
  • result_id round-trips from serialized JSON
  • CI green on 3.11 / 3.12 / 3.13 (confirm on this PR)

Pre-release pass from a multi-agent review. Fixes correctness bugs,
finishes the version-source migration, tightens the output contract,
and makes the package PyPI-publishable. 137 tests green, ~90% coverage;
ruff + mypy --strict clean; build + twine check + wheel install smoke ok.

Correctness
- result_id: hash the result's own canonical JSON (model_dump(mode=json),
  result_id blanked, sorted keys) instead of a hand-built parallel dict.
  The old payload used isoformat() (+00:00) while pydantic serializes Z,
  so the documented audit hash never verified against the artifact.
- export/niivue: write the real NiiVue MZ3 format (uint16 magic 0x5A4D,
  attr=8/isSCALAR, 16-byte <HHIII header, gzip, deterministic). The
  previous port wrote a header NiiVue rejects at the magic check.
- score_from_predictions: reject 1-D/non-finite inputs and unsupported
  meshes (new UnsupportedMeshError) at the boundary instead of an opaque
  IndexError / raw CLI traceback.
- build_network_summary: raise AtlasMismatchError on out-of-range Yeo
  indices instead of silently zero-filling a network.
- cli: catch FileNotFoundError/ValueError so bad input exits 1 cleanly.
- tribev2: surface previously-silent segment-parse failures as warnings.

Version source
- Complete the importlib.metadata migration: track version.py, drop the
  hatch-vcs version-file hook and its ruff/coverage refs, remove the dead
  generated _version.py from the wheel. Fallback is a 0.0.0+unknown
  sentinel for uninstalled source trees.

Contract / types
- Constrain sha256 fields to hex and audit-identity strings to non-empty.
- NetworkScore label/index check -> model_validator (no field-order dep).
- Make cached atlas arrays read-only and labels immutable.

Packaging / CI
- PEP 639 SPDX license metadata (license = "MIT" + license-files),
  hatchling>=1.27, Python 3.13 classifier.
- CI: add 3.13, least-privilege permissions, SHA-pinned actions, a
  built-wheel install smoke gate, and run the packaging tests.
- release.yml: pin the publish action, add a pre-publish wheel smoke,
  enable attestations.

Docs / cleanup
- Add docs/install-gpu.md; fix README "not on PyPI yet" + result_id copy;
  add CHANGELOG 0.1.0 and CITATION version/date; honest cache wording.
- Remove the no-op `score --no-cache` flag and dead code.
@madhavcodez madhavcodez merged commit 60980a4 into main Jun 11, 2026
5 checks passed
@madhavcodez madhavcodez deleted the fix/pre-release-hardening branch June 11, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant