Skip to content

Contributor workflow

This page is for contributors and maintainers changing Morana’s source code, tests, documentation, examples, or build dependencies. Package users do not need these checks to run Morana; start instead with installation and quickstart. Development takes place in the Morana repository; proposed changes are submitted as pull requests, and bugs or scoped feature requests can be reported through the issue tracker.

Development disclosure

Generative-AI coding tools assist Morana development. Project maintainers remain responsible for design decisions, review, testing, documentation, and released code. This disclosure does not alter the Apache-2.0 license or its warranty disclaimer; it records the maintainers’ responsibility for reviewing and releasing AI-assisted contributions.

Development environment

Use the editable Conda environment described in installation and quickstart. Update it after dependency or documentation-tool changes:

conda env update -n morana-dev -f environment.yml --prune
conda activate morana-dev

The development environment includes Python VTK so tests can reopen Morana’s VTU and VTM output. Morana’s export implementation does not depend on VTK at runtime.

Checks before submitting changes

After changing Python source, run:

pytest
pylint src/morana
python -m compileall src examples studies tests

Run reuse lint after adding, moving, or renaming repository files and before every completed milestone handoff:

reuse lint

After changing CITATION.cff, validate it against the Citation File Format schema:

cffconvert --validate -i CITATION.cff

After changing a package version, release DOI, dated changelog heading, or current-release URL, check that the explicit release metadata and user-facing references agree:

python scripts/check_release_consistency.py

The checker treats pyproject.toml as the source of the current package version and canonical release URLs. It compares them with CITATION.cff, the newest dated changelog entry, and the current-release references in the README and installation, home, and citation pages. Historical references to older releases remain valid.

After adding or editing a release-note fragment or changing its allocation counter, validate the fragment collection:

python scripts/check_change_fragments.py

After changing tracked documentation, public NumPy-style docstrings, exported API declarations, or MkDocs configuration, build the complete site strictly:

mkdocs build --strict
python scripts/check_internal_links.py --site-path /morana/
python scripts/check_reference_exports.py
python scripts/check_spelling_and_terms.py

These checks run locally without testing unrelated external sites. The link check covers generated local links and project links back to the checkout; the reference check compares public __all__ declarations with their generated mkdocstrings entries. The spelling check covers authored prose throughout the repository. Add legitimate technical words to scripts/spelling_vocabulary.txt, and keep canonical terminology replacements in scripts/terminology_rules.toml.

After a substantial change, also run every maintained artifact in scope. The routine example suite is:

examples/run_all.sh

Maintained development studies

The studies/ tree contains maintained computational investigations used to inform Morana development. These workflows answer bounded design or implementation questions; they are not user examples, routine tests, numerical-verification claims, recognized reference problems, or portable performance guarantees. Run a study explicitly only when it is in scope.

Each maintained study owns its operating instructions and artifact guidance on its directory README. See the finite-volume performance study README for the current many-group smoke, scaling, selected-workload, complete production-matrix, and plotting commands.

Computationally expensive staged workflows, such as the OpenMC comparison, document their own reproduction commands and are run separately when in scope.

Continuous integration

Make changes on the long-lived devel branch; keep main for integrated, release-ready work. Run the applicable local checks before integration or release work. Pull requests into main and pushes to main run the hosted test, static, licensing, and documentation checks; workflows can also be dispatched manually for clean-environment verification. Only pushes to main deploy documentation. The protected branch requires every hosted check on an up-to-date pull request. Integrate devel into main using Create a merge commit so both long-lived branches retain the same commit identities.

Synchronizing the development branch

Synchronize the long-lived devel branch immediately after each pull request merge and before beginning new work:

scripts/sync_devel_after_pr.sh

The script takes no arguments, requires a clean worktree, fetches origin, fast-forwards local devel to origin/main, and pushes devel. It creates no commit. Immediately after integration and synchronization, main and devel should point to the same commit. The fast-forward step fails if devel contains new work that has not been integrated into main; keep that work and integrate it through another checked pull request before synchronizing.

If main advances while a pull request is open, merge origin/main into devel and push the updated branch, then wait for the required checks again. This is a separate operation from the post-integration fast-forward above.

Integration and publication

Merging a pull request into main integrates the change; it does not publish a Morana release. Ordinary pull requests may update project-level documentation or contributor automation. A change that affects a specific release—the package version, CITATION.cff version or version DOI, dated changelog entry, release URL, source archive, tag, or GitHub and Zenodo publication—must use the explicit release procedure below. During development, record relevant public changes as the committed fragments described below instead of editing the changelog. When the availability of an already released version changes, update that dated release entry instead of creating a fragment for a future release.

Morana currently uses sole-maintainer release approval. The maintainer may approve publication by publishing the GitHub release without a second-person review. This starts automatic PyPI publication; there is no separate manual PyPI approval step. The protected pull-request and verification gates still apply.

Change fragments

Change fragments are temporary, reviewable inputs to the next changelog, not a second authority for implemented behavior. Add one with the implementation of each notable user-facing capability, behavior or compatibility change, fix, deprecation, removal, or security correction. Do not add fragments for routine documentation corrections, citation metadata, repository maintenance, CI, tests, internal planning, or maintainer-only tooling.

Fragments live under changes/ and use NNNNNN.category.md, where NNNNNN is a six-digit repository sequence number and category is one of added, changed, deprecated, removed, fixed, or security. Allocate the value currently stored in changes/next_id.txt, then increment that file in the same change. Never decrement the counter, fill an old gap, or reuse the number of a released fragment. Sequence numbers identify fragments; GitHub issue numbers remain separate metadata.

Each fragment consists of YAML front matter followed by concise Markdown:

---
issues: []
breaking: false
upgrade: null
documentation:
  - docs/modeling_workflow.md
---
Added a reproducible many-group performance workflow for evaluating
finite-volume solver scaling on deterministic synthetic workloads.

The four metadata fields are required:

  • issues is a list of positive GitHub issue numbers and may be empty.
  • breaking is true only when existing users may need to change their code, data, or workflow.
  • upgrade is null when no action is needed; otherwise it is a short, actionable instruction. A breaking fragment must provide one.
  • documentation lists repository-relative paths to the tracked pages or public source files that own the changed behavior and may be empty when no such page applies. Every listed path must exist.

Write the body for package users and describe observable impact rather than commits, file edits, tests, or implementation mechanics. Keep related effects in one fragment when they will naturally form one release-note item. A fragment may be revised or combined before release if its implementation changes.

Run python scripts/check_change_fragments.py before submitting the change. The checker validates filenames, allocation state, metadata types, referenced paths, and nonempty bodies. The ordinary test suite and CI also exercise this check.

PyPI publication automation

Publishing a GitHub release (the release: published event) triggers .github/workflows/publish.yml. The workflow removes the leading v from the release tag, checks out that tag, verifies that it is annotated and points to the checked-out commit, then runs:

python scripts/check_release_consistency.py --expected-version "<VERSION>"

The command checks the version derived from v<VERSION> against the metadata committed in that tag. The workflow then builds and validates exactly one source distribution and one pure-Python wheel before passing those files to a separate publishing job.

Configure the PyPI Trusted Publisher for the repository, publish.yml, and the pypi GitHub environment. The environment currently restricts deployment branches or tags and has no required reviewers. After the build passes, the publishing job runs automatically. Referencing an environment in the workflow does not itself require manual approval; that depends on its separately configured environment protection rules. Only the publishing job receives permission to request short-lived OIDC credentials; do not store a package-index API token in GitHub.

Publishing a release

The following procedure turns an exact main commit into a Morana release through GitHub, Zenodo, and PyPI.

Before opening that release pull request, inspect the Git range since the previous tag and reconcile it with every pending fragment. Account for notable public changes, combine related fragments into concise user-facing entries, and retain any required upgrade actions and important limitations. Add the result as the new dated section of docs/changelog.md; do not mechanically concatenate fragment bodies. Delete all consumed fragment files in the same release change, leave changes/next_id.txt at its current value, and run python scripts/check_change_fragments.py again.

Keep release notes separate from release verification. Entries describe notable user-facing changes, required upgrade actions, and release-specific limitations. They do not require a verification or limitations section and should not repeat general test coverage or unchanged platform, API-stability, and validation disclaimers. Mention verification only when a change in its scope or conclusions materially affects users’ interpretation of the release, and link to the verification guide or other owning page. Public coverage and validation limits belong in those guides; dated, commit-specific check outcomes belong in the local handoff snapshot. All release verification gates below remain required regardless of what appears in the changelog.

Prepare a release on devel, then use a pull request from devel into main to integrate the package version, dated changelog, citation metadata, installation guidance, and public URLs. Reserve the version DOI in the prepared Zenodo draft so it is already present in the source. Run python scripts/check_release_consistency.py as part of that preparation. After the pull request is merged with a merge commit, synchronize devel as described above, then update local main without creating another commit and record the exact release merge commit:

git switch main
git pull --ff-only
git status --short
git rev-parse HEAD

The status output must be empty. Run the complete local verification suite from this page, including all maintained routine examples and the strict documentation checks. Confirm that the hosted verification and documentation workflows also pass on the same commit. Any failure requires a new pull request and a complete rerun on its merged commit.

Build the release archive from the verified commit rather than from the working directory. Replace <VERSION> with the version being published and <RELEASE_COMMIT> with the recorded full commit hash before running each command block:

release_version="<VERSION>"
release_commit="<RELEASE_COMMIT>"
archive_name="morana-${release_version}.tar.gz"
mkdir -p dist
test ! -e "dist/${archive_name}"
test ! -e "dist/${archive_name}.sha256"
git archive \
  --format=tar.gz \
  --prefix="morana-${release_version}/" \
  --output="dist/${archive_name}" \
  "$release_commit"
cd dist
sha256sum "$archive_name" > "${archive_name}.sha256"
cd ..

Inspect the archive before publication:

release_version="<VERSION>"
archive_name="morana-${release_version}.tar.gz"
tar -tzf "dist/${archive_name}"
gzip -dc "dist/${archive_name}" | git get-tar-commit-id
cd dist
sha256sum --check "${archive_name}.sha256"
cd ..

It must have one morana-<VERSION>/ top-level directory and contain the package source, pyproject.toml, README, citation metadata, canonical license files, documentation, examples, and tests. It must exclude generated sites, caches, local environments, result archives, raw study data, and other untracked artifacts.

Clean-install and test the archive outside the repository before tagging:

release_version="<VERSION>"
archive_name="morana-${release_version}.tar.gz"
archive_path="$(pwd)/dist/${archive_name}"
release_test_dir="$(mktemp -d)"
python -m venv "$release_test_dir/venv"
"$release_test_dir/venv/bin/python" -m pip install "$archive_path"
"$release_test_dir/venv/bin/python" -c \
  'import sys; from importlib.metadata import version; import morana; assert version("morana") == sys.argv[1]' \
  "$release_version"
"$release_test_dir/venv/bin/python" -m examples.quickstart
"$release_test_dir/venv/bin/python" -m examples.result_archive \
  --output-dir "$release_test_dir/output"

Create a signed tag if signing is configured; otherwise create an annotated tag. The tag must point to release_commit:

release_version="<VERSION>"
release_commit="<RELEASE_COMMIT>"
git tag -a "v${release_version}" "$release_commit" \
  -m "Morana ${release_version}"
git show --no-patch --decorate "v${release_version}"

During one coordinated release window:

  1. Push the annotated v<VERSION> tag for the verified commit.
  2. Upload morana-<VERSION>.tar.gz and its SHA-256 file to the prepared Zenodo draft, then publish it and verify the version DOI.
  3. Create and publish the GitHub release from the same tag, attach the identical two files, link the Zenodo record and documentation, and use the dated changelog as the release-note basis. Publication triggers the PyPI workflow described above, including automatic publication after build validation.
  4. Confirm that both PyPI workflow jobs succeeded and inspect the build logs and python-distributions-<VERSION> artifact. It must contain exactly morana-<VERSION>.tar.gz and morana-<VERSION>-py3-none-any.whl. These are package distributions built from the tag; the GitHub and Zenodo attachments are the separately prepared source archive and checksum. Inspection does not pause automatic publication. Then perform a clean, no-cache installation of the exact version and run the installed metadata, quickstart, and result-archive smoke paths.
  5. Verify the deployed documentation and clean-install again from the published GitHub release archive.
  6. Record Zenodo’s concept DOI for project-level citation links while retaining the version DOI for citations of the specific release.

Do not use automatic GitHub-release ingestion: the archived source must already contain its version DOI. Stop publication after any failed gate. Do not rebuild the archive between publication steps, replace an accepted archive, reuse the version, or move the public tag.

Licensing files and dependencies

Morana source code, authored documentation, tests, examples, and ordinary project assets are Apache-2.0. The root CONTRIBUTING.md records the contribution agreement. The root LICENSE is the canonical project license, pyproject.toml declares the package license, and LICENSES/ contains canonical SPDX license texts.

REUSE.toml supplies copyright and SPDX license annotations for the repository paths. When adding or moving a file:

  1. Check whether its new path is covered by the intended REUSE.toml annotation. Extend the path list or add an explicit annotation when it is not.
  2. For third-party, generated, or separately licensed material, record its actual provenance, copyright holder, and SPDX identifier with a higher-precedence override. Do not let it inherit Morana’s authored-file annotation merely because it sits under docs/ or another covered path.
  3. Preserve the upstream license and required notices. Add a canonical text under LICENSES/ when the SPDX license is not already present; keep bundled asset licenses with the assets when upstream distribution requires it.
  4. If an asset or frontend component is copied into the generated site, update licenses and third-party notices so deployed documentation carries the required notice. Merely declaring a Python dependency does not make its source part of Morana’s distributed documentation.
  5. Run reuse lint and inspect its file count and license summary before handing off the change.

The Morana logo is the exception to the authored Apache-2.0 default: REUSE.toml records its ChatGPT provenance and CC0-1.0 dedication. Vendored MathJax has its own Apache-2.0 override under The MathJax Consortium. Keep these overrides synchronized with the assets they cover.

Previewing documentation

The generated site/ directory is an untracked build artifact. The tracked Markdown and source docstrings remain the documentation sources. The build uses flat .html links, so site/index.html and its navigation also work when opened directly through a local file:// URL without a web server.

Use the live local preview while reviewing interactive documentation features:

mkdocs serve

Open the local address printed by MkDocs (normally http://127.0.0.1:8000/) and stop the preview with Ctrl+C. Material for MkDocs 9.x provides the responsive light theme, navigation, search presentation, and code controls. Morana-specific colors, content width, table treatment, and generated-signature styling live in docs/stylesheets/extra.css; keep those overrides small and verify the light presentation after changing them. The generated site includes the required third-party notices for Material for MkDocs and MkDocs.

Regenerating authored figures

The maintained verification examples generate their tracked result figures when given the documentation asset directory:

python -m examples.one_group_keff --documentation-assets-dir docs/assets
python -m examples.fixed_source_mms --documentation-assets-dir docs/assets
python -m examples.keff_mms --documentation-assets-dir docs/assets
python -m examples.openmc_comparison.plot_documentation \
  --documentation-assets-dir docs/assets

Each owning verification page documents which figures and numerical evidence its command regenerates. The OpenMC comparison command additionally requires the complete local CE and Morana study records described by the comparison workflow. Review the numerical output before accepting an asset change.

The affine-Robin parameter figure has a separate Matplotlib generator. After changing the boundary equations or figure presentation, run:

python scripts/generate_boundary_parameter_figure.py

This deterministically rewrites the tracked Matplotlib-generated image docs/assets/boundary_parameter_relations.png. The script also accepts --output path.png for inspection copies.

Regenerating the synthetic OpenMC MGXS fixture

The checked-in OpenMC runtime-MGXS fixture is a small integration artifact for the material-data importer and is exercised by its routine tests. Regenerate it only when intentionally changing its synthetic arrays or updating the documented OpenMC writer version:

conda run -n OPENMC_ENVIRONMENT python scripts/generate_openmc_mgxs_fixture.py

Replace OPENMC_ENVIRONMENT with a separately managed environment that provides OpenMC. It is used solely for fixture generation; Morana’s importer and normal development environment do not depend on OpenMC. Review the generated HDF5 change, update the OpenMC version recorded in the generator and REUSE provenance when appropriate, and run reuse lint.

Maintaining the documentation toolchain

The tasks in this section are for maintainers changing documentation dependencies or vendored rendering assets. Routine prose and docstring changes do not require refreshing the toolchain.

The active documentation toolchain is deliberately constrained to MkDocs 1.x (>=1.6,<2) and Material for MkDocs 9.x. Do not upgrade to MkDocs 2 without a separately verified migration: the source-derived reference depends on the MkDocs plugin interface through mkdocstrings-python.

Mathematics uses pymdownx.arithmatex and the pinned MathJax 4.1.3 component, New Computer Modern font ranges, and boldsymbol extension under docs/assets/mathjax/ (Apache-2.0; see its bundled license). Upgrade those assets together. Do not replace them with a CDN reference: the complete rendered site must retain equation support when opened through file:// without network access.

Refresh the selected component and font assets with an exact matching release:

bash scripts/refresh_mathjax.sh 4.1.3

The script downloads mathjax and @mathjax/mathjax-newcm-font from npm, stages only the component, boldsymbol extension, license, and CommonHTML font files used by this site, then synchronizes that selection into docs/assets/mathjax/. When changing the version, update the pin in this section and the MathJax comment in REUSE.toml, then run the applicable checks on this page. The site retains semantic MathJax markup but disables the optional speech and braille worker, which is not vendored.

Documentation rendering smoke check

After building the site, run the offline browser check when changing the theme, representative content structures, generated reference, equations, MathJax configuration, or vendored frontend assets:

BROWSER=chromium bash scripts/check_documentation_rendering.sh

It opens the built home, quickstart, core-reference, and theory pages directly from file:// with host-name resolution disabled. The check verifies Material JavaScript initialization, representative content and code-copy controls, mkdocstrings signatures, and the project-status presentation. On the theory page it additionally confirms that every arithmatex expression becomes a MathJax container, rejects MathJax errors and literal \boldsymbol text, and verifies retained TeX annotations plus semantic, keyboard-accessible MathJax markup.

Where documentation changes belong

Use these ownership rules when adding or revising user-facing information. Verify technical claims against the relevant implementation, tests, and project README. Documentation ownership determines where to explain that behavior; it does not make another documentation page evidence for an implementation claim.

Documentation ownership

  • The modeling and solver workflow owns the implemented capability inventory, cross-object behavior, result conventions, and capability boundaries.
  • The geometry, theory, and output pages own their named conventions. The examples page catalogs maintained user-facing runnable workflows. The contributor workflow owns current instructions for maintained development studies, while dated developer notes retain only conclusions that materially guide later work. Developer notes are not progress logs, proposed designs, API documentation, or general contributor guidance.
  • The OpenMC MGXS import guide owns its accepted external artifact, selection, conversion, warnings, units, and interoperability limits.
  • The OpenMC–Morana comparison page owns its physical model, published numerical evidence, figures, interpretation, and references. The OpenMC comparison workflow owns reproduction commands and artifact handling; the example README links to both pages.
  • The verification page owns test-coverage claims and the distinction between verification and experimental validation.
  • The licenses page owns notices for assets distributed with the generated site; REUSE.toml owns repository file annotations.
  • The changelog owns dated, user-relevant release notes; it does not duplicate the modeling and solver workflow’s capability inventory or record routine documentation, metadata, repository-process, CI, or maintainer-tooling changes.
  • The README and documentation home provide concise summaries and route readers to these owners without duplicating detailed capability contracts.
  • docs/reference/ is rendered from explicit __all__ declarations, type annotations, and NumPy-style public docstrings using mkdocstrings-python.
  • Generated HTML is never edited directly.

Writing and navigation

Use sentence case for page and section headings: capitalize the first word and retain capitals only for proper nouns and established acronyms such as Morana, OpenMC, API, VTK, and MathJax.

Use lowercase snake-case filenames that describe the rendered page purpose. Every tracked Markdown page under docs/ must appear in mkdocs.yml navigation. Prefer relative Markdown links between sources; MkDocs produces the flat .html targets.

Link the first explanatory mention of a public Python object in each section to its exact anchor in docs/reference/ when readers may need its constructor, attributes, or methods. Do not mechanically link repeated mentions or code examples. Keep cross-object behavior in the modeling and solver workflow, and link to the owning geometry, theory, output, or verification page when that context matters more than the object interface.

Document externally defined theory and interoperability conventions with open-access technical references or official project documentation where available. State clearly which conclusions come from those sources and which are Morana-specific numerical or API conventions. Reproducible authored figures should keep their generator scripts under scripts/ and document the regeneration command on this page.

Citations and references

Use a direct inline link for official project documentation or an authoritative online reference work when it supports a specific API, file-format, interoperability, definition, or formula claim. Link to the relevant stable page or section, not a search result or a project home page.

For a technical paper, report, textbook, or other scholarly source, use an author–year citation at the claim and link it to a page-local ## References section. Give each cited work a stable anchor and a complete bibliographic record, including authors, title, venue or report number, publisher or institution, and year as applicable. Include a link to open full text; a DOI or publisher record may accompany it but is not a substitute for open access. Name the supporting figure, section, or page when that specificity matters. Pages that cite at least one technical source must include this bibliography; pages that only link to official project documentation need not add one.

See verification and comparisons for numerical and file-format test coverage.