Development and automation¶
This page is for anyone changing the code or the documentation of this
repository: how to set up, what to run before you push, how to add an
example or a page, and how a release is cut. Everything is a make
target, so a person, CI and an automated agent all run the same
commands, and uv.lock pins every tool version, so a result on one
machine is the result on every machine. make help prints the list.
Related pages: Writing the documentation (the standard every page follows), Package structure (where each piece of code lives) and Internals (how the compiler and solvers work).
The development loop¶
make setup # once: uv sync --all-groups (runtime, dev, docs)
# edit code or docs
make format # rewrite into the project style, apply safe lint fixes
make check # exactly what CI runs; green here means green there
The Python version is pinned too: the repository commits a
.python-version file (3.12), so uv creates every checkout's .venv
with Python 3.12 even when your default interpreter is newer. The
repository's .gitignore contains !.python-version, which overrides a
global git ignore rule that would otherwise leave the file out.
requires-python = ">=3.12" in pyproject.toml remains the floor for
people who install the package.
make check runs, in order:
| Step | Target | Tool | Fails when |
|---|---|---|---|
| Formatting | format-check |
ruff format | a file is not in the project style |
| Lint | lint |
ruff | a lint rule fires |
| Types | typecheck |
pyright, strict | any annotation is missing, unknown, or wrong |
| Tests | test |
pytest | a unit, docs-integrity, golden-output or performance-budget test fails |
| Docs | docs |
mkdocs --strict |
a nav entry, link, or autodoc reference is broken |
| Package | build |
uv build | the sdist or wheel cannot be built |
| Web | web-check |
tsc, svelte-check, ESLint, Prettier, Vitest, Vite | only once web/ is installed (below); any web check fails |
Strict type checking is deliberate: the package ships py.typed, which
tells every downstream type checker to trust these annotations.
Configuration lives in pyproject.toml under [tool.pyright].
Slow tests and the memory budget¶
make test skips the few long measurements marked
@pytest.mark.slow (a regime table over 20 openings, a 20 x 20 snake
measurement, the largest Chinese-checkers board); each has a quicker
test of the same claim in the default run. make test-slow runs only
the slow ones, and the Nightly workflow runs them every night. Mark a
new test slow only when it takes several seconds and a faster test
already covers what it proves, or when it measures speed (throughput,
latency): make test runs on every core, so a timing would measure the
other tests too, and make test-slow runs one test at a time.
How a run behaves (addopts in pyproject.toml): tests run in
parallel on every core with pytest-xdist, each file's tests on one
worker, so a file's fixtures and the files its tests write are never
shared; each test fails after five minutes (pytest-timeout), so a hang
says where it is instead of running for hours; and every run ends with
its fifteen slowest tests. A rule that must hold for every case is
tested with Hypothesis, on generated cases, besides the chosen examples
(tests/test_session_arrows.py).
Every catalog example runs once per test session (the example_runs
fixture in tests/conftest.py), and the golden-output tests and the
catalog smoke test share that run. tests/conftest.py also guards
memory: a full run peaks at about 350 MB, and the test during which the
process first passes 1.5 GB fails, naming the culprit, while a watchdog
stops the run at 4 GB. tests/test_memory_budget.py pins the caps of
the caches that grow with use (search tables, per-board memos): a cache
must be bounded and belong to the object that uses it.
The web showcase¶
web/ holds the website described in the web plan: a
pnpm workspace of TypeScript packages and a SvelteKit app built to
static files. It needs Node 26 and pnpm 10, pinned in web/package.json;
Python-only work never needs them.
make web-setup # once: install the pinned packages from web/pnpm-lock.yaml
make web-dev # live-reloading site at http://localhost:5173
make web-check # type check, lint, format check, unit tests, build
make web-e2e # Playwright against the built site (downloads Chromium once)
make web-lighthouse # Lighthouse CI: every category at least 95 on the replay-first pages
make web-pyodide-golden # every game's frames in Pyodide equal CPython's, byte for byte
make web-pyodide-test # the session, game and learning tests inside Pyodide
make check adds web-check only when web/node_modules exists, so a
Python-only checkout stays green without Node; make check WEB=1 forces
it and WEB=0 skips it. CI never skips it: the web job in
.github/workflows/ci.yml runs make web-setup web-check web-e2e and
make web-lighthouse on every push and pull request (and its pyodide job the two Pyodide
checks). make clean also removes the web build output.
ESLint and Prettier cover web/ only; Python stays on ruff, and
Prettier never formats Markdown, because it breaks MathJax formulas.
web/README.md lists the packages, the commands and the architecture
standard the TypeScript code follows. Dependencies are pinned exactly
and change only when someone updates them by hand and the checks pass;
the choices behind the tooling are D17 to D27 in
Design decisions.
The site serves the live engine (Pyodide and this package's wheel), so
make web-check and make web-build first run make web-engine, which
needs uv too: The engine in the browser.
Building and reading the docs¶
The site is MkDocs with the Material theme.
Sources are the Markdown files in docs/; API pages are generated from
the docstrings in src/ by mkdocstrings. site/ is build output and is
never committed.
| Command | Result |
|---|---|
make docs-serve |
live-reloading site at http://127.0.0.1:8000; watches docs/, src/ |
make docs |
static HTML in site/; fails on any warning or broken link |
The docs dependencies are a separate uv group (uv sync --group docs,
included in make setup), so installing the runtime never pulls MkDocs.
Publishing the site¶
The documentation is served from our own server; nothing is deployed
anywhere else. Run make docs and copy site/ to the server.
Stack¶
| Piece | Role |
|---|---|
| Lark 1.x | LALR front-end for the policy DSL (grammar/policy.lark) |
| MkDocs 1.6 | static site generator (stay on 1.x; Material documents MkDocs 2 as a break) |
| Material for MkDocs 9 | theme, search, tabs, admonitions, mermaid fences |
| mkdocstrings + mkdocstrings-python | API pages from live docstrings (Google style) |
| Ruff | formats autodoc signatures; lints and formats the code |
| MathJax 3 | \( ... \) and \[ ... \] via pymdownx.arithmatex |
Configuration: mkdocs.yml at the repository root.
Golden-output tests for examples¶
Every example in the catalog is a fixed story with a fixed outcome.
tests/test_example_snapshots.py pins two files per example under
tests/snapshots/examples/:
<id>.txt— the human-readable report (grounded-decision examples <id>);<id>.decisions.json— verdict, action, proposal, state and failed checks for every record, plus the run summary.
Any refactor that changes a decision or a line of explanation fails with a diff. When the change is intended:
Commit the snapshot diff together with the code change, so a reviewer
sees the behaviour change next to its cause. Output is deterministic:
every example seeds its randomness, and parser error messages sort
their expected tokens, so results do not depend on PYTHONHASHSEED.
Adding things¶
Extending the system¶
Three guides cover the larger extensions step by step, with a worked example and a definition-of-done checklist each:
- Add a game: a new game module, from
make new-gameto a game that is played, compared, audited, recorded and taught. - Add a protocol operation: a new kind of request, from the session layer through the protocol, the engine and our own service to the page that uses it.
- Add a web page: a new page of the showcase site, its models, tests and accessibility checks.
A new example¶
- Copy the shape of
src/grounded_decision/examples/fraud_review.py: module docstring with the business problem, the evidence graph in domain terms, and a "how the pieces map to a real system" section;ID,TITLE,build(),run(*, as_json=False). - Build evidence from realistic raw records with
snapshot_from_graphwhere you can; name every constant and say how to pick it. - Register it in
examples/catalog.py. - For a
productexample, add a case pagedocs/examples/cases/<id>.mdthat names the id; for any other track, name the id in that track's page (docs/examples/steps.md,adaptive.md,combo.md,live.mdormarkets.md). Add the id todocs/examples/index.mdand any new page tonavinmkdocs.yml. make snapshots, read the new report, thenmake check.
A new documentation page¶
Read Writing the documentation first. Write the page
under docs/, add it to nav in mkdocs.yml, link it from at least
one existing page, and run make docs (strict mode catches broken
links). tests/test_docs.py fails if a nav entry points at a missing
file, if a page is not in the nav, or if a page does not open with a
title and a plain paragraph.
A new math catalog result¶
A check or helper that relies on a result from the companion math catalog records which one.
- Add a
CatalogRefconstant insrc/grounded_decision/certificates.py(familyis the catalog result number; alsotitle,lean,note). - Implement the module, with tests for the honesty caveats (vacuous width, banned step, unused constant).
- Add
docs/theory/<slug>.md: statement used, what the code does, what it does not do, product reading, pin name. - Link it from
docs/theory/index.mdandmkdocs.yml, and add a row to Where each result stands. - If there is a product scenario, add an example (above).
tests/test_docs.py checks that every REF_* family string appears in
docs/theory/.
A public API change¶
- Public names go in
grounded_decision.__all__and in the table on Stability and versioning; check that page's breaking-change rules and add a changelog entry. - Docstrings are the autodoc source (Google style).
- Narrative pages that mention the old name need a sentence, not just autodoc.
uv run grounded-decision interfacesshould still describeDecisionRecord.to_dict().
Integrity tests¶
tests/test_docs.py runs without MkDocs installed:
- every markdown file listed in
mkdocs.ymlnavexists, and every page underdocs/is in the nav; - no page is a placeholder, and every page opens with a title and a plain paragraph;
- every
CatalogRef.familyincertificates.pyappears underdocs/theory/; - every example id appears in
docs/examples/index.md, eachproductexample has a case page, and every other example appears on its track page; docs/how-to/integrate.mdnames the integration entry points, and this page names the documentation stack.
tests/test_docs_math.py fails if a Markdown formatter has escaped
underscores or asterisks inside formulas.
tests/test_docs_commands.py keeps quoted commands honest:
- every
make <target>mentioned in the docs, README, or CONTRIBUTING exists in theMakefile; - every
grounded-decision examples <id>command names a real example.
make docs is the second half: it builds the site in strict mode.
Releasing¶
The version lives in one place, version in pyproject.toml;
grounded_decision.__version__ reads it from the installed metadata.
- Bump
versioninpyproject.toml, runuv lock, updateCHANGELOG.md. Before 1.0, a minor release may change behaviour; every such change goes under "Changed" or "Removed" with a migration note (Stability and versioning). make check.- Commit and tag the version (
git tag v0.2.0). - Build the artifacts locally (
make buildfor the wheel,make docsfor the documentation,make web-buildfor the site) and copy them to our own server.
Nothing is published to a package index or a hosting service. Users install from the wheel or a checkout (Install).
tests/test_performance_budget.py builds the fraud_review module and
checks that the median of 200 decide calls stays under
Limits.decide_ms_budget (50 ms) and the median of 20 builds under
compile_ms_budget (200 ms). The budgets are generous on purpose: the
test catches an accidental slowdown by orders of magnitude, not noise.
README vs this site¶
README.md |
This site |
|---|---|
| Compact contract for the repository | Full depth |
| Start-here table, commands, 30-second usage | Tutorials, concepts, how-to guides, examples, theory, API |
| Must stay accurate | Must stay accurate and complete |
If they disagree, certificates.py and tests/ win; then fix both.
PLAN.md¶
PLAN.md is the design the code was written against (status: done). It
is not the public guide; it is kept as a historical architecture note.
Where it and the code disagree, the code and this site are current.