diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..39563c7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ + + +## What changed + + + +## Checks I ran locally + +- [ ] `ruff check src tests --exclude tests/_legacy_test_pathways_source.py` +- [ ] `./.venv/bin/python -m pytest tests/ -q` +- [ ] `PYTHONPATH=src python3 scripts/check_pathway_integration.py` (if pathways touched) +- [ ] `python3 tools/check_pmids.py --strict` and committed the refreshed `tools/pmid_cache.json` (if a citation was added) +- [ ] Rebased onto `dev` and re-ran the suite afterwards + +## Invariants + +- [ ] No runtime dependency added (`dependencies = []` untouched) +- [ ] No number without a source; no citation invented; missing data stays `OPEN`/`UNEVALUABLE` +- [ ] Nothing from the product side (score weights, vendor variables, human rows) diff --git a/.github/PULL_REQUEST_TEMPLATE/pathway.md b/.github/PULL_REQUEST_TEMPLATE/pathway.md index 293801b..a772c20 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pathway.md +++ b/.github/PULL_REQUEST_TEMPLATE/pathway.md @@ -19,7 +19,7 @@ Fill: docs/python/templates/NEW_PATHWAY_CHECKLIST.md (paste below or attach). - [ ] Wired in `pathways/registry.py` → `pathway_loaders()` only - [ ] `PYTHONPATH=src python3 scripts/export_pathway_packs.py` - [ ] Pack present: `pathways/packs//{pathway.mermaid,tests.md,README.md}` -- [ ] `packs/COVERAGE.md` updated +- [ ] `pathways/packs/COVERAGE.md` updated - [ ] Tests added/updated - [ ] `PYTHONPATH=src python3 scripts/check_pathway_integration.py --pathway ` → exit 0 - [ ] `PYTHONPATH=src python3 tests/test_pathway_packs.py` → pass diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3b573bb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# Keeps the workflow action pins current. Every action in ci.yml, docs.yml and +# publish.yml was two majors behind on 2026-09-07 and GitHub was emitting a +# Node 20 deprecation annotation on every run; nobody saw it because the runs +# were green. Pull requests land on dev, like every other change. +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + target-branch: dev + schedule: + interval: monthly + groups: + actions: + patterns: ["*"] + labels: ["ci"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8b94cd..7cea232 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: name: Separation gate (no product identifiers) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 # Rule E. The separation gate keeps product IP out; this keeps PEOPLE out. # Both are unrecoverable once pushed, so both run before anything expensive. - name: No human rows in a public repository @@ -61,9 +61,9 @@ jobs: matrix: python-version: ["3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install @@ -83,6 +83,11 @@ jobs: run: pytest tests/ -q - name: Pathway packs (script form) run: PYTHONPATH=src python tests/test_pathway_packs.py + - name: Pathway integration gate + # CONTRIBUTING.md has said "must exit 0 before merge" since the pathway + # template landed, but nothing ran it here — a contributor could follow + # the guide, skip the step, and still see green. + run: PYTHONPATH=src python scripts/check_pathway_integration.py - name: Build wheel run: python -m build - name: Twine check @@ -114,8 +119,8 @@ jobs: name: Coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install @@ -133,7 +138,7 @@ jobs: --cov=biology_as_code.packets \ --cov-report=term-missing \ --cov-fail-under=90 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: coverage-xml path: coverage.xml \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5b08bfc..e179c83 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,7 +11,10 @@ name: Docs on: push: - branches: [main] + # dev is built (never deployed) on push as well as on pull request, so the + # integration branch carries the same evidence as main. ci.yml explains why + # every merge target must appear in these lists. + branches: [main, dev] # Build (not deploy) on dev pull requests too, so a broken nav entry or dead # link fails in review rather than after the merge to main. Deployment stays # gated on main below. @@ -32,8 +35,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Install @@ -46,7 +49,7 @@ jobs: run: pytest tests/test_cookbook.py -q - name: Build site (strict — warnings are errors) run: mkdocs build --strict - - uses: actions/upload-pages-artifact@v3 + - uses: actions/upload-pages-artifact@v5 with: path: site @@ -59,6 +62,6 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - - uses: actions/configure-pages@v5 + - uses: actions/configure-pages@v6 - id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b088e41..2b70381 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,9 +43,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.12" @@ -95,7 +95,7 @@ jobs: - name: Twine check run: twine check dist/* - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: python-package-distributions path: dist/ @@ -117,7 +117,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v7 with: name: python-package-distributions path: dist/ @@ -146,7 +146,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v7 with: name: python-package-distributions path: dist/ @@ -178,7 +178,7 @@ jobs: CONCEPT_RECID: "21536448" ZENODO_TOKEN: ${{ secrets.ZENODO_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index dd772ef..41e98bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ All notable changes to the **biology-as-code** Python package are documented her ### Changed +- **CI practice flow** (QA pass, 2026-09-07). Every action pin was two majors + behind and emitting Node 20 deprecation notices on green runs; bumped, and a + Dependabot config now keeps them current with PRs against `dev`. CI runs + `scripts/check_pathway_integration.py`, which CONTRIBUTING had required + without enforcing. Docs build on pushes to `dev`, not only on PRs. A default + pull-request template, a `ci-required` ruleset on `main` and `dev`, and a + CONTRIBUTING section on hotfix sync-back and releasing. `release_check.sh` + was pinned to `0.1.0` and ran three test files; it now reads the version from + `pyproject.toml` and runs the suite. - **Zenodo job refuses to publish outside our concept chain** (`publish.yml`): the release deposit now checks that the target record belongs to concept `10.5281/zenodo.21536448` before it uploads, so a mis-set token or a fresh deposit diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4c87f77 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,138 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +### One standard specific to this project + +Disagreement about evidence is welcome and is the point of the register. Argue +with the source, the method, or the number. Do not argue with the person, and do +not attribute motive. A claim that a contributor invented a figure is a serious +accusation; raise it privately with the maintainer with the evidence attached. + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the maintainer at paulmorf@morfengineering.tech. All complaints will +be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 644acaa..62c69c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,56 @@ Two rules that follow from the same incident: Merged branches delete themselves. Before this was switched on they accumulated until nobody could tell which were live. +**Both branches take changes only through a pull request with green checks.** A +repository ruleset (`ci-required`) enforces it: a direct push to `main` or `dev` +is refused, and a pull request cannot merge until the separation gate, the three +Python test jobs, coverage, and the docs build all report. Repository admins can +bypass on a pull request for a genuine emergency; the bypass is recorded on the +PR, so use it as the audit trail it is. No approving review is required — a +solo maintainer cannot approve their own PR — so the checks *are* the review. + +### Hotfixes and the sync back + +A hotfix goes to `main` on its own branch (`fix/...`, `gh pr create --base main`). +The moment it merges, `dev` is behind, and the next feature PR will carry a +conflict nobody authored. Sync immediately: + + gh pr create --base dev --head main --title "sync: main → dev" + +The sync PR is a fast-forward and its checks are already green on the same +commits; merge it as soon as they re-report. This happened once already +(`f0199c8` sat on `main` alone) and was caught by hand. + +### Releasing + +`dev` → `main` is a pull request like any other (`gh pr create --base main +--head dev`). After it merges, run `scripts/release_check.sh` on `main`, bump +`version` in `pyproject.toml`, `CITATION.cff` and +`src/biology_as_code/data/VERSION_MANIFEST.json` together (`__version__` is read from +the manifest, not from package metadata; the CI wheel smoke fails if the two disagree), move the +`[Unreleased]` section of `CHANGELOG.md` under the new version, tag `vX.Y.Z`, and +publish a GitHub Release from the tag. The release event is what runs +`publish.yml` (PyPI + a new Zenodo version); a push never does. + +### When to cut a release branch (and why there is none) + +There is no standing `release/*` branch, on purpose. Every commit on `main` is +shippable and the tag *is* the release, so a third branch would only add a +third place for the sync-back problem above to live. Cut one, from the tag and +at that moment, in exactly two cases: + +- **An old line needs a patch while `main` has moved on.** A paper cites + `0.2.x`, `main` is already `0.3` with a breaking change, and a citation on the + `0.2` line is wrong. `git switch -c release/0.2.x v0.2.1`, fix there, tag + `v0.2.2` from it, then merge the fix forward into `dev`. +- **A release needs a freeze while `dev` keeps taking features.** That takes + several people merging daily and a QA window measured in days; the suite runs + in about ninety seconds, so for now the freeze is the `dev` → `main` PR. + +If you do cut one, add it to the `on:` lists in `ci.yml` and `docs.yml` and to +the `ci-required` ruleset in the same commit, or it is an unwatched merge +target, and delete it once the line is no longer supported. + ## Data — strengthen the register Evidence, packet fills, claims, and gate/bound rules go through a **fail-closed @@ -53,7 +103,7 @@ structured workflow: code first, export mermaid, tests, coverage, integration ga | [**docs/python/templates/pathway_module_stub.py**](docs/python/templates/pathway_module_stub.py) | Copy to `src/.../pathways/` | | `scripts/export_pathway_packs.py` | Regenerate `packs//pathway.mermaid` | | `scripts/check_pathway_integration.py` | **Must exit 0** before merge | -| `packs/COVERAGE.md` | Graphs ↔ modules honesty map | +| `src/biology_as_code/pathways/packs/COVERAGE.md` | Graphs ↔ modules honesty map | ```bash pip install -e ".[dev]" @@ -63,6 +113,9 @@ PYTHONPATH=src python3 scripts/check_pathway_integration.py PYTHONPATH=src python3 tests/test_pathway_packs.py ``` +CI runs the same integration check, so a skipped step turns the PR red rather +than slipping through. + **Single wire point:** register loaders only in `src/biology_as_code/pathways/registry.py` (`pathway_loaders`). Export uses that list — do not duplicate module lists in the export script. diff --git a/ROADMAP.md b/ROADMAP.md index 739e675..efc5e81 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -28,15 +28,15 @@ and the engine; we need the rest. *Make it trustworthy and easy to adopt.* -- [ ] **Full-suite CI, not a subset.** CI currently runs only 3 test files + pathway packs. Run the - whole suite, add a **coverage** gate + badge, and matrix over 3.11/3.12/3.13 (+ Windows/macOS/Linux). -- [ ] **Docs site.** Turn `docs/` into a real site (MkDocs-Material or Sphinx): quickstart, the +- [x] **Full-suite CI, not a subset.** Done: `ci.yml` runs the whole suite on 3.11/3.12/3.13 with a + 90% coverage floor on `audit` + `packets`. Still open: a coverage badge, and Windows/macOS runners. +- [x] **Docs site.** MkDocs-Material at murffious.github.io/biology_as_code, built strict on every PR. Open: quickstart, the `simulate_meal` / `run_digestion` / law-cards / provenance walkthroughs, an auto API reference (`py.typed` is already there), and the mermaid diagrams inline. -- [ ] **Badges** in the README: CI, coverage, PyPI version, Python versions, license, DOI. -- [ ] **Zenodo DOI.** Connect the GitHub repo to Zenodo so every release is archived and **citable** — - the single cheapest credibility win for a science package. -- [ ] **Community files:** `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, issue/PR templates. +- [x] **Badges** in the README: CI, docs, release, DOI, Python versions, license. Open: coverage, PyPI version. +- [x] **Zenodo DOI.** Concept DOI 10.5281/zenodo.21536448; `publish.yml` versions it on every release + (through the REST API, not the webhook — see the comment there for why). +- [x] **Community files:** `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, issue and PR templates. - [ ] **conda-forge feedstock** — many scientific users install via conda, not pip. - [ ] **Validation invariants, expanded.** Pin more pathways to textbook stoichiometry the way glycolysis (+2 ATP/+2 NADH), TCA, and ETC (P/O 2.5/1.5) already are, and fail CI on drift. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1c8b463 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,45 @@ +# Security policy + +## What counts as a security issue here + +This is a zero-dependency Python package that reads its own packaged JSON and +YAML and does no network I/O unless you opt in to the PubMed fetcher. The +surface is small, but it exists. Report privately if you find: + +- Code execution or path traversal through a packaged data file, a fixture, a + contribution JSON, or a pathway pack. +- The opt-in PubMed fetcher sending anything other than the identifiers you gave + it, or following a redirect it should not. +- A release artefact on PyPI or Zenodo that does not match the tagged source. +- A real person's data anywhere in the repository. The `check_no_human_rows.py` + gate exists to keep it out; if it missed something, that is a security report, + not a bug. + +A wrong number, a citation that does not say what we claim, or a fabricated +magnitude is **not** a security issue. It is the most serious kind of bug this +project recognises, and it goes through the public +[evidence issue form](https://github.com/murffious/biology_as_code/issues/new?template=evidence.yml) +so the correction is on the record. + +## How to report + +Use GitHub's private reporting: +**Security → Report a vulnerability** on the repository page, or +. + +If you cannot use GitHub, email paulmorf@morfengineering.tech with `[security]` +in the subject. Do not open a public issue for anything on the list above. + +## What to expect + +- Acknowledgement within 7 days. +- A fix or a written decision within 30 days for anything confirmed. +- Credit in the release notes if you want it. + +This is a one-maintainer research package. Those windows are honest, not +generous. + +## Supported versions + +Only the latest release on PyPI receives fixes. There is no long-term support +line; see CONTRIBUTING for when a release branch would be cut. diff --git a/scripts/release_check.sh b/scripts/release_check.sh index b4815dc..799f426 100755 --- a/scripts/release_check.sh +++ b/scripts/release_check.sh @@ -7,7 +7,9 @@ echo "== install dev tools ==" python3 -m pip install -q -e ".[dev]" build twine echo "== tests ==" -python3 -m pytest tests/test_public_api.py tests/test_quiet.py tests/test_fixtures_packaged.py -q +# Full suite, same as CI. This once ran three files, which is how a green +# release check and a red CI run could describe the same tree. +python3 -m pytest tests/ -q PYTHONPATH=src python3 tests/test_pathway_packs.py PYTHONPATH=src python3 scripts/check_pathway_integration.py @@ -29,9 +31,15 @@ python3 -m venv .venv-release-check source .venv-release-check/bin/activate pip install -q dist/*.whl python - <<'PY' +import tomllib from biology_as_code import simulate_meal, __version__, list_pathways from biology_as_code.data.fixtures import list_meal_ids, load_meal -assert __version__ == "0.1.0" +# Agreement with pyproject, not a pinned string: the pin ("0.1.0") stayed +# behind when 0.2.0 and 0.2.1 shipped, so this script has failed on every +# release since without anyone noticing, because CI has its own smoke test. +with open("pyproject.toml", "rb") as f: + declared = tomllib.load(f)["project"]["version"] +assert __version__ == declared, (__version__, declared) assert len(list_pathways()) >= 10 assert list_meal_ids() m = load_meal(list_meal_ids()[0]) @@ -44,6 +52,6 @@ deactivate rm -rf .venv-release-check echo "" -echo "Release check passed. Next:" -echo " twine upload --repository testpypi dist/*" -echo " twine upload dist/*" +echo "Release check passed. Next: tag and publish a GitHub Release from main;" +echo "publish.yml uploads to PyPI and versions the Zenodo record on the release event." +echo "Manual uploads bypass the separation gate — see docs/python/PUBLISHING.md."