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
14 changes: 12 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,18 @@ jobs:
CTRLRUN_SOURCE: ${{ github.workspace }}/ctrlrun
run: ./scripts/check.sh

# The four drift checks, named individually because `STYLE.md` says the CI job runs them
# and `test_ci_runs_the_three_checks_and_the_drift_check` reads this file to prove it.
# The drift checks, named individually because `STYLE.md` says the CI job runs them and
# `test_ci_runs_the_three_checks_and_the_drift_check` reads this file to prove it.
# A guard CI does not run is prose.
#
# `render_readiness.py --check` joined them on 2026-09-14. Its own docstring called it
# "what CI runs" and CI did not run it, which is the same sentence one level up: the test
# count is the one figure on the site that is a measurement rather than a claim, and the
# pytest check on it compares the embedded block against the *stored* `readiness.json`
# rather than against the suite, so the two agreed with each other while both drifted away
# from the library. It cannot flap between a paired kernel and docs merge, because the
# count is a **floor**: it fails when the suite has fewer tests than the page claims, and
# passes when it has more.
- name: The documentation audit
working-directory: ctrlrun-docs
env:
Expand All @@ -103,3 +112,4 @@ jobs:
python tools/docs_audit/lint.py
python tools/docs_audit/links.py
python tools/docs_audit/render_capabilities.py --check
python tools/docs_audit/render_readiness.py --check
2 changes: 1 addition & 1 deletion docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ the framework's own interrupt, and a framework with no such primitive does not n

{/* generated from the suite, pyproject and the soak (mdx) — run the generator */}
- **Version 0.10.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,070 tests**, every version specified before it was written and every requirement mutation-tested.
- **6,080 tests**, every version specified before it was written and every requirement mutation-tested.
- **27 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
2 changes: 1 addition & 1 deletion docs/production/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ need. `test_the_first_line_of_the_section_says_which_store_and_why` asserts the

{/* generated from the suite, pyproject and the soak (full) — run the generator */}
- **Version 0.10.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,070 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built).
- **6,080 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built).
- **27 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/reference/api/banner.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: "Log SPEC-v0.3 §6.5's observe banner, once per `Control`. An adapt

{/* generated by tools/docs_audit/render_api.py from the docstrings — edit the docstring, never this page */}

`ctrlrun.banner` — function, defined at `src/ctrlrun/adapter.py:475`
`ctrlrun.banner` — function, defined at `src/ctrlrun/adapter.py:484`

```python
from ctrlrun import banner
Expand Down
9 changes: 8 additions & 1 deletion docs/reference/api/needs_approval.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ from ctrlrun import needs_approval


```python
def needs_approval(control: Control, action: str, arguments: Mapping[str, Any], *, resource: str | None = None) -> bool
def needs_approval(control: Control, action: str, arguments: Mapping[str, Any], *, resource: str | None = None, task: str | None = None, hop: str | None = None) -> bool
```

Does this call need a human? For a framework that asks before it invokes (SPEC-v0.5 §3.5).
Expand Down Expand Up @@ -42,6 +42,13 @@ it to belongs in the evidence log.
matters: authority matches on resource patterns (SPEC-v0.3 §4.2), so a predicate that
skipped it would evaluate a different action from the one that runs.

`task` and `hop` are that same argument one frame further out (SPEC-v0.10 §9). Without them
this predicate evaluates against the receiver's **whole candidate set** while `execute`
evaluates against the hop **alone** (§2.3), so it answers "no human needed" for a call
`execute` then refuses. That is not a wider grant -- `Control.execute` is the enforcement
point and decides against the hop either way -- but it costs the framework its own approval
item, and a human is not asked before an invocation that then fails.

## Next

- [Python API index](/docs/reference/api/index).
Expand Down
2 changes: 1 addition & 1 deletion 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.10.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,070 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built).
- **6,080 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built).
- **27 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 generated/readiness.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
"positive_control": true,
"unexplained": 0
},
"tests": 6070,
"tests": 6080,
"version": "0.10.0"
}
2 changes: 1 addition & 1 deletion 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.10.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,070 tests**, every version specified before it was written and every requirement mutation-tested.
- **6,080 tests**, every version specified before it was written and every requirement mutation-tested.
- **27 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
2 changes: 1 addition & 1 deletion 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.10.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,070 tests**, every version specified before it was written and every requirement mutation-tested.
- **6,080 tests**, every version specified before it was written and every requirement mutation-tested.
- **27 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
11 changes: 10 additions & 1 deletion tests/test_docs_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,16 @@ def test_ci_runs_the_three_checks_and_the_drift_check():
"""`STYLE.md` says the `docs` job runs them. A guard that CI does not run is prose."""
workflow = (REPO_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")

for script in ("snippets.py", "lint.py", "links.py", "render_capabilities.py --check"):
for script in (
"snippets.py",
"lint.py",
"links.py",
"render_capabilities.py --check",
# The count is a measurement, and until 2026-09-14 nothing in CI re-took it: the pytest
# check compares the embedded block against the stored `readiness.json`, so the two
# agreed with each other while both drifted from the library.
"render_readiness.py --check",
):
assert f"python tools/docs_audit/{script}" in workflow, script


Expand Down