Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ the framework's own interrupt, and a framework with no such primitive does not n
## Where it stands

{/* generated from the suite, pyproject and the soak (mdx) — run the generator */}
- **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,220 tests**, every version specified before it was written and every requirement mutation-tested.
- **Version 0.12.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,221 tests**, every version specified before it was written and every requirement mutation-tested.
- **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates.
- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite.
- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak).
Expand Down
12 changes: 9 additions & 3 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,11 +427,17 @@ Exit: **the truncation case** that `SPEC-v0.6.md` §6.4 lists as undetected now

Standards: RFC 3161 consumed as code. None claimed.

## v0.12 — Hardening
## v0.12 — Hardening · shipped 2026-09-15

Fuzzing, property tests, concurrency stress, failure injection, benchmarks, upgrade testing, compatibility guarantees, CodeQL/SAST/SBOM/signed artifacts.
Fuzzing, property tests, concurrency stress, failure injection, upgrade testing, compatibility guarantees, CodeQL/SAST/SBOM/signed artifacts.

- **The import cycle `state` → `receipt` → `policy` → `authority` → `state`, and the sentence in `docs/ARCHITECTURE.md` §6 it contradicts.** Found by a v0.7 review, deferred with a written decision, and named here because deciding which edge to break is design work and not a release pass. `state.py` imports `receipt.py`, `receipt.py` imports `policy.py` for `Decision`, `authority.py` imports `state.py`, and `policy.py` reaches `authority.py` from inside two functions. Nothing is broken at run time: the two edges out of `policy.py` are function-level, so `import ctrlrun` still loads in one order and every test passes, which is why it went unnoticed. What it costs is §6's own rule, **dependencies point downward only**, which is a claim about the module map and not about import order, and the map is what tells a contributor what a module may know about. The candidate fixes are a shared type for `Decision` below both, or moving the two deferred imports out of `policy.py`; each is a public-surface question of its own. `docs/ARCHITECTURE.md` §6 states the cycle where the false sentence was. Added 2026-09-12.
**Reconciled against what shipped.** Most of the line above was already true when v0.12 opened: fuzzing (`fuzz.yml`), CodeQL, signed artifacts (trusted publishing with PEP 740 attestations), concurrency stress (`test_soak.py` and the multi-process standard), failure injection (`tests/failure_injection.py`) and upgrade testing against released wheels all shipped in earlier milestones. What was genuinely missing was **property tests** and an **SBOM**, and both are here.

**Benchmarks are struck from this line rather than deferred.** `docs/postgres.md` withholds a throughput figure on purpose and `production/soak.mdx` says three times that its number is not one to plan against. A benchmark suite manufactures exactly the figure those two pages decline to give, and the first thing anyone would do with it is quote it. There is no performance claim in this project to defend, and a test is owed by a claim. If one is ever made, this is where its benchmark goes.

**Compatibility guarantees stay open** and belong to v1.0's *stable has an operational definition*, which is the same document under another name.

- **The import cycle is closed, and §6's rule is a test.** Broken in two places, neither of which was the pair the line above predicted. `Decision` and `POLICY_UNAPPROVED` moved to `ctrlrun.decision`, which imports nothing from the package; then the policy **document grammar** moved to `ctrlrun.grammar`, so `authority.py` no longer imports `policy.py` at all. `SPEC-v0.3.md` §4.5's requirement that the two axes share **one** condition evaluator is better served than before: it is now owned by neither axis. **No public name moved** -- `policy.py` re-exports all thirty-four, so §8's frozen block is unchanged, and this was therefore not the public-surface question the line assumed. `tests/test_module_graph.py` asserts both the import-order and the layering graph are acyclic, and its allow-list of recorded exceptions is **empty**. Writing that guard found a second cycle nobody had recorded. Closed 2026-09-15.

**An external security review is optional and gates nothing, decided 2026-09-10.** It was a line in the list above and a sentence in v1.0's exit; both are gone. A review by a third party is bought, scheduled and scoped by whoever pays for it, and a milestone that waits on a purchase is a milestone with a date nobody on this project controls. If one happens, its scope names the approver path (v0.8) explicitly, because it is the surface a product selling action governance is attacked through, and its report is published beside `docs/how-this-is-built.md` with what it did and did not look at. Until then that page says what has and has not been reviewed, which is the same sentence it says today.

Expand Down
2 changes: 1 addition & 1 deletion docs/cookbook/verify-in-github-actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ by tag where you want a ref nobody can move.
The agent sees nothing; this is the operator's check. The build sees:

```text
CTRLRun verify — ctrlrun 0.11.0, catalogue ctrlrun.guarantees/v7
CTRLRun verify — ctrlrun 0.12.0, catalogue ctrlrun.guarantees/v7
policy /Users/arpanghoshal/ctrlrun-project/wt/v11-i3/examples/cookbook/verify-in-github-actions/ctrlrun.yaml (ctrlrun.policy/v2, mode: enforce)
authority none
store sqlite, scratch (created and destroyed for this run)
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/verify-in-ci.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ guarantees pass.
```

```text
CTRLRun verify — ctrlrun 0.11.0, catalogue ctrlrun.guarantees/v7
CTRLRun verify — ctrlrun 0.12.0, catalogue ctrlrun.guarantees/v7
policy /Users/arpanghoshal/ctrlrun-project/wt/v11-i3/examples/cookbook/verify-in-github-actions/ctrlrun.yaml (ctrlrun.policy/v2, mode: enforce)
authority none
store sqlite, scratch (created and destroyed for this run)
Expand Down
4 changes: 2 additions & 2 deletions docs/production/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ need. `test_the_first_line_of_the_section_says_which_store_and_why` asserts the
## Where it stands

{/* generated from the suite, pyproject and the soak (full) — run the generator */}
- **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built).
- **Version 0.12.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,221 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built).
- **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/docs/security/verify-guarantees).
- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/docs/production/postgres).
- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/docs/production/soak).
Expand Down
2 changes: 1 addition & 1 deletion docs/verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ what could not be tested at all.

```console
$ ctrlrun verify
CTRLRun verify — ctrlrun 0.11.0, catalogue ctrlrun.guarantees/v7
CTRLRun verify — ctrlrun 0.12.0, catalogue ctrlrun.guarantees/v7
policy examples/authority/payments.yaml (ctrlrun.policy/v7, mode: enforce)
authority same document, 3 grants
store sqlite, scratch (created and destroyed for this run)
Expand Down
4 changes: 2 additions & 2 deletions generated/readiness.full.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{/* generated from the suite, pyproject and the soak (full) — run the generator */}
- **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built).
- **Version 0.12.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,221 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built).
- **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/docs/security/verify-guarantees).
- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/docs/production/postgres).
- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/docs/production/soak).
Expand Down
6 changes: 3 additions & 3 deletions generated/readiness.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"3.14"
]
},
"released": "0.11.0",
"released": "0.12.0",
"soak": {
"actions": 889735,
"backend": "postgres",
Expand All @@ -18,6 +18,6 @@
"positive_control": true,
"unexplained": 0
},
"tests": 6220,
"version": "0.11.0"
"tests": 6221,
"version": "0.12.0"
}
4 changes: 2 additions & 2 deletions generated/readiness.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{/* generated from the suite, pyproject and the soak (mdx) — run the generator */}
- **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,220 tests**, every version specified before it was written and every requirement mutation-tested.
- **Version 0.12.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,221 tests**, every version specified before it was written and every requirement mutation-tested.
- **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates.
- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite.
- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak).
Expand Down
4 changes: 2 additions & 2 deletions generated/readiness.readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- generated from the suite, pyproject and the soak (readme) — run the generator -->
- **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,220 tests**, every version specified before it was written and every requirement mutation-tested.
- **Version 0.12.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,221 tests**, every version specified before it was written and every requirement mutation-tested.
- **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates.
- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite.
- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak).
Expand Down
5 changes: 5 additions & 0 deletions tests/test_release_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ def _load(name: str) -> set[str]:
# SPEC-v0.11 §1.1 rule 1 — the anchor consumes a timestamp and issues nothing,
# which is a sentence about what CTRLRun does NOT do and has to say the word.
"no revocation, no signing. Signing stays off the roadmap for the reason `SPEC-v0.6.md` §11", # noqa: E501
# v0.12's SBOM. "Signed" here is the *distribution*, by the attestation step in
# `release.yml`, and not a receipt. The distinction is the whole of what T180 guards, so
# the line is allow-listed rather than reworded: an SBOM that ships unsigned beside signed
# artifacts is the weakest link in the pair, and saying so requires the word.
"`release.yml` writes it into `dist/` **before** the attestation step, so it is signed with the", # noqa: E501
),
"docs/postgres.md": (
"Receipts are not signed, alteration is not authorship, and the chain is not tamper-proof",
Expand Down