diff --git a/docs.mdx b/docs.mdx index 01463cf..8e702a7 100644 --- a/docs.mdx +++ b/docs.mdx @@ -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.10.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,201 tests**, every version specified before it was written and every requirement mutation-tested. +- **Version 0.11.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). diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 3cc19e8..64bc161 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -371,7 +371,7 @@ raises. The upgrade was checked against the **released** 0.9.0 from PyPI rather Standards: A2A, as code. No conformance claim. -## v0.11 — Evidence +## v0.11 — Evidence · shipped 2026-09-14 One question: can the record be trusted after the fact, and kept? @@ -384,6 +384,43 @@ One question: can the record be trusted after the fact, and kept? **Does not close.** Authorship. An anchor proves the log existed in this form at that time; it does not prove who wrote it, and a malicious administrator who rewrites everything before the next anchor is still out of scope. Signed receipts stay off the roadmap for the reason `SPEC-v0.6.md` §11 gives. +Exit criteria met: `ctrlrun.guarantees/v7` with `G28` to `G32` each grading `PASS` and each grading +the same under `--only` as in a full run; `examples/anchored-chain` exercises an anchor and prints +what one does **not** prove; a chain written by the **released** 0.6.1, 0.7.0, 0.8.0, 0.9.0 and +0.10.0 wheels verifies end to end across five receipt schema versions; a prune across a checkpoint +verifies and anchors that checkpoint before deleting anything; a held range refuses to prune; and +two prunes racing under the multi-process standard against Postgres leave no break the store did not +already have. The upgrade was checked against the **released** 0.10.0 from PyPI rather than a +fixture: 0.11.0 migrates the store, the chain verifies across the boundary, and 0.10.0 then refuses +it with `SchemaMismatch` rather than corrupting it. + +**Reconciled against what shipped**, because three sentences above were written before the code +existed and two of them were wrong. + +- **The anchor detects truncation and NOT append**, and the bullet above said "erased or appended" + until 2026-09-14. A forged receipt lands at head + 1, above every anchored `seq`, so nothing stops + reproducing and the next anchor freezes it like any other. `T531` runs a forged append and + requires both reports to stay clean, so the limit is a tested property rather than a sentence + somebody has to remember. +- **Enforcement coverage does not come "from events already written."** `ACTION_PROPOSED` carries an + `action_hash` and nothing that maps it back to a name. It comes from receipts, which every decided + action leaves, **a denial included** -- so an action that is always denied counts as exercised. +- **`ctrlrun scan --coverage` is the surface**, and `docs/CONTROL-MAPPING.md` is still not written. + The v0.11 line cited it in the present tense; roadmap line 136 says it is written only when a + design partner asks, and none has. +- **Retention shipped twice.** The build order made an independent review required for the prune, + *the one not to skip*, and the merge did not wait for it. The review found seven defects, the + first of which made a forged checkpoint launderable with one row in the store's own `anchors` + table, and the fixes went in as a second pull request. `SPEC-v0.11.md` §13.4 records all seven + and the rule this milestone adds: **a required review is a merge gate, not a step in the item.** + +Three surfaces this milestone amends rather than adds to, each named because an amendment to a +frozen surface is not a patch: `StateStore` gains anchor, checkpoint and hold methods +(`SPEC-v0.6.md` §9.2), `StateStore.receipts()` may now hand back an `UnreadableReceipt` instead of +raising, and `verify_chain` seeds from a checkpoint where a store has one. **`CHAIN_BREAKS` is +unchanged and stays closed at six**: the anchor has its own set, because putting its kinds in +`CHAIN_BREAKS` would fail `G11`'s control with `control failed` on every anchoring deployment. + Do not build: a SIEM · dashboards over receipts · a receipt query language · export formats beyond JSON and OTel. Exit: **the truncation case** that `SPEC-v0.6.md` §6.4 lists as undetected now detects, with the anchor as the positive control and the two-statement attack as the scenario (append is **not** in this criterion, and §2.4 of `SPEC-v0.11.md` says why); a prune across a checkpoint verifies, and the prune's checkpoint is anchored before anything is deleted; a held range refuses to prune; a chain written across **five** receipt schema versions verifies end to end, and the checkpoint receipt of a prune carries the version current when it was written. diff --git a/docs/cookbook/verify-in-github-actions.mdx b/docs/cookbook/verify-in-github-actions.mdx index 8116a4f..a1dd3be 100644 --- a/docs/cookbook/verify-in-github-actions.mdx +++ b/docs/cookbook/verify-in-github-actions.mdx @@ -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.10.0, catalogue ctrlrun.guarantees/v7 +CTRLRun verify — ctrlrun 0.11.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) diff --git a/docs/guides/verify-in-ci.mdx b/docs/guides/verify-in-ci.mdx index 7f2b17c..52348eb 100644 --- a/docs/guides/verify-in-ci.mdx +++ b/docs/guides/verify-in-ci.mdx @@ -32,7 +32,7 @@ guarantees pass. ``` ```text - CTRLRun verify — ctrlrun 0.10.0, catalogue ctrlrun.guarantees/v7 + CTRLRun verify — ctrlrun 0.11.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) diff --git a/docs/production/index.mdx b/docs/production/index.mdx index 6bb76fb..c4355fc 100644 --- a/docs/production/index.mdx +++ b/docs/production/index.mdx @@ -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.10.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,201 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). +- **Version 0.11.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). diff --git a/docs/production/operations.mdx b/docs/production/operations.mdx index 32dabae..3d29d7c 100644 --- a/docs/production/operations.mdx +++ b/docs/production/operations.mdx @@ -49,11 +49,24 @@ and there will not be one. - **A reporting role still needs `CREATE` on the schema.** Opening the store migrates it, so a read-only role cannot run `--verify-chain` — it is refused before a receipt is read, naming the missing privilege. Read-only is for your own queries against the tables, not for the CLI. +- **Anchor on an interval you choose, and say what it is.** `ctrlrun anchor --provider ...` + records the chain's head outside the database, which is what makes a truncation detectable at + all: the head that would catch one is otherwise a row in the same database. The window you are + exposed to is `(last anchored seq, current head]`, so the interval **is** the claim, and it is + the number to quote rather than any sentence about tamper-evidence. See + [anchoring](/docs/production/anchoring). +- **Prune with a window, not with a `DELETE`.** `ctrlrun prune` removes a prefix and leaves a + checkpoint the reader seeds from; deleting receipts yourself is detected as a break, by design. + It refuses rather than warns, and there is no `--force`. See + [retention](/docs/production/retention). ## What this does not do - **There is nothing to run.** No agent, no sidecar, no scheduler. Every command above is a - one-shot read of the store. + one-shot read of the store, and the two that write — `anchor` and `prune` — run when **your** + scheduler runs them. Nothing sweeps, nothing reaps, and **no hold expires**: a hold that lapsed + on a timer would release evidence on a schedule nobody reviewed, so a person places it and a + person ends it. - **It does not retry for you.** A proven non-execution leaves the key retryable — the next attempt is admitted rather than refused — and nothing performs that attempt but your code. Every other outcome refuses one. @@ -64,11 +77,13 @@ and there will not be one. **Verified by** `T160` and `T177d` — an expired lease frees nothing, and displaying it changes nothing — `T161` for the two authorities that can move an unknown outcome and what each is -recorded as, and `T177c` for the CLI surface these commands come from, asserted against the -command list so a new one cannot appear unnoticed. +recorded as, `T545c` for the hold that no timer lifts, and `T177c` for the CLI surface these +commands come from, asserted against the command list so a new one cannot appear unnoticed. ## Next - [Recovery after a crash](/docs/production/recovery): why nothing sweeps. +- [Anchoring](/docs/production/anchoring) and [retention](/docs/production/retention): the two + things this milestone gives an operator to schedule. - [Reconcile automatically](/docs/guides/reconcile-automatically): the hook that answers without a person. - [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/verify.md b/docs/verify.md index b10b59c..1da34ff 100644 --- a/docs/verify.md +++ b/docs/verify.md @@ -14,7 +14,7 @@ what could not be tested at all. ```console $ ctrlrun verify -CTRLRun verify — ctrlrun 0.10.0, catalogue ctrlrun.guarantees/v7 +CTRLRun verify — ctrlrun 0.11.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) diff --git a/generated/readiness.full.mdx b/generated/readiness.full.mdx index 144e7cf..b14d7bf 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,201 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). +- **Version 0.11.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). diff --git a/generated/readiness.json b/generated/readiness.json index 96c0544..012afe1 100644 --- a/generated/readiness.json +++ b/generated/readiness.json @@ -9,7 +9,7 @@ "3.14" ] }, - "released": "0.10.0", + "released": "0.11.0", "soak": { "actions": 889735, "backend": "postgres", @@ -18,6 +18,6 @@ "positive_control": true, "unexplained": 0 }, - "tests": 6201, - "version": "0.10.0" + "tests": 6221, + "version": "0.11.0" } diff --git a/generated/readiness.mdx b/generated/readiness.mdx index 552c0be..322708b 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,201 tests**, every version specified before it was written and every requirement mutation-tested. +- **Version 0.11.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). diff --git a/generated/readiness.readme.md b/generated/readiness.readme.md index 7e2f5f3..ba89b4b 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,201 tests**, every version specified before it was written and every requirement mutation-tested. +- **Version 0.11.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). diff --git a/tests/test_release_documents.py b/tests/test_release_documents.py index 61ddb0a..0a2db31 100644 --- a/tests/test_release_documents.py +++ b/tests/test_release_documents.py @@ -93,7 +93,12 @@ def _load(name: str) -> set[str]: # not do". Both halves survived the cut, which is the half §6.4 cares about: the chain # detects alteration, and the same sentence says alteration is not authorship. "README.md": ( - "is detected. They are not signed: alteration is not authorship. The badge above means the", + # v0.11 rewrapped this paragraph: the chain detects alteration, and the sentence beside + # it now names the two things it does **not** detect, a truncation at the end and a + # forged append, and says `ctrlrun anchor` is what closes them. Only this line carries a + # word the scan looks for, and the list must not carry lines that do not: T180's staleness + # check fails on an entry matching nothing, which is how a softened allow-list is caught. + "signed: alteration is not authorship. The badge above means the", ), "CHANGELOG.md": ( '- **`docs/docs/ROADMAP.md`\'s v0.6 bullet said "receipt integrity (hash chain / signatures)", and the', # noqa: E501