Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Contributor Guide

This page is for maintainers working on the tutorial itself, not learners working through it. It assumes you can read Python and SysML and that you have the environment from Getting Started already set up.

What we accept

The contributions we want keep this tutorial current to its toolchain (the OpenSysML runtime, sysml-toolkit and the other pinned tools) and to the OMG SysML v2 specifications; we are not adding new content. Existing content may be refined, clarified or otherwise improved against three priorities: (1) conformance with the SysML v2 specifications (the OMG SysML v2 language, API and Services, and KerML specifications); (2) didactic clarity; (3) effective, demonstrative use of tools from the OpenSysML stack (the OpenSysML runtime and sysml-toolkit). An improvement is accepted only if it is strictly dominant: better on at least one of these and worse on none. The binding statement is AGENTS.md §1.12.

The reviewer applies this test. A pull request passes when one “better” line (or, for keeping current, the currency event) and all three “not worse” lines are evidenced; any “worse” means the change is not accepted; what the reviewer cannot tell goes to the ACE.

Priority“Worse” meansVerified by, with what evidence
(1) Spec conformanceText or model violating a normative clause of one of the three specifications; a spec-anchored construct replaced by a tool idiom; a conformance check or its negative control dropped or weakenedReviewer: the clause citation, uv run python scripts/check_conformance.py, a strict load under the pinned runtime; the OMG SysML v2 Pilot Implementation is the baseline where a clause is ambiguous
(2) Didactic clarityLonger or denser without the learner’s task getting harder without it; a second construct or operation in one sub-notebook; a check presented as proof; the pacing rule broken; a figure whose omissions are no longer statedReviewer: the pull request’s statement, the pacing check, and a simulated-learner checkpoint when the change alters what a learner does or sees beyond wording
(3) Tool useA construct described as working but not run under the pinned version; a new workaround without a DEFERRED.md entry and comment cell; a tool demonstration removed; meaning moved from the model into PythonCI (TOASTER_REQUIRE_TOOLS=1, myst build --strict, scripts/check-site.py) and the reviewer re-running the touched notebook; the executed outputs and the DEFERRED.md entries touched

Who is Z

“Z” is the contributor identity mzargham (Michael Zargham, GitHub user mzargham), the project’s author and chief engineer. Project files such as AGENTS.md and the decision log in decisions/ say “Z” for that person. Published pages that mention “Z” name the handle at first mention, as “mzargham (Z)”. Other contributors are named by their own handles, and “Z” is never reused for anyone else.

How this repo is built, tested, and reviewed

The tutorial’s content — every chapter notebook, exercise, and model file — is built and reviewed through a small multi-agent harness that lives alongside the content itself. Three root files and two directories carry that harness, and they’re worth knowing about before you touch anything, even if you never run an agent yourself:

If you want to keep a chapter current, clarify a definition, or review didactic content, the harness tools above are built for exactly that — start at CLAUDE.md’s own read order rather than improvising a workflow from scratch. The sections below cover the maintenance tasks directly; none of them require running an agent, but all of them follow conventions the harness itself enforces (the recipe’s pacing rule, the layer boundary tests, the review gate), and every change passes the test in What we accept.

Deployment status

CI builds the book on every pull request and deploys it from main to https://open-mbee.github.io/toaster/. Pull-request builds do not deploy: the deploy job runs only for a push to main, and only after the build job passes. The workflow is .github/workflows/ci.yml.

The build job provisions the tools with scripts/provision-tools.py (see Getting Started), runs the test suite, builds the book with every notebook executed, and then runs the release gate, scripts/check-site.py. The gate fails the build unless all five checks pass:

  1. the build log has no code-execution or Jupyter-session failure (MyST exits 0 even when a cell raised unless it is run with --strict, which CI does; the log check backs that up, so a failed cell is caught even if the flag is ever dropped);

  2. the built content has the expected number of figures, recorded in scripts/site-baseline.json;

  3. no built text file contains a host path (a home directory, a Homebrew prefix or a CI runner path);

  4. MyST has not published exercises/ files or DEFERRED.md as raw downloads under build/;

  5. every root-relative link and asset in the HTML carries the /toaster base path and points at a file that exists in the built site.

To reproduce the build and the gate locally, from a clone with the tools provisioned:

set -o pipefail
BASE_URL=/toaster uv run --frozen npx myst build --html --execute --strict 2>&1 | tee build.log
uv run python scripts/check-site.py --site _build/html --content _build/site/content \
    --log build.log --base-url /toaster

Keep current: update a dependency or tool pin and regenerate outputs

  1. Change the version in pyproject.toml (Python), package.json (Node) or scripts/tool-pins.json (the sysmlv2 binary, Z3 and the PlantUML jar with their sha256 hashes, and the standard-library commit), then uv lock / npm install to update the lockfile, or uv run python scripts/provision-tools.py to re-provision .tools/. The OpenSysML runtime binary is pinned separately from the opensysml package: change the version="v0.9.0" defaults in src/toaster/bootstrap.py (and its _CLI_SUMS hashes) and src/toaster/connect.py, and every opensysml.connect(version=...) call in scripts/check_conformance.py, scripts/check_construction.py, chapters/, exercises/ and tests/ (git grep -n 'connect(version=' -- src scripts chapters exercises tests), together. Code cells, stored outputs and models/ are protected during editorial passes, not during a bump: the notebooks are re-executed and their outputs regenerate (step 3). scripts/probes/ and scripts/diagram_study/ are dated probes and keep the version they probed.

  2. Run TOASTER_REQUIRE_TOOLS=1 uv run pytest tests/ glossary/tests/ and uv run python scripts/check-tools.py (check-tools.py on GitHub).

  3. Rebuild the local preview (uv run npx mystmd start --execute) and spot-check a chapter that exercises the changed dependency; a version bump in opensysml or sympy can change printed output even when no test fails.

  4. Re-read the DEFERRED.md entries that name the bumped tool. If the new release meets an entry’s resolution condition, retire the workaround, add a dated status line to the entry (or update the one it has), update the comment cell at the workaround (do not rename the heading), and recompute the content_hash of any judgment record the model change touches (below). A release that breaks something gets a new entry, not a silently dropped demonstration.

  5. Commit the lockfile or the pins alongside the version change; never bump a version without regenerating and committing what matches it. Update the version strings in docs/setup.md, docs/reproducibility.md and AGENTS.md §1.2 in the same change, and re-check the prose, tests and DEFERRED.md entries that state a version (git grep -n 'v0\.9\.[01]' -- chapters exercises tests DEFERRED.md): a test or fixture that pins the version moves with it; a statement re-probed under the new release takes the new version; one not re-probed keeps the version it was probed against.

How a change is built and reviewed

New chapters are not accepted (What we accept); the rules that governed building the existing ones govern every change to them:

  1. toaster-recipe’s sub-notebook skeleton and architecture-layers’ boundary tests bind any model element a change touches; both are binding, not stylistic suggestions.

  2. Each cumulative fixture (models/chNN-cumulative.sysml) must keep containing everything the previous chapter’s fixture has; see tests/test_predecessor_containment.py for how that invariant is checked. After changing a notebook’s construction cells or a fixture, run uv run python scripts/check_construction.py --check --chapter=N (scripts/check_construction.py): it executes the registered construction zones and loads each TOASTER_INCREMENT and the cumulative fixture. Update a notebook’s existing CONSTRUCTION_NOTEBOOKS entry only if its stubs change; new entries are for new notebooks, which are not accepted.

  3. Before committing prose, check that uv run python -m glossary lint reports no hit the base branch does not (it is not a CI gate and exits 1 on pre-existing hits: run --write-baseline base.json on the base, then --baseline base.json on your branch, which exits 1 only on a new error); run the pacing check in tutorial-style-guide (consecutive code cells with no markdown between them) on every notebook you touched.

  4. Get an independent review on a different AI model than whoever authored the change, per decisions/task-states.md’s merge gate.

Change a model element and review stale judgment records

A ReviewRecord’s content_hash is computed from the model source it was written against. Changing that source without updating the record leaves it silently stale.

A model change is accepted only as keeping current (a workaround retired) or as a strictly dominant improvement (What we accept); either way:

  1. Find every ReviewRecord whose model_ref touches the element you are changing (grep -rl model_ref= chapters/).

  2. After changing the model, recompute each record’s content_hash and re-run validate_record on it.

  3. Re-read the record’s claim, rationale, and counterevidence fields against the new model state. A hash mismatch tells you the record is stale; it does not tell you whether the claim is still true. Rewrite what no longer holds; do not just refresh the hash.

Run the full CI pipeline locally

uv sync --locked
npm ci
uv run python scripts/provision-tools.py
uv run python scripts/check-tools.py
uv run pytest tests/ glossary/tests/ -v
uv run npx mystmd start --execute

The first five commands mirror the build job’s provisioning and tests (the job also installs Graphviz and a Java runtime from apt); the last serves the book for preview rather than building it. CI runs pytest with TOASTER_REQUIRE_TOOLS=1, so a missing external tool fails the tests instead of skipping them; prefix the pytest line with it to get the same behavior locally. To run the build itself and the release gate, use the two commands under Deployment status. There is no local equivalent of the deploy job, and there should not be, since publishing is a decision, not a build artifact.