diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd06ae7..c1c36ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,23 @@ semantics have been stable since 1.0.0.
## [Unreleased]
+### Changed — documentation
+- **README onboarding and claim wording.** "Try it in 30 seconds" is now "Try it": the recipe starts
+ in a fresh virtual environment (`python -m pip install dorian-vwp==1.4.0`) and commits with a
+ command-local Git identity, so it needs no prior install and no global Git configuration; the
+ documented exits stay `0` (verify) and `4` (revalidate). The opening states what dorian checks
+ and what its strength depends on; "read-only" now describes only C1, C3, and typed C5; the
+ benchmark entry is labelled a historical synthetic result, reproducible from a source checkout;
+ the httpx entry is described as a retrospective, scoped reproduction.
+ `tests/test_readme_example.py` now mirrors the recipe's commit, runs without inherited Git
+ identity or global Git config, and pins the recipe's install line to the package version.
+- **`docs/BENCHMARK_CURRENT.md`** is retitled "Archived benchmark results and compatibility notes";
+ the figures are unchanged.
+- **`docs/releases/v1.4.0.md`** matches the published release body: a narrower summary, the pane/TUI
+ deferral stated plainly, and documentation links pinned to the `v1.4.0` tag.
+- **`CONTRIBUTING.md`** added: setup, `make lint` / `make test`, the README-example test, the
+ zero-runtime-dependency rule, security reporting, and commit conventions.
+
## [1.4.0] — 2026-07-02
### Added — Dorian Loop Guard (1.4.0)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..c5c9e64
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,57 @@
+# Contributing to dorian
+
+Small, focused pull requests with tests are welcome. This page covers the development setup, the
+checks a pull request must pass, and the rules the codebase keeps.
+
+## Set up
+
+You need Python 3.11+, Git, and [uv](https://docs.astral.sh/uv/).
+
+```bash
+git clone https://github.com/ajaysurya1221/dorian.git
+cd dorian
+make install # uv sync: the package plus the dev dependency group
+```
+
+## Checks
+
+Run both before you open a pull request; CI runs the same suite on Python 3.11, 3.12, and 3.13.
+
+```bash
+make lint # ruff check and ruff format --check over src, tests, and bench
+make test # the full pytest suite, slow tests included
+```
+
+While iterating, `make test-fast` skips the slow tests (benchmark runs, the wheel build, and real
+pytest subprocesses), and `make fmt` applies the linter fixes and the formatter.
+
+**The README example is a test.** `tests/test_readme_example.py` runs the README's "Try it" recipe as
+a black box and checks that the README still shows those commands. If you change the recipe, change
+the test in the same pull request and run it:
+
+```bash
+uv run pytest tests/test_readme_example.py
+```
+
+Other documentation guard tests pin public wording (for example `tests/test_docs_polish.py` and
+`tests/test_benchmark_evidence.py`). If you change pinned text on purpose, update its guard in the
+same pull request and say why. What results and docs may claim is set by
+[`docs/VALIDATION_HONESTY.md`](docs/VALIDATION_HONESTY.md).
+
+## Rules
+
+- **Zero runtime dependencies.** `[project].dependencies` in `pyproject.toml` stays empty and the
+ core imports only the standard library. Optional features belong in an extra (`data`, `extract`);
+ development tools belong in the `dev` dependency group, locked in `uv.lock`.
+ `make dependency-report` prints the current posture.
+- **No model on the verification path.** `tests/test_firewall_import_closure.py` fails if a model SDK
+ or network client is imported at module level anywhere in the verdict path's import graph.
+- **Benchmark contributions carry aggregate numbers only.**
+- **Conventional Commits.** Write commit subjects as `type: summary`, using the types already in the
+ history: `feat`, `fix`, `docs`, `test`, `chore`, `ci`, `release`. Explain the why in the body.
+
+## Security
+
+Do not report vulnerabilities in public issues. Follow [SECURITY.md](SECURITY.md): open a
+[GitHub security advisory](https://github.com/ajaysurya1221/dorian/security/advisories/new), or a
+regular issue without exploit details asking for a private channel.
diff --git a/README.md b/README.md
index 7d8480d..9fc5f86 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,9 @@
**Hold AI agents to what they said they did.**
-*The summary still reads perfectly. Its portrait doesn't.*
-
-
+
@@ -27,26 +25,39 @@
-`dorian` holds an AI coding agent to what it *said* it did. The agent (or you) writes a `claims.json`
-of checkable claims about a change — "`handler()` lives in `app.py`", "the login timeout is 30
-seconds", "`test_login_ratelimit` passes". `dorian verify` turns each claim into a deterministic
-check, runs it against the real code, and seals the result beside the code in a git-committed
-`.warrant` sidecar. On every later commit, `dorian revalidate` re-runs only the checks whose watched
-files changed and flips the warrant to **REVOKED** the moment a claim stops being true — naming the
-claim. No model is called at verification time (**zero model tokens at check time**), so the checker
-cannot be talked past by the code it verifies. It ships as a CLI, a GitHub Action, and Claude Code
-hooks, with **zero runtime dependencies**.
+`dorian` turns explicit claims about a code change into executable checks and stores their results
+in a `.warrant` beside the code. Later revalidation reruns affected checks and revokes warrants when
+those checks fail. Verification makes no model calls (**zero model tokens at check time**); its
+strength depends on the claims, checkers and trusted execution environment. Its question is:
+**"Does the code still satisfy this recorded claim?"**
+
+The agent (or you) writes a `claims.json` of checkable claims about a change — "`handler()` lives in
+`app.py`", "the login timeout is 30 seconds", "`test_login_ratelimit` passes". `dorian verify` runs
+each claim's checker against the code and seals the results in a git-committed `.warrant` sidecar. On
+each later commit, `dorian revalidate` re-runs only the checks whose watched files changed and flips
+the warrant to **REVOKED** when a load-bearing claim's check fails, naming the claim. It ships as a
+CLI, a GitHub Action, and Claude Code hooks, with **zero runtime dependencies**.
-## Try it in 30 seconds
-A self-contained run on a throwaway repo — copy-paste it; it leaves nothing behind but a
-temp directory. (A black-box test pins this exact sequence, so it stays runnable.)
+## Try it
+
+A self-contained run in a throwaway directory, with its own virtual environment and a command-local
+Git identity, so it needs no prior install and no global Git configuration. The demo repository and virtual environment stay in the temporary directory. (A black-box test pins the commands and their exit codes.)
```bash
-tmp=$(mktemp -d) && cd "$tmp" && git init -q
+tmp=$(mktemp -d)
+cd "$tmp"
+python3 -m venv .venv
+source .venv/bin/activate
+python -m pip install dorian-vwp==1.4.0
+git init -q
+printf '.venv/\n' > .gitignore
+
printf 'def handler():\n return 200\n' > app.py
printf '# change note\n\n`handler()` lives in app.py.\n' > note.md
-git add -A && git commit -q -m "app + note"
+git add app.py note.md .gitignore
+git -c user.name=Demo -c user.email=demo@example.invalid \
+ -c commit.gpgsign=false commit -q -m "app + note"
cat > claims.json <<'JSON'
{"claims": [
@@ -63,19 +74,20 @@ printf 'def renamed():\n return 200\n' > app.py
dorian revalidate --since HEAD # -> handler-exists BROKEN; WARRANTED -> REVOKED (exit 4)
```
-`note.md` never changed and `git`/CI stay quiet — but the warrant flips to REVOKED, naming
-the exact claim that stopped being true. (Don't have `dorian` yet? See
-[Install](#install).)
+`note.md` never changed, but the warrant flips to REVOKED and names the claim whose check failed.
+`python3` must be 3.11 or newer. For other ways to install, see [Install](#install).
## What it checks and what it does not
-A claim is bound to one of four read-only checker families: **C1** (a quoted span of the artifact
-itself), **C3** (a path, symbol, string, or regex in a file, plus the structural `py-signature:` /
+A claim is bound to one of four checker families: **C1** (a quoted span of the artifact itself),
+**C3** (a path, symbol, string, or regex in a file, plus the structural `py-signature:` /
`py-const:` / `code:` / `config-value:` forms), **C4** (a `pytest:` node id), or **C5** (typed data
-checks on CSV/parquet files, or a `shell:` command). `verify` is *born-verifiable*: it seals only if
-every claim holds right now (exit 0) and writes nothing otherwise (exit 4). A warrant is born
-**WARRANTED** and folds on each `revalidate` to **TRUSTED**, **DEGRADED**, **REVOKED**, or **UNKNOWN**
-(a checker could not run — never counted as broken, never silently green).
+checks on CSV/parquet files, or a `shell:` command). C1, C3, and typed C5 only read files; C4 and C5
+`shell:` execute code (see [Security](#security-claims-are-executable-input)). `verify` is
+*born-verifiable*: it seals only if every claim holds right now (exit 0) and writes nothing
+otherwise (exit 4). A warrant is born **WARRANTED** and folds on each `revalidate` to **TRUSTED**,
+**DEGRADED**, **REVOKED**, or **UNKNOWN** (a checker could not run — never counted as broken, never
+silently green).
The honest limits:
@@ -155,10 +167,13 @@ jobs:
One command scaffolds a project-local skill: `dorian claude-code install-claim-warrants`. After a
change, `/dorian-claim-warrants` drafts the change note + `claims.json` for the checkable facts the
-agent claimed and prints the verify command — **the model only drafts; `dorian verify` proves**.
-`dorian revalidate --since ` on later PRs REVOKEs whatever the code drifted away from. Add
-`--with-hook` for an opt-in, reminder-only Stop hook. Paste-ready prompt, runnable example pack, and
-`settings.json` sample: [`docs/USE_WITH_CLAUDE_CODE.md`](docs/USE_WITH_CLAUDE_CODE.md) and
+agent claimed and prints the verify command. **The model drafts; deterministic checkers evaluate the
+specified claims.** Review the drafted `claims.json` before running `verify`: C4 and C5 `shell:`
+checkers execute code ([Security](#security-claims-are-executable-input)). On later PRs,
+`dorian revalidate --since ` re-runs the affected checks and revokes a warrant when a
+load-bearing check fails. Add `--with-hook` for an opt-in, reminder-only Stop hook. Paste-ready
+prompt, runnable example pack, and `settings.json` sample:
+[`docs/USE_WITH_CLAUDE_CODE.md`](docs/USE_WITH_CLAUDE_CODE.md) and
[`examples/claude-code/`](examples/claude-code/).
Using dorian inside AI coding loops: `dorian loop preflight --since ` re-checks the warrants a
@@ -182,7 +197,8 @@ install` adds a `SubagentStop` gate and a fail-closed `PreToolUse` veto for unat
claim and C5 checker suggestions to paste into `claims.json`.
- `dorian report --audit` — the event log as byte-identical JSONL.
- `dorian bench mutation` · `bench large-mutation` · `bench binding-lifecycle` · `bench public-repos`
- — the reproducible benchmark suites.
+ — the benchmark suites; they need a source checkout with development dependencies (the wheel does
+ not ship `bench/`).
Exit codes: `0` ok/TRUSTED · `2` usage/infra · `3` DEGRADED · `4` REVOKED/integrity · `5`
ERRORED-only (checkers could not run; never conflated with broken) · `6` scope violation.
@@ -195,15 +211,17 @@ Full reference: [`docs/COMMANDS.md`](docs/COMMANDS.md).
recall **0.93**, versus file-change watchers at recall 1.00 but precision **0.34** (naive),
**0.56** (path-scope), and **0.59** (line-aware) — **11.6x** fewer false alarms than the path-scope
watcher (58 → 5) and **10.4x** fewer than the line-aware one (52 → 5). Synthetic, not your
- repository; measured at v0.7.0 and **historical** — last re-run, unchanged, at v1.2.0
- ([`docs/BENCHMARK_CURRENT.md`](docs/BENCHMARK_CURRENT.md),
- [`docs/BENCHMARK_v0.7.0.md`](docs/BENCHMARK_v0.7.0.md)). Reproduce: `dorian bench large-mutation`.
-- **One real catch.** A load-bearing claim sealed against [`encode/httpx`](https://github.com/encode/httpx)
- — `requires-python` is `">=3.8"` — was flipped WARRANTED → REVOKED (exit 4) by a later upstream PR
- ([#3592](https://github.com/encode/httpx/pull/3592), "Drop Python 3.8 support") while httpx's own
- test suite stayed green. Full output and a from-scratch reproduction:
- [`docs/REAL_CATCH_LOG.md`](docs/REAL_CATCH_LOG.md). One documented catch — evidence, not universal
- validation.
+ repository; measured at v0.7.0 ([`docs/BENCHMARK_v0.7.0.md`](docs/BENCHMARK_v0.7.0.md);
+ compatibility notes in [`docs/BENCHMARK_CURRENT.md`](docs/BENCHMARK_CURRENT.md)). Historical
+ synthetic result, last rerun at v1.2.0. To reproduce from a source checkout with development
+ dependencies installed, run `uv run dorian bench large-mutation`.
+- **Retrospective public-repository check.** Revalidating a warrant across two pinned httpx commits
+ detected the documented Python-support change. This is a scoped reproduction, not evidence of
+ deployment adoption or a prevented production defect. The load-bearing claim was that
+ [`encode/httpx`](https://github.com/encode/httpx) declares `requires-python = ">=3.8"`; the upstream
+ commit for [#3592](https://github.com/encode/httpx/pull/3592) ("Drop Python 3.8 support") flipped the
+ warrant WARRANTED → REVOKED (exit 4). Full output and a from-scratch reproduction:
+ [`docs/REAL_CATCH_LOG.md`](docs/REAL_CATCH_LOG.md). One documented case, not universal validation.
## Docs
@@ -213,7 +231,8 @@ Full reference: [`docs/COMMANDS.md`](docs/COMMANDS.md).
- [`docs/BINDING.md`](docs/BINDING.md) — binding semantics: trigger vs. truth.
- [`docs/AGENT_CLAIMS.md`](docs/AGENT_CLAIMS.md) and [`spec/checkers.md`](spec/checkers.md) —
writing claims; the checker grammar.
-- [`docs/BENCHMARK_CURRENT.md`](docs/BENCHMARK_CURRENT.md) — current-version benchmark reruns.
+- [`docs/BENCHMARK_CURRENT.md`](docs/BENCHMARK_CURRENT.md) — archived benchmark results and
+ compatibility notes.
- [`SECURITY.md`](SECURITY.md) and [`docs/SECURITY_BOUNDARY.md`](docs/SECURITY_BOUNDARY.md) — security.
- [`docs/ROADMAP_BACKLOG.md`](docs/ROADMAP_BACKLOG.md) — the structured roadmap backlog.
@@ -225,6 +244,7 @@ make install && make lint && make test
```
Small, focused PRs with tests are welcome. Benchmark contributions carry aggregate numbers only.
+Development setup, checks, and conventions: [`CONTRIBUTING.md`](CONTRIBUTING.md).
## License
diff --git a/docs/BENCHMARK_CURRENT.md b/docs/BENCHMARK_CURRENT.md
index ac6ad8b..0245510 100644
--- a/docs/BENCHMARK_CURRENT.md
+++ b/docs/BENCHMARK_CURRENT.md
@@ -1,10 +1,13 @@
-# Current-version benchmark results
+# Archived benchmark results and compatibility notes
-Version-stamped reruns of dorian's reproducible benchmark suites on the **current** code,
-so the published numbers track the implementation rather than lagging behind it. The older
+The benchmark figures dorian publishes, with a note per release on whether that release could
+move them. The suites were last re-run at v1.2.0; the version stamp below records the current
+package version after a review of what each later release changed, not a new run. The older
result docs ([`BENCHMARK_v0.7.0.md`](BENCHMARK_v0.7.0.md) = v0.7.0,
[`BENCHMARK_BINDING_LIFECYCLE.md`](BENCHMARK_BINDING_LIFECYCLE.md) = 0.9.0) are **historical**
-and are kept as-is for provenance.
+and are kept as-is for provenance. The suites are synthetic, and reproducing them needs a source
+checkout with development dependencies installed (`uv run dorian bench …`); the published wheel
+does not ship `bench/`.
## Measurement environment
diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md
index e723cf9..06bfd80 100644
--- a/docs/OVERVIEW.md
+++ b/docs/OVERVIEW.md
@@ -1,6 +1,6 @@
# dorian — the long-form tour
-The [README](../README.md) is the 30-second version. This page keeps the full narrative it used to
+The [README](../README.md) is the short version. This page keeps the full narrative it used to
carry: the illustrative walkthrough, the story behind the name, the Claude Code and loop-guard
integrations, the governance preview, the complete getting-started path, and the roadmap. The
command reference is in [`COMMANDS.md`](COMMANDS.md); binding semantics (trigger vs. truth) are in
@@ -22,7 +22,7 @@ now and is re-checked on every future change, so a confident summary doesn't qui
## The 60-second aha
*(Illustrative — these files are not in your checkout; run the copy-paste demo in the
-[README](../README.md#try-it-in-30-seconds) to try it yourself.)*
+[README](../README.md#try-it) to try it yourself.)*
An agent finishes a change and emits the claims it just made — a `claims.json` next to
the work, each claim bound to a read-only deterministic checker:
@@ -322,7 +322,7 @@ The workflow snippet, pinned to the released Action tag, lives in the
those two copies are the ones the release tests keep in sync with the package version.
Now that `dorian` is installed, the copy-paste runnable demo at the top —
-[Try it in 30 seconds](../README.md#try-it-in-30-seconds) — runs end to end against a throwaway repo.
+[Try it](../README.md#try-it) — runs end to end against a throwaway repo.
## Writing claims an agent can be held to
diff --git a/docs/releases/v1.4.0.md b/docs/releases/v1.4.0.md
index 1562a3c..d06e21d 100644
--- a/docs/releases/v1.4.0.md
+++ b/docs/releases/v1.4.0.md
@@ -9,12 +9,9 @@ the verification path** (a build-time import firewall now proves it).
## Summary
-dorian is growing from *"deterministically verify AI claims about code"* into a **deterministic
-governance kernel** for agentic engineering. The guiding thesis: *dorian does not train agents to be
-good — it makes bad actions non-continuable, non-mergeable, or human-reviewable.* "Crime" means an
-observable contract violation; "punishment" means a mechanical loss of affordance (a blocked tool
-call, a forced repair, a failed gate, a revoked warrant, a human escalation). dorian is the court;
-host adapters and CI/branch-protection are the enforcement.
+This release adds optional loop steering and host hooks around claim revalidation. It reports
+continue, repair or escalate from observable records; the host and CI configuration decide whether
+execution or merging is blocked.
## What shipped
@@ -51,10 +48,11 @@ host adapters and CI/branch-protection are the enforcement.
- **Release hardening** — functional PreToolUse-veto subprocess tests (identity mismatch, godmode,
freshness boundary, empty-path escalate, malformed-stdin open/closed) and an installed-wheel
`dorian governance install` scaffold test.
-- **Docs** — [`GOVERNANCE_DATA_MODEL.md`](../GOVERNANCE_DATA_MODEL.md),
- [`DORIAN_PANE.md`](../../archive/docs/DORIAN_PANE.md) (pane vision — deferred), [`DORIAN_LOOP_GUARD.md`](../DORIAN_LOOP_GUARD.md),
- and gate/C4-flakiness notes in [`SECURITY_BOUNDARY.md`](../SECURITY_BOUNDARY.md) /
- [`VALIDATION_HONESTY.md`](../VALIDATION_HONESTY.md).
+- **Docs** — [`GOVERNANCE_DATA_MODEL.md`](https://github.com/ajaysurya1221/dorian/blob/v1.4.0/docs/GOVERNANCE_DATA_MODEL.md),
+ [`DORIAN_PANE.md`](https://github.com/ajaysurya1221/dorian/blob/v1.4.0/docs/DORIAN_PANE.md) (pane vision — deferred),
+ [`DORIAN_LOOP_GUARD.md`](https://github.com/ajaysurya1221/dorian/blob/v1.4.0/docs/DORIAN_LOOP_GUARD.md),
+ and gate/C4-flakiness notes in [`SECURITY_BOUNDARY.md`](https://github.com/ajaysurya1221/dorian/blob/v1.4.0/docs/SECURITY_BOUNDARY.md) /
+ [`VALIDATION_HONESTY.md`](https://github.com/ajaysurya1221/dorian/blob/v1.4.0/docs/VALIDATION_HONESTY.md).
## Deterministic boundary (unchanged invariants)
@@ -115,8 +113,5 @@ feature = delete `.claude/hooks/dorian_*.py` and the settings entries; the core
## Pane / TUI boundary
-The **pane / TUI / brand** are **deferred**. [`DORIAN_PANE.md`](../../archive/docs/DORIAN_PANE.md)
-(now archived) is a future-facing vision and data contract, **not** an implementation — no UI/TUI code
-ships in this release. This release owns only the deterministic data contracts and governance
-invariants; the visual design (layout, typography, color, interaction, banner, logo) is a separate,
-later effort.
+The pane and TUI are deferred. This release provides CLI commands and host-hook templates; it does
+not include a user interface.
diff --git a/tests/test_readme_example.py b/tests/test_readme_example.py
index 0470eb8..94dbdf8 100644
--- a/tests/test_readme_example.py
+++ b/tests/test_readme_example.py
@@ -1,10 +1,16 @@
-"""The README's "Try it in 30 seconds" recipe, executed as a black box.
+"""The README's "Try it" recipe, executed as a black box.
-This pins the headline runnable example to reality: it runs the exact sequence the README
+This pins the headline runnable example to reality: it runs the sequence the README
shows (out-of-process, via `python -m dorian`) and asserts the observable result, and it
-checks the README still contains that command — so the demo a new user copy-pastes can
+checks the README still contains those commands — so the demo a new user copy-pastes can
never silently become a broken claim. (A tool whose whole pitch is "don't ship false
claims" must not ship a false claim in its own README.)
+
+The recipe commits with a command-local Git identity, so the test strips inherited identity
+variables and ignores global/system Git config: the README's own commit command must supply
+the identity (Git can still guess one from the hostname on some hosts). The recipe's
+`pip install` step is not run here (no network); the test exercises this checkout and pins
+the README's install line to its version.
"""
from __future__ import annotations
@@ -13,16 +19,41 @@
import re
import subprocess
import sys
+import tomllib
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
-_GIT_ENV = {
- "GIT_AUTHOR_NAME": "t",
- "GIT_AUTHOR_EMAIL": "t@t",
- "GIT_COMMITTER_NAME": "t",
- "GIT_COMMITTER_EMAIL": "t@t",
-}
+_IDENTITY_VARS = (
+ "GIT_AUTHOR_NAME",
+ "GIT_AUTHOR_EMAIL",
+ "GIT_COMMITTER_NAME",
+ "GIT_COMMITTER_EMAIL",
+ "EMAIL",
+)
+
+# the README's commit command, identity included (kept identical to the README block)
+_COMMIT_ARGS = (
+ "-c",
+ "user.name=Demo",
+ "-c",
+ "user.email=demo@example.invalid",
+ "-c",
+ "commit.gpgsign=false",
+ "commit",
+ "-q",
+ "-m",
+ "app + note",
+)
+
+
+def _clean_env() -> dict[str, str]:
+ """No inherited identity, no global or system Git config: a machine without Git setup."""
+ env = {k: v for k, v in os.environ.items() if k not in _IDENTITY_VARS}
+ env["GIT_CONFIG_GLOBAL"] = os.devnull
+ env["GIT_CONFIG_NOSYSTEM"] = "1"
+ return env
+
# the claims.json the README's recipe writes (kept identical to the README block)
_CLAIMS_JSON = (
@@ -35,25 +66,26 @@
def _git(repo: Path, *args: str) -> None:
- subprocess.run(
- ["git", *args], cwd=repo, env={**os.environ, **_GIT_ENV}, check=True, capture_output=True
- )
+ subprocess.run(["git", *args], cwd=repo, env=_clean_env(), check=True, capture_output=True)
def _dorian(*args: str, repo: Path) -> subprocess.CompletedProcess:
# mirror the README recipe exactly: cd into the repo, default --repo=".", relative paths
cmd = [sys.executable, "-m", "dorian", *args]
- return subprocess.run(cmd, cwd=repo, capture_output=True, text=True, timeout=120)
+ return subprocess.run(
+ cmd, cwd=repo, env=_clean_env(), capture_output=True, text=True, timeout=120
+ )
def test_readme_try_it_recipe_runs_end_to_end(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
_git(repo, "init", "-q")
+ (repo / ".gitignore").write_text(".venv/\n")
(repo / "app.py").write_text("def handler():\n return 200\n")
(repo / "note.md").write_text("# change note\n\n`handler()` lives in app.py.\n")
- _git(repo, "add", "-A")
- _git(repo, "commit", "-q", "-m", "app + note")
+ _git(repo, "add", "app.py", "note.md", ".gitignore")
+ _git(repo, *_COMMIT_ARGS)
(repo / "claims.json").write_text(_CLAIMS_JSON, encoding="utf-8")
# verify: the claim holds against the real code -> sealed, exit 0
@@ -78,6 +110,21 @@ def test_readme_still_contains_the_runnable_commands() -> None:
assert "dorian verify note.md --claims claims.json" in readme
assert "dorian revalidate --since HEAD" in readme
assert "symbol:app.py::handler" in readme
+ assert "git add app.py note.md .gitignore" in readme
+ assert "git -c user.name=Demo -c user.email=demo@example.invalid" in readme
+ assert '-c commit.gpgsign=false commit -q -m "app + note"' in readme
+
+
+def test_readme_recipe_installs_the_current_release() -> None:
+ """The recipe starts from a fresh venv with a pinned install; the pin must be this version."""
+ readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
+ version = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"][
+ "version"
+ ]
+ assert "python3 -m venv .venv" in readme
+ pins = re.findall(r"python -m pip install dorian-vwp==(\d+\.\d+\.\d+)", readme)
+ assert pins, "the Try it recipe must install a pinned dorian-vwp release"
+ assert all(pin == version for pin in pins), f"README pins {pins}, package is {version}"
def _github_slug(heading_text: str) -> str: