diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 903afe3..cfa9db7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: @@ -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 diff --git a/docs.mdx b/docs.mdx index f343dd4..9ffb2d3 100644 --- a/docs.mdx +++ b/docs.mdx @@ -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). diff --git a/docs/production/index.mdx b/docs/production/index.mdx index 1165fe8..20df822 100644 --- a/docs/production/index.mdx +++ b/docs/production/index.mdx @@ -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). diff --git a/docs/reference/api/banner.mdx b/docs/reference/api/banner.mdx index 92e33ba..9f01813 100644 --- a/docs/reference/api/banner.mdx +++ b/docs/reference/api/banner.mdx @@ -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 diff --git a/docs/reference/api/needs_approval.mdx b/docs/reference/api/needs_approval.mdx index 86d5b20..eafed2c 100644 --- a/docs/reference/api/needs_approval.mdx +++ b/docs/reference/api/needs_approval.mdx @@ -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). @@ -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). diff --git a/generated/readiness.full.mdx b/generated/readiness.full.mdx index 3fbafe3..f797016 100644 --- a/generated/readiness.full.mdx +++ b/generated/readiness.full.mdx @@ -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). diff --git a/generated/readiness.json b/generated/readiness.json index d031ea6..24cdd23 100644 --- a/generated/readiness.json +++ b/generated/readiness.json @@ -18,6 +18,6 @@ "positive_control": true, "unexplained": 0 }, - "tests": 6070, + "tests": 6080, "version": "0.10.0" } diff --git a/generated/readiness.mdx b/generated/readiness.mdx index fead10d..5d095a0 100644 --- a/generated/readiness.mdx +++ b/generated/readiness.mdx @@ -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). diff --git a/generated/readiness.readme.md b/generated/readiness.readme.md index 4113bc2..2539a25 100644 --- a/generated/readiness.readme.md +++ b/generated/readiness.readme.md @@ -1,6 +1,6 @@ - **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). diff --git a/tests/test_docs_audit.py b/tests/test_docs_audit.py index c1c4324..f70c19d 100644 --- a/tests/test_docs_audit.py +++ b/tests/test_docs_audit.py @@ -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