Development¶
Local Setup¶
Python¶
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .\.venv\Scripts\activate # Windows
pip install -e ".[dev]"
Rust (optional)¶
Requires Rust 1.70+ and maturin:
pip install maturin
cd scpn-control-rs
cargo build --release
maturin develop --release -m crates/control-python/Cargo.toml
After maturin develop, RUST_BACKEND becomes True automatically.
Architecture¶
Four Python packages under src/scpn_control/:
| Package | Purpose |
|---|---|
core |
GS equilibrium solver, transport, scaling laws, TokamakConfig presets |
control |
normalized DGKF H-infinity, MPC, SNN, flight sim, disruption predictor, digital twin |
scpn |
Stochastic Petri Net → SNN compiler with formal contracts |
phase |
Paper 27 oscillator-model engine, UPDE, model-local Lyapunov guard, WebSocket stream; no reactor feedback closure |
Five Rust crates under scpn-control-rs/crates/:
| Crate | Purpose |
|---|---|
control-types |
PlasmaState, EquilibriumConfig |
control-math |
LIF neurons, Boris pusher, Kuramoto |
control-core |
Rust GS solver, transport |
control-control |
Rust PID, MPC, H-inf, SNN |
control-python |
PyO3 bindings |
Running Tests¶
pytest tests/ -v # full suite
pytest tests/test_h_infinity_controller.py # single file
pytest -m "not slow" # skip slow markers
pytest --cov=scpn_control --cov-report=term --cov-fail-under=100
The project enforces a 100% configured package coverage gate for admitted CI contexts
(configured in pyproject.toml). This is a statement/branch gate for
the dependencies and variants actually admitted to the merged coverage jobs;
it is not a claim that unavailable hardware, private datasets, facility
services, or every optional package executed. Coverage claims should come from
the latest local coverage run or the GitHub coverage lane, not from static
documentation text.
Type Checking¶
Scope: all modules under src/scpn_control/ (disallow_untyped_defs = true, warn_return_any = true).
PEP 561 marker: src/scpn_control/py.typed.
tools/run_mypy_strict.py is the local preflight gate for strict-typing debt. It
first runs the configured repository mypy check, then runs a whole-package
mypy --strict src/scpn_control/ probe and compares the result with
tools/mypy_strict_debt.json. The ratchet may stay flat or fall; increases need
an explicit baseline update with the increase flag so added strict debt remains a
reviewed change.
Wiring Checks¶
Two local guards keep source modules connected to real repository surfaces:
check_test_module_linkage.py confirms production modules have direct test
linkage or an explicit allowlist entry. check_runtime_wiring.py parses imports
across src, tests, benchmarks, examples, tools, and validation; it
fails if a source module is referenced by no package API, test, tool, or
pipeline file. Both gates run through tools/preflight.py.
Documentation links¶
Run the deterministic public-document graph audit before changing docs, manuscripts, generated navigation, or submission metadata:
python tools/document_link_audit.py
mkdocs build --strict
python tools/document_link_audit.py --site-dir site
The local gate checks tracked Markdown, HTML, TeX, BibTeX, MkDocs navigation, and public metadata. It resolves relative files and Markdown anchors, validates the rendered site tree when supplied, and rejects credential-shaped public URLs. It never crawls the network.
External availability runs only in the scheduled Docs Link Audit workflow or
through an explicit --external invocation. That mode limits retries and URL
count, paces each host, reuses a TTL cache, records tool/policy/source/URL-set
provenance, and separates reachable, restricted, transient, and permanent
results. Restricted or transient responses remain visible without being
misreported as permanent breakage; confirmed permanent failures fail the job.
Authenticated or secret-bearing URLs are outside this public audit by policy.
Coverage Exclusions¶
Every source # pragma: no cover exclusion must explain why the line is not
covered by the local Python coverage job:
Use a one-line reason such as an optional dependency path, native backend path,
or defensive invariant branch. The gate runs through tools/preflight.py and
fails on bare exclusions.
The broader exception ledger covers source pragmas, coverage.py exclusion
patterns, skipif, runtime skips, and strict xfails. Every entry records its
owner, reason/condition, external dependency, executing CI lane or explicit
blocker, review date, and removal condition:
The committed machine-readable output is
tools/coverage_exception_ledger.json. Source locations and conditions are
digest-sealed, so a new or moved exception fails CI until its ownership and
variant classification are reviewed. Runtime skips are not accepted as
coverage success merely because the configured context excludes them.
Native-dependent modules use a two-environment coverage matrix because the
authoritative Python job runs without the optional scpn_control_rs extension
while the interop job builds it and executes every test-file owner with a
Rust/PyO3 conditional entry in the exception ledger. CI uploads coverage-data-python and
coverage-data-rust, then native-coverage-combine runs:
coverage combine --keep artifacts/coverage/python artifacts/coverage/rust
coverage report --fail-under=100
python tools/native_coverage_matrix.py checks that the workflow, threshold, and public
docs still describe the scpn-control.native-coverage-matrix.v1 contract. The
same guard runs through tools/preflight.py.
The per-commit CI is a distributed responsibility graph. The small
.github/workflows/ci.yml coordinator owns only triggers, concurrency,
explicit reusable calls, and the fail-closed ci-gate; executable jobs live in
the cohesive .github/workflows/ci-*.yml owners declared by
tools/ci_workflow_policy.json. Run
python tools/check_ci_workflow_modularity.py after any workflow change. The
guard verifies exclusive job ownership, original dependency and artifact
ordering, conditional behavior, explicit secrets, immutable action pins,
workflow sizes, and complete aggregate-gate failure semantics. It is also part
of the normal local preflight surface and the hosted python-lint job.
Repository audit and generation helpers under tools/ are source-tree
commands, not installed package entry points. Release artifacts expose only the
shipped scpn-control CLI. Build wheel and sdist through
python tools/build_release_artifacts.py; it fixes archive metadata to the
source commit epoch, rejects private/build-only members, verifies that every
console-script target exists in the wheel, and validates the SPDX license
expression.
GitHub Token Format Guard¶
The security lane runs python tools/check_github_token_format_readiness.py in
CI. The guard scans tracked text files and workflow files for brittle GitHub
installation-token assumptions: exact-width ghs_ regexes, fixed token-length
checks, undersized storage columns, and installation-token endpoint calls that
omit X-GitHub-Stateless-S2S-Token.
Treat installation tokens as opaque strings. Code may check for presence,
prefixes needed for routing, or provider errors, but it must not assume a fixed
length or storage width. Test fixtures live under tests/ and are intentionally
excluded from the repository scan so negative examples remain possible without
making the gate flag itself.
Public Surface Hygiene¶
This guard scans tracked outward-facing text files and fails on bare
self-applied promotion terms. It also blocks path-specific leaks where a public
surface would expose internal implementation names, local-host details, or
operational gateway wording, and it rejects public bank or wallet coordinates
on payment surfaces. It also blocks stale public tutorial paths that point
outside the repository's artifacts/ directory. Private operational records
are excluded; bounded negative language and candidate labels remain allowed
because they do not assert an achieved public claim. Public Markdown and JSON
are also rejected when they expose task lists, prioritisation, internal task
identifiers, or private paths.
Changelog Mirror¶
CHANGELOG.md is the authoritative release history. docs/changelog.md is the
rendered MkDocs mirror and must stay byte-identical to the root file. The guard
runs in CI, local preflight, pre-commit, and make lint.
Tracked Source Headers¶
The source-header gate enforces the repository's seven-line semantic identity
block across owned source, tests, workflows, build files, and commentable
configuration. tools/source_header_policy.toml records the reviewed format
families that cannot carry this header without changing legal text, generated
integrity data, serialisation, manuscript rendering, or binary content. A new
tracked format must be classified explicitly; it cannot silently bypass the
gate. The same command runs in CI, local preflight, pre-commit, and
make lint.
Competitive Evidence¶
The competitive-evidence gate treats
docs/_data/competitive_evidence.json as the dated source registry for
docs/competitive_analysis.md. Release-backed entries require exact tags and
commit SHAs; papers require stable DOI sources. The public page must carry every
source, state the empty numeric-comparison set when no matched protocol is
admitted, use not assessed instead of inferred absence, and exclude ranking
language and private planning markers. Any quantitative row must declare the
same problem, inputs, precision, tolerances, convergence, warm-up, samples,
hardware/load, isolation, failures, and result artifact.
Python lint scope¶
ruff check src/scpn_control/
ruff format --check src/scpn_control/ tests/
python tools/check_python_lint_contract.py
Ruff lint rules apply to the production package. Tests remain under the same
formatter, but test-function docstrings are not treated as public API. The
contract checker keeps Make, hosted CI, pre-commit, and local preflight aligned
on those scopes. The separate public-API docstring gate below remains a hard
zero-debt check over src/scpn_control/.
Documentation builds intentionally constrain MkDocs to >=1.6.1,<2 and
Material for MkDocs to >=9.7.7,<10. Material 9.x does not support MkDocs 2;
the upper bound prevents an unsupported resolver combination until the theme,
plugins, navigation, search, and rendered output have a supported parity path.
Public API Docstrings¶
The docstring gate runs ruff's public API pydocstyle rules for classes,
functions, methods, packages, and nested classes. The recorded debt is zero, so
new public APIs without docstrings fail CI, local preflight, and make lint.
Docstrings should name the technical contract, units, failure modes, and claim
boundaries where those details matter.
Studio Custody Guards¶
check_studio_deploy_key.py validates the tracked Studio deploy public key, the
CI rsync deploy workflow, and private deploy-key exclusion.
check_studio_offline_sealing.py keeps Studio publication signing custody
offline: workflows, Studio surfaces, docs, and tools must not reference
Hub/Studio sealing or signing private-key secrets, and tracked policy surfaces
must not contain private-key blocks. The guard deliberately allows deploy-only
SSH credentials because they do not sign evidence; sealed evidence keys stay
with the Studio keeper.
Release Process¶
- Bump version in
pyproject.toml,CITATION.cff, and.zenodo.jsonand runpython tools/check_version_sync.pyto verify release notes, README PyPI/Python-version badges, the Pepy all-time downloads badge, and local version metadata. -
Tag and push:
-
CI publishes to PyPI via
publish-pypi.yml - Verify:
pip install scpn-control==X.Y.Z
Docs¶
pip install -e ".[docs]"
mkdocs serve # preview at http://127.0.0.1:8000
mkdocs build # static site in site/
CI deploys to GitHub Pages on push to main via .github/workflows/docs-pages.yml.
JOSS Submission Review¶
Run python tools/check_joss_submission.py before sending the paper to an
external JOSS workflow. The guard checks the canonical
papers/submissions/001_neuro_symbolic_tokamak_control_software/manuscript.md
front matter, its references.bib bibliography, bracketed citation coverage,
the docs/joss_paper.md pointer, and the claim-boundary/editorial text that keeps
benchmark and validation statements tied to admitted evidence.
The guard runs in local preflight and CI lint so the paper, docs mirror, and bibliography drift together instead of relying on a manual editorial pass.
How to use this guide in practice¶
This page defines the engineering path to stable work, not the path for first contact. Use it after onboarding is complete when you need to:
- reproduce an existing result,
- add a new module behind an existing interface,
- or prepare a release candidate with validation and documentation updates.
Each section is intentionally scoped: setup to make the stack runnable, test and type gates to keep it safe, and release steps to keep claim and version metadata aligned.
The docs site includes:
- Full API reference via mkdocstrings, including a complete module index for
every tracked Python module under
src/scpn_control/ - Theory page with rendered MathJax equations
- Architecture diagrams via Mermaid
- Notebook gallery with execution instructions
- Changelog, benchmarks, and validation reports
Practical use and scope¶
Use this guide for change workflow in scpn-control itself.
- Run setup and local checks here before editing core modules.
- Use the workflow before opening implementation tasks that affect CI or packaging.
- Keep claim-boundary and admission checks in lockstep with this guide when production-relevant files change.