> ## Documentation Index
> Fetch the complete documentation index at: https://ctrlrun.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Claims

> Every sentence of the README mapped to the code that implements it and the test that proves it, re-derived at every release.

Every sentence of the README that asserts something about the shipped code, mapped to the code
that implements it and the test that proves it.

**This table is regenerated at every release.** A claim that loses its code or its test is
removed from the README in the same commit — the README is not allowed to describe behaviour
that no longer ships. If you find a row here that does not hold against the version you
installed, that is a bug: please open an issue.

Regenerated for: **v0.6.0**. Line numbers refer to the commit this file was regenerated on, and `scripts/repoint-claims.py` re-derives them for the commits in between — it **refuses** rather than guessing when a symbol has no definition, because a row pointing at a docstring that happens to contain the right word makes the guard green and the claim false. `test_the_claims_table_line_numbers_point_at_what_they_name` resolves every one against the line it cites.

The rows follow the README's sections. `docs/capabilities.yaml` names one row per capability
by its quoted claim, and `tests/test_docs_audit.py` fails if a named row is not here.

## The header

> The last check before an AI agent does something it can't undo. Autonomy belongs to the
> action, not the agent. A consequential action happens at most once, exactly as approved, and
> leaves a receipt — and when the outcome is unknown, CTRLRun says so instead of guessing.
> A Python library that sits between the decision to act and the call that acts.

| Claim                                                                                                                                                            | Code                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Proof                                                                                                                                                                                                                                                        |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| "The last check before an AI agent does something it can't undo."                                                                                                | `Control.execute` — `control.py:547` — resolves the principal, evaluates authority and policy, consumes the approval and reserves the effect key **before** the executor runs; nothing in the wrapper calls the function first                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `test_T1_a_blind_retry_is_refused_and_never_reaches_the_remote`, `test_T3_the_fake_remote_is_called_exactly_once`                                                                                                                                            |
| "Autonomy belongs to the action, not the agent."                                                                                                                 | `Policy.evaluate(action)` — `policy.py:495` — passes only the action's **name and arguments** to `_ActionPolicy.evaluate` (`policy.py:495`), whose signature has no principal in it. A rule cannot read who is acting even by accident. `agent_eq` and `user_eq` are refused at load by `RESERVED_ARGUMENTS` (`policy.py:150`) rather than silently matching nothing.                                                                                                                                                                                                                                                                                                              | `test_T6_an_action_name_is_matched_exactly`, `test_a_condition_naming_an_action_field_is_refused_at_load`                                                                                                                                                    |
| "A consequential action happens at most once, exactly as approved, and leaves a receipt — and when the outcome is unknown, CTRLRun says so instead of guessing." | At most once: `plan_reservation` — `effect.py:163`. Exactly as approved: the approval is bound to `action_hash` and consumed with the reservation — `_authorize_and_reserve` — `state.py:832`. Or not at all: a refusal raises before the executor — `Control.execute` — `control.py:547`. Says so instead of guessing: only `NotExecuted` maps to `FAILED` — `control.py:1067` — and everything else is `AMBIGUOUS`. A receipt: `Receipt` — `receipt.py:245`. **This sentence read *happens once … or not at all* until 0.6**, a two-way disjunction that excluded the third outcome the product exists for: a lost reply is neither, and the README's own first section says so. | `test_T3_exactly_one_agent_reserves_and_seven_are_blocked`, `test_T2_a_mutated_action_presenting_the_approval_raises_ApprovalMismatch`, `test_T1_a_lost_response_leaves_the_effect_ambiguous`, `test_T11_every_demo_receipt_carries_every_field_in_the_spec` |
| "A Python library that sits between the decision to act and the call that acts."                                                                                 | `@protect` — `control.py` — wraps the callable that acts, and `Control.execute` runs every check before invoking it. The category noun was on `docs/docs.mdx` and in `pyproject.toml`'s `description` and nowhere in the README until 0.6, so a reader had to infer what CTRLRun **is** from three slogans.                                                                                                                                                                                                                                                                                                                                                                        | `test_the_header_carries_the_fixed_copy_and_the_five_badges`, `test_T1_a_blind_retry_is_refused_and_never_reaches_the_remote`                                                                                                                                |
| "Runs in production on a single file, or on Postgres across hosts"                                                                                               | SQLite: `SQLiteStateStore` reserves inside the `BEGIN IMMEDIATE` of `_authorize_and_reserve` — `state.py:1006` — which is a write lock on the file and holds across OS processes. Postgres: `PostgresStateStore` over `UNIQUE(effect_key)` with `INSERT … ON CONFLICT DO NOTHING` and checked row counts (SPEC-v0.6 §4.2), the same `StateStore` protocol, extended by nothing                                                                                                                                                                                                                                                                                                     | `test_T3_exactly_one_agent_reserves_and_seven_are_blocked` (8 OS processes, both backends), `test_T141_the_shipped_backends_pass`, `test_T154_postgres_passes_the_store_conformance_suite`                                                                   |

## The refund that happened twice

| Claim                                                                                                                                                                                    | Code                                                                                                                                                                                                                                                                                            | Proof                                                                                                                                                                                                                       |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "A lost reply is `AMBIGUOUS`, never `FAILED`, and a retry against an `AMBIGUOUS` effect is refused — until a human, or a `reconcile` hook, says what happened."                          | Only `NotExecuted` maps to `FAILED` — `control.py:1067`; a retry against an `AMBIGUOUS` key is refused by `plan_reservation` — `effect.py:163`; the two things permitted to move the record on and nothing else — `resolve` — `cli/main.py:518` — and `Control._reconciled` — `control.py:1495` | `test_T1_a_lost_response_leaves_the_effect_ambiguous`, `test_T1_a_blind_retry_is_refused_and_never_reaches_the_remote`, `test_T160_there_is_no_reaper`, `test_T13_a_hook_answering_not_executed_moves_the_record_to_failed` |
| "The customer is refunded twice, and nothing in the stack noticed." — said of a stack without CTRLRun; the demo runs the same sequence with it, and counts the calls the remote received | `ctrlrun demo` scenario 1, which retries against a fake remote that counts its calls and prints the count                                                                                                                                                                                       | `test_T3_the_fake_remote_is_called_exactly_once`, `test_T1_a_blind_retry_is_refused_and_never_reaches_the_remote`                                                                                                           |

## Protect your first action

| Claim                                                                                                                    | Code                                                                                     | Proof                                                                                                                                                                                                                                                                                                     |
| ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| the starter says a namespace delete needs a human, and holds a refund by amount                                          | `EXAMPLE_POLICY` — `cli/main.py` — is `ctrlrun.example.yaml`, byte for byte              | `test_the_shipped_example_policy_is_the_one_in_the_repository`, `test_the_shipped_example_policy_loads_and_evaluates`                                                                                                                                                                                     |
| the `ctrlrun init` and decorator blocks, and the `else:` that fails if the delete ran without a human                    | the blocks are `runnable` and run in one temporary directory, in document order, offline | `test_the_readme_and_docs_snippets_run`                                                                                                                                                                                                                                                                   |
| the six-row table: allowed, `ApprovalRequired`, `ApprovalMismatch`, `ActionDenied`, `AmbiguousEffect`, `DuplicateEffect` | one exception per row — `errors.py`; each is the matrix row that names it, below         | the "What it guarantees" rows, and `test_T2_a_mutated_action_presenting_the_approval_raises_ApprovalMismatch`, `test_T6_unknown_action_is_denied_with_reason_unknown_action`, `test_T1_a_blind_retry_is_refused_and_never_reaches_the_remote`, `test_T3_exactly_one_agent_reserves_and_seven_are_blocked` |

## The problem, and how it works

| Claim                                                                                                | Code                                                                                                                                                                                                              | Proof                                                                                                                                                     |
| ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "A lost reply is `AMBIGUOUS`, never `FAILED`, and a retry against an `AMBIGUOUS` effect is refused." | Only `NotExecuted` maps to `FAILED` — `control.py:1067`; `plan_reservation` — `effect.py:163`                                                                                                                     | `test_T1_a_lost_response_leaves_the_effect_ambiguous`, `test_T1_a_blind_retry_is_refused_and_never_reaches_the_remote`                                    |
| "reserved atomically across processes and hosts; one worker wins"                                    | `reserve_effect` — `state.py:405`; `PostgresStateStore.reserve_effect` — `postgres.py:528`                                                                                                                        | `test_T3_exactly_one_agent_reserves_and_seven_are_blocked`, `test_T154_postgres_passes_the_store_conformance_suite`                                       |
| "bound to the hash of the exact action a human saw, used once, and refused for anything else"        | `_authorize_and_reserve` — `state.py:832`                                                                                                                                                                         | `test_T2_a_mutated_action_presenting_the_approval_raises_ApprovalMismatch`, `test_T4_replaying_the_approval_raises_ApprovalMismatch_with_reason_consumed` |
| "An action the policy does not list is denied"                                                       | `Policy.evaluate` — `policy.py:495`                                                                                                                                                                               | `test_T6_unknown_action_is_denied_with_reason_unknown_action`                                                                                             |
| "Authority first ... then policy" / "authority first"                                                | `Control.execute` evaluates authority before policy and a denial appends `AUTHORITY_DENIED` and never `POLICY_EVALUATED` — `control.py:547`                                                                       | `test_T74_a_denial_leaves_no_pending_approval_request`                                                                                                    |
| "Neither axis reads the agent's instructions"                                                        | `Policy.evaluate` — `policy.py:495` — sees the action's name and arguments; `Authority.evaluate` — `authority.py:806` — sees the action and the principal; neither is handed a prompt, a message or a tool result | `test_T6_an_action_name_is_matched_exactly`, `test_T67_a_principal_with_no_grant_is_denied`                                                               |
| "canonical arguments (sorted keys, no floats) ... Its SHA-256 is the action hash"                    | `canonicalize` / `action_hash` — `action.py`; `float` refused at any depth — `action.py:71`                                                                                                                       | `test_T7_canonical_form_is_exactly_the_specified_serialization`, `test_T7_nested_dicts_are_sorted_recursively`                                            |
| "The approval is single-use, expires, and matches nothing but that exact action."                    | `_authorize_and_reserve` — `state.py:832` — checks expiry at consumption                                                                                                                                          | `test_T5_expiry_is_checked_at_consumption_not_only_at_grant`, `test_T4_replaying_the_approval_raises_ApprovalMismatch_with_reason_consumed`               |
| "Only `NotExecuted`, raised by you, means `FAILED`."                                                 | `control.py:1067`; `NotExecuted` — `errors.py:157`                                                                                                                                                                | `test_T1_a_lost_response_leaves_the_effect_ambiguous`                                                                                                     |
| "the hash of the policy that decided it, chained to the receipt before it"                           | `Policy.policy_hash` — `policy.py:609`; `prev_hash`, `GENESIS_HASH` for the first — `receipt.py:284`                                                                                                              | `test_T172_every_receipt_carries_the_hash_and_the_declared_version`, `test_T164_an_altered_receipt_is_content_altered_at_its_seq`                         |

## Three ways to use it

| Claim                                                                                                                            | Code                                                                                                                                    | Proof                                                                                                                                                                              |
| -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "You probably do not need an adapter"                                                                                            | Three ways in, and `@protect` (`control.py:2006`) covers this process while the gateway covers MCP — an adapter buys only the interrupt | `test_T139_the_adapter_section_says_when_you_do_not_need_one_up_front`                                                                                                             |
| "`ctrlrun init` writes a starter"                                                                                                | `init` — `cli/main.py:340`                                                                                                              | CI's `package` job runs `ctrlrun init` from the wheel and asserts `ctrlrun.yaml` exists                                                                                            |
| "The human runs `ctrlrun approve <request id>` and the agent calls again inside `ctrlrun.with_approval(request_id)`"             | `approve` — `cli/main.py:364`; `with_approval` — `control.py:164`; `ApprovalRequired` (`errors.py:88`) carries `request_id`             | `test_T2_a_mutated_action_presenting_the_approval_raises_ApprovalMismatch` (the granted path first), `test_T4_replaying_the_approval_raises_ApprovalMismatch_with_reason_consumed` |
| "No agent changes"                                                                                                               | `INTERCEPTED_METHOD` is `tools/call` and every other method is relayed unchanged — `gateway/mcp.py:40`                                  | `test_a_non_intercepted_method_is_relayed_with_no_ctrlrun_outcome`                                                                                                                 |
| "Point the MCP client at the gateway instead of at the tool server"                                                              | `Gateway.handle` — `gateway/server.py:385`; `serve` — `gateway/__init__.py:41`                                                          | `test_T19_the_upstream_receives_the_canonical_arguments`                                                                                                                           |
| "Tools become actions named `mcp.<alias>.<tool>`"                                                                                | `Gateway._intercept` — `gateway/server.py:442`                                                                                          | `test_T19_the_action_is_named_for_the_alias_and_the_tool`                                                                                                                          |
| "they are declared in the policy" (effect and resource templates for a tool call)                                                | `Policy.effect_template` / `resource_template` — `policy.py:705`; `McpOptions` — `policy.py:460`                                        | `test_T16_a_v2_document_loads_and_exposes_its_templates`, `test_T16_a_decorator_and_a_policy_template_produce_the_same_action_hash`                                                |
| "Everything but `tools/call` is relayed untouched"                                                                               | `parse_request(...).intercept` — `gateway/mcp.py:84`                                                                                    | `test_every_other_method_is_relayed_not_intercepted`                                                                                                                               |
| "A lost response over the wire blocks the retry exactly as it does in process"                                                   | `classify` — `gateway/outcome.py:144`, translated into v0.1 §5.5's own vocabulary by the gateway's executor                             | `test_T23_the_identical_call_sent_again_is_refused_and_the_upstream_called_once`                                                                                                   |
| "the gateway prints, on the line that starts it, every action in your policy that has no `effect:` template"                     | `_announce` — `gateway/__init__.py:146`                                                                                                 | `test_the_startup_block_names_the_environment_identity_and_authority`, `test_the_startup_block_says_so_when_there_is_no_authority_section`                                         |
| "route an `approve` decision through **the framework's own interrupt**"                                                          | `FrameworkInterrupt` — `adapter.py:180` — is a Protocol with one method returning a value; it holds no state and writes nothing         | `test_T135b_the_adapter_reuses_the_sdks_primitive_and_reimplements_nothing`                                                                                                        |
| "one core provider writes the grant through the same calls `ctrlrun approve` makes" / "There is never a second place to say yes" | `InterruptApprovalProvider.wait` — `adapter.py:254` — calls `grant_approval` / `deny_approval`, and an adapter calls neither            | `test_T130_each_broken_fixture_fails_the_suite_named_for_it`                                                                                                                       |
| "an adapter never constructs one and never supplies a principal"                                                                 | `needs_approval` — `adapter.py:408` — resolves the principal from the `Control` so no adapter builds an `Action`                        | `test_T129_no_public_callable_takes_a_principal`, `test_T129_the_module_exposes_no_way_to_construct_a_control`                                                                     |
| "prevention" / "attribution"                                                                                                     | `carries_approved_arguments` gates §3.4's rebuild in `_check_answer` — `adapter.py:320`                                                 | `test_T137b_the_readme_says_the_binding_is_attribution_and_why`                                                                                                                    |
| "Adapters ship on their own version line"                                                                                        | `adapters/*/pyproject.toml`, never in the `ctrlrun` wheel or sdist                                                                      | `test_T136_the_ctrlrun_distributions_contain_no_adapter`                                                                                                                           |

## Write down what the agent may do

| Claim                                                                                                                   | Code                                                                                                                                                                                                                              | Proof                                                                                                                                                                                           |
| ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "cheap to undo is autonomous, anything that leaves the building needs a human, money is by amount with both ends bound" | `Decision` — `policy.py:234` — is exactly `allow`, `approve`, `deny`; rules match first-wins over `Condition` (`policy.py:298`) with the operators `eq`, `neq`, `in`, `lt`, `lte`, `gt`, `gte` — `_OPERATORS` — `policy.py:83`    | `test_T6_an_action_name_is_matched_exactly`, `test_T176_the_operators_behave_as_they_do_everywhere_else`                                                                                        |
| "Unknown actions are denied; there is no default-allow."                                                                | `Policy.evaluate` — `policy.py:495`                                                                                                                                                                                               | `test_T6_unknown_action_is_denied_with_reason_unknown_action`                                                                                                                                   |
| "Amounts are integer minor units; floats are rejected outright"                                                         | `float` refused at any depth — `action.py:71`                                                                                                                                                                                     | `test_T7_canonical_form_is_exactly_the_specified_serialization`                                                                                                                                 |
| "The policy cannot see who is asking — deliberately, since v0.1"                                                        | `Policy.evaluate` still takes only the action's name and arguments; `RESERVED_ARGUMENTS` — `policy.py:150` — refuses `agent_eq` and every other principal-addressing condition at load, in a document of **every** schema version | `test_T74b_a_reserved_name_in_a_policy_rule_is_a_load_error`, `test_T74b_a_reserved_name_in_a_grant_constraint_is_a_load_error`                                                                 |
| "the second axis, `authority:`"                                                                                         | `Authority.evaluate` — `authority.py:798`; `Control._authority_result` — `control.py:408`                                                                                                                                         | `test_T67_a_principal_with_no_grant_is_denied`                                                                                                                                                  |
| "opt-in, and then fail-closed"                                                                                          | `_optional_authority` returns `None` for a document with no section — `control.py`; `Control.authority is None` is v0.2 behaviour exactly                                                                                         | `test_T66_a_document_with_no_authority_section_leaves_control_authority_none`, `test_T66_no_authority_event_is_appended_without_a_section`, and T66's session-wide guard in `tests/conftest.py` |
| "every principal needs a grant and no grant means denied"                                                               | `NO_AUTHORITY` — the fail-closed default of `Authority.evaluate` (`authority.py:64`), reached for reads and for actions with no effect key alike                                                                                  | `test_T67_an_action_the_policy_allows_outright_still_needs_a_grant`                                                                                                                             |
| "A grant carries no `decision:`"                                                                                        | `_GRANT_KEYS` — `authority.py` — is a closed set that does not contain `decision`                                                                                                                                                 | `test_T73b_grant_refuses_what_the_loader_refuses`                                                                                                                                               |
| "combine as the **stricter of the two**"                                                                                | `Control.evaluate` returns the combined result — `control.py`; a denial on either axis is a denial                                                                                                                                | `test_T70_the_stricter_of_the_two_wins`                                                                                                                                                         |
| "narrow it at runtime with `ctrlrun delegate`"                                                                          | `Control.delegate` — `control.py:1673`; `Authority.plan_delegation` — `authority.py:877`; `ctrlrun delegate` — `cli/main.py:898`                                                                                                  | `test_t75_the_delegation_authorizes_an_action_within_its_limits`                                                                                                                                |
| "provably a subset of its parent on every dimension, at creation and again at every evaluation"                         | `contained_dimension` — `authority.py:603` — runs from `plan_delegation` (`authority.py:877`) **and** from the chain walk in `Authority.evaluate` (`authority.py:806`)                                                            | `test_t76_each_dimension_violated_alone`, `test_t77b_a_narrowed_parent_narrows_its_children`                                                                                                    |
| "omitting a dimension the parent constrains is rejected rather than inherited"                                          | `contained_dimension` treats an absent child dimension as unconstrained and therefore wider — `authority.py:603`; the subject half is `_subject_contained` (`authority.py:634`)                                                   | `test_t81_omission_is_not_unlimited`, `test_T73b_a_subject_addressed_to_every_principal_is_refused`, `test_t76_each_dimension_violated_alone`                                                   |
| "`ctrlrun revoke` cuts a chain of any depth with one write"                                                             | `Control.revoke` — `control.py:1689` — writes one row — `revoke_delegation` — `state.py:554` and visits no children; every evaluation walks to the root                                                                           | `test_t78_a_revoked_parent_denies_its_grandchild`, `test_put_delegation_is_never_an_upsert`                                                                                                     |
| "`mode: observe` … records what *would* have been blocked, without blocking anything"                                   | `_parse_mode` — `policy.py:594`; `Control._observed` — `control.py:743`; `_WouldHave` — `receipt.py:191`; `ReceiptResult.OBSERVED` — `receipt.py:191`                                                                             | `test_T82_observe_executes_what_enforce_would_deny`, `test_T83_a_duplicate_is_recorded_and_still_runs`                                                                                          |
| "One top-level line"                                                                                                    | `mode:` is refused anywhere but the top level — `reject_nested_mode`, `policy.py:594`                                                                                                                                             | `test_T84_mode_is_refused_anywhere_but_the_top_level`                                                                                                                                           |
| "`ctrlrun stats` gives you the numbers"                                                                                 | `stats` — `cli/main.py:727`; counted from `would_have.blocked_reason` and nothing else                                                                                                                                            | `test_T86_stats_counts_what_observe_mode_recorded`, `test_T86_stats_reaches_no_network`                                                                                                         |
| "It is not a dry run: it executes"                                                                                      | `_observed` runs the executor on every path, including the ones enforce mode would have refused — `control.py:743`                                                                                                                | `test_T82_observe_executes_what_enforce_would_deny`, `test_T83_an_executor_that_fails_on_a_held_key_still_writes_the_record`                                                                    |

## Prove it holds in your setup

| Claim                                                                              | Code                                                                                                                                                                          | Proof                                                                                                                                                                        |
| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "runs the kernel's own failure scenarios against the configuration in front of it" | `ctrlrun.verify.run` — `verify/__init__.py:154`; the eleven guarantees — `GUARANTEES` — `verify/guarantees.py:39`; the scenarios — `verify/scenarios.py`                      | `test_T100_the_authority_example_passes_every_non_authority_guarantee` (11/11), `test_T100_a_v1_document_with_no_templates_and_no_grants`                                    |
| "in a scratch store, with fake executors, and no network"                          | One scratch store per guarantee under a temporary directory — `verify/scenarios.py`, `Engine.control`; `state_path()` is never called and `Control.from_file()` is never used | `test_T103_the_operators_store_is_byte_identical_before_and_after`, `test_T103_a_store_that_does_not_exist_is_not_created`, `test_T107_a_full_run_completes_with_no_network` |
| "Your `.ctrlrun/state.db` is byte-identical before and after"                      | The scratch path is a `tempfile.mkdtemp` removed in a `finally` — `verify/__init__.py`                                                                                        | `test_T103_the_operators_store_is_byte_identical_before_and_after` (SHA-256 and `st_mtime_ns`), `test_T103_CTRLRUN_STATE_is_not_read_and_not_created`                        |
| "Not applicable is not a pass"                                                     | `Report.applicable` is passes plus failures — `verify/report.py`; every N/A reason is a statement about the document — `verify/guarantees.py`                                 | `test_T101_a_policy_with_no_approve_rule_makes_G1_and_G2_not_applicable`, `test_T102_a_policy_with_no_effect_templates_makes_G3_G4_and_G5_not_applicable`                    |
| "`6/6 (5 not applicable)`, never `11/11`"                                          | `Report.summary_line` — the N/A ids are a separate sentence, never a parenthesis inside the fraction                                                                          | `test_T113_the_summary_is_the_last_line_and_names_the_not_applicable_ids` (asserts `11/11` appears nowhere in an N/A run)                                                    |
| "There is no flag that folds one into the count"                                   | There is no such parameter on `run()` (§9.1 freezes the signature) and no such option on the CLI                                                                              | `test_T101b_zero_applicable_guarantees_is_not_a_pass` — `0/0` exits **2**                                                                                                    |
| The two quoted reports                                                             | Both are real runs; the first is asserted line by line against `run(examples/authority/payments.yaml)` and against `docs/docs/verify.md`'s copy                               | `test_the_readme_quotes_the_real_verify_output`, `test_the_readme_and_the_verify_page_quote_the_same_report`                                                                 |
| "means the **declared guarantees pass**"                                           | `badge_from_document` — `verify/report.py`; the phrase is the first sentence under `docs/docs/verify.md#what-the-badge-means`                                                 | `test_T119_the_rendered_badge_text_is_exactly_CTRLRun_verified_N_over_M`, `test_T119_the_link_target_carries_the_exact_phrase`                                               |
| "It does not mean secure, safe, compliant, certified or audited"                   | Those words appear in `docs/docs/verify.md` only inside the sentence that refuses them, and nowhere in the badge, the summary, `action.yml` or the workflow                   | `test_T119_no_claim_uses_the_forbidden_vocabulary`, `test_T119_the_action_and_the_workflow_make_no_forbidden_claim`                                                          |
| "There is a GitHub Action"                                                         | `action.yml` at the repository root — composite, one verify run, summary and badge rendered from its JSON                                                                     | `test_T118_the_action_is_a_composite_action_at_the_repository_root`, and CI's own `verify` job against both example configurations                                           |
| "verify has no flag that relaxes a check"                                          | No argument and no environment variable changes what `verify` builds — SPEC-v0.4 §3.9                                                                                         | `test_T101b_zero_applicable_guarantees_is_not_a_pass`, `test_T107_a_full_run_completes_with_no_network`                                                                      |

And the four things the verify section deliberately does **not** claim, each with the test that
keeps it honest:

| Not claimed                                              | Why                                                                                    | Where the limit is asserted                                                                 |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| That verify checks the operator's executors              | It never calls the function behind `@protect` and never imports the module it lives in | `docs/docs/verify.md`, "What it does not mean"; `THREAT_MODEL.md`, "Known v0.4 limitations" |
| That a green badge means the configuration is a good one | The guarantees are about the kernel doing what it says *under* that configuration      | `test_T119_no_claim_uses_the_forbidden_vocabulary`                                          |
| That a guarantee reported N/A was checked                | It was not, and the reason is on the line                                              | `test_T113_every_not_applicable_line_carries_its_reason`                                    |
| That a partial run means anything about the whole        | `--only` writes no badge at all                                                        | `test_T120_a_partial_run_writes_no_badge`                                                   |

## The capability matrix

Rendered from `docs/capabilities.yaml`; the six rows are the six groups of the verify
catalogue, `GUARANTEES` (`verify/guarantees.py:39`).

| Claim                                                                                | Code                                                                                                                                                                                                                                                                                                            | Proof                                                                                                                                                                                                                                      |
| ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| "An approval is bound to the exact action; a mutated or replayed one is refused."    | `action_hash` — `action.py`; the approval record stores it and `_authorize_and_reserve` compares it — `state.py:418`; single use is the `granted → consumed` transition in the same `BEGIN IMMEDIATE`                                                                                                           | `test_T2_a_mutated_action_presenting_the_approval_raises_ApprovalMismatch`, `test_T4_replaying_the_approval_raises_ApprovalMismatch_with_reason_consumed`, `test_T5_expiry_is_checked_at_consumption_not_only_at_grant`                    |
| "One logical effect happens at most once, across threads, processes and hosts."      | `reserve_effect` — `state.py:405`, decided inside the `BEGIN IMMEDIATE` of `_authorize_and_reserve` (`state.py:832`) against `effect_key TEXT PRIMARY KEY` (`migrations.py:107`; `COLLATE "C"` on Postgres, §4.4)                                                                                               | `test_T3_exactly_one_agent_reserves_and_seven_are_blocked` (8 OS processes, both backends), `test_T3_the_fake_remote_is_called_exactly_once`                                                                                               |
| "An unknown outcome is AMBIGUOUS, never FAILED, and blocks a blind retry."           | Only `NotExecuted` maps to `FAILED` — `control.py:1067`. Every other exception, timeouts included, yields `AMBIGUOUS`. A retry against an `AMBIGUOUS` key is refused — `effect.py:163`, the one place `plan_reservation` decides it for every store                                                             | `test_T1_a_blind_retry_writes_a_blocked_receipt`, `test_T1_the_ambiguous_record_survives_the_blocked_retry`, `test_T1_a_lost_response_leaves_the_effect_ambiguous`                                                                         |
| "An unknown action, a missing policy or a missing principal is denied."              | Unknown action: `Policy.evaluate` — `policy.py:678` — answers `deny` for a name the document does not list. Missing or malformed policy: `Policy.from_file` — `policy.py:628` — raises `PolicyError`, and there is no `Control` without a policy. Missing principal: `_refuse_no_principal` — `control.py:1888` | `test_T6_unknown_action_raises_ActionDenied_with_reason_unknown_action`, `test_missing_policy_file_is_a_policy_error`, `test_malformed_policy_document_is_a_policy_error`, `test_T62_a_declining_provider_with_no_context_is_no_principal` |
| "With authority on, every principal needs a grant, and delegation cannot widen one." | `NO_AUTHORITY` — the fail-closed default of `Authority.evaluate` (`authority.py:64`); `contained_dimension` — `authority.py:603` — runs from `plan_delegation` (`authority.py:877`) and from the chain walk in `Authority.evaluate`                                                                             | `test_T67_a_principal_with_no_grant_is_denied`, `test_t76_each_dimension_violated_alone`                                                                                                                                                   |
| "Every executed action leaves a portable JSON receipt"                               | `ReceiptResult` — `receipt.py:429`; `Event` — `receipt.py:429`; the store is authoritative — `append_event` — `state.py:565`; the JSONL export — `JSONLEventSink` — `receipt.py:429`                                                                                                                            | `test_T11_every_demo_receipt_carries_every_field_in_the_spec`, `test_T11_every_demo_receipt_parses_back_into_a_Receipt`                                                                                                                    |

## What it guarantees

| Claim                                                                                                                                        | Code                                                                                                                                                                                                                                                                                    | Proof                                                                                                                                                                                                                                                                    |
| -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| "On SQLite that is `BEGIN IMMEDIATE`"                                                                                                        | `_authorize_and_reserve` — `state.py:832`                                                                                                                                                                                                                                               | `test_T3_exactly_one_agent_reserves_and_seven_are_blocked`                                                                                                                                                                                                               |
| "a unique index on the effect key and compare-and-set updates whose row counts are checked"                                                  | `reserve_effect` — `postgres.py:528` — `INSERT … ON CONFLICT DO NOTHING` against `effect_key TEXT PRIMARY KEY COLLATE "C"` (`migrations.py:107`)                                                                                                                                        | `test_T3_exactly_one_agent_reserves_and_seven_are_blocked` (8 OS processes, both backends)                                                                                                                                                                               |
| "Same `StateStore` protocol, extended by nothing"                                                                                            | `PostgresStateStore.reserve_effect` — `postgres.py:528` — and every other method implement `v0.1 §5.3`'s frozen protocol; the decisions stay in `plan_reservation` (`effect.py:163`)                                                                                                    | `test_T154_postgres_passes_the_store_conformance_suite`                                                                                                                                                                                                                  |
| "graded by the suite written for SQLite"                                                                                                     | `ctrlrun.conformance.store.run` — `conformance/store/__init__.py:52`                                                                                                                                                                                                                    | `test_T140_every_fixture_fails_the_suite_named_for_it`                                                                                                                                                                                                                   |
| "It will not *knowingly* execute the same logical effect twice, and will never treat an unknown outcome as a failure."                       | `plan_reservation` — `effect.py:163` (refuse retry on `AMBIGUOUS`) and `control.py:1067` (only `NotExecuted` → `FAILED`)                                                                                                                                                                | `test_T1_a_blind_retry_is_refused_and_never_reaches_the_remote`, `test_T1_a_lost_response_leaves_the_effect_ambiguous`                                                                                                                                                   |
| "a lost connection during `COMMIT` ... are `AMBIGUOUS`"                                                                                      | `_resolve_lost_insert` — `postgres.py:670`; `_resolve_lost_update` — `postgres.py:1017`; only `NotExecuted` maps to `FAILED` — `control.py:1067`                                                                                                                                        | `test_T155_a_connection_killed_during_commit_is_resolved_by_the_re_read`, `test_T155_no_effect_is_ever_recorded_failed_by_a_lost_commit`                                                                                                                                 |
| "the store re-reads the row to find out which"                                                                                               | The six branches, named and logged — `A2_LANDED` — `postgres.py:127`                                                                                                                                                                                                                    | `test_T155b_a_landed_commit_on_a_transition_is_seen_as_landed`, `test_T155d_a_commit_the_server_never_received_retries_the_insert`                                                                                                                                       |
| "A crashed worker's effect stays `AMBIGUOUS` until a human runs `ctrlrun resolve` or a `reconcile` hook asks the remote what happened"       | An expired lease is `AMBIGUOUS` and nothing sweeps it — `LEASE_EXPIRED` — `effect.py:63`; who resolved it — `resolved_by` — `effect.py:63`; `resolve` — `cli/main.py:518`                                                                                                               | `test_T159_ambiguous_survives_a_restart_and_still_refuses_a_blind_retry`, `test_T160_there_is_no_reaper`, `test_T161_a_human_resolution_records_who`                                                                                                                     |
| "the only thing besides a human permitted to move a record out of `AMBIGUOUS`"                                                               | `Control._reconciled` — `control.py:1495`; `RECONCILED_STATES` — `effect.py`                                                                                                                                                                                                            | `test_T13_a_hook_answering_not_executed_moves_the_record_to_failed`, `test_T14_a_hook_answering_committed_refuses_the_retry_as_a_duplicate`                                                                                                                              |
| "and only in the direction its answer points"                                                                                                | `"unknown"` is absent from `RECONCILED_STATES` — `effect.py`                                                                                                                                                                                                                            | `test_T15_a_hook_that_cannot_answer_leaves_the_record_ambiguous`                                                                                                                                                                                                         |
| "Unknown action, missing policy, malformed policy, missing principal, missing or mismatched approval and inconsistent state are all `deny`." | `Policy.evaluate` — `policy.py:495`; `Policy.from_file` — `policy.py:628`; `_refuse_no_principal` — `control.py:1888`; `_authorize_and_reserve` — `state.py:832`                                                                                                                        | `test_T6_unknown_action_raises_ActionDenied_with_reason_unknown_action`, `test_malformed_policy_document_is_a_policy_error`, `test_T62_a_declining_provider_with_no_context_is_no_principal`, `test_T2_a_mutated_action_presenting_the_approval_raises_ApprovalMismatch` |
| "No flag makes a consequential action permissive by default"                                                                                 | There is no such option on `Control`, on `@protect`, on the CLI or in the policy schema's closed key sets — `_TOP_LEVEL_KEYS` — `policy.py:87`                                                                                                                                          | `test_T84_mode_is_refused_anywhere_but_the_top_level`, `test_T101b_zero_applicable_guarantees_is_not_a_pass`                                                                                                                                                             |
| "With `authority:` on, every principal needs a grant, delegation cannot widen one, and `ctrlrun revoke` cuts a chain with one write."        | `Authority.evaluate` — `authority.py:798`; `contained_dimension` — `authority.py:603`; `Control.revoke` — `control.py:1689`                                                                                                                                                             | `test_T67_a_principal_with_no_grant_is_denied`, `test_t76_each_dimension_violated_alone`, `test_t78_a_revoked_parent_denies_its_grandchild`                                                                                                                              |
| "verifies a bearer token against a JWKS or a pinned key"                                                                                     | `JWTIdentityProvider._verified` — `jwt_identity.py:175`; the algorithm comes from the configured list and never from the token                                                                                                                                                          | `test_T88_a_valid_token_becomes_a_principal`, `test_T89_every_invalid_token_is_refused_by_cause`                                                                                                                                                                         |
| "maps the verified claims onto a principal"                                                                                                  | `_principal` — `jwt_identity.py` — copies only the claims named in `claim_names`                                                                                                                                                                                                        | `test_T88_only_the_named_claims_reach_the_principal`                                                                                                                                                                                                                     |
| "`pip install \"ctrlrun[identity]\"`"                                                                                                        | `identity = ["pyjwt[crypto]>=2.8"]` in `pyproject.toml`; imported lazily by `_jwt()` — `jwt_identity.py`                                                                                                                                                                                | `test_T92_constructing_without_the_extra_names_the_install_command`, `test_T92_importing_ctrlrun_pulls_in_no_jwt_module`                                                                                                                                                 |
| "CTRLRun issues no credential and defines no identity format"                                                                                | There is no minting, signing or issuing code path in the package: `jwt_identity.py` calls `decode` and never `encode`                                                                                                                                                                   | `test_the_package_never_encodes_a_token`                                                                                                                                                                                                                                 |
| "every receipt records which policy decided it"                                                                                              | `Policy.policy_hash` — `policy.py:609`, over `_canonical_policy` — `policy.py:744`; carried into the receipt by `_record` — `control.py:1802`                                                                                                                                           | `test_T172_every_receipt_carries_the_hash_and_the_declared_version`, `test_T172_two_policies_sharing_a_version_string_are_told_apart_by_the_hash`                                                                                                                        |
| "the policy's declared `version:` and a hash of its canonical content"                                                                       | `version:` is recorded and never authoritative; `policy_hash` is what tells two documents apart — `policy.py:599`                                                                                                                                                                       | `test_T171_the_declared_version_alone_does_not_change_the_hash`, `test_T171_comments_key_order_and_whitespace_do_not_change_the_hash`                                                                                                                                    |
| "the approval is re-checked against the policy in force at execution"                                                                        | `Control.execute` — `control.py:547`; `_spend_unneeded_approval` — `control.py:1435`                                                                                                                                                                                                    | `test_T173_the_DENY_row_refuses_and_leaves_the_approval_granted`, `test_T173_the_ALLOW_row_invalidates_the_approval_it_did_not_need`                                                                                                                                     |
| "Each receipt carries the hash of the one before it"                                                                                         | `Receipt.chain_hash` — `receipt.py:284`; `prev_hash` — `receipt.py:41`; `GENESIS_HASH` — `receipt.py:41`; `put_receipt` takes the head row's lock first — `postgres.py:1486`                                                                                                            | `test_T164_an_altered_receipt_is_content_altered_at_its_seq`, `test_T164_reordering_two_receipts_is_detected_either_way`                                                                                                                                                 |
| "`ctrlrun receipts --verify-chain` reports it by `seq`"                                                                                      | `verify_chain` — `receipt.py:479`; the six names — `CHAIN_BREAKS` — `receipt.py:479`                                                                                                                                                                                                    | `test_the_verify_chain_flag_reports_a_break_by_seq_and_by_name`, `test_verify_chain_reads_a_postgres_store_through_store_url`                                                                                                                                            |
| "migrations are automatic at open, forward-only"                                                                                             | `migrate` — `migrations.py:525`, called from both stores' constructors; `HEAD` — `migrations.py:306`                                                                                                                                                                                    | `test_T147_a_v05_database_migrates_and_keeps_every_row`, `test_T150_reopening_does_not_rerun`                                                                                                                                                                            |
| "An older binary against a newer schema refuses immediately"                                                                                 | `_refuse` — `migrations.py:451`; `SchemaMismatch` — `errors.py`                                                                                                                                                                                                                         | `test_T148_an_older_binary_refuses_a_newer_database`, `test_T148_no_other_table_is_read_before_the_refusal`                                                                                                                                                              |
| "Releases carry PyPI provenance attestations from GitHub Actions"                                                                            | `.github/workflows/publish.yml` — `pypa/gh-action-pypi-publish` pinned at v1.14.2, which generates and uploads PEP 740 attestations by default since v1.11.0 (its release notes, read 2026-09-06), with no `attestations: false`; the `pypi` job's only permission is `id-token: write` | `test_the_publish_workflow_attests_through_trusted_publishing`, `test_every_action_is_pinned_to_a_commit`                                                                                                                                                                |
| "`ctrlrun approve`, `deny`, `resolve`, `inspect`, `receipts` and `stats` work from the shell against any store"                              | `approve` — `cli/main.py:366`; `receipts` — `cli/main.py:426`; `effects` — `cli/main.py:500`; `resolve` — `cli/main.py:518`; `inspect` — `cli/main.py:554`; `stats` — `cli/main.py:727`; every one takes `--store-url` (SPEC-v0.6 §9.4)                                                 | `test_T10_resolve_failed_permits_a_retry`, `test_T18_inspect_json_emits_the_inspection_schema`, `test_T86_stats_counts_what_observe_mode_recorded`, `test_verify_chain_reads_a_postgres_store_through_store_url`                                                         |
| "`WebhookApprovalProvider` sends an approval request to a webhook, such as Slack, and takes the answer back through the same grant calls"    | `WebhookApprovalProvider` — `webhook.py:141` — one signed POST on `APPROVAL_REQUESTED`; the inbound answer lands through `grant_approval` / `deny_approval` like the CLI's                                                                                                              | `test_T27_the_outbound_post_carries_a_signature_over_the_exact_bytes_sent`, `test_T27_the_payload_carries_what_the_spec_names`                                                                                                                                           |
| "one OpenTelemetry span per action, one span event per step"                                                                                 | `OTelEventSink` — `otel.py:45`                                                                                                                                                                                                                                                          | `test_T29_one_action_produces_one_span_named_for_the_action`, `test_T29_every_event_becomes_a_span_event_named_by_its_type`                                                                                                                                              |
| "argument values stay out of it unless you ask for them"                                                                                     | `OTelEventSink(arguments=...)` — `otel.py:45`                                                                                                                                                                                                                                           | `test_T29_argument_values_are_not_attributes_by_default`                                                                                                                                                                                                                 |
| "Receipts in a `ctrlrun.policy/v4` document can cite the `controls:` an action satisfies"                                                    | `PolicyControl` — `policy.py:420`; `Receipt` — `receipt.py:245` — carries `controls`; attribution only, never a decision                                                                                                                                                                | `test_T175_the_receipt_carries_the_union_of_the_action_and_the_matched_rule`, `test_T175_a_control_is_attribution_and_changes_no_decision`                                                                                                                               |
| "a rule can condition on the `data:` labels present in an action's arguments"                                                                | `DataLabel` — `policy.py:403`; `Policy.data_scope` — `policy.py:487`; `data_scope_in` in `v0.1 §3.2`'s grammar with no new operator                                                                                                                                                     | `test_T176_the_derived_set_is_the_labels_of_the_arguments_actually_supplied`, `test_T176_the_derived_set_drives_a_decision`                                                                                                                                              |

## What it can't, stated as limits

The README also makes negative claims. They matter as much as the positive ones.

| Claim                                                                                                                                                 | Where it holds                                                                                                                                                                                                                                                                                                                                                                                         |                                                                                                                                                                                                        |
| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| "CTRLRun cannot guarantee exactly-once execution against external systems it doesn't control."                                                        | Stated, not implemented — see `THREAT_MODEL.md`, "Out of scope". CTRLRun never asserts what a remote did; only `NotExecuted`, raised by the executor, claims that.                                                                                                                                                                                                                                     |                                                                                                                                                                                                        |
| "CTRLRun is not a transaction manager: it rolls nothing back"                                                                                         | There is no compensation, saga or rollback code path in the package; an `AMBIGUOUS` effect is resolved by a human or a reconcile hook and never undone — `RECONCILED_STATES` — `effect.py`                                                                                                                                                                                                             |                                                                                                                                                                                                        |
| "The receipt chain detects alteration, and alteration is not authorship."                                                                             | n/a — a disclaimer, and the scan that keeps it one: `test_T180_the_release_documents_do_not_blur_alteration_and_authorship`                                                                                                                                                                                                                                                                            |                                                                                                                                                                                                        |
| "erasing the end of the log costs two statements"                                                                                                     | No code — this is what the chain does **not** cover, and it is asserted rather than argued: `test_erasing_a_suffix_and_rewinding_the_head_is_two_statements_and_undetected`                                                                                                                                                                                                                            |                                                                                                                                                                                                        |
| "CTRLRun does not detect prompt injection"                                                                                                            | No code — and that is the point. Nothing in the package reads the agent's instructions: `Policy.evaluate` takes the action's name and arguments (`policy.py:495`) and `Authority` matches a grant against the action, so neither axis has the prompt to inspect. The README's problem table claims containment of the consequence, and this row is the sentence that stops it being read as detection. | `test_T6_an_action_name_is_matched_exactly`, `test_a_condition_naming_an_action_field_is_refused_at_load`                                                                                              |
| "`ctrlrun verify` cannot see your executors"                                                                                                          | `docs/docs/verify.md`, "What it does not mean"; `THREAT_MODEL.md`, "Known v0.4 limitations"                                                                                                                                                                                                                                                                                                            |                                                                                                                                                                                                        |
| "`ctrlrun scan` … reports the consequential call sites and policy entries CTRLRun is **not** covering" and "has no score, no percentage and no badge" | `ctrlrun/scan/` reads the tree with `ast` and never imports it, resolves no principal, evaluates no policy and opens no store (SPEC-scan §9.2); the limits sentence is emitted on every run including a clean one, and no percentage is computed anywhere                                                                                                                                              | `test_T194_scan_never_imports_the_tree_it_reads`, `test_T205_scan_resolves_no_principal_evaluates_no_policy_and_opens_no_store`, `test_T203_the_limits_sentence_is_in_every_run_including_a_clean_one` |
| "`ctrlrun mcp-operator` … It authenticates who answered and records it; it does not check that they were entitled to."                                | the write tools refuse without a principal and attribute the answer to the verified one; there is no entitlement check, and `docs/SPEC-mcp-operator.md` §10 says so                                                                                                                                                                                                                                    | `test_T184_approve_refuses_without_a_principal`, `test_T184_approve_succeeds_with_one_and_is_attributed`, `test_T183_there_is_no_flag_that_permits_a_remote_bind`                                      |
| "it makes no claim about any standard"                                                                                                                | No standards vocabulary outside a sentence that negates it, in the README, in a docstring or in CLI output: `test_T139_the_readme_makes_no_conformance_claim`, and `tools/docs_audit/lint.py` on every document                                                                                                                                                                                        |                                                                                                                                                                                                        |

## Running it in production: beyond one process

| Claim                                                                                                                                             | Code                                                                                                                                                                        | Proof                                                                                                                                                                                     |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "the same `StateStore` protocol, extended by nothing, graded by the suite written for SQLite rather than one written for it"                      | `PostgresStateStore` — `postgres.py` — satisfies `StateStore` and adds no method (SPEC-v0.6 §9.1); `ctrlrun.conformance.store.SUITES` is the SQLite suite, run against both | `test_T141_the_shipped_backends_pass`, `test_T154_postgres_passes_the_store_conformance_suite`                                                                                            |
| "automatic at open and forward-only, with no flag that opens a database un-migrated. An older binary against a newer schema refuses immediately." | `migrate` — `migrations.py:525` — called from both stores' constructors; `_refuse` — `migrations.py:451` — raises `SchemaMismatch` on a newer `user_version`                | `test_T147_a_v05_database_migrates_and_keeps_every_row`, `test_T148_an_older_binary_refuses_a_newer_database`, `test_T152b_no_flag_opens_a_database_without_migrating`                    |
| "an edit, a deletion from the middle or a reordering is detected and named by `seq`"                                                              | `verify_chain` — `receipt.py:479` — and the six break names in `CHAIN_BREAKS`                                                                                               | `test_T164_an_altered_receipt_is_content_altered_at_its_seq`, `test_T164_reordering_two_receipts_is_detected_either_way`, `test_the_verify_chain_flag_reports_a_break_by_seq_and_by_name` |
| "It detects **alteration**, which is not authorship: receipts are not signed."                                                                    | No signing code, and a release scan keeps the vocabulary out                                                                                                                | `test_T180_the_release_documents_do_not_blur_alteration_and_authorship`                                                                                                                   |
| "every receipt records the policy that decided it, so a receipt from six months ago says what the rules were"                                     | `Policy.policy_hash` — `policy.py:609` — over the parsed decision inputs, recorded on the receipt                                                                           | `test_T172_every_receipt_carries_the_hash_and_the_declared_version`, `test_T171_any_decision_input_changes_the_hash`, `test_T171_the_declared_version_alone_does_not_change_the_hash`     |

## The docs site: Home and Concepts

Every claim on the docs site's Home page and Concepts pages, mapped the same way. Most of them
are the README's claims in a second place, so the rows point at the rows above rather than
restating the code; the ones that are new to the site carry their own code and proof.

| Page                                | Claim                                                                                                                                                                                | Proved by                                                                                                                                                                                                                                                               |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `index`                             | the hero, the promise, the demo transcript and the capability grid                                                                                                                   | the header rows above; the grid is the generator's output for `docs/capabilities.yaml`, checked by `test_the_generated_copies_match_the_generator`                                                                                                                      |
| `index`                             | "This site is an MCP server"                                                                                                                                                         | Mintlify hosts one at `/mcp` for every site (its documentation, read 2026-09-06); the URL is the site's and changes with the domain, and `test_the_documentation_root_preserves_the_technical_overview` asserts the configuration line is present                       |
| `get-started/install`               | "installs the kernel and exactly two dependencies, `pyyaml` and `click`"                                                                                                             | `test_core_declares_only_pyyaml_and_click`, `test_the_core_dependencies_have_not_grown`                                                                                                                                                                                 |
| `get-started/install`               | "importing `ctrlrun` imports nothing from an extra"                                                                                                                                  | `test_T30_a_subprocess_importing_ctrlrun_pulls_in_no_module_from_an_extra`                                                                                                                                                                                              |
| `get-started/install`               | "raises `MissingDependency` with the install command in the message"                                                                                                                 | `test_a_missing_extra_raises_MissingDependency_naming_the_install_command`                                                                                                                                                                                              |
| `get-started/quickstart`            | every block on the page, and the outputs shown                                                                                                                                       | the blocks are `runnable` and pass `tools/docs_audit/snippets.py` in one temporary directory, in order; the outputs are pasted from one run of the same blocks                                                                                                          |
| `concepts/action-and-hash`          | "The action hash is the SHA-256 of that canonical form"; sorted keys, no whitespace, UTF-8, `float` rejected; `action_id` excluded                                                   | `canonicalize` / `action_hash` — `action.py`; `float` refused — `action.py:71`; `test_T7_canonical_form_is_exactly_the_specified_serialization`, `test_T7_nested_dicts_are_sorted_recursively`, `test_T60_claims_do_not_change_the_action_hash`                         |
| `concepts/decisions`                | three decisions, first match wins, unknown denied, principal-addressing conditions refused at load                                                                                   | the "Write down what the agent may do" rows above                                                                                                                                                                                                                       |
| `concepts/approval-binding`         | A1–A4, the mismatch leaving the approval granted, one core provider writing every grant                                                                                              | the matrix row "An approval is bound to the exact action…", the "Three ways to use it" adapter rows, and `test_T2_a_mutated_action_leaves_the_approval_granted`                                                                                                         |
| `concepts/approval-binding`         | the `DENY` and `ALLOW` rows when the policy changed between grant and consumption                                                                                                    | "the approval is re-checked against the policy in force at execution" above                                                                                                                                                                                             |
| `concepts/effect-keys`              | reservation atomic across threads, processes and hosts; an expired lease is `AMBIGUOUS`, never free; `COMMITTED` refuses, `FAILED` permits, `AMBIGUOUS` refuses a blind retry        | the matrix row "One logical effect happens at most once…"; `LEASE_EXPIRED` — `effect.py:63`; `test_T160_an_expired_lease_frees_nothing_and_no_read_transitions_it`, `test_T8_a_failed_attempt_permits_a_retry_that_commits`                                             |
| `concepts/outcomes-and-ambiguous`   | the outcome table; only a human or a reconcile hook moves a record on, and only in the direction the answer points; nothing sweeps; a lost `COMMIT` on Postgres is `AMBIGUOUS`       | the matrix row "An unknown outcome is AMBIGUOUS…", the reconciliation rows, "A crashed worker's effect stays `AMBIGUOUS`…" and the Postgres rows above; `test_T160_there_is_no_reaper`                                                                                  |
| `concepts/receipts-and-evidence`    | the receipt's fields, the JSONL sink, the policy hash and version, the chain and what it does not prove                                                                              | the matrix row "Every executed action leaves a portable JSON receipt", the receipt-chain and policy-versioning rows above, and `test_T11_every_demo_receipt_carries_every_field_in_the_spec`                                                                            |
| `concepts/authority-and-delegation` | opt-in then fail-closed, no `decision:` on a grant, stricter of the two, containment at creation and at every evaluation, omission rejected, one-write revocation, identity consumed | the authority rows under "Write down what the agent may do" and "What it guarantees" above                                                                                                                                                                              |
| `concepts/observe-mode`             | executes, records `would_have`, one top-level line, counted by `ctrlrun stats`, never asks a human                                                                                   | the observe-mode rows above; `_observed` — `control.py:743`                                                                                                                                                                                                             |
| `concepts/fail-closed`              | the refusal table, one exception per row                                                                                                                                             | the matrix row "An unknown action, a missing policy or a missing principal is denied.", `ActionDenied` — `errors.py:29`, `DuplicateEffect` — `errors.py:126`, `AmbiguousEffect` — `errors.py:141`, and `test_a_policy_deny_is_denied_the_same_way_as_an_unknown_action` |

## The docs site: Production

The section a stranger reads to decide whether to adopt. Two things on it are generated and one
is a statement about what has **not** happened; all three are here because each is the kind of
sentence that rots quietly.

| Page                               | Claim                                                                                                        | Proved by                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `production/index`                 | the readiness block — version, test count, guarantee count, the two stores, the soak, the chain, the licence | rendered by `tools/docs_audit/render_readiness.py` from `pyproject.toml`, `pytest --collect-only`, the `GUARANTEES` catalogue — `verify/guarantees.py:39` — and `research/soak/results/`; `test_the_readiness_block_is_the_generators_in_every_place_it_appears` asserts the same block in the README, the docs home and this page, and `test_the_readiness_block_refuses_a_shrunken_suite_and_accepts_a_grown_one` makes the count a floor                                                                                                                                                                                                                                                                                                                                                          |
| `production/index`                 | the **Not yet** list: no external security audit, no third-party review of the kernel, no sector packs       | stated rather than measured, because nothing in a repository can measure an absence. A fourth line — *no soak of the length the roadmap asks for* — was **derived** from the published run until `SPEC-v0.6.md` §8.1 removed the duration from the criterion on 2026-09-07, which removed the thing being derived; the run's own duration is still printed on the soak line above the list. The list lives inside the generated block so it cannot be scrolled past. `test_the_not_yet_list_is_inside_the_block_and_not_below_it`, `test_the_not_yet_list_is_the_constant_and_derives_nothing_from_the_soak` and `test_the_readiness_block_does_not_report_the_soak_as_an_unmet_gate` assert all of it; removing a stated line is its own pull request with the row that makes the new sentence true |
| `production/index`                 | "SQLite is the default and it is production-grade on one host… Postgres is for many hosts"                   | the header row above; `test_the_first_line_of_the_section_says_which_store_and_why` asserts the order, because Postgres first would tell a reader with one host something false                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `production/how-reservation-works` | the two rows: an exception before `COMMIT` is a failed write; one during it is unknown and is re-read        | SPEC-v0.6 §4.3 Tables A, A1 and A2; `test_T155_a_connection_killed_during_commit_is_resolved_by_the_re_read`, `test_T155e_a_commit_the_server_never_received_re_issues_the_update`, `test_T155c_the_re_read_identity_check_is_not_an_action_id_match`, `test_T156_a_failed_re_read_refuses_to_proceed`; `test_the_two_rows_of_the_lost_commit_are_not_merged` asserts the page keeps them apart                                                                                                                                                                                                                                                                                                                                                                                                      |
| `production/migrations`            | five shapes, three refusals, nothing half-applies, no flag that opens a database un-migrated                 | the "migrations are automatic at open, forward-only" row above; `test_T149_a_half_applied_migration_rolls_back`, `test_T152b_no_flag_opens_a_database_without_migrating`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `production/recovery`              | nothing sweeps; an expired lease is a refusal and not a reclaim; no process identity is inferable            | the "A crashed worker's effect stays `AMBIGUOUS`…" row above; `test_T160_an_expired_lease_frees_nothing_and_no_read_transitions_it`, `test_T162_no_process_identity_is_inferable_from_a_record`, `test_T177d_an_expired_lease_is_displayed_as_expired_and_the_display_transitions_nothing`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `production/receipt-integrity`     | the six names, `unchained` never a pass, a failed receipt write raises and leaves no gap                     | the receipt-chain rows above; `test_T164_an_altered_receipt_is_content_altered_at_its_seq`, `test_T168_pre_chain_receipts_are_unchained_and_never_a_pass`, `test_T170_a_failed_receipt_write_raises_and_leaves_no_gap`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `production/soak`                  | every number on the page, the measured duration, and what a run of that length does **not** establish        | rendered by `tools/docs_audit/render_soak.py` from `research/soak/results/*.json`, which **recomputes** the exit criterion from the published counts rather than reading `exit_criterion_met` out of the same file — a weaker gate checked twice; `test_the_soak_page_is_the_render_of_the_published_results`, `test_the_soak_page_states_the_measured_duration_and_what_it_does_not_establish` and `test_the_soak_page_derives_the_criterion_and_agrees_with_the_harness`                                                                                                                                                                                                                                                                                                                           |
| `production/operations`            | the signals table, and that there is nothing to run                                                          | the recovery rows above and `reference/cli`; `test_T177c_the_command_list_is_exactly_the_one_the_spec_froze` asserts the command list these are drawn from                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| the badge row                      | the test-count badge                                                                                         | written by `tools/docs_audit/render_badges.py --write-count` in CI **after** `scripts/check.sh` has passed, published to the `badges` branch only on a push to `main`; `test_ci_publishes_the_test_count_badge_after_the_suite_has_passed` asserts the order                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |

## The browser playground

The Try-it page says every line in its box was produced by `ctrlrun` in the tab, and that the
sequence it tells the reader to try ends the way it says.

| Claim                                                                                                                                                                                                       | Code                                                                                                                                                                                            | Proof                                                                                                                                                                                                                                    |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "Every line in the box was produced by `ctrlrun` here: the page owns the controls and nothing else."                                                                                                        | `PLAYGROUND` in `docs/try-it.js` is a module over `Control`, `InMemoryStateStore`, `LocalApprovalProvider` and `@protect`; the JavaScript builds a request and prints the JSON `step()` returns | `test_the_playground_runs_the_sequence_the_page_tells_the_reader_to_try` runs that module natively through the six steps and asserts each outcome; `docs/assets/verify-browser-demo.mjs` runs the same module and sequence under Pyodide |
| the six steps: allowed · `ApprovalRequired` then `ApprovalMismatch` then executed · `consumed` · `ActionDenied` with no request · `AMBIGUOUS` then a refused retry with one remote call · `DuplicateEffect` | the same kernel paths the matrix rows above cite                                                                                                                                                | the same test, and `test_the_page_names_every_outcome_the_module_can_return`, which holds the page's vocabulary to the module's                                                                                                          |
| an approval is the reader pressing *Approve*, recorded by `grant_approval`; no auto-approve, no dry run                                                                                                     | one `grant_approval(` in the module and nothing else that grants                                                                                                                                | `test_the_playground_has_no_way_to_grant_but_the_human_button`                                                                                                                                                                           |
| the policy shown is the policy that ran                                                                                                                                                                     | `POLICY` in the module                                                                                                                                                                          | `test_the_playground_policy_on_the_page_is_the_policy_in_the_module`                                                                                                                                                                     |
| the versions and date the page quotes are a run's                                                                                                                                                           | `docs/assets/browser-demo.verified.json`, written by the harness after a passing run                                                                                                            | `test_the_page_quotes_the_run_the_harness_recorded`                                                                                                                                                                                      |

## Demo output

The README quotes `ctrlrun demo` verbatim.
`test_the_readme_demo_section_quotes_the_demo_output_verbatim` runs the demo and asserts every
line it prints appears in the README, masking only the generated approval and delegation ids.
The animation at the top of the README ends on lines `docs/assets/demo.expected.txt` lists, and
`tests/test_readme_assets.py` asserts each is a line the demo prints and the README quotes.

## How these line numbers are kept honest

They are not, automatically — a citation is prose, and prose drifts. Every row above was
re-derived against the tree at the tag named at the top of this file by reading the line each
one names.

The v0.6 pass moved **fifteen** of them, and none for an interesting reason: the v0.1 and v0.2
rows were written against v0.3.0 and the files have grown since. The two that had drifted
*semantically* were fixed in the previous pass and still point where their sentences say —
the `BEGIN IMMEDIATE` citation at the reservation path rather than `grant_approval`'s, and the
"no principal" claim at the public `Policy.evaluate` rather than the private
`_ActionPolicy.evaluate`. One row moved between files: "blocks duplicate execution attempts"
cited `state.py` for the `AMBIGUOUS` refusal, which now lives in `effect.py`'s
`plan_reservation`, decided once for both stores.

If you are regenerating this file, re-derive every row. Do not carry one forward on trust.

**And from v0.5, you do not have to take that on trust either.**
`test_the_claims_table_line_numbers_point_at_what_they_name` resolves every `file.py:NNN` in
this document against the line it cites and fails if the symbol the cell names is not on it.
It found **nine** stale references the first time it ran, four of which pointed at a string
literal, a comment or the middle of another function. The instruction above had been followed
by hand at three releases and the table had drifted anyway, which is the argument for the test
rather than against the instruction.


## Related topics

- [How this is built](/docs/how-this-is-built.md)
- [CTRLRun and agent oversight toolkits](/docs/compare/governance-toolkits.md)
- [Reporting a vulnerability](/docs/security/disclosure.md)
- [OWASP Top 10 for Agentic Applications](/docs/OWASP-AGENTIC-TOP10.md)
- [Threat model](/docs/THREAT_MODEL.md)
