diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 0b640568..ad31e6d9 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -18,7 +18,7 @@ body: id: expected attributes: label: What the documentation says should happen - description: Quote the sentence, with a link. Every sentence in the README has a row in `docs/CLAIMS.md`; if you found the row, name it. + description: Quote the sentence, with a link. Every sentence in the README has a row in `docs/docs/CLAIMS.md`; if you found the row, name it. validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index f451cac6..76485f8e 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -5,7 +5,7 @@ body: - type: markdown attributes: value: | - Every addition answers one question first: *does this improve control or correctness of an agent action that can affect the real world?* If it helps a model think, improves prompts, stores memory, retrieves documents, chooses models or visualises traces, it belongs somewhere else. `docs/ROADMAP.md` lists what is planned and `VISION.md` what is not a build spec. + Every addition answers one question first: *does this improve control or correctness of an agent action that can affect the real world?* If it helps a model think, improves prompts, stores memory, retrieves documents, chooses models or visualises traces, it belongs somewhere else. `docs/docs/ROADMAP.md` lists what is planned and `VISION.md` what is not a build spec. - type: dropdown id: guarantee attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6e2e3303..cd2f236f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,7 +9,7 @@ - [ ] **Specification first.** The section this implements is named above; a change to a frozen name or a new entry point amends the spec in this same PR. - [ ] **Tests first.** The acceptance tests were red before the implementation and are green after it; every polling or waiting test bounds its clock or iteration count. - [ ] **Mutation table.** Each MUST in the touched sections was removed, its named test confirmed red, and the guard restored. The table is in the description, checked against the four shapes in CONTRIBUTING.md. -- [ ] **`docs/CLAIMS.md`.** Every new or changed README sentence has a row with code and a test; every removed capability took its row and its sentence with it. +- [ ] **`docs/docs/CLAIMS.md`.** Every new or changed README sentence has a row with code and a test; every removed capability took its row and its sentence with it. - [ ] **Docs audit green.** `python tools/docs_audit/snippets.py`, `lint.py`, `links.py` and `render_capabilities.py --check` pass; a capability table was edited in `docs/capabilities.yaml`, never by hand. - [ ] **`scripts/check.sh` green** under the project's interpreter. - [ ] **Independent review** requested for anything touching authorization, identity, delegation, the gateway, an adapter or the store. diff --git a/CHANGELOG.md b/CHANGELOG.md index b439f970..dcd7a5ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,7 +124,7 @@ policy or authority document — and both are listed below with what they did be of at least one week with no unexplained `AMBIGUOUS`*; the week was removed rather than waited out, and the criterion is now a published run with no unattributed `AMBIGUOUS` and a positive control that fired — the two things a harness is allowed to decide about itself. `SPEC-v0.6.md` -§8.1 carries the reasoning, what it costs and what did not change; `docs/ROADMAP.md` records it +§8.1 carries the reasoning, what it costs and what did not change; `docs/docs/ROADMAP.md` records it in the milestone's own reconciliation. The short version: elapsed hours were a proxy for a question the injection ledger already answers, and what a week would actually have bought — whether anything **accumulates** over days — is unestablished by anything in this repository and @@ -133,7 +133,7 @@ is now claimed by nothing rather than owed by a gate. The published run is twenty minutes, 889,735 actions, 133,393 ambiguous outcomes all attributed, 0 unattributed, positive control fired. **The duration is printed on every surface that quotes the run** so a reader can discount it: the README's readiness block, the docs home, the -production index, and `docs/production/soak.mdx`, which now recomputes the criterion from the +production index, and `docs/docs/production/soak.mdx`, which now recomputes the criterion from the published counts instead of reading `exit_criterion_met` out of the same file. v0.5 asked *can somebody else implement this?* v0.6 asks: **does it still hold when the process @@ -245,7 +245,7 @@ have recovered or retrying work nothing can. runs in its own store: a deliberately unrecorded ambiguity that the table must report. A soak with no unexplained `AMBIGUOUS` is a result; a soak whose harness could not have detected one is not. -- **`docs/postgres.md`** — the operator's page: connection strings, what to grant, what happens +- **`docs/docs/postgres.md`** — the operator's page: connection strings, what to grant, what happens on failover, the one row every receipt write serializes on, and what the store does **not** do for you. @@ -357,12 +357,12 @@ have recovered or retrying work nothing can. database with no `continuations` and no `delegations` table. `docs/SPEC-v0.6.md` §9.6.1 records all twenty-one with where each landed. -- **`docs/ROADMAP.md`'s v0.6 bullet said "receipt integrity (hash chain / signatures)", and the +- **`docs/docs/ROADMAP.md`'s v0.6 bullet said "receipt integrity (hash chain / signatures)", and the slash was the problem.** A chain detects **alteration**; a signature proves **origin**, and proving origin brings key generation, rotation and revocation with it — which is issuing, and this project verifies what it is handed. Signing is out of scope for v0.6 (`SPEC-v0.6.md` §11). Corrected in the same commit as the specification, on the rule `SPEC-v0.4.md` §9.4 set. -- **`docs/THREAT_MODEL.md`'s "Receipts are not signed; a database admin can alter history +- **`docs/docs/THREAT_MODEL.md`'s "Receipts are not signed; a database admin can alter history (v0.6)"** promised something v0.6 does not deliver. Rewritten to say which half v0.6 closes — the partial tamper: an `UPDATE` on one row, a `DELETE` from the middle, a reordering — and which half it does not: **truncation at the end**, authorship, an adversary who can rewrite @@ -515,7 +515,7 @@ exercise, not the two adapters, is what v0.5 is for. the same refusals as one driven through `@protect`?* Fifteen deliberately broken fixtures were written **first**, and each fails the suite named for it and no other. -- **`docs/adapters.md`**, and a README section that opens by saying when you do **not** need an +- **`docs/docs/adapters.md`**, and a README section that opens by saying when you do **not** need an adapter (T139) — `@protect` covers anything in this process and the gateway anything over MCP. - **The framework probe was run** against LangGraph 1.2.11 and openai-agents 0.22.0, five @@ -596,7 +596,7 @@ reading a coarser answer as though it answered a finer question. ### Changed -- **`docs/ROADMAP.md`'s v0.5 bullet was wrong and is corrected here**, not silently. It said +- **`docs/docs/ROADMAP.md`'s v0.5 bullet was wrong and is corrected here**, not silently. It said the reference adapters map their frameworks' interrupts onto `Suspended` / `Control.resume`, "which v0.2 already ships for exactly this shape". It does not: `Suspended` exists for the remote asking a question *mid-execution*, where the reservation is already taken and must @@ -677,7 +677,7 @@ than to storage: `ctrlrun.verify/v1`, `ctrlrun.guarantees/v1` and `ctrlrun.frame document structurally as well — a permissive schema is not a check. `xmlschema` joins the **dev** extra for that test and for nothing else. -- **The GitHub Action, the badge and `docs/verify.md`** (SPEC-v0.4 §5). `action.yml` at the +- **The GitHub Action, the badge and `docs/docs/verify.md`** (SPEC-v0.4 §5). `action.yml` at the repository root is a composite action: it installs `ctrlrun`, runs `ctrlrun verify --json --junit`, renders the job summary and the badge **from that report** rather than from a second run — so the badge, the summary and the uploaded artifact can never @@ -692,14 +692,14 @@ than to storage: `ctrlrun.verify/v1`, `ctrlrun.guarantees/v1` and `ctrlrun.frame The badge is a Shields endpoint JSON the action **writes and never publishes**. Committing it would need `contents: write` in every consumer's workflow, and asking for write access to a repository as the price of a verification badge is a bad trade for a tool whose subject is - least privilege; `docs/verify.md` shows the one-job publishing pattern once, with its cost + least privilege; `docs/docs/verify.md` shows the one-job publishing pattern once, with its cost visible. Rendered, it reads exactly `CTRLRun verified N/M`, where `M` is **applicable** guarantees and never the catalogue size. A partial run and a run that exited 2 or 3 write no badge at all. The badge means **"declared guarantees pass"** — that phrase, on the badge's link target, and no other. Not secure, not safe, not compliant, not certified, not audited. - `docs/verify.md#what-the-badge-means` says it in its first sentence and, on the same screen, + `docs/docs/verify.md#what-the-badge-means` says it in its first sentence and, on the same screen, what verify cannot see: the operator's executors, their `reconcile` hooks, where they put the decorator, their deployment, and whether the policy is the right policy. @@ -708,7 +708,7 @@ than to storage: `ctrlrun.verify/v1`, `ctrlrun.guarantees/v1` and `ctrlrun.frame so a change that made verify silently count N/As as passes is caught in CI rather than in a badge. -- **`docs/OWASP-AGENTIC-TOP10.md`** (SPEC-v0.4 §6) — a reading of the OWASP Top 10 for Agentic +- **`docs/docs/OWASP-AGENTIC-TOP10.md`** (SPEC-v0.4 §6) — a reading of the OWASP Top 10 for Agentic Applications (2026 edition, announced 2025-12-09) against the ten guarantees. Its first line, before any table, says what it is not: not a compliance claim, not a conformance claim, not a certification, and not a statement that CTRLRun covers the Top 10. @@ -756,7 +756,7 @@ than to storage: `ctrlrun.verify/v1`, `ctrlrun.guarantees/v1` and `ctrlrun.frame request from a fork must not be able to write the badge, and a read-only fork token is a default rather than a refusal — and publishing the badge the `verify` job already produced, downloaded as an artifact rather than regenerated, so §5.1's one-run rule holds across the - job boundary. `docs/verify.md` shows the job and names the permission it costs. + job boundary. `docs/docs/verify.md` shows the job and names the permission it costs. - **`docs/SPEC-v0.4.md` gains a §12**, recording the readings the implementation had to take where the specification could not be satisfied as written. A specification that @@ -782,7 +782,7 @@ than to storage: `ctrlrun.verify/v1`, `ctrlrun.guarantees/v1` and `ctrlrun.frame - **`docs/SPEC-v0.3.md` §4.3.1 gains an informational row** for `ctrlrun.verify.run` (SPEC-v0.4 §3.9, §9.4 item 3). Verify is not a new entry point: it proposes no action of its own and drives the rows already there. The row exists because a reader will look for one. -- `docs/ARCHITECTURE.md` §6's module map gains `verify/`, above `control.py` and beside `cli/`. +- `docs/docs/ARCHITECTURE.md` §6's module map gains `verify/`, above `control.py` and beside `cli/`. - **`docs/SPEC-v0.4.md`** — the v0.4 contract, a delta over v0.1, v0.2 and v0.3. v0.4 answers the question the first three releases could not: *does it hold in **my** setup?* Everything @@ -845,11 +845,11 @@ a token rotation. - **`examples/authority/`** — a payments delegation chain and a DevOps chain, as complete documents to read rather than run, with a README that says in its first paragraph that every principal in them is invented. -- **`docs/authority.md`** — grants, delegation and the omission rule in plain language, +- **`docs/docs/authority.md`** — grants, delegation and the omission rule in plain language, including the two things it is worth knowing before you need them: an `Authority` is built at load time and is not hot-reloaded, and there is no way to list delegations, so cutting a chain of unknown width means `delegable: false` on the root and a restart. -- **`docs/THREAT_MODEL.md` gains v0.3's boundary.** In scope: delegation escalation, omission +- **`docs/docs/THREAT_MODEL.md` gains v0.3's boundary.** In scope: delegation escalation, omission as widening, expired and revoked authority, token forgery, cross-JWT confusion, and signing keys fetched from somewhere else. Out of scope, and stated rather than implied: a compromised identity provider, a `HeaderIdentityProvider` behind a proxy that does not overwrite, a @@ -1085,7 +1085,7 @@ a token rotation. provider, `agent_id` is ignored — not merged, not a fallback, not compared — and a provider that names nobody is a denial with `reason_codes: ["no_principal"]`, never a fall back to the envelope. `handle()` gains an optional `headers=` for the transport's own headers, which is - what a provider reads. `docs/ACS.md`'s mapping table is amended in the same change. + what a provider reads. `docs/docs/ACS.md`'s mapping table is amended in the same change. - **All three v0.2 call sites now use the combined decision** (§8.3): the gateway's `tools/call` path, `ctrlrun.acs`'s request hook, and `Control.resume`. Left as `Policy.evaluate`, an action a grant forbids outright would still have its approval flow run, and a human would be paged @@ -1281,7 +1281,7 @@ Everything below ships. `pip install ctrlrun` still installs nothing but `pyyaml and the policy disagree, the decorator wins and the mismatch is warned about once. - **ACS control hook** — `ctrlrun.acs.AcsControlHook`, in `ctrlrun[gateway]`. Answers the OWASP Agent Control Standard's `steps/toolCallRequest` and `steps/toolCallResult`. See - `docs/ACS.md` for the mapping and for the four places ACS is silent. **No compliance + `docs/docs/ACS.md` for the mapping and for the four places ACS is silent. **No compliance claim**: at the commit read there is no ACS reference implementation and no conformance suite, so there is nothing to be conformant with. - **`examples/`** — four standalone failure scenarios, an ACS integration example, and nine @@ -1381,7 +1381,7 @@ First packaged release. The v0.1 kernel is complete: every acceptance test in - **Receipts and events** — portable JSONL evidence for every action. - **CLI** — `init`, `demo`, `approve`, `deny`, `receipts`, `effects`, `resolve`. - **`ctrlrun demo`** — four failure scenarios, in process, no network. -- `SECURITY.md` and `docs/CLAIMS.md`, which maps every README claim to its code and test. +- `SECURITY.md` and `docs/docs/CLAIMS.md`, which maps every README claim to its code and test. ### Config-breaking rules @@ -1413,7 +1413,7 @@ until it gets one, which is the point. webhook provider in v0.2, and an id format cannot be widened after records exist. - Effect key templates do not escape placeholder values, so a crafted argument can make two distinct effects share one key. The result is a refusal rather than a double execution; - `docs/THREAT_MODEL.md` states the limit and the workaround. + `docs/docs/THREAT_MODEL.md` states the limit and the workaround. - Policy conditions address an action's arguments only. Scoping a rule by environment, resource or principal arrives with the authority model in v0.3. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86aa80f1..0f9d0513 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -103,7 +103,7 @@ mutants (say so in the table instead of claiming to have closed one). ## Every claim maps to a test -`docs/CLAIMS.md` maps every sentence in the README to the code that implements it and the +`docs/docs/CLAIMS.md` maps every sentence in the README to the code that implements it and the test that proves it. A sentence with no row is cut. A row whose test disappears takes its sentence with it in the same commit. A test resolves every `file.py:NNN` in the table against the line it cites and fails if the named symbol is not on it. diff --git a/MANIFEST.in b/MANIFEST.in index 7e5d7e88..242213b6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -17,7 +17,7 @@ recursive-include docs *.md # `tests/test_docs_audit.py` compares the two. `tools/docs_audit/` is the generator and the # three checks, imported by that test, so an sdist that ran its tests without them would be # the same failure as shipping tests without their data. -recursive-include docs *.yaml *.mdx *.txt *.json *.svg *.png *.js *.mjs +recursive-include docs *.yaml *.mdx *.txt *.json *.svg *.png *.js *.mjs *.jsx *.css *.cjs include docs/.mintignore recursive-include tools *.py *.txt # The README's header assets, and the sources they are rendered from. diff --git a/README.md b/README.md index 1acbb173..fd185830 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ Docs CI CodeQL - Tests - CTRLRun verified + Tests + CTRLRun verified OpenSSF Scorecard Ruff Checked with mypy --strict @@ -58,14 +58,14 @@ an `AMBIGUOUS` effect is refused — until a human, or a `reconcile` hook, says pip install ctrlrun && ctrlrun demo ``` -No Python to hand? [Break a protected action in your browser](https://ctrlrun.dev/try-it): +No Python to hand? [Break a protected action in your browser](https://ctrlrun.dev/docs/try-it): one refund under one policy on the released wheel, in the tab, with nothing sent anywhere. Approve €2,000, execute €5,000, lose a reply, retry — and read what refused you. The same boundary has five other faces: two workers running one `kubectl delete namespace`, an approval for `grant reader` spent on `grant admin`, a `delete_customer` nobody put in the policy, quarterly numbers mailed to a personal address, and a web page that talks the agent -into a refund. [Why](https://ctrlrun.dev/why) is the 700-word version. +into a refund. [Why](https://ctrlrun.dev/docs/why) is the 700-word version. ## What `ctrlrun demo` shows @@ -266,10 +266,10 @@ That is the whole integration: a policy file, a decorator, a context, and `with_ present a grant. Money is one more action with a rule — `amount_gte`/`amount_lte` in place of `role_in`, and both ends of every band bound, because an upper bound alone lets a negative amount through and a refund of a negative amount is a charge. -[Protect your first action](https://ctrlrun.dev/get-started/quickstart) is the -same walkthrough with every output explained · [Try it in your browser](https://ctrlrun.dev/try-it) · -[Policy YAML reference](https://ctrlrun.dev/reference/policy-yaml) · -[Cookbook](https://ctrlrun.dev/cookbook/index): refunds, deploys, IAM, deletions, email, MCP, +[Protect your first action](https://ctrlrun.dev/docs/get-started/quickstart) is the +same walkthrough with every output explained · [Try it in your browser](https://ctrlrun.dev/docs/try-it) · +[Policy YAML reference](https://ctrlrun.dev/docs/reference/policy-yaml) · +[Cookbook](https://ctrlrun.dev/docs/cookbook/index): refunds, deploys, IAM, deletions, email, MCP, each a recipe that runs. ## How it works @@ -334,7 +334,7 @@ actions: Everything but `tools/call` is relayed untouched, a lost response over the wire blocks the retry exactly as it does in process, and the gateway prints on the line that starts it every action in your policy with no `effect:` template — because a write with no effect key is the configuration -this exists to prevent. [`ctrlrun.dev/mcp/overview`](https://ctrlrun.dev/mcp/overview) is the +this exists to prevent. [`ctrlrun.dev/mcp/overview`](https://ctrlrun.dev/docs/mcp/overview) is the whole section. **An adapter** exists for one reason: to route an `approve` decision through **the framework's @@ -351,7 +351,7 @@ You build the `Control` with your policy, store, identity provider and authority hand it over: an adapter never constructs one and never supplies a principal. Adapters ship on their own version line, `adapters-langgraph-1.0` and never `0.6.1`, because an adapter breaks when its framework makes a breaking release, which is not a kernel event. -[`docs/adapters.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/adapters.md) has the three +[`docs/docs/adapters.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/adapters.md) has the three ways in and how to write one for a framework not listed here. ## The same shape in nine domains @@ -466,7 +466,7 @@ the two**. A `delegable` grant can be narrowed at runtime with `ctrlrun delegate widened — a delegation must be provably a subset of its parent on every dimension, at creation and again at every evaluation, and omitting a dimension the parent constrains is rejected rather than inherited — and `ctrlrun revoke` cuts a chain of any depth with one write. -[`docs/authority.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/authority.md) has it in +[`docs/docs/authority.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/authority.md) has it in plain language. **Roll it out with `mode: observe` first.** One top-level line runs every real decision against @@ -516,10 +516,10 @@ grants ends `6/6 declared guarantees pass. 5 not applicable: G3, G4, G5, G8, G9. The badge at the top of this page means the **declared guarantees pass**: every guarantee this configuration can exercise was exercised, and none failed. It does not mean secure, safe, -compliant, certified or audited, and [`docs/verify.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/verify.md#what-the-badge-means) +compliant, certified or audited, and [`docs/docs/verify.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/verify.md#what-the-badge-means) says on the same screen what verify cannot see: your executors, your `reconcile` hooks, where you put the decorator, your deployment, and whether your policy is the right policy. There is a -[GitHub Action](https://github.com/CTRLRun/ctrlrun/blob/main/docs/verify.md#in-ci): +[GitHub Action](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/verify.md#in-ci): ```yaml - uses: CTRLRun/ctrlrun@v0.6.1 @@ -547,8 +547,8 @@ The six guarantees, and which of the three ways in carries each: **It guarantees** what the matrix says, plus the mechanics behind it. Every line has a test in -[`docs/CLAIMS.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/CLAIMS.md), and -[`docs/how-this-is-built.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/how-this-is-built.md) +[`docs/docs/CLAIMS.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/CLAIMS.md), and +[`docs/docs/how-this-is-built.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/how-this-is-built.md) says how those tests came to exist: - **One effect, once, across hosts.** `BEGIN IMMEDIATE` on SQLite; on Postgres a unique index on @@ -575,7 +575,7 @@ says how those tests came to exist: - The receipt chain detects alteration, and alteration is not authorship. Receipts are not signed, the chain is no evidence of who wrote one, and it is not tamper-proof: it does not survive an administrator who can rewrite every row including the chain head, and erasing the - end of the log costs two statements. [`docs/THREAT_MODEL.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/THREAT_MODEL.md) states + end of the log costs two statements. [`docs/docs/THREAT_MODEL.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/THREAT_MODEL.md) states what remains open. - `ctrlrun verify` cannot see your executors. An executor that raises `NotExecuted` after the remote acted turns the one retryable exception into a licence to act twice, and nothing here @@ -586,7 +586,7 @@ says how those tests came to exist: human, and an approval bound to the recipient the human saw. Containment of the consequence, not detection of the cause. - It does not host models, plan, prompt, retrieve, route, remember or orchestrate, and it makes - no claim about any standard: [`docs/OWASP-AGENTIC-TOP10.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/OWASP-AGENTIC-TOP10.md) + no claim about any standard: [`docs/docs/OWASP-AGENTIC-TOP10.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/OWASP-AGENTIC-TOP10.md) is a reading of somebody else's taxonomy against the guarantees, and names the four entries it does not address. @@ -606,7 +606,7 @@ SQLite. Choose by how many machines write, not by how serious you are. - **4,404 tests**, every version specified before it was written and every requirement mutation-tested. - **11 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. -- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/production/soak). +- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). - **Each receipt carries the hash of the one before it**, so an alteration is detected and named. - **Apache-2.0**, and the enforcement kernel stays open source. Releases carry PyPI provenance attestations from GitHub Actions. @@ -631,7 +631,7 @@ effect, once* has to be re-earned. What 0.6 added to re-earn it: deletion from the middle or a reordering is detected and named by `seq`. It detects **alteration**, which is not authorship: receipts are not signed. -[ctrlrun.dev/production/index](https://ctrlrun.dev/production/index) is the whole section: +[ctrlrun.dev/production/index](https://ctrlrun.dev/docs/production/index) is the whole section: choosing a store, what reservation does under a lost `COMMIT`, migrations, recovery after a crash, the receipt chain, the soak, and what to watch once it is running. [`CHANGELOG.md`](https://github.com/CTRLRun/ctrlrun/blob/main/CHANGELOG.md) has the entry, @@ -645,19 +645,19 @@ cookbook, the reference, and a browser demo that runs `ctrlrun demo` with no ins | Section | Where | |---|---| -| Start here | [Why](https://ctrlrun.dev/why) · [Protect your first action](https://ctrlrun.dev/get-started/quickstart) · [Try it in your browser](https://ctrlrun.dev/try-it) | -| The ideas | [Concepts](https://ctrlrun.dev/concepts/outcomes-and-ambiguous) | -| Doing something | [Guides](https://ctrlrun.dev/guides/protect-a-function) · [Cookbook](https://ctrlrun.dev/cookbook/index) | -| Running it for real | [Production](https://ctrlrun.dev/production/index) · [Postgres](https://ctrlrun.dev/production/postgres) · [Recovery](https://ctrlrun.dev/production/recovery) · [Operations](https://ctrlrun.dev/production/operations) | -| MCP | [Overview](https://ctrlrun.dev/mcp/overview) · [The gateway in five minutes](https://ctrlrun.dev/mcp/gateway-in-5-minutes) | -| Every key, flag and error | [Reference](https://ctrlrun.dev/reference/policy-yaml) | -| Compared with other things | [Compare](https://ctrlrun.dev/compare/idempotency-keys) · [FAQ](https://ctrlrun.dev/faq) | -| Security | [Threat model](https://ctrlrun.dev/THREAT_MODEL) · [What verify guarantees](https://ctrlrun.dev/security/verify-guarantees) · [SECURITY.md](https://github.com/CTRLRun/ctrlrun/blob/main/SECURITY.md) | -| How this is built, and what is not done | [How this is built](https://ctrlrun.dev/how-this-is-built) | -| Every sentence above, mapped to the code and the test that proves it | [`docs/CLAIMS.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/CLAIMS.md) | +| Start here | [Why](https://ctrlrun.dev/docs/why) · [Protect your first action](https://ctrlrun.dev/docs/get-started/quickstart) · [Try it in your browser](https://ctrlrun.dev/docs/try-it) | +| The ideas | [Concepts](https://ctrlrun.dev/docs/concepts/outcomes-and-ambiguous) | +| Doing something | [Guides](https://ctrlrun.dev/docs/guides/protect-a-function) · [Cookbook](https://ctrlrun.dev/docs/cookbook/index) | +| Running it for real | [Production](https://ctrlrun.dev/docs/production/index) · [Postgres](https://ctrlrun.dev/docs/production/postgres) · [Recovery](https://ctrlrun.dev/docs/production/recovery) · [Operations](https://ctrlrun.dev/docs/production/operations) | +| MCP | [Overview](https://ctrlrun.dev/docs/mcp/overview) · [The gateway in five minutes](https://ctrlrun.dev/docs/mcp/gateway-in-5-minutes) | +| Every key, flag and error | [Reference](https://ctrlrun.dev/docs/reference/policy-yaml) | +| Compared with other things | [Compare](https://ctrlrun.dev/docs/compare/idempotency-keys) · [FAQ](https://ctrlrun.dev/docs/faq) | +| Security | [Threat model](https://ctrlrun.dev/docs/THREAT_MODEL) · [What verify guarantees](https://ctrlrun.dev/docs/security/verify-guarantees) · [SECURITY.md](https://github.com/CTRLRun/ctrlrun/blob/main/SECURITY.md) | +| How this is built, and what is not done | [How this is built](https://ctrlrun.dev/docs/how-this-is-built) | +| Every sentence above, mapped to the code and the test that proves it | [`docs/docs/CLAIMS.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/CLAIMS.md) | | The contract, per version | [`docs/SPEC-v0.1.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/SPEC-v0.1.md) · [v0.2](https://github.com/CTRLRun/ctrlrun/blob/main/docs/SPEC-v0.2.md) · [v0.3](https://github.com/CTRLRun/ctrlrun/blob/main/docs/SPEC-v0.3.md) · [v0.4](https://github.com/CTRLRun/ctrlrun/blob/main/docs/SPEC-v0.4.md) · [v0.5](https://github.com/CTRLRun/ctrlrun/blob/main/docs/SPEC-v0.5.md) · [v0.6](https://github.com/CTRLRun/ctrlrun/blob/main/docs/SPEC-v0.6.md) | | Contributing | [`CONTRIBUTING.md`](https://github.com/CTRLRun/ctrlrun/blob/main/CONTRIBUTING.md), [`CODE_OF_CONDUCT.md`](https://github.com/CTRLRun/ctrlrun/blob/main/CODE_OF_CONDUCT.md) | -| Changelog and roadmap | [`CHANGELOG.md`](https://github.com/CTRLRun/ctrlrun/blob/main/CHANGELOG.md), [`docs/ROADMAP.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/ROADMAP.md) | +| Changelog and roadmap | [`CHANGELOG.md`](https://github.com/CTRLRun/ctrlrun/blob/main/CHANGELOG.md), [`docs/docs/ROADMAP.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/ROADMAP.md) | ## License diff --git a/SECURITY.md b/SECURITY.md index 457473c7..9e2460ec 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,7 +16,7 @@ warranted you will be credited in the release notes unless you ask not to be. Releases carry PyPI provenance attestations from GitHub Actions. Distributions are published through trusted publishing, so there is no API token to leak or replay, and each wheel and sdist carries an attestation naming the workflow that built it. Every GitHub Action the workflows use -is pinned to a commit. `docs/how-this-is-built.md` says what has and has not been reviewed. +is pinned to a commit. `docs/docs/how-this-is-built.md` says what has and has not been reviewed. From v0.6.0, every GitHub Release also carries signed SLSA build provenance for the same distributions — `ctrlrun-.intoto.jsonl` (the DSSE envelopes) and @@ -64,7 +64,7 @@ these as a security issue, not a bug: - An unknown execution outcome is recorded as `failed` rather than `ambiguous`. - A receipt does not reflect what happened. -`docs/THREAT_MODEL.md` states what is deliberately out of scope — a compromised host, a +`docs/docs/THREAT_MODEL.md` states what is deliberately out of scope — a compromised host, a malicious administrator with write access to the state database, a lying external service, or code that bypasses the decorator entirely. Those are documented limits rather than vulnerabilities, but if you think one is stated too generously, say so. diff --git a/VISION.md b/VISION.md index bf2a9494..abd26681 100644 --- a/VISION.md +++ b/VISION.md @@ -1,6 +1,6 @@ # VISION.md -> **This is not a build spec, and it is not the state of the code.** It was written before v0.1 shipped, so that the long-term shape is written down once and stops leaking into READMEs, schemas, and PRs. What has actually shipped is in [`docs/ROADMAP.md`](docs/ROADMAP.md) and in each version's `docs/SPEC-v0.x.md`; where a section below has since been built, a *Status* line says which version built it and where it differs from the sketch. Everything without one is still a sketch, and nothing here is a commitment. Do not derive tasks from this file. +> **This is not a build spec, and it is not the state of the code.** It was written before v0.1 shipped, so that the long-term shape is written down once and stops leaking into READMEs, schemas, and PRs. What has actually shipped is in [`docs/docs/ROADMAP.md`](docs/docs/ROADMAP.md) and in each version's `docs/SPEC-v0.x.md`; where a section below has since been built, a *Status* line says which version built it and where it differs from the sketch. Everything without one is still a sketch, and nothing here is a commitment. Do not derive tasks from this file. --- @@ -106,7 +106,7 @@ Delegation attenuates, never amplifies: `child ⊆ parent`. Human €100k → fi **Verify** — `ctrlrun verify` runs deterministic adversarial scenarios against a real configuration and reports per-guarantee pass/fail with counterexamples. Badge means "declared guarantees pass", never "secure". -*Status: built in v0.4 (`docs/SPEC-v0.4.md`, `docs/verify.md`). One thing the sketch did not have: a guarantee the configuration cannot exercise reports `not_applicable` with a reason, and not applicable is not a pass.* +*Status: built in v0.4 (`docs/SPEC-v0.4.md`, `docs/docs/verify.md`). One thing the sketch did not have: a guarantee the configuration cannot exercise reports `not_applicable` with a reason, and not applicable is not a pass.* ## 6. Standards posture diff --git a/action.yml b/action.yml index 2803c6b2..006b2f19 100644 --- a/action.yml +++ b/action.yml @@ -49,7 +49,7 @@ inputs: description: >- Where to write the Shields endpoint JSON. The action writes it and never publishes it: publishing is your decision, in your own workflow, with whatever permissions you choose - (SPEC-v0.4 §5.2, `docs/verify.md`). + (SPEC-v0.4 §5.2, `docs/docs/verify.md`). required: false default: verify-badge.json upload-artifact: diff --git a/docs/.mintignore b/docs/.mintignore index eb0f06ad..a5ec7143 100644 --- a/docs/.mintignore +++ b/docs/.mintignore @@ -11,3 +11,7 @@ assets/ # words written for somebody building against the kernel, and they stay public on GitHub. # docs/architecture/specifications.mdx links to each one. SPEC-v*.md + +# Implementation specifications remain linked on GitHub, outside the reader-facing site. +SPEC-mcp-operator.md +SPEC-scan.md diff --git a/docs/IA.md b/docs/IA.md index dbf9ed48..32ba4b01 100644 --- a/docs/IA.md +++ b/docs/IA.md @@ -20,7 +20,7 @@ The rules every page is held to are in `docs/STYLE.md`. The tools that hold them ## Conventions -- Paths are root-relative on the site: `concepts/effect-keys` is `docs/concepts/effect-keys.mdx`. +- Paths are root-relative on the site: `docs/concepts/effect-keys` is `docs/docs/concepts/effect-keys.mdx`. - **Purpose** is the page's first-paragraph answer, compressed. **Query** is the search a stranger types, and the first sentence of the page is written to answer it. - The existing Markdown documents that stay where they are — the specifications, @@ -113,18 +113,18 @@ Get the badge verify/get-the-badge | Path | Purpose | Query | |---|---|---| -| `index` | The tagline, the principle, the promise; the demo; the capability grid rendered from `capabilities.yaml`; the three ways in; three start-here cards; the one line that adds this site as an MCP server to a coding tool. | *ctrlrun* · *AI agent safety layer* | -| `why` | Opens with the opener line. Five sections, one principle each: FAILED is not UNKNOWN · an approval is bound to what the human saw · autonomy belongs to the action · unknown means no · evidence leaves the building. Ends at How this is built. | *why do AI agents double execute actions* · *AI agent consequential actions* | -| `not-only-agents` | For the reader who runs a task queue, a webhook handler or a cron job rather than an agent: the same failure with no model in it, the three examples under `examples/without-an-agent/`, what an agent actually changes, and the two guarantees that do the work when nobody is delegating authority to a worker. | *celery task retried twice* · *webhook delivered twice duplicate* · *retry safe background job python* | +| `docs` | The tagline, the principle, the promise; the demo; the capability grid rendered from `capabilities.yaml`; the three ways in; three start-here cards; the one line that adds this site as an MCP server to a coding tool. | *ctrlrun* · *AI agent safety layer* | +| `docs/why` | Opens with the opener line. Five sections, one principle each: FAILED is not UNKNOWN · an approval is bound to what the human saw · autonomy belongs to the action · unknown means no · evidence leaves the building. Ends at How this is built. | *why do AI agents double execute actions* · *AI agent consequential actions* | +| `docs/not-only-agents` | For the reader who runs a task queue, a webhook handler or a cron job rather than an agent: the same failure with no model in it, the three examples under `examples/without-an-agent/`, what an agent actually changes, and the two guarantees that do the work when nobody is delegating authority to a worker. | *celery task retried twice* · *webhook delivered twice duplicate* · *retry safe background job python* | ## Get started | Path | Purpose | Query | |---|---|---| -| `get-started/install` | `pip install ctrlrun`; what it installs (pyyaml, click, nothing else); the extras and what each adds; Python 3.11+. | *install ctrlrun* | -| `get-started/quickstart` | Protect one function end to end in sixty seconds, with the real output: a policy, a decorator, a refused mutation, a receipt. | *ctrlrun quickstart* · *protect an AI agent action python* | -| `get-started/three-ways-in` | Decorator, gateway, adapter: what each covers and what each needs. The negative sentence: most readers need the decorator and should not look for an adapter. | *ctrlrun langgraph* · *ctrlrun mcp* · *do I need an adapter* | -| `get-started/choosing` | The decision table: in-process Python → decorator; tools behind MCP → gateway; a framework with its own approval UI → adapter. What you do not need for the single-host case: a server, a database, a dashboard. | *ctrlrun decorator vs gateway* | +| `docs/get-started/install` | `pip install ctrlrun`; what it installs (pyyaml, click, nothing else); the extras and what each adds; Python 3.11+. | *install ctrlrun* | +| `docs/get-started/quickstart` | Protect one function end to end in sixty seconds, with the real output: a policy, a decorator, a refused mutation, a receipt. | *ctrlrun quickstart* · *protect an AI agent action python* | +| `docs/get-started/three-ways-in` | Decorator, gateway, adapter: what each covers and what each needs. The negative sentence: most readers need the decorator and should not look for an adapter. | *ctrlrun langgraph* · *ctrlrun mcp* · *do I need an adapter* | +| `docs/get-started/choosing` | The decision table: in-process Python → decorator; tools behind MCP → gateway; a framework with its own approval UI → adapter. What you do not need for the single-host case: a server, a database, a dashboard. | *ctrlrun decorator vs gateway* | ## Production @@ -137,21 +137,21 @@ it before they ask what an effect key is. on one host; Postgres is for many hosts. Written in that order, because a reader with one host must not be told they are not really in production. -`production/soak` is a **render** of `research/soak/results/*.json` and is never hand-edited, on -`study/does-your-framework-double-execute`'s precedent: the duration on the page is the measured +`docs/production/soak` is a **render** of `research/soak/results/*.json` and is never hand-edited, on +`docs/study/does-your-framework-double-execute`'s precedent: the duration on the page is the measured one, and the page says in the same paragraph that the roadmap's exit criterion is not met by it. | Path | Purpose | Query | |---|---|---| -| `production/index` | The section's front door: which store and why, what this section answers, the generated readiness block with its **Not yet** list, and the four things the store holds. | *is ctrlrun production ready* | -| `production/postgres` | The choice, in one table: SQLite until a second host writes, Postgres after. What changes (a URL and a schema) and what does not (everything else). | *ctrlrun sqlite vs postgres* | -| `production/how-reservation-works` | One winner per effect key, and the two rows nobody merges: an exception before `COMMIT` is a failed write to retry; one during it is unknown and is re-read. | *lost commit ambiguous* · *exactly once database* | -| `production/migrations` | Five shapes, three of them a refusal; nothing half-applies; the backward direction that corrupts; what a rolling deploy can and cannot do. | *ctrlrun schema migration* | -| `production/recovery` | A restarted process repairs nothing and cannot know the holder is dead. Nothing sweeps; an expired lease is a refusal, not a reclaim. | *agent crashed mid action* | -| `production/receipt-integrity` | The runbook: run `--verify-chain`, read the six names, know what each means. `security/receipt-chain` keeps *what it proves*; this page is *what to do*. | *verify receipt chain* | -| `production/soak` | Generated. One published run: how long, how many actions, how many unattributed ambiguous outcomes, and what it is not evidence of. | *ctrlrun soak test* | -| `production/operations` | What to watch, what to page on, what to back up, and the fact that there is nothing to run. | *ctrlrun monitoring* | -| `postgres` | The operational reference that already existed: connection strings, grants, pooling, failover, the throughput ceiling. Moved here from Architecture, because this is where a reader looks for it. | *ctrlrun postgres connection* | +| `docs/production/index` | The section's front door: which store and why, what this section answers, the generated readiness block with its **Not yet** list, and the four things the store holds. | *is ctrlrun production ready* | +| `docs/production/postgres` | The choice, in one table: SQLite until a second host writes, Postgres after. What changes (a URL and a schema) and what does not (everything else). | *ctrlrun sqlite vs postgres* | +| `docs/production/how-reservation-works` | One winner per effect key, and the two rows nobody merges: an exception before `COMMIT` is a failed write to retry; one during it is unknown and is re-read. | *lost commit ambiguous* · *exactly once database* | +| `docs/production/migrations` | Five shapes, three of them a refusal; nothing half-applies; the backward direction that corrupts; what a rolling deploy can and cannot do. | *ctrlrun schema migration* | +| `docs/production/recovery` | A restarted process repairs nothing and cannot know the holder is dead. Nothing sweeps; an expired lease is a refusal, not a reclaim. | *agent crashed mid action* | +| `docs/production/receipt-integrity` | The runbook: run `--verify-chain`, read the six names, know what each means. `docs/security/receipt-chain` keeps *what it proves*; this page is *what to do*. | *verify receipt chain* | +| `docs/production/soak` | Generated. One published run: how long, how many actions, how many unattributed ambiguous outcomes, and what it is not evidence of. | *ctrlrun soak test* | +| `docs/production/operations` | What to watch, what to page on, what to back up, and the fact that there is nothing to run. | *ctrlrun monitoring* | +| `docs/postgres` | The operational reference that already existed: connection strings, grants, pooling, failover, the throughput ceiling. Moved here from Architecture, because this is where a reader looks for it. | *ctrlrun postgres connection* | ## MCP @@ -160,28 +160,28 @@ row landed with `docs/SPEC-mcp-operator.md`, which is when it stopped being plan | Path | Purpose | Query | |---|---|---| -| `mcp/overview` | CTRLRun works with MCP in four ways: enforcement (the gateway in front of any MCP server), answering (the operator server, for approvers), learning (this site is an MCP server), discovery (the registries, once listed). | *MCP gateway* · *MCP server human approval* | -| `mcp/gateway-in-5-minutes` | For a reader who already runs an MCP server: before/after, the two commands, what the agent sees on deny and on approval-required, the supported revisions, the principal-flag choice and its security note. | *protect MCP server* · *MCP tool call approval gateway* | -| `mcp/approve-from-your-assistant` | For the person who answers approvals rather than the one who deploys: what `ctrlrun mcp-operator` is, the two flags, a client configuration, a real transcript ending in the receipt that names the approver, and the five things it will not do. | *approve MCP tool call from an assistant* · *MCP human approval server* | -| `mcp/use-the-docs-from-your-editor` | The exact configuration for this site's MCP server, three questions an assistant can then answer, a screenshot spec. | *ctrlrun mcp docs* | +| `docs/mcp/overview` | CTRLRun works with MCP in four ways: enforcement (the gateway in front of any MCP server), answering (the operator server, for approvers), learning (this site is an MCP server), discovery (the registries, once listed). | *MCP gateway* · *MCP server human approval* | +| `docs/mcp/gateway-in-5-minutes` | For a reader who already runs an MCP server: before/after, the two commands, what the agent sees on deny and on approval-required, the supported revisions, the principal-flag choice and its security note. | *protect MCP server* · *MCP tool call approval gateway* | +| `docs/mcp/approve-from-your-assistant` | For the person who answers approvals rather than the one who deploys: what `ctrlrun mcp-operator` is, the two flags, a client configuration, a real transcript ending in the receipt that names the approver, and the five things it will not do. | *approve MCP tool call from an assistant* · *MCP human approval server* | +| `docs/mcp/use-the-docs-from-your-editor` | The exact configuration for this site's MCP server, three questions an assistant can then answer, a screenshot spec. | *ctrlrun mcp docs* | ## Concepts Each Concepts page opens with one definitional sentence an assistant can quote standalone, has one diagram or code block, names the guarantee it supports and what it does not do, and -ends with Next links. Every claim on a Concepts page has a `docs/CLAIMS.md` row. +ends with Next links. Every claim on a Concepts page has a `docs/docs/CLAIMS.md` row. | Path | Purpose | Query | |---|---|---| -| `concepts/action-and-hash` | An action is a named operation with canonical arguments, and its hash is what everything binds to: sorted keys, no whitespace, no floats. | *ctrlrun action hash* · *canonical action hash AI agent* | -| `concepts/decisions` | Three decisions — allow, approve, deny — decided per action by a policy that cannot see who is asking. First matching rule wins; unknown is denied. | *AI agent action policy allow approve deny* | -| `concepts/approval-binding` | An approval is bound to the exact action hash a human saw, is single-use, expires, and is consumed atomically with the reservation. A mutated action is refused. | *human-in-the-loop approval bound to action* · *approval mutation AI agent* | -| `concepts/effect-keys` | An effect key names the real-world consequence, so the same intent from a retry, a second agent or a second host is one effect. This is the page that says *idempotency* once. | *idempotency key AI agent* · *prevent duplicate execution agent tool call* | -| `concepts/outcomes-and-ambiguous` | Three outcomes — COMMITTED, FAILED, AMBIGUOUS — and why a timeout is not a failure. Only `NotExecuted` means failed; everything else after the first byte is unknown, and unknown blocks a blind retry. The page that explains the product. | *what happens when an agent tool call times out* · *double execution AI agent retry* | -| `concepts/receipts-and-evidence` | Every executed action leaves a portable JSON receipt: who, what, decision, approval, effect key, outcome, and the policy hash that decided it. Chained, not signed. | *AI agent audit trail receipts* | -| `concepts/authority-and-delegation` | Authority is the second axis: opt-in, then fail-closed. A grant says who may ask; the policy says how much autonomy the action has. Delegation only narrows, at creation and at every evaluation. Identity is consumed, never issued. | *AI agent authorization delegation* · *least privilege AI agents* | -| `concepts/observe-mode` | One line runs every real decision against real traffic and records what enforcement would have blocked, without blocking. It executes; it is not a dry run. | *AI agent policy shadow mode* | -| `concepts/fail-closed` | Unknown action, missing policy, malformed policy, missing principal, missing or mismatched approval, inconsistent state: all deny. No flag makes a consequential action permissive by default. | *fail closed AI agent* | +| `docs/concepts/action-and-hash` | An action is a named operation with canonical arguments, and its hash is what everything binds to: sorted keys, no whitespace, no floats. | *ctrlrun action hash* · *canonical action hash AI agent* | +| `docs/concepts/decisions` | Three decisions — allow, approve, deny — decided per action by a policy that cannot see who is asking. First matching rule wins; unknown is denied. | *AI agent action policy allow approve deny* | +| `docs/concepts/approval-binding` | An approval is bound to the exact action hash a human saw, is single-use, expires, and is consumed atomically with the reservation. A mutated action is refused. | *human-in-the-loop approval bound to action* · *approval mutation AI agent* | +| `docs/concepts/effect-keys` | An effect key names the real-world consequence, so the same intent from a retry, a second agent or a second host is one effect. This is the page that says *idempotency* once. | *idempotency key AI agent* · *prevent duplicate execution agent tool call* | +| `docs/concepts/outcomes-and-ambiguous` | Three outcomes — COMMITTED, FAILED, AMBIGUOUS — and why a timeout is not a failure. Only `NotExecuted` means failed; everything else after the first byte is unknown, and unknown blocks a blind retry. The page that explains the product. | *what happens when an agent tool call times out* · *double execution AI agent retry* | +| `docs/concepts/receipts-and-evidence` | Every executed action leaves a portable JSON receipt: who, what, decision, approval, effect key, outcome, and the policy hash that decided it. Chained, not signed. | *AI agent audit trail receipts* | +| `docs/concepts/authority-and-delegation` | Authority is the second axis: opt-in, then fail-closed. A grant says who may ask; the policy says how much autonomy the action has. Delegation only narrows, at creation and at every evaluation. Identity is consumed, never issued. | *AI agent authorization delegation* · *least privilege AI agents* | +| `docs/concepts/observe-mode` | One line runs every real decision against real traffic and records what enforcement would have blocked, without blocking. It executes; it is not a dry run. | *AI agent policy shadow mode* | +| `docs/concepts/fail-closed` | Unknown action, missing policy, malformed policy, missing principal, missing or mismatched approval, inconsistent state: all deny. No flag makes a consequential action permissive by default. | *fail closed AI agent* | ## Guides @@ -192,17 +192,17 @@ turn readers into users. | Path | Purpose | Query | |---|---|---| -| `guides/protect-a-function` | Decorate a function, declare an effect key, write the policy, see a refund refused and a Kubernetes delete sent for approval. | *protect python function AI agent approval* | -| `guides/gateway-in-front-of-mcp` | Two commands put every guarantee in front of an existing MCP server with no agent changes: the alias, the principal, the effect templates, the banner that names writes with no effect key. | *MCP gateway human approval* · *MCP server tool call approval* | -| `guides/approvals-in-slack` | `WebhookApprovalProvider`: the request goes to a webhook, a human answers in Slack, the answer comes back through the same grant calls the CLI uses. | *slack approval AI agent actions* | -| `guides/resolve-an-ambiguous-effect` | Reading `ctrlrun effects --state ambiguous`, asking the remote, `ctrlrun resolve --committed` or `--failed`, and what the receipt then says, including who resolved it. | *ctrlrun resolve ambiguous* | -| `guides/reconcile-automatically` | `@protect(reconcile=...)`: a hook that asks the remote what happened, and the only thing besides a human that moves a record out of AMBIGUOUS. | *reconcile AI agent action stripe kubernetes* | -| `guides/observe-to-enforce` | `mode: observe` for a week, `ctrlrun stats` to read the cost of enforcement, then `mode: enforce`. | *roll out AI agent policy without breaking production* | -| `guides/run-on-postgres` | `pip install "ctrlrun[postgres]"`, the connection string, what to grant, migrations at open, the lost-COMMIT case. | *ctrlrun postgres* | -| `guides/verify-in-ci` | The GitHub Action, the two shapes of report, the N/A line and what it means, the badge. | *verify AI agent safety configuration CI* | -| `guides/export-to-opentelemetry` | `OTelEventSink`: one span per action, one event per step, argument values opt-in. | *opentelemetry AI agent actions* | -| `guides/langgraph-adapter` | Route an approval through `interrupt()`: the operator builds the `Control`, `wait=True`, `Command(resume=...)`, and prevention versus attribution. | *langgraph interrupt human approval tool call* | -| `guides/openai-agents-adapter` | Route an approval through the SDK's tool-approval interruption: `protected_tool`, `gate.run`, why a rejection leaves no CTRLRun evidence. | *openai agents sdk tool approval* | +| `docs/guides/protect-a-function` | Decorate a function, declare an effect key, write the policy, see a refund refused and a Kubernetes delete sent for approval. | *protect python function AI agent approval* | +| `docs/guides/gateway-in-front-of-mcp` | Two commands put every guarantee in front of an existing MCP server with no agent changes: the alias, the principal, the effect templates, the banner that names writes with no effect key. | *MCP gateway human approval* · *MCP server tool call approval* | +| `docs/guides/approvals-in-slack` | `WebhookApprovalProvider`: the request goes to a webhook, a human answers in Slack, the answer comes back through the same grant calls the CLI uses. | *slack approval AI agent actions* | +| `docs/guides/resolve-an-ambiguous-effect` | Reading `ctrlrun effects --state ambiguous`, asking the remote, `ctrlrun resolve --committed` or `--failed`, and what the receipt then says, including who resolved it. | *ctrlrun resolve ambiguous* | +| `docs/guides/reconcile-automatically` | `@protect(reconcile=...)`: a hook that asks the remote what happened, and the only thing besides a human that moves a record out of AMBIGUOUS. | *reconcile AI agent action stripe kubernetes* | +| `docs/guides/observe-to-enforce` | `mode: observe` for a week, `ctrlrun stats` to read the cost of enforcement, then `mode: enforce`. | *roll out AI agent policy without breaking production* | +| `docs/guides/run-on-postgres` | `pip install "ctrlrun[postgres]"`, the connection string, what to grant, migrations at open, the lost-COMMIT case. | *ctrlrun postgres* | +| `docs/guides/verify-in-ci` | The GitHub Action, the two shapes of report, the N/A line and what it means, the badge. | *verify AI agent safety configuration CI* | +| `docs/guides/export-to-opentelemetry` | `OTelEventSink`: one span per action, one event per step, argument values opt-in. | *opentelemetry AI agent actions* | +| `docs/guides/langgraph-adapter` | Route an approval through `interrupt()`: the operator builds the `Control`, `wait=True`, `Command(resume=...)`, and prevention versus attribution. | *langgraph interrupt human approval tool call* | +| `docs/guides/openai-agents-adapter` | Route an approval through the SDK's tool-approval interruption: `protected_tool`, `gate.run`, why a rejection leaves no CTRLRun evidence. | *openai agents sdk tool approval* | ## Cookbook (session 4) @@ -214,27 +214,27 @@ non-zero. | Path | Situation | |---|---| -| `cookbook/refund-agent` | money — a refund agent with amount tiers | -| `cookbook/payout-maker-checker` | money — a payout agent with maker/checker via delegation | -| `cookbook/deploy-agent` | infrastructure — restart auto, apply-prod approve, delete-namespace deny | -| `cookbook/database-migration-agent` | infrastructure — a migration agent | -| `cookbook/iam-agent` | permissions — grant read, never admin | -| `cookbook/credential-rotation-agent` | permissions — rotate, with approval to revoke | -| `cookbook/crm-update-agent` | records — update a record, approve a merge | -| `cookbook/data-deletion-agent` | records — deletion under a retention rule | -| `cookbook/outbound-email-agent` | communications — external recipients need approval | -| `cookbook/customer-notification-agent` | communications — batch notifications, one effect each | -| `cookbook/manager-and-worker` | multi-agent — a manager delegates bounded authority to a worker | -| `cookbook/protect-an-mcp-server` | integrations — an existing MCP server in five minutes | -| `cookbook/langgraph-interrupt` | integrations — LangGraph with `interrupt()` | -| `cookbook/openai-agents-tool-approval` | integrations — the OpenAI Agents SDK's tool approval | -| `cookbook/slack-approvals` | integrations — approvals in Slack via webhook | -| `cookbook/receipts-to-opentelemetry` | integrations — receipts into a tracing backend | -| `cookbook/observe-then-enforce` | operations — observe for a week, then enforce | -| `cookbook/resolve-an-ambiguous-effect` | operations — a human resolves an unknown outcome | -| `cookbook/reconcile-against-the-remote` | operations — reconcile against Stripe or Kubernetes automatically | -| `cookbook/verify-in-github-actions` | operations — the composite action in a workflow | -| `cookbook/sqlite-to-postgres` | operations — move the store | +| `docs/cookbook/refund-agent` | money — a refund agent with amount tiers | +| `docs/cookbook/payout-maker-checker` | money — a payout agent with maker/checker via delegation | +| `docs/cookbook/deploy-agent` | infrastructure — restart auto, apply-prod approve, delete-namespace deny | +| `docs/cookbook/database-migration-agent` | infrastructure — a migration agent | +| `docs/cookbook/iam-agent` | permissions — grant read, never admin | +| `docs/cookbook/credential-rotation-agent` | permissions — rotate, with approval to revoke | +| `docs/cookbook/crm-update-agent` | records — update a record, approve a merge | +| `docs/cookbook/data-deletion-agent` | records — deletion under a retention rule | +| `docs/cookbook/outbound-email-agent` | communications — external recipients need approval | +| `docs/cookbook/customer-notification-agent` | communications — batch notifications, one effect each | +| `docs/cookbook/manager-and-worker` | multi-agent — a manager delegates bounded authority to a worker | +| `docs/cookbook/protect-an-mcp-server` | integrations — an existing MCP server in five minutes | +| `docs/cookbook/langgraph-interrupt` | integrations — LangGraph with `interrupt()` | +| `docs/cookbook/openai-agents-tool-approval` | integrations — the OpenAI Agents SDK's tool approval | +| `docs/cookbook/slack-approvals` | integrations — approvals in Slack via webhook | +| `docs/cookbook/receipts-to-opentelemetry` | integrations — receipts into a tracing backend | +| `docs/cookbook/observe-then-enforce` | operations — observe for a week, then enforce | +| `docs/cookbook/resolve-an-ambiguous-effect` | operations — a human resolves an unknown outcome | +| `docs/cookbook/reconcile-against-the-remote` | operations — reconcile against Stripe or Kubernetes automatically | +| `docs/cookbook/verify-in-github-actions` | operations — the composite action in a workflow | +| `docs/cookbook/sqlite-to-postgres` | operations — move the store | ## Reference @@ -243,13 +243,13 @@ CI where it cannot. | Path | Purpose | Query | |---|---|---| -| `reference/policy-yaml` | Every key of `ctrlrun.policy/v1`–`v4`: type, default, example, and the fail-closed behaviour when omitted. `controls:` and `data:` documented as registry primitives; the word *pack* does not appear. | *ctrlrun.yaml reference* · *ctrlrun policy schema* | -| `reference/authority-yaml` | Every key of `authority:`: grants, subjects, constraints, `delegable`, `expires_at`, `max_delegation_depth`, and the omission rule. | *ctrlrun authority grants yaml* | -| `reference/cli` | Every command and flag, generated from click's help; a test asserts the page matches `--help`. | *ctrlrun cli* · *ctrlrun resolve* | -| `reference/api/index` | Every frozen public name from SPEC §8 and §11 across versions, generated from docstrings into one page per name. | *ctrlrun Control* · *ctrlrun protect decorator* | -| `reference/receipt-and-event-schemas` | `ctrlrun.receipt/v3` and the event types, field by field, from the code. | *ctrlrun receipt json schema* | -| `reference/exit-codes` | Every CLI exit code and what it means, from the code. | *ctrlrun verify exit code* | -| `reference/errors` | The closed error hierarchy: `ActionDenied`, `ApprovalRequired`, `ApprovalMismatch`, `DuplicateEffect`, `AmbiguousEffect`, `NotExecuted` and the rest, with when each is raised. | *ctrlrun ApprovalMismatch* · *ctrlrun AmbiguousEffect* | +| `docs/reference/policy-yaml` | Every key of `ctrlrun.policy/v1`–`v4`: type, default, example, and the fail-closed behaviour when omitted. `controls:` and `data:` documented as registry primitives; the word *pack* does not appear. | *ctrlrun.yaml reference* · *ctrlrun policy schema* | +| `docs/reference/authority-yaml` | Every key of `authority:`: grants, subjects, constraints, `delegable`, `expires_at`, `max_delegation_depth`, and the omission rule. | *ctrlrun authority grants yaml* | +| `docs/reference/cli` | Every command and flag, generated from click's help; a test asserts the page matches `--help`. | *ctrlrun cli* · *ctrlrun resolve* | +| `docs/reference/api/index` | Every frozen public name from SPEC §8 and §11 across versions, generated from docstrings into one page per name. | *ctrlrun Control* · *ctrlrun protect decorator* | +| `docs/reference/receipt-and-event-schemas` | `ctrlrun.receipt/v3` and the event types, field by field, from the code. | *ctrlrun receipt json schema* | +| `docs/reference/exit-codes` | Every CLI exit code and what it means, from the code. | *ctrlrun verify exit code* | +| `docs/reference/errors` | The closed error hierarchy: `ActionDenied`, `ApprovalRequired`, `ApprovalMismatch`, `DuplicateEffect`, `AmbiguousEffect`, `NotExecuted` and the rest, with when each is raised. | *ctrlrun ApprovalMismatch* · *ctrlrun AmbiguousEffect* | ## Compare @@ -258,15 +258,15 @@ short table. No vendor name in a heading. | Path | Purpose | Query | |---|---|---| -| `compare/framework-hitl` | A framework's interrupt lets a human say yes; it does not bind the yes to the arguments that execute, refuse a retry after a lost response, or leave a receipt. Use both: the adapter routes through the interrupt. | *langgraph human in the loop vs* · *agent framework approval limitations* | -| `compare/guardrail-libraries` | Guardrails inspect inputs and outputs; CTRLRun sits at the boundary between intention and effect. Different layer; use both. | *AI guardrails vs execution control* | -| `compare/governance-toolkits` | Governance toolkits catalogue, monitor and report; CTRLRun refuses, in the execution path, per action. | *AI agent governance vs runtime enforcement* | -| `compare/durable-workflows` | Durable workflow engines retry until success and make that safe with idempotent activities; CTRLRun refuses to retry an unknown outcome and binds approvals. Complementary. | *temporal vs ctrlrun* · *durable execution AI agents idempotency* | -| `compare/idempotency-keys` | An idempotency key deduplicates at one remote that supports it; an effect key deduplicates at the agent side across remotes, refuses on unknown, and is bound to an approval. The page that says *idempotency* precisely. | *idempotency keys AI agents* · *stripe idempotency key vs* | +| `docs/compare/framework-hitl` | A framework's interrupt lets a human say yes; it does not bind the yes to the arguments that execute, refuse a retry after a lost response, or leave a receipt. Use both: the adapter routes through the interrupt. | *langgraph human in the loop vs* · *agent framework approval limitations* | +| `docs/compare/guardrail-libraries` | Guardrails inspect inputs and outputs; CTRLRun sits at the boundary between intention and effect. Different layer; use both. | *AI guardrails vs execution control* | +| `docs/compare/governance-toolkits` | Governance toolkits catalogue, monitor and report; CTRLRun refuses, in the execution path, per action. | *AI agent governance vs runtime enforcement* | +| `docs/compare/durable-workflows` | Durable workflow engines retry until success and make that safe with idempotent activities; CTRLRun refuses to retry an unknown outcome and binds approvals. Complementary. | *temporal vs ctrlrun* · *durable execution AI agents idempotency* | +| `docs/compare/idempotency-keys` | An idempotency key deduplicates at one remote that supports it; an effect key deduplicates at the agent side across remotes, refuses on unknown, and is bound to an approval. The page that says *idempotency* precisely. | *idempotency keys AI agents* · *stripe idempotency key vs* | ## FAQ -`faq` — the twelve questions engineers ask, answer-first, at most eighty words each, marked up +`docs/faq` — the twelve questions engineers ask, answer-first, at most eighty words each, marked up as FAQ structured data: isn't this idempotency keys · why not a workflow engine · do I need an adapter · is it exactly-once · what happens on timeout · can I bypass it · does it phone home · what if the human takes an hour · single host or many · what's in a receipt · is the chain a @@ -276,18 +276,18 @@ signature · what is not covered. Query: *ctrlrun faq* and each question verbati | Path | Purpose | Query | |---|---|---| -| `security/threat-model` | What CTRLRun defends against, what it does not, and the fail-closed rules that follow; renders `docs/THREAT_MODEL.md`. | *ctrlrun threat model* | -| `security/verify-guarantees` | The guarantee catalogue G1–G11, what each exercises, what N/A means, what verify cannot see. | *ctrlrun verify guarantees* | -| `security/receipt-chain` | Each receipt carries the hash of the one before; what the chain detects, what it does not prove, and the two statements that erase the end of the log. Alteration, not authorship. | *tamper evident audit log AI agent* | +| `security/threat-model` | What CTRLRun defends against, what it does not, and the fail-closed rules that follow; renders `docs/docs/THREAT_MODEL.md`. | *ctrlrun threat model* | +| `docs/security/verify-guarantees` | The guarantee catalogue G1–G11, what each exercises, what N/A means, what verify cannot see. | *ctrlrun verify guarantees* | +| `docs/security/receipt-chain` | Each receipt carries the hash of the one before; what the chain detects, what it does not prove, and the two statements that erase the end of the log. Alteration, not authorship. | *tamper evident audit log AI agent* | | `security/how-this-is-built` | Spec-first, every MUST mutation-tested with the real numbers, independent review sessions, CLAIMS.md, N/A is not a pass, AI coding agents used throughout with the constraints that make that safe, and what has not been done: no external audit yet. Session 1b. | *is ctrlrun trustworthy* · *how ctrlrun is tested* | -| `security/disclosure` | How to report a vulnerability and what happens to the report; renders `SECURITY.md`. | *ctrlrun security report* | +| `docs/security/disclosure` | How to report a vulnerability and what happens to the report; renders `SECURITY.md`. | *ctrlrun security report* | ## Architecture and specifications | Path | Purpose | Query | |---|---|---| -| `architecture/overview` | The boundary CTRLRun owns, the canonical flow (normalize · decide · approve · reserve · execute · record), the module map; renders `docs/ARCHITECTURE.md`. | *ctrlrun architecture* | -| `architecture/specifications` | The six specifications, unchanged, with one line each on what the version asked; plus the OWASP and ACS readings. | *ctrlrun spec* | +| `architecture/overview` | The boundary CTRLRun owns, the canonical flow (normalize · decide · approve · reserve · execute · record), the module map; renders `docs/docs/ARCHITECTURE.md`. | *ctrlrun architecture* | +| `docs/architecture/specifications` | The six specifications, unchanged, with one line each on what the version asked; plus the OWASP and ACS readings. | *ctrlrun spec* | ## Changelog @@ -297,11 +297,11 @@ signature · what is not covered. Query: *ctrlrun faq* and each question verbati | Path | Purpose | Query | |---|---|---| -| `demos/index` | The two pages that run the released wheel in the reader's own browser, and the one line that runs the same thing faster offline. | *ctrlrun demo online* | -| `demos/medical-affairs` | An approval bound to revision A of a letter to a physician, and the send of revision B refused after new evidence redrafts it — the browser demo under a policy whose consequential action is a document rather than a payment. | *AI medical information letter review · agent citation drift* | -| `try-it` | `ctrlrun demo` in the browser via Pyodide, the same five refusals the README shows, a run-again button, and one line to install it for real. Any scenario the browser cannot run says so by name. | *try ctrlrun* | -| `study/does-your-framework-double-execute` | The framework-probe results, rendered from `research/framework-probe/results/*.json` by script; *No published results yet* until a file exists. Behaviour, not quality. | *does langgraph retry tool calls* · *agent framework double execution study* | -| `verify/get-the-badge` | The two-minute version of adding the verified badge: the workflow, what *declared guarantees pass* means, what N/A means. No gallery until a repo carries it. | *ctrlrun verified badge* | +| `docs/demos/index` | The two pages that run the released wheel in the reader's own browser, and the one line that runs the same thing faster offline. | *ctrlrun demo online* | +| `docs/demos/medical-affairs` | An approval bound to revision A of a letter to a physician, and the send of revision B refused after new evidence redrafts it — the browser demo under a policy whose consequential action is a document rather than a payment. | *AI medical information letter review · agent citation drift* | +| `docs/try-it` | `ctrlrun demo` in the browser via Pyodide, the same five refusals the README shows, a run-again button, and one line to install it for real. Any scenario the browser cannot run says so by name. | *try ctrlrun* | +| `docs/study/does-your-framework-double-execute` | The framework-probe results, rendered from `research/framework-probe/results/*.json` by script; *No published results yet* until a file exists. Behaviour, not quality. | *does langgraph retry tool calls* · *agent framework double execution study* | +| `docs/verify/get-the-badge` | The two-minute version of adding the verified badge: the workflow, what *declared guarantees pass* means, what N/A means. No gallery until a repo carries it. | *ctrlrun verified badge* | --- diff --git a/docs/README.md b/docs/README.md index dba943c0..400ef06e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,42 +1,50 @@ -# The documentation site +# The Mintlify website -The site is built by Mintlify from this directory: `docs.json` is the configuration, every -`.mdx` file is a page, and the specifications and reference documents that were here before -the site stay where they are and are linked from the *Architecture and specifications* tab. +One Mintlify project, deployed from this repository's `/docs` directory. -## Preview locally +- `index.mdx` serves `/`: the product homepage, execution boundary, failure example, and scenario explorer. +- `docs.mdx` serves `/docs`: the preserved technical overview. +- `docs/` contains all technical pages, published under `/docs/...`. The three technical navigation tabs retain their full sidebar structure. +- `risk-check.mdx` and `protect-my-agent.mdx` are custom-mode product pages. +- `snippets/` holds small client-side React components. Mintlify injects React hooks; do not add cross-snippet imports or third-party browser dependencies. +- `style.css` scopes product styling to `.cr-site`; documentation keeps the native Mintlify layout. +- `docs.json` defines navigation, metadata, and permanent redirects from the former technical URLs. +- `images/` contains public assets. `assets/` contains source assets and browser verification scripts. +- `capabilities.yaml` and `generated/` retain their source/render workflow. Generators now target technical pages in `docs/`. + +## Preview and validate ```bash -npm i -g mint -cd docs && mint dev +cd docs +mint dev ``` -`mint dev` serves the site at . `mint validate` builds it without serving, -and `mint broken-links` checks the internal links the way the deployed site resolves them. - -## Where things are - -| | | -|---|---| -| `docs.json` | navigation, theme, SEO defaults, redirects | -| `index.mdx`, `why.mdx` | Home and Why | -| `get-started/`, `concepts/`, `guides/`, `cookbook/`, `reference/`, `compare/`, `security/` | the sections in `IA.md` | -| `images/` | assets the site serves; `assets/` holds the sources (the vhs tape, the SVGs) | -| `capabilities.yaml`, `generated/` | the capability tables' source and its renders; edit the YAML, never a render | -| `IA.md`, `STYLE.md` | what to write and how; not pages | -| `.mintignore` | what in this directory is not a page | - -## Before a pull request - -From the repository root: +The preview runs at `http://localhost:3000`. From the repository root: ```bash python tools/docs_audit/snippets.py python tools/docs_audit/lint.py python tools/docs_audit/links.py python tools/docs_audit/render_capabilities.py --check +node --test integrations/website-form/review.test.mjs +NODE_PATH="$(npm root -g)" node docs/assets/verify-website.cjs cd docs && mint validate && mint broken-links ``` -Deployment is Mintlify's GitHub App on pushes to `main`, configured as a monorepo with the path -`/docs`. That setup is a dashboard step, listed in the launch checklist. +The browser harness requires Playwright and the preview server. It covers the demo state transitions, keyboard picker, mobile overflow, risk scoring, form validation, mocked email failure/retry/success, documentation sidebar, and key canonical URLs. It never sends an email. Set `WEBSITE_BASE_URL` to the hosted Mintlify preview URL to run the same checks against a deployment. Hosted checks also catch redirect normalization that differs from the local server. Do not add `/index` → `/` or `/docs/index` → `/docs` redirects: Mintlify normalizes those sources to their destinations, causing self-redirects. + +## Scenario and conversion behavior + +The homepage explorer contains 48 domains and 238 actions. All rules are illustrative, client-side simulations. The original Python runtime demos remain at `/docs/try-it` and `/docs/demos/medical-affairs`; they load their runtime only when invoked. + +The risk check stores no answers and sends no form data until the visitor submits an architecture review. Its result distinguishes indicated patterns from unknown answers and explains the scoring rule. + +The review form uses the private Vercel Function in `../integrations/website-form`, which calls Resend. Work email is required for follow-up. The recipient is fixed server-side. No API key appears in the Mintlify files. A mailto/copy fallback remains available. Browser retries reuse a request ID; success appears only after the server confirms Resend accepted the message. + +`website-events.js` and the components emit `ctrlrun:conversion` custom events for page visits, CTA clicks, selections, scenario outcomes, risk-check completion, and review submission. No analytics provider was configured, so these are integration hooks, not stored analytics. A consent-aware listener can connect an existing provider later. Event payloads exclude contact details and free-text form contents. + +## Deployment + +The Mintlify GitHub App deploys the website from `main`, with content directory `/docs`. The redesign stays on its review branch until merged. The Vercel project serves only the form API; it does not host a second frontend. + +Keep the `/mcp` platform endpoint unchanged. The `/docs/mcp/...` pages document MCP integrations; they are separate from Mintlify's generated documentation search endpoint. diff --git a/docs/SEO.md b/docs/SEO.md index c30096e5..766e6c42 100644 --- a/docs/SEO.md +++ b/docs/SEO.md @@ -35,107 +35,107 @@ that page's frontmatter, never here. | Page | Target query | The sentence that answers it | |---|---|---| -| `index` | ctrlrun · AI agent safety layer | The last check before an AI agent does something it can't undo. | -| `why` | why do AI agents double execute · AI agent consequential actions | Everyone is rushing to ship AI agents without thinking about consequences. | -| `not-only-agents` | celery task retried twice · webhook delivered twice duplicate · retry safe background job python | Every page on this site says agent, and the failure underneath them does not require one. | -| `get-started/install` | install ctrlrun | `pip install ctrlrun` installs the kernel and exactly two dependencies, `pyyaml` and `click`. | -| `get-started/quickstart` | protect an AI agent action python · ctrlrun quickstart | In sixty seconds you will write a policy, protect a refund function, and watch a mutated approval be refused. | -| `get-started/three-ways-in` | do I need a ctrlrun adapter · ctrlrun langgraph | There are three ways to put CTRLRun in front of a consequential action, and only one of them is an adapter. | -| `get-started/choosing` | ctrlrun decorator vs gateway | In-process Python takes the decorator, tools behind MCP take the gateway, and a framework with its own approval UI takes an adapter. | -| `production/index` | is ctrlrun production ready | SQLite is the default and is production-grade on one host; Postgres is for many hosts. | -| `production/postgres` | ctrlrun sqlite vs postgres | Choose by how many machines write to the store, not by how serious you are. | -| `production/how-reservation-works` | lost commit ambiguous outcome | An exception before COMMIT is a failed write to retry; one during COMMIT is unknown and is re-read. | -| `production/migrations` | ctrlrun schema migration | Migrations run at open, forward only, with no flag that opens a database un-migrated. | -| `production/recovery` | agent crashed mid action | A restarted process repairs nothing and cannot know the holder is dead. | -| `production/receipt-integrity` | verify receipt chain | Run ctrlrun receipts --verify-chain and read the six names it can report. | -| `production/soak` | ctrlrun soak test results | One published run, its measured duration, and the exit criterion it does not meet. | -| `production/operations` | ctrlrun monitoring | Watch how many effects are sitting in an unknown outcome that nobody has answered. | -| `mcp/overview` | MCP gateway human approval | CTRLRun works with MCP in four ways. | -| `mcp/gateway-in-5-minutes` | protect MCP server · MCP tool call approval | Point the MCP client at `ctrlrun gateway` instead of the tool server. | -| `mcp/approve-from-your-assistant` | approve MCP tool call from an assistant · MCP human approval server | Run `ctrlrun mcp-operator` and answer a pending approval from an MCP client, under your own name. | -| `mcp/use-the-docs-from-your-editor` | ctrlrun docs mcp server | This documentation is an MCP server, hosted with the site. | -| `concepts/action-and-hash` | canonical action hash AI agent | An action is one proposed operation, normalized. | -| `concepts/decisions` | AI agent action policy allow approve deny | A decision is what the policy says may happen to one action. | -| `concepts/approval-binding` | approval bound to action · approval mutation AI agent | An approval is a human's yes to one exact action, bound to that action's hash. | -| `concepts/effect-keys` | idempotency key AI agent · prevent duplicate tool execution | An effect key is the name of a consequence in the real world. | -| `concepts/outcomes-and-ambiguous` | agent tool call timeout · double execution AI agent retry | An outcome is what CTRLRun knows about the consequence, and there are three. | -| `concepts/receipts-and-evidence` | AI agent audit trail receipts | A receipt is the portable JSON record of one action that reached the executor. | -| `concepts/authority-and-delegation` | AI agent authorization delegation · least privilege AI agents | Authority answers the question the policy cannot: may this principal propose this action at all? | -| `concepts/observe-mode` | AI agent policy shadow mode | Observe mode is one top-level line that evaluates every action and executes it regardless. | -| `concepts/fail-closed` | fail closed AI agent | Fail closed means that anything CTRLRun cannot decide, it denies. | -| `guides/protect-a-function` | protect python function approval | Decorate the function that acts, name the action and the consequence. | -| `guides/gateway-in-front-of-mcp` | MCP gateway policy | Point the MCP client at `ctrlrun gateway` instead of the tool server. | -| `guides/approvals-in-slack` | slack approval AI agent | `WebhookApprovalProvider` sends one signed POST to a URL you own for every approval request. | -| `guides/resolve-an-ambiguous-effect` | ctrlrun resolve ambiguous | An `AMBIGUOUS` effect is one whose executor raised something other than `NotExecuted`. | -| `guides/reconcile-automatically` | reconcile agent action stripe kubernetes | A `reconcile` hook asks the remote what happened to an effect key. | -| `guides/observe-to-enforce` | roll out agent policy safely | Start with `mode: observe`: every action is decided exactly as enforce mode would decide it. | -| `guides/run-on-postgres` | ctrlrun postgres | Use Postgres when workers on more than one host must share one store. | -| `guides/verify-in-ci` | verify agent safety configuration CI | `ctrlrun verify` runs the kernel's own failure scenarios against your policy. | -| `guides/export-to-opentelemetry` | opentelemetry AI agent actions | `OTelEventSink` turns every action into one OpenTelemetry span. | -| `guides/langgraph-adapter` | langgraph interrupt human approval | `ctrlrun-langgraph` makes an `approve` decision surface as a LangGraph `interrupt()`. | -| `guides/openai-agents-adapter` | openai agents sdk tool approval | `ctrlrun-openai-agents` makes an `approve` decision stop the run with the SDK's own `ToolApprovalItem`. | -| `cookbook/index` | AI agent policy examples | Each recipe is a situation an agent is put in. | -| `cookbook/refund-agent` | AI agent refund approval tiers | A support agent issues refunds: small ones run, larger ones wait for a human. | -| `cookbook/payout-maker-checker` | maker checker AI agent payout | A payout agent acts on a delegated slice; above the desk limit a second person approves. | -| `cookbook/deploy-agent` | AI agent kubernetes deploy approval | Restart runs, production apply waits for a human, namespace delete is refused. | -| `cookbook/database-migration-agent` | AI agent database migration safety | Staging migrations run; every production migration waits; a dropped connection stays unknown. | -| `cookbook/iam-agent` | AI agent grant role approval | Read roles run on their own, admin is refused, and an approval for one role is not another. | -| `cookbook/credential-rotation-agent` | AI agent rotate api key safely | Minting a key runs; revoking the old one waits; a lost mint is never repeated. | -| `cookbook/crm-update-agent` | AI agent update crm record | A field update runs, a merge waits for a human, a delete is refused. | -| `cookbook/data-deletion-agent` | AI agent data deletion retention | Past retention purges run, inside retention waits, legal hold is refused. | -| `cookbook/outbound-email-agent` | AI agent send email approval | Internal mail goes; external mail waits, bound to the exact recipient. | -| `cookbook/customer-notification-agent` | AI agent notify customers once | One effect per customer per incident, so a retry or a second worker sends once. | -| `cookbook/manager-and-worker` | multi agent delegation bounded authority | A manager delegates a narrower slice; the worker cannot exceed or widen it. | -| `cookbook/protect-an-mcp-server` | protect existing MCP server | Name the tools in a policy and put the gateway between the agent and the server. | -| `cookbook/langgraph-interrupt` | langgraph approval refund | The approval surfaces as LangGraph's own interrupt and the resumption carries the arguments. | -| `cookbook/openai-agents-tool-approval` | openai agents sdk approval refund | The run stops with the SDK's own tool-approval interruption. | -| `cookbook/slack-approvals` | slack approve AI agent action | One signed POST out, a signed answer back, and the same grant call the CLI makes. | -| `cookbook/receipts-to-opentelemetry` | AI agent actions in traces | One span per action, one event per step, argument values kept out. | -| `cookbook/observe-then-enforce` | test agent policy without blocking | Everything executes and the receipts record what enforcement would have blocked. | -| `cookbook/resolve-an-ambiguous-effect` | resolve unknown agent action outcome | Ask the remote, then record the answer with ctrlrun resolve. | -| `cookbook/reconcile-against-the-remote` | reconcile agent effect stripe kubernetes | A hook asks the remote and moves the record only the way the answer points. | -| `cookbook/verify-in-github-actions` | verify agent policy CI | One workflow step proves the declared guarantees still hold against your policy. | -| `cookbook/sqlite-to-postgres` | share agent state across hosts | One line changes: the store, and the guarantee now holds across hosts. | -| `compare/framework-hitl` | langgraph human in the loop limitations | A framework's human-in-the-loop primitive is the right place for a human to answer, and CTRLRun uses it rather than replacing it. | -| `compare/guardrail-libraries` | AI guardrails vs execution control | A guardrail library reads text; CTRLRun sits one layer down, where a decision becomes an effect. | -| `compare/governance-toolkits` | AI agent oversight vs enforcement | A toolkit describes; CTRLRun refuses. | -| `compare/durable-workflows` | temporal vs ctrlrun · durable execution agents | One drives work forward; the other decides whether the work may happen. | -| `compare/idempotency-keys` | idempotency keys AI agents · stripe idempotency vs | An idempotency key deduplicates at one API; an effect key deduplicates at the agent, across every API it touches. | -| `demos/index` | ctrlrun demo online · try agent safety library | Every demo here runs real Python in your tab. | -| `demos/medical-affairs` | AI medical information letter review · agent citation drift · medical affairs LLM harness | A harness that retrieves biomedical evidence, reasons over it, cites it and drafts a reply is four problems, and CTRLRun solves none of them. | -| `try-it` | try ctrlrun in browser · ctrlrun demo online | Press the button and `ctrlrun demo` runs in this tab: real Python, the released wheel. | -| `verify/get-the-badge` | ctrlrun verified badge | Two minutes, three steps: verify on every push, publish the badge JSON, point Shields at it. | -| `study/does-your-framework-double-execute` | does langgraph retry tool calls · agent framework double execution | When a remote commits a refund and the reply is lost, what does an agent framework do? | -| `faq` | ctrlrun faq, and each question verbatim | The twelve questions that come up first, answered in under eighty words each. | -| `security/receipt-chain` | tamper evident audit log AI agent | The receipt chain is a hash chain over the evidence log. | -| `security/verify-guarantees` | ctrlrun verify guarantees | `ctrlrun verify` runs eleven guarantees against the configuration in front of it. | -| `security/disclosure` | ctrlrun security report | Report vulnerabilities privately to contact@arpanghoshal.com. | -| `how-this-is-built` | is ctrlrun trustworthy · how ctrlrun is tested | CTRLRun is built specification-first, every requirement in it is mutation-tested. | -| `reference/policy-yaml` | ctrlrun.yaml reference · ctrlrun policy schema | `ctrlrun.yaml` is one document: a `schema`, an `actions` map, and from v3 the `mode`, `environment` and `authority` keys. | -| `reference/authority-yaml` | ctrlrun authority grants yaml | The `authority:` section says which principal may propose which action. | -| `reference/cli` | ctrlrun cli | The `ctrlrun` command reads the policy in the working directory and the store beside it. | -| `reference/errors` | ctrlrun ApprovalMismatch · ctrlrun AmbiguousEffect | Every refusal is an exception of its own, raised as itself before the executor runs. | -| `reference/exit-codes` | ctrlrun verify exit code | Every `ctrlrun` command exits 0 when it did what it was asked, 1 when CTRLRun refused, 2 on a usage error. | -| `reference/receipt-and-event-schemas` | ctrlrun receipt json schema | A receipt is one executed action; an event is one step on the way. | -| `reference/api/index` | ctrlrun Control · ctrlrun protect decorator | Every frozen public name of the package and its extras, one page each. | -| `architecture/specifications` | ctrlrun specification | Every version of CTRLRun was a specification before it was code. | -| `ARCHITECTURE` | ctrlrun architecture | The boundary CTRLRun owns, and the six steps every protected call takes. | -| `THREAT_MODEL` | ctrlrun threat model | What CTRLRun defends against, and what it deliberately does not. | -| `how-this-is-built` | is ctrlrun trustworthy · how ctrlrun is tested | Specification first, every requirement mutation-tested, every claim mapped to a test. | -| `verify` | ctrlrun verify guarantees badge | Running the guarantee catalogue against your own configuration. | -| `adapters` | ctrlrun adapter langgraph openai | The three ways in, and when you do not need an adapter. | -| `authority` | ctrlrun authority delegation grants | Grants, containment and the omission rule, in plain language. | -| `postgres` | ctrlrun postgres store | Connection strings, what to grant, migrations, and failover. | -| `CLAIMS` | ctrlrun claims tests | Every README sentence mapped to the code and the test that proves it. | -| `ROADMAP` | ctrlrun roadmap v1.0 | What each version asked and answered, and what is not on the list. | -| `ACS` | agent control standard ctrlrun | What was read, what maps, and where the standard is silent. | -| `OWASP-AGENTIC-TOP10` | OWASP agentic top 10 mapping | A reading of somebody else's taxonomy against the guarantees CTRLRun tests. | +| `docs` | ctrlrun · AI agent safety layer | The last check before an AI agent does something it can't undo. | +| `docs/why` | why do AI agents double execute · AI agent consequential actions | Everyone is rushing to ship AI agents without thinking about consequences. | +| `docs/not-only-agents` | celery task retried twice · webhook delivered twice duplicate · retry safe background job python | Every page on this site says agent, and the failure underneath them does not require one. | +| `docs/get-started/install` | install ctrlrun | `pip install ctrlrun` installs the kernel and exactly two dependencies, `pyyaml` and `click`. | +| `docs/get-started/quickstart` | protect an AI agent action python · ctrlrun quickstart | In sixty seconds you will write a policy, protect a refund function, and watch a mutated approval be refused. | +| `docs/get-started/three-ways-in` | do I need a ctrlrun adapter · ctrlrun langgraph | There are three ways to put CTRLRun in front of a consequential action, and only one of them is an adapter. | +| `docs/get-started/choosing` | ctrlrun decorator vs gateway | In-process Python takes the decorator, tools behind MCP take the gateway, and a framework with its own approval UI takes an adapter. | +| `docs/production/index` | is ctrlrun production ready | SQLite is the default and is production-grade on one host; Postgres is for many hosts. | +| `docs/production/postgres` | ctrlrun sqlite vs postgres | Choose by how many machines write to the store, not by how serious you are. | +| `docs/production/how-reservation-works` | lost commit ambiguous outcome | An exception before COMMIT is a failed write to retry; one during COMMIT is unknown and is re-read. | +| `docs/production/migrations` | ctrlrun schema migration | Migrations run at open, forward only, with no flag that opens a database un-migrated. | +| `docs/production/recovery` | agent crashed mid action | A restarted process repairs nothing and cannot know the holder is dead. | +| `docs/production/receipt-integrity` | verify receipt chain | Run ctrlrun receipts --verify-chain and read the six names it can report. | +| `docs/production/soak` | ctrlrun soak test results | One published run, its measured duration, and the exit criterion it does not meet. | +| `docs/production/operations` | ctrlrun monitoring | Watch how many effects are sitting in an unknown outcome that nobody has answered. | +| `docs/mcp/overview` | MCP gateway human approval | CTRLRun works with MCP in four ways. | +| `docs/mcp/gateway-in-5-minutes` | protect MCP server · MCP tool call approval | Point the MCP client at `ctrlrun gateway` instead of the tool server. | +| `docs/mcp/approve-from-your-assistant` | approve MCP tool call from an assistant · MCP human approval server | Run `ctrlrun mcp-operator` and answer a pending approval from an MCP client, under your own name. | +| `docs/mcp/use-the-docs-from-your-editor` | ctrlrun docs mcp server | This documentation is an MCP server, hosted with the site. | +| `docs/concepts/action-and-hash` | canonical action hash AI agent | An action is one proposed operation, normalized. | +| `docs/concepts/decisions` | AI agent action policy allow approve deny | A decision is what the policy says may happen to one action. | +| `docs/concepts/approval-binding` | approval bound to action · approval mutation AI agent | An approval is a human's yes to one exact action, bound to that action's hash. | +| `docs/concepts/effect-keys` | idempotency key AI agent · prevent duplicate tool execution | An effect key is the name of a consequence in the real world. | +| `docs/concepts/outcomes-and-ambiguous` | agent tool call timeout · double execution AI agent retry | An outcome is what CTRLRun knows about the consequence, and there are three. | +| `docs/concepts/receipts-and-evidence` | AI agent audit trail receipts | A receipt is the portable JSON record of one action that reached the executor. | +| `docs/concepts/authority-and-delegation` | AI agent authorization delegation · least privilege AI agents | Authority answers the question the policy cannot: may this principal propose this action at all? | +| `docs/concepts/observe-mode` | AI agent policy shadow mode | Observe mode is one top-level line that evaluates every action and executes it regardless. | +| `docs/concepts/fail-closed` | fail closed AI agent | Fail closed means that anything CTRLRun cannot decide, it denies. | +| `docs/guides/protect-a-function` | protect python function approval | Decorate the function that acts, name the action and the consequence. | +| `docs/guides/gateway-in-front-of-mcp` | MCP gateway policy | Point the MCP client at `ctrlrun gateway` instead of the tool server. | +| `docs/guides/approvals-in-slack` | slack approval AI agent | `WebhookApprovalProvider` sends one signed POST to a URL you own for every approval request. | +| `docs/guides/resolve-an-ambiguous-effect` | ctrlrun resolve ambiguous | An `AMBIGUOUS` effect is one whose executor raised something other than `NotExecuted`. | +| `docs/guides/reconcile-automatically` | reconcile agent action stripe kubernetes | A `reconcile` hook asks the remote what happened to an effect key. | +| `docs/guides/observe-to-enforce` | roll out agent policy safely | Start with `mode: observe`: every action is decided exactly as enforce mode would decide it. | +| `docs/guides/run-on-postgres` | ctrlrun postgres | Use Postgres when workers on more than one host must share one store. | +| `docs/guides/verify-in-ci` | verify agent safety configuration CI | `ctrlrun verify` runs the kernel's own failure scenarios against your policy. | +| `docs/guides/export-to-opentelemetry` | opentelemetry AI agent actions | `OTelEventSink` turns every action into one OpenTelemetry span. | +| `docs/guides/langgraph-adapter` | langgraph interrupt human approval | `ctrlrun-langgraph` makes an `approve` decision surface as a LangGraph `interrupt()`. | +| `docs/guides/openai-agents-adapter` | openai agents sdk tool approval | `ctrlrun-openai-agents` makes an `approve` decision stop the run with the SDK's own `ToolApprovalItem`. | +| `docs/cookbook/index` | AI agent policy examples | Each recipe is a situation an agent is put in. | +| `docs/cookbook/refund-agent` | AI agent refund approval tiers | A support agent issues refunds: small ones run, larger ones wait for a human. | +| `docs/cookbook/payout-maker-checker` | maker checker AI agent payout | A payout agent acts on a delegated slice; above the desk limit a second person approves. | +| `docs/cookbook/deploy-agent` | AI agent kubernetes deploy approval | Restart runs, production apply waits for a human, namespace delete is refused. | +| `docs/cookbook/database-migration-agent` | AI agent database migration safety | Staging migrations run; every production migration waits; a dropped connection stays unknown. | +| `docs/cookbook/iam-agent` | AI agent grant role approval | Read roles run on their own, admin is refused, and an approval for one role is not another. | +| `docs/cookbook/credential-rotation-agent` | AI agent rotate api key safely | Minting a key runs; revoking the old one waits; a lost mint is never repeated. | +| `docs/cookbook/crm-update-agent` | AI agent update crm record | A field update runs, a merge waits for a human, a delete is refused. | +| `docs/cookbook/data-deletion-agent` | AI agent data deletion retention | Past retention purges run, inside retention waits, legal hold is refused. | +| `docs/cookbook/outbound-email-agent` | AI agent send email approval | Internal mail goes; external mail waits, bound to the exact recipient. | +| `docs/cookbook/customer-notification-agent` | AI agent notify customers once | One effect per customer per incident, so a retry or a second worker sends once. | +| `docs/cookbook/manager-and-worker` | multi agent delegation bounded authority | A manager delegates a narrower slice; the worker cannot exceed or widen it. | +| `docs/cookbook/protect-an-mcp-server` | protect existing MCP server | Name the tools in a policy and put the gateway between the agent and the server. | +| `docs/cookbook/langgraph-interrupt` | langgraph approval refund | The approval surfaces as LangGraph's own interrupt and the resumption carries the arguments. | +| `docs/cookbook/openai-agents-tool-approval` | openai agents sdk approval refund | The run stops with the SDK's own tool-approval interruption. | +| `docs/cookbook/slack-approvals` | slack approve AI agent action | One signed POST out, a signed answer back, and the same grant call the CLI makes. | +| `docs/cookbook/receipts-to-opentelemetry` | AI agent actions in traces | One span per action, one event per step, argument values kept out. | +| `docs/cookbook/observe-then-enforce` | test agent policy without blocking | Everything executes and the receipts record what enforcement would have blocked. | +| `docs/cookbook/resolve-an-ambiguous-effect` | resolve unknown agent action outcome | Ask the remote, then record the answer with ctrlrun resolve. | +| `docs/cookbook/reconcile-against-the-remote` | reconcile agent effect stripe kubernetes | A hook asks the remote and moves the record only the way the answer points. | +| `docs/cookbook/verify-in-github-actions` | verify agent policy CI | One workflow step proves the declared guarantees still hold against your policy. | +| `docs/cookbook/sqlite-to-postgres` | share agent state across hosts | One line changes: the store, and the guarantee now holds across hosts. | +| `docs/compare/framework-hitl` | langgraph human in the loop limitations | A framework's human-in-the-loop primitive is the right place for a human to answer, and CTRLRun uses it rather than replacing it. | +| `docs/compare/guardrail-libraries` | AI guardrails vs execution control | A guardrail library reads text; CTRLRun sits one layer down, where a decision becomes an effect. | +| `docs/compare/governance-toolkits` | AI agent oversight vs enforcement | A toolkit describes; CTRLRun refuses. | +| `docs/compare/durable-workflows` | temporal vs ctrlrun · durable execution agents | One drives work forward; the other decides whether the work may happen. | +| `docs/compare/idempotency-keys` | idempotency keys AI agents · stripe idempotency vs | An idempotency key deduplicates at one API; an effect key deduplicates at the agent, across every API it touches. | +| `docs/demos/index` | ctrlrun demo online · try agent safety library | Every demo here runs real Python in your tab. | +| `docs/demos/medical-affairs` | AI medical information letter review · agent citation drift · medical affairs LLM harness | A harness that retrieves biomedical evidence, reasons over it, cites it and drafts a reply is four problems, and CTRLRun solves none of them. | +| `docs/try-it` | try ctrlrun in browser · ctrlrun demo online | Press the button and `ctrlrun demo` runs in this tab: real Python, the released wheel. | +| `docs/verify/get-the-badge` | ctrlrun verified badge | Two minutes, three steps: verify on every push, publish the badge JSON, point Shields at it. | +| `docs/study/does-your-framework-double-execute` | does langgraph retry tool calls · agent framework double execution | When a remote commits a refund and the reply is lost, what does an agent framework do? | +| `docs/faq` | ctrlrun faq, and each question verbatim | The twelve questions that come up first, answered in under eighty words each. | +| `docs/security/receipt-chain` | tamper evident audit log AI agent | The receipt chain is a hash chain over the evidence log. | +| `docs/security/verify-guarantees` | ctrlrun verify guarantees | `ctrlrun verify` runs eleven guarantees against the configuration in front of it. | +| `docs/security/disclosure` | ctrlrun security report | Report vulnerabilities privately to contact@arpanghoshal.com. | +| `docs/how-this-is-built` | is ctrlrun trustworthy · how ctrlrun is tested | CTRLRun is built specification-first, every requirement in it is mutation-tested. | +| `docs/reference/policy-yaml` | ctrlrun.yaml reference · ctrlrun policy schema | `ctrlrun.yaml` is one document: a `schema`, an `actions` map, and from v3 the `mode`, `environment` and `docs/authority` keys. | +| `docs/reference/authority-yaml` | ctrlrun authority grants yaml | The `authority:` section says which principal may propose which action. | +| `docs/reference/cli` | ctrlrun cli | The `ctrlrun` command reads the policy in the working directory and the store beside it. | +| `docs/reference/errors` | ctrlrun ApprovalMismatch · ctrlrun AmbiguousEffect | Every refusal is an exception of its own, raised as itself before the executor runs. | +| `docs/reference/exit-codes` | ctrlrun verify exit code | Every `ctrlrun` command exits 0 when it did what it was asked, 1 when CTRLRun refused, 2 on a usage error. | +| `docs/reference/receipt-and-event-schemas` | ctrlrun receipt json schema | A receipt is one executed action; an event is one step on the way. | +| `docs/reference/api/index` | ctrlrun Control · ctrlrun protect decorator | Every frozen public name of the package and its extras, one page each. | +| `docs/architecture/specifications` | ctrlrun specification | Every version of CTRLRun was a specification before it was code. | +| `docs/ARCHITECTURE` | ctrlrun architecture | The boundary CTRLRun owns, and the six steps every protected call takes. | +| `docs/THREAT_MODEL` | ctrlrun threat model | What CTRLRun defends against, and what it deliberately does not. | +| `docs/how-this-is-built` | is ctrlrun trustworthy · how ctrlrun is tested | Specification first, every requirement mutation-tested, every claim mapped to a test. | +| `docs/verify` | ctrlrun verify guarantees badge | Running the guarantee catalogue against your own configuration. | +| `docs/adapters` | ctrlrun adapter langgraph openai | The three ways in, and when you do not need an adapter. | +| `docs/authority` | ctrlrun authority delegation grants | Grants, containment and the omission rule, in plain language. | +| `docs/postgres` | ctrlrun postgres store | Connection strings, what to grant, migrations, and failover. | +| `docs/CLAIMS` | ctrlrun claims tests | Every README sentence mapped to the code and the test that proves it. | +| `docs/ROADMAP` | ctrlrun roadmap v1.0 | What each version asked and answered, and what is not on the list. | +| `docs/ACS` | agent control standard ctrlrun | What was read, what maps, and where the standard is silent. | +| `docs/OWASP-AGENTIC-TOP10` | OWASP agentic top 10 mapping | A reading of somebody else's taxonomy against the guarantees CTRLRun tests. | ## The words that appear once -`idempotency` on `concepts/effect-keys` and `compare/idempotency-keys`; `human-in-the-loop` on -`get-started/three-ways-in` and `compare/framework-hitl`; `MCP gateway` on `mcp/overview`; -`double execution` on `concepts/outcomes-and-ambiguous`; `AI agent safety` on `index`. Anywhere +`idempotency` on `docs/concepts/effect-keys` and `docs/compare/idempotency-keys`; `human-in-the-loop` on +`docs/get-started/three-ways-in` and `docs/compare/framework-hitl`; `MCP gateway` on `docs/mcp/overview`; +`double execution` on `docs/concepts/outcomes-and-ambiguous`; `AI agent safety` on `docs`. Anywhere else they are noise. ## What is deliberately not done @@ -144,3 +144,11 @@ No keyword density targets, no repeated phrases across pages, no content written rather than a reader, and no `llms.txt` hand-maintained beside the generated one. Google's own guidance (July 2026) is that the fundamentals are what matter and that extra machine files are not needed; the assistants that do read `llms.txt` get Mintlify's. + +## Product pages + +| Page | Search intent | +|---|---| +| `index` | Execution safety for AI agents | +| `risk-check` | AI agent execution risk assessment | +| `protect-my-agent` | AI agent architecture review and integration help | diff --git a/docs/STYLE.md b/docs/STYLE.md index 012e38a7..f1a84895 100644 --- a/docs/STYLE.md +++ b/docs/STYLE.md @@ -26,7 +26,7 @@ reviewer reads for. - **One idea per paragraph.** Two or three sentences is a paragraph; six is two. - **No exclamation marks.** No *we're excited*, no *simply*, no *just*, no *easy*. - **Plain claims.** A sentence either describes what the shipped code does, and has a row in - `docs/CLAIMS.md`, or it is marked *(design)*, or it is cut. + `docs/docs/CLAIMS.md`, or it is marked *(design)*, or it is cut. - **Numbers travel with their units.** Amounts are integer minor units and the page says so the first time one appears. @@ -99,3 +99,7 @@ python tools/docs_audit/lint.py python tools/docs_audit/links.py python tools/docs_audit/render_capabilities.py --check ``` + +## Product experience + +The `/`, `/risk-check`, and `/protect-my-agent` pages use Mintlify custom mode. The product brief governs their concise copy and layout; the documentation-only Next section, fixed-copy, and three-domain-list rules do not apply to these pages. Technical pages under `/docs` retain the rules above. diff --git a/docs/assets/verify-browser-demo.mjs b/docs/assets/verify-browser-demo.mjs index c574f13b..2e9643b9 100644 --- a/docs/assets/verify-browser-demo.mjs +++ b/docs/assets/verify-browser-demo.mjs @@ -1,4 +1,4 @@ -// Does the Try-it page's Python run in Pyodide? The honest test behind docs/try-it.mdx: +// Does the Try-it page's Python run in Pyodide? The honest test behind docs/docs/try-it.mdx: // // npm install pyodide // node docs/assets/verify-browser-demo.mjs diff --git a/docs/assets/verify-website.cjs b/docs/assets/verify-website.cjs new file mode 100644 index 00000000..a97d3c63 --- /dev/null +++ b/docs/assets/verify-website.cjs @@ -0,0 +1,116 @@ +/* Run with Mintlify preview on port 3000 and Playwright installed: + * NODE_PATH="$(npm root -g)" node docs/assets/verify-website.cjs + * No emails are opened or sent. No real actions or network-backed demo runs. + */ +module.exports = async function verifyWebsite(page, base = 'http://localhost:3000') { + const checks = []; + const assert = (value, message) => { if (!value) throw new Error(message); checks.push(message); }; + const result = () => page.locator('.cr-result').innerText(); + const choose = value => page.getByLabel('Explore a situation').selectOption(value); + const errors = []; + page.on('pageerror', error => errors.push(error.message)); + await page.setViewportSize({ width: 1280, height: 900 }); + assert((await page.goto(base)).status() === 200, 'Homepage responds without a redirect loop'); + await page.getByRole('button', { name: 'Approve this exact action →' }).waitFor(); + assert(await page.locator('h1').count() === 1, 'Homepage has one H1'); + assert(await page.locator('.cr-footer').isVisible(), 'Final CTAs render in custom mode'); + assert(await page.locator('link[rel=canonical]').getAttribute('href') === 'https://ctrlrun.dev/', 'Homepage canonical points to /'); + await page.evaluate(() => { window.crTestEvents = []; window.addEventListener('ctrlrun:conversion', event => window.crTestEvents.push(event.detail)); }); + await page.getByRole('button', { name: 'Approve this exact action →' }).click(); + assert((await result()).includes('Exact action approved'), 'Approval binds to the exact action'); + await page.getByRole('button', { name: 'Change to $5,000' }).click(); + assert((await result()).includes('Approval was for $500, not $5,000.'), 'Changing $500 to $5,000 is blocked'); + await choose('allowed'); + await page.getByRole('button', { name: 'Execute action →' }).click(); + await page.getByRole('button', { name: 'Retry the same action →' }).click(); + assert((await result()).includes('Duplicate blocked'), 'A completed action cannot execute twice'); + await choose('blocked'); + assert((await result()).includes('does not have permission'), 'Permission denial is explained'); + await choose('uncertain'); + assert((await result()).includes('Outcome uncertain'), 'A lost response does not imply failure'); + await page.getByRole('button', { name: 'Try again →' }).click(); + assert((await result()).includes('Reconciliation required'), 'Unconfirmed retries remain blocked'); + await page.getByRole('button', { name: 'Simulate provider confirming success →' }).click(); + await page.getByRole('button', { name: 'Retry the same action →' }).click(); + assert((await result()).includes('Duplicate blocked'), 'Confirmed success retains duplicate protection'); + await page.getByRole('button', { name: 'Choose your domain Finance' }).click(); + assert(await page.getByRole('option').count() >= 48, 'Industry breadth is discoverable'); + await page.getByRole('combobox', { name: 'Search domains' }).fill('DevOps'); + await page.keyboard.press('Enter'); + assert(await page.getByLabel('Choose an action').inputValue() === '0', 'Changing domain resets the selected action'); + assert((await page.locator('.cr-domain-cta').innerText()).includes('production infrastructure'), 'Commercial CTA follows the selected domain'); + await page.getByLabel('Choose an action').selectOption('2'); + assert((await page.locator('.cr-request').innerText()).includes('Delete infrastructure'), 'Changing action updates the request'); + await page.getByRole('button', { name: 'Choose your domain DevOps' }).click(); + await page.getByRole('combobox', { name: 'Search domains' }).fill('does-not-exist'); + assert(await page.getByText('No matching domain.').isVisible(), 'Empty search has a useful recovery message'); + await page.keyboard.press('Escape'); + assert(await page.getByRole('button', { name: 'Choose your domain DevOps' }).evaluate(node => node === document.activeElement), 'Escape restores focus to the domain button'); + assert((await page.evaluate(() => window.crTestEvents)).some(event => event.name === 'scenario_completed'), 'Scenario conversion events are emitted'); + for (const width of [375, 768, 1280]) { + await page.setViewportSize({ width, height: 900 }); + assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Homepage fits viewport ' + width); + } + await page.goto(base + '/risk-check'); + await page.getByLabel('Move money', { exact: true }).check(); + for (let index = 0; index < 5; index++) await page.locator('input[name="risk-' + index + '"][value="' + ([2, 4].includes(index) ? 'No' : 'Yes') + '"]').check(); + await page.getByRole('button', { name: 'Check my execution risk →' }).click(); + assert((await page.locator('.cr-risk-result').innerText()).includes('5 execution-risk patterns'), 'All five indicated patterns appear in the risk result'); + assert((await page.locator('.cr-risk-result').innerText()).includes('Execution risk: High'), 'High result has a transparent threshold'); + await page.locator('input[name="risk-0"][value="Unsure"]').check(); + assert(await page.locator('.cr-risk-result').count() === 0, 'Changing an answer clears the stale result'); + for (let index = 0; index < 5; index++) await page.locator('input[name="risk-' + index + '"][value="' + ([2, 4].includes(index) ? 'Yes' : 'No') + '"]').check(); + await page.getByRole('button', { name: 'Check my execution risk →' }).click(); + assert((await page.locator('.cr-risk-result').innerText()).includes('Lower indicated risk'), 'Controls present produce a lower indicated result'); + await page.setViewportSize({ width: 375, height: 812 }); + assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Risk check fits mobile'); + await page.goto(base + '/protect-my-agent?domain=DevOps&risk=High&patterns=4&unknowns=1'); + assert((await page.locator('.cr-domain-context').innerText()).includes('DevOps'), 'Domain context carries into the review form'); + await page.getByRole('button', { name: 'Review my agent →' }).click(); + assert(await page.locator('.cr-email-preview').count() === 0, 'Empty form cannot prepare a request'); + await page.getByLabel('Work email', { exact: true }).fill('engineer@example.com'); + await page.getByLabel('Company', { exact: true }).fill('Example test company'); + await page.getByLabel('What does your agent do?').fill('Test deployment workflow'); + await page.getByLabel('Which actions can it execute?').fill('Deploy production releases'); + await page.getByLabel('Retry safety', { exact: true }).check(); + await page.getByRole('button', { name: 'Review my agent →' }).click(); + const href = await page.getByRole('link', { name: 'Use my email app instead ↗' }).getAttribute('href'); + assert(href.startsWith('mailto:contact@arpanghoshal.com?'), 'Review handoff uses the approved recipient'); + const body = decodeURIComponent(href.split('&body=')[1]); + assert(body.includes('Example test company') && body.includes('Deploy production releases') && body.includes('Retry safety') && body.includes('High'), 'Email brief includes qualification and risk context'); + assert((await page.locator('.cr-email-preview').innerText()).includes('has not been sent'), 'The form never falsely claims delivery'); + assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Review form fits mobile'); + await page.getByLabel('Company', { exact: true }).fill('Updated test company'); + assert(await page.locator('.cr-email-preview').count() === 0, 'Editing the brief clears the prepared handoff'); + await page.getByRole('button', { name: 'Review my agent →' }).click(); + const submissions = []; + await page.route('https://ctrlrun-review-form.vercel.app/api/review', async route => { + submissions.push(route.request().postDataJSON()); + await route.fulfill({ status: submissions.length === 1 ? 502 : 200, contentType: 'application/json', headers: { 'Access-Control-Allow-Origin': '*' }, body: JSON.stringify(submissions.length === 1 ? { error: 'Provider temporarily unavailable.' } : { ok: true, id: 'mock-only-no-email-sent' }) }); + }); + await page.getByRole('button', { name: 'Send review request →' }).click(); + await page.getByRole('alert').filter({ hasText: 'Provider temporarily unavailable.' }).waitFor(); + assert((await page.locator('.cr-email-preview').innerText()).includes('has not been sent'), 'Provider failure never claims success'); + await page.getByRole('button', { name: 'Retry submission →' }).click(); + await page.getByRole('heading', { name: 'Review request submitted.' }).waitFor(); + assert(submissions.length === 2 && submissions[0].requestId === submissions[1].requestId, 'Uncertain email retries reuse the same idempotency key'); + assert(submissions[1].email === 'engineer@example.com', 'Work email is included for replies'); + assert(await page.getByRole('button', { name: 'Send review request →' }).count() === 0, 'Successful submission cannot be double-clicked'); + await page.unroute('https://ctrlrun-review-form.vercel.app/api/review'); + await page.setViewportSize({ width: 1280, height: 900 }); + assert((await page.goto(base + '/docs')).status() === 200, 'Documentation landing responds without a redirect loop'); + await page.locator('#sidebar').waitFor(); + assert(await page.locator('#sidebar').isVisible(), 'Documentation retains the native sidebar'); + assert((await page.locator('main').innerText()).includes('Protect one function'), 'The original technical overview remains at /docs'); + const docLinks = await page.locator('#sidebar a[href]').evaluateAll(nodes => nodes.map(node => node.getAttribute('href')).filter(href => href.startsWith('/'))); + assert(docLinks.every(href => href === '/docs' || href.startsWith('/docs/')), 'Technical sidebar links stay under /docs'); + await page.goto(base + '/docs/get-started/quickstart'); + assert(await page.locator('main h1').count() === 1, 'Quickstart renders at its migrated route'); + assert(await page.locator('link[rel=canonical]').getAttribute('href') === 'https://ctrlrun.dev/docs/get-started/quickstart', 'Documentation canonical uses the migrated route'); + assert(errors.length === 0, 'No browser runtime errors: ' + errors.join('; ')); + return { passed: checks.length, checks }; +}; +if (typeof require !== 'undefined' && require.main === module) { + const { chromium } = require('playwright'); + (async () => { const browser = await chromium.launch(); try { const page = await browser.newPage(); console.log(JSON.stringify(await module.exports(page, process.env.WEBSITE_BASE_URL), null, 2)); } finally { await browser.close(); } })().catch(error => { console.error(error); process.exitCode = 1; }); +} diff --git a/docs/capabilities.yaml b/docs/capabilities.yaml index d2769d87..5553bb80 100644 --- a/docs/capabilities.yaml +++ b/docs/capabilities.yaml @@ -14,7 +14,7 @@ # ways_in decorator / gateway / adapter: true, false, or a short note for a qualified yes # since the version that shipped it, v0.1 … v0.6 # page the docs-site path that owns it (docs/IA.md), without extension -# claim the exact quoted text of the docs/CLAIMS.md row that proves it, or null with a +# claim the exact quoted text of the docs/docs/CLAIMS.md row that proves it, or null with a # claim_note saying which session adds the sentence and its row # # Entries are rendered in this order. The six guarantees come first because the README @@ -30,7 +30,7 @@ capabilities: gateway: true adapter: prevention or attribution, per adapter since: v0.1 - page: concepts/approval-binding + page: docs/concepts/approval-binding claim: An approval is bound to the exact action; a mutated or replayed one is refused. - id: one-effect-once @@ -42,7 +42,7 @@ capabilities: gateway: true adapter: true since: v0.1 - page: concepts/effect-keys + page: docs/concepts/effect-keys claim: One logical effect happens at most once, across threads, processes and hosts. - id: ambiguous-outcomes @@ -54,7 +54,7 @@ capabilities: gateway: true adapter: true since: v0.1 - page: concepts/outcomes-and-ambiguous + page: docs/concepts/outcomes-and-ambiguous claim: An unknown outcome is AMBIGUOUS, never FAILED, and blocks a blind retry. - id: fail-closed @@ -66,7 +66,7 @@ capabilities: gateway: true adapter: true since: v0.1 - page: concepts/fail-closed + page: docs/concepts/fail-closed claim: An unknown action, a missing policy or a missing principal is denied. - id: authority-containment @@ -78,7 +78,7 @@ capabilities: gateway: true adapter: true since: v0.3 - page: concepts/authority-and-delegation + page: docs/concepts/authority-and-delegation claim: provably a subset of its parent on every dimension, at creation and again at every evaluation - id: receipts @@ -90,7 +90,7 @@ capabilities: gateway: true adapter: true since: v0.1 - page: concepts/receipts-and-evidence + page: docs/concepts/receipts-and-evidence claim: Every executed action leaves a portable JSON receipt - id: per-action-policy @@ -102,7 +102,7 @@ capabilities: gateway: true adapter: true since: v0.1 - page: reference/policy-yaml + page: docs/reference/policy-yaml claim: cheap to undo is autonomous, anything that leaves the building needs a human, money is by amount with both ends bound - id: operator-cli @@ -114,7 +114,7 @@ capabilities: gateway: true adapter: true since: v0.1 - page: reference/cli + page: docs/reference/cli claim: "`ctrlrun approve`, `deny`, `resolve`, `inspect`, `receipts` and `stats` work from the shell against any store" - id: mcp-gateway @@ -126,7 +126,7 @@ capabilities: gateway: true adapter: false since: v0.2 - page: guides/gateway-in-front-of-mcp + page: docs/guides/gateway-in-front-of-mcp claim: No agent changes - id: reconciliation @@ -138,7 +138,7 @@ capabilities: gateway: false adapter: true since: v0.2 - page: guides/reconcile-automatically + page: docs/guides/reconcile-automatically claim: the only thing besides a human permitted to move a record out of `AMBIGUOUS` - id: webhook-approvals @@ -150,7 +150,7 @@ capabilities: gateway: true adapter: false since: v0.2 - page: guides/approvals-in-slack + page: docs/guides/approvals-in-slack claim: "`WebhookApprovalProvider` sends an approval request to a webhook, such as Slack, and takes the answer back through the same grant calls" - id: otel-export @@ -162,7 +162,7 @@ capabilities: gateway: true adapter: true since: v0.2 - page: guides/export-to-opentelemetry + page: docs/guides/export-to-opentelemetry claim: one OpenTelemetry span per action, one span event per step - id: identity @@ -174,7 +174,7 @@ capabilities: gateway: true adapter: true since: v0.3 - page: concepts/authority-and-delegation + page: docs/concepts/authority-and-delegation claim: CTRLRun issues no credential and defines no identity format - id: delegation @@ -186,7 +186,7 @@ capabilities: gateway: true adapter: true since: v0.3 - page: concepts/authority-and-delegation + page: docs/concepts/authority-and-delegation claim: "`ctrlrun revoke` cuts a chain of any depth with one write" - id: observe-mode @@ -198,7 +198,7 @@ capabilities: gateway: true adapter: true since: v0.3 - page: concepts/observe-mode + page: docs/concepts/observe-mode claim: "`mode: observe` … records what *would* have been blocked, without blocking anything" - id: verify @@ -210,7 +210,7 @@ capabilities: gateway: true adapter: true since: v0.4 - page: guides/verify-in-ci + page: docs/guides/verify-in-ci claim: Not applicable is not a pass - id: verify-badge @@ -222,7 +222,7 @@ capabilities: gateway: true adapter: true since: v0.4 - page: verify/get-the-badge + page: docs/verify/get-the-badge claim: means the **declared guarantees pass** - id: framework-adapters @@ -234,7 +234,7 @@ capabilities: gateway: false adapter: true since: v0.5 - page: get-started/three-ways-in + page: docs/get-started/three-ways-in claim: There is never a second place to say yes - id: durable-runtime @@ -246,7 +246,7 @@ capabilities: gateway: true adapter: true since: v0.6 - page: production/index + page: docs/production/index claim: Runs in production on a single file, or on Postgres across hosts - id: postgres @@ -258,7 +258,7 @@ capabilities: gateway: true adapter: true since: v0.6 - page: production/postgres + page: docs/production/postgres claim: Same `StateStore` protocol, extended by nothing - id: migrations @@ -270,7 +270,7 @@ capabilities: gateway: true adapter: true since: v0.6 - page: production/migrations + page: docs/production/migrations claim: migrations are automatic at open, forward-only - id: recovery @@ -282,7 +282,7 @@ capabilities: gateway: true adapter: true since: v0.6 - page: production/recovery + page: docs/production/recovery claim: A crashed worker's effect stays `AMBIGUOUS` until a human runs `ctrlrun resolve` or a `reconcile` hook asks the remote what happened - id: receipt-chain @@ -294,7 +294,7 @@ capabilities: gateway: true adapter: true since: v0.6 - page: security/receipt-chain + page: docs/security/receipt-chain claim: Each receipt carries the hash of the one before it - id: policy-versioning @@ -306,7 +306,7 @@ capabilities: gateway: true adapter: true since: v0.6 - page: concepts/receipts-and-evidence + page: docs/concepts/receipts-and-evidence claim: every receipt records which policy decided it - id: control-registry @@ -318,7 +318,7 @@ capabilities: gateway: true adapter: true since: v0.6 - page: reference/policy-yaml + page: docs/reference/policy-yaml claim: Receipts in a `ctrlrun.policy/v4` document can cite the `controls:` an action satisfies - id: data-scope @@ -330,5 +330,5 @@ capabilities: gateway: true adapter: true since: v0.6 - page: reference/policy-yaml + page: docs/reference/policy-yaml claim: a rule can condition on the `data:` labels present in an action's arguments diff --git a/docs/cookbook/index.mdx b/docs/cookbook/index.mdx deleted file mode 100644 index 8ec3b1c2..00000000 --- a/docs/cookbook/index.mdx +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: "Cookbook" -description: "One recipe per real situation: the policy, the code with a fake remote, what the agent sees when refused or asked, the receipt." ---- - -Each recipe is a situation an agent is put in, across money, infrastructure, permissions, -records and communications, and shows the policy, the code against a stand-in remote, what the -agent sees when it is refused or asked, the receipt, and what to do when an effect's outcome is -unknown. Every runnable block runs offline in this repository's CI, and every recipe with code -is also a directory under `examples/cookbook/` extracted from its page. To run one instead of -reading it: - -```bash -git clone https://github.com/CTRLRun/ctrlrun && cd ctrlrun/examples/cookbook/refund-agent -pip install ctrlrun && python main.py -``` - -## Money - -- [A refund agent with amount tiers](/cookbook/refund-agent) -- [A payout agent with maker/checker via delegation](/cookbook/payout-maker-checker) - -## Infrastructure - -- [A deploy agent](/cookbook/deploy-agent) -- [A database-migration agent](/cookbook/database-migration-agent) - -## Permissions - -- [An IAM agent that can grant read but never admin](/cookbook/iam-agent) -- [A credential-rotation agent](/cookbook/credential-rotation-agent) - -## Records - -- [A CRM-update agent](/cookbook/crm-update-agent) -- [A data-deletion agent under a retention rule](/cookbook/data-deletion-agent) - -## Communications - -- [An outbound-email agent with external-recipient approval](/cookbook/outbound-email-agent) -- [A customer-notification agent](/cookbook/customer-notification-agent) - -## Multi-agent - -- [A manager agent delegating bounded authority to a worker](/cookbook/manager-and-worker) - -## Integrations - -- [Protect an existing MCP server in five minutes](/cookbook/protect-an-mcp-server) -- [LangGraph with interrupt()](/cookbook/langgraph-interrupt) -- [OpenAI Agents SDK tool approval](/cookbook/openai-agents-tool-approval) -- [Approvals in Slack via webhook](/cookbook/slack-approvals) -- [Receipts into OpenTelemetry](/cookbook/receipts-to-opentelemetry) - -## Operations - -- [Observe for a week, then enforce](/cookbook/observe-then-enforce) -- [Resolve an ambiguous effect](/cookbook/resolve-an-ambiguous-effect) -- [Reconcile against Stripe or Kubernetes automatically](/cookbook/reconcile-against-the-remote) -- [Run verify in GitHub Actions](/cookbook/verify-in-github-actions) -- [Move from SQLite to Postgres](/cookbook/sqlite-to-postgres) - -## Next - -- [Get started](/get-started/quickstart) · [Why](/why). diff --git a/docs/docs.json b/docs/docs.json index 7b5d9242..bd5c46c0 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -13,165 +13,178 @@ "href": "/" }, "favicon": "/images/favicon.svg", - "description": "The execution safety layer for AI agents.", + "description": "Execution safety for AI agents. Control consequential actions before they affect real systems.", "navbar": { "links": [ { - "label": "GitHub", - "href": "https://github.com/CTRLRun/ctrlrun" + "href": "/docs", + "label": "Documentation" }, { - "label": "PyPI", - "href": "https://pypi.org/project/ctrlrun/" + "label": "GitHub", + "href": "https://github.com/CTRLRun/ctrlrun" } - ] + ], + "primary": { + "type": "button", + "label": "Protect my agent", + "href": "/protect-my-agent" + } }, "navigation": { "tabs": [ + { + "tab": "CTRLRun", + "pages": [ + "index", + "risk-check", + "protect-my-agent" + ] + }, { "tab": "Documentation", "groups": [ { "group": "Start", "pages": [ - "index", - "why", - "not-only-agents" + "docs", + "docs/why", + "docs/not-only-agents" ] }, { "group": "Get started", "pages": [ - "get-started/install", - "get-started/quickstart", - "get-started/three-ways-in", - "get-started/choosing" + "docs/get-started/install", + "docs/get-started/quickstart", + "docs/get-started/three-ways-in", + "docs/get-started/choosing" ] }, { "group": "Demos", "pages": [ - "demos/index", - "try-it", - "demos/medical-affairs" + "docs/demos/index", + "docs/try-it", + "docs/demos/medical-affairs" ] }, { "group": "Production", "pages": [ - "production/index", - "production/postgres", - "postgres", - "production/how-reservation-works", - "production/migrations", - "production/recovery", - "production/receipt-integrity", - "production/soak", - "production/operations" + "docs/production/index", + "docs/production/postgres", + "docs/postgres", + "docs/production/how-reservation-works", + "docs/production/migrations", + "docs/production/recovery", + "docs/production/receipt-integrity", + "docs/production/soak", + "docs/production/operations" ] }, { "group": "MCP", "pages": [ - "mcp/overview", - "mcp/gateway-in-5-minutes", - "mcp/approve-from-your-assistant", - "mcp/use-the-docs-from-your-editor" + "docs/mcp/overview", + "docs/mcp/gateway-in-5-minutes", + "docs/mcp/approve-from-your-assistant", + "docs/mcp/use-the-docs-from-your-editor" ] }, { "group": "Concepts", "pages": [ - "concepts/action-and-hash", - "concepts/decisions", - "concepts/approval-binding", - "concepts/effect-keys", - "concepts/outcomes-and-ambiguous", - "concepts/receipts-and-evidence", - "concepts/authority-and-delegation", - "concepts/observe-mode", - "concepts/fail-closed" + "docs/concepts/action-and-hash", + "docs/concepts/decisions", + "docs/concepts/approval-binding", + "docs/concepts/effect-keys", + "docs/concepts/outcomes-and-ambiguous", + "docs/concepts/receipts-and-evidence", + "docs/concepts/authority-and-delegation", + "docs/concepts/observe-mode", + "docs/concepts/fail-closed" ] }, { "group": "Guides", "pages": [ - "guides/protect-a-function", - "guides/gateway-in-front-of-mcp", - "guides/approvals-in-slack", - "guides/resolve-an-ambiguous-effect", - "guides/reconcile-automatically", - "guides/observe-to-enforce", - "guides/run-on-postgres", - "guides/verify-in-ci", - "guides/export-to-opentelemetry", - "guides/langgraph-adapter", - "guides/openai-agents-adapter" + "docs/guides/protect-a-function", + "docs/guides/gateway-in-front-of-mcp", + "docs/guides/approvals-in-slack", + "docs/guides/resolve-an-ambiguous-effect", + "docs/guides/reconcile-automatically", + "docs/guides/observe-to-enforce", + "docs/guides/run-on-postgres", + "docs/guides/verify-in-ci", + "docs/guides/export-to-opentelemetry", + "docs/guides/langgraph-adapter", + "docs/guides/openai-agents-adapter" ] }, { "group": "Cookbook", "pages": [ - "cookbook/index", + "docs/cookbook/index", { "group": "Money", "pages": [ - "cookbook/refund-agent", - "cookbook/payout-maker-checker" + "docs/cookbook/refund-agent", + "docs/cookbook/payout-maker-checker" ] }, { "group": "Infrastructure", "pages": [ - "cookbook/deploy-agent", - "cookbook/database-migration-agent" + "docs/cookbook/deploy-agent", + "docs/cookbook/database-migration-agent" ] }, { "group": "Permissions", "pages": [ - "cookbook/iam-agent", - "cookbook/credential-rotation-agent" + "docs/cookbook/iam-agent", + "docs/cookbook/credential-rotation-agent" ] }, { "group": "Records", "pages": [ - "cookbook/crm-update-agent", - "cookbook/data-deletion-agent" + "docs/cookbook/crm-update-agent", + "docs/cookbook/data-deletion-agent" ] }, { "group": "Communications", "pages": [ - "cookbook/outbound-email-agent", - "cookbook/customer-notification-agent" + "docs/cookbook/outbound-email-agent", + "docs/cookbook/customer-notification-agent" ] }, { "group": "Multi-agent", "pages": [ - "cookbook/manager-and-worker" + "docs/cookbook/manager-and-worker" ] }, { "group": "Integrations", "pages": [ - "cookbook/protect-an-mcp-server", - "cookbook/langgraph-interrupt", - "cookbook/openai-agents-tool-approval", - "cookbook/slack-approvals", - "cookbook/receipts-to-opentelemetry" + "docs/cookbook/protect-an-mcp-server", + "docs/cookbook/langgraph-interrupt", + "docs/cookbook/openai-agents-tool-approval", + "docs/cookbook/slack-approvals", + "docs/cookbook/receipts-to-opentelemetry" ] }, { "group": "Operations", "pages": [ - "cookbook/observe-then-enforce", - "cookbook/resolve-an-ambiguous-effect", - "cookbook/reconcile-against-the-remote", - "cookbook/verify-in-github-actions", - "cookbook/sqlite-to-postgres" + "docs/cookbook/observe-then-enforce", + "docs/cookbook/resolve-an-ambiguous-effect", + "docs/cookbook/reconcile-against-the-remote", + "docs/cookbook/verify-in-github-actions", + "docs/cookbook/sqlite-to-postgres" ] } ], @@ -180,24 +193,24 @@ { "group": "Compare", "pages": [ - "compare/framework-hitl", - "compare/guardrail-libraries", - "compare/governance-toolkits", - "compare/durable-workflows", - "compare/idempotency-keys" + "docs/compare/framework-hitl", + "docs/compare/guardrail-libraries", + "docs/compare/governance-toolkits", + "docs/compare/durable-workflows", + "docs/compare/idempotency-keys" ] }, { "group": "FAQ", "pages": [ - "faq" + "docs/faq" ] }, { "group": "More", "pages": [ - "verify/get-the-badge", - "study/does-your-framework-double-execute" + "docs/verify/get-the-badge", + "docs/study/does-your-framework-double-execute" ] } ] @@ -208,88 +221,88 @@ { "group": "Reference", "pages": [ - "reference/policy-yaml", - "reference/authority-yaml", - "reference/cli", - "reference/receipt-and-event-schemas", - "reference/exit-codes", - "reference/errors" + "docs/reference/policy-yaml", + "docs/reference/authority-yaml", + "docs/reference/cli", + "docs/reference/receipt-and-event-schemas", + "docs/reference/exit-codes", + "docs/reference/errors" ] }, { "group": "Python API", "pages": [ - "reference/api/index", - "reference/api/Action", - "reference/api/ActionDenied", - "reference/api/AmbiguousEffect", - "reference/api/Approval", - "reference/api/ApprovalAnswer", - "reference/api/ApprovalMismatch", - "reference/api/ApprovalProvider", - "reference/api/ApprovalRequest", - "reference/api/ApprovalRequired", - "reference/api/ApprovalTimeout", - "reference/api/Authority", - "reference/api/AuthorityDenied", - "reference/api/AuthorityEscalation", - "reference/api/AuthorityResult", - "reference/api/CTRLRunError", - "reference/api/Condition", - "reference/api/Control", - "reference/api/Decision", - "reference/api/Delegation", - "reference/api/DelegationRecord", - "reference/api/DuplicateEffect", - "reference/api/EffectKeyError", - "reference/api/EffectRecord", - "reference/api/EffectState", - "reference/api/Event", - "reference/api/EventSink", - "reference/api/FrameworkInterrupt", - "reference/api/Grant", - "reference/api/HeaderIdentityProvider", - "reference/api/IdentityContext", - "reference/api/IdentityError", - "reference/api/IdentityProvider", - "reference/api/InMemoryStateStore", - "reference/api/InterruptApprovalProvider", - "reference/api/InvalidArgument", - "reference/api/JSONLEventSink", - "reference/api/LocalApprovalProvider", - "reference/api/MissingDependency", - "reference/api/NotExecuted", - "reference/api/PendingApproval", - "reference/api/Policy", - "reference/api/PolicyError", - "reference/api/Principal", - "reference/api/Receipt", - "reference/api/ReconcileOutcome", - "reference/api/SQLiteStateStore", - "reference/api/SchemaMismatch", - "reference/api/ScriptedApprovalProvider", - "reference/api/StateStore", - "reference/api/StaticIdentityProvider", - "reference/api/Subject", - "reference/api/Suspended", - "reference/api/WebhookApprovalProvider", - "reference/api/acs-AcsControlHook", - "reference/api/action_hash", - "reference/api/banner", - "reference/api/canonical_bytes", - "reference/api/canonicalize", - "reference/api/conformance-run", - "reference/api/conformance-store-run", - "reference/api/context", - "reference/api/gateway-serve", - "reference/api/jwt_identity-JWTIdentityProvider", - "reference/api/needs_approval", - "reference/api/otel-OTelEventSink", - "reference/api/parse_conditions", - "reference/api/postgres-PostgresStateStore", - "reference/api/protect", - "reference/api/verify-run", - "reference/api/with_approval" + "docs/reference/api/index", + "docs/reference/api/Action", + "docs/reference/api/ActionDenied", + "docs/reference/api/AmbiguousEffect", + "docs/reference/api/Approval", + "docs/reference/api/ApprovalAnswer", + "docs/reference/api/ApprovalMismatch", + "docs/reference/api/ApprovalProvider", + "docs/reference/api/ApprovalRequest", + "docs/reference/api/ApprovalRequired", + "docs/reference/api/ApprovalTimeout", + "docs/reference/api/Authority", + "docs/reference/api/AuthorityDenied", + "docs/reference/api/AuthorityEscalation", + "docs/reference/api/AuthorityResult", + "docs/reference/api/CTRLRunError", + "docs/reference/api/Condition", + "docs/reference/api/Control", + "docs/reference/api/Decision", + "docs/reference/api/Delegation", + "docs/reference/api/DelegationRecord", + "docs/reference/api/DuplicateEffect", + "docs/reference/api/EffectKeyError", + "docs/reference/api/EffectRecord", + "docs/reference/api/EffectState", + "docs/reference/api/Event", + "docs/reference/api/EventSink", + "docs/reference/api/FrameworkInterrupt", + "docs/reference/api/Grant", + "docs/reference/api/HeaderIdentityProvider", + "docs/reference/api/IdentityContext", + "docs/reference/api/IdentityError", + "docs/reference/api/IdentityProvider", + "docs/reference/api/InMemoryStateStore", + "docs/reference/api/InterruptApprovalProvider", + "docs/reference/api/InvalidArgument", + "docs/reference/api/JSONLEventSink", + "docs/reference/api/LocalApprovalProvider", + "docs/reference/api/MissingDependency", + "docs/reference/api/NotExecuted", + "docs/reference/api/PendingApproval", + "docs/reference/api/Policy", + "docs/reference/api/PolicyError", + "docs/reference/api/Principal", + "docs/reference/api/Receipt", + "docs/reference/api/ReconcileOutcome", + "docs/reference/api/SQLiteStateStore", + "docs/reference/api/SchemaMismatch", + "docs/reference/api/ScriptedApprovalProvider", + "docs/reference/api/StateStore", + "docs/reference/api/StaticIdentityProvider", + "docs/reference/api/Subject", + "docs/reference/api/Suspended", + "docs/reference/api/WebhookApprovalProvider", + "docs/reference/api/acs-AcsControlHook", + "docs/reference/api/action_hash", + "docs/reference/api/banner", + "docs/reference/api/canonical_bytes", + "docs/reference/api/canonicalize", + "docs/reference/api/conformance-run", + "docs/reference/api/conformance-store-run", + "docs/reference/api/context", + "docs/reference/api/gateway-serve", + "docs/reference/api/jwt_identity-JWTIdentityProvider", + "docs/reference/api/needs_approval", + "docs/reference/api/otel-OTelEventSink", + "docs/reference/api/parse_conditions", + "docs/reference/api/postgres-PostgresStateStore", + "docs/reference/api/protect", + "docs/reference/api/verify-run", + "docs/reference/api/with_approval" ], "expanded": false } @@ -301,30 +314,30 @@ { "group": "Architecture", "pages": [ - "ARCHITECTURE", - "architecture/specifications" + "docs/ARCHITECTURE", + "docs/architecture/specifications" ] }, { "group": "Security", "pages": [ - "security/verify-guarantees", - "security/receipt-chain", - "security/disclosure", - "THREAT_MODEL", - "how-this-is-built" + "docs/security/verify-guarantees", + "docs/security/receipt-chain", + "docs/security/disclosure", + "docs/THREAT_MODEL", + "docs/how-this-is-built" ] }, { "group": "Readings and references", "pages": [ - "OWASP-AGENTIC-TOP10", - "ACS", - "adapters", - "authority", - "verify", - "CLAIMS", - "ROADMAP" + "docs/OWASP-AGENTIC-TOP10", + "docs/ACS", + "docs/adapters", + "docs/authority", + "docs/verify", + "docs/CLAIMS", + "docs/ROADMAP" ] } ] @@ -345,5 +358,821 @@ }, "indexing": "navigable" }, - "redirects": [] + "redirects": [ + { + "source": "/why", + "destination": "/docs/why", + "permanent": true + }, + { + "source": "/not-only-agents", + "destination": "/docs/not-only-agents", + "permanent": true + }, + { + "source": "/get-started/install", + "destination": "/docs/get-started/install", + "permanent": true + }, + { + "source": "/get-started/quickstart", + "destination": "/docs/get-started/quickstart", + "permanent": true + }, + { + "source": "/get-started/three-ways-in", + "destination": "/docs/get-started/three-ways-in", + "permanent": true + }, + { + "source": "/get-started/choosing", + "destination": "/docs/get-started/choosing", + "permanent": true + }, + { + "source": "/demos/index", + "destination": "/docs/demos/index", + "permanent": true + }, + { + "source": "/try-it", + "destination": "/docs/try-it", + "permanent": true + }, + { + "source": "/demos/medical-affairs", + "destination": "/docs/demos/medical-affairs", + "permanent": true + }, + { + "source": "/production/index", + "destination": "/docs/production/index", + "permanent": true + }, + { + "source": "/production/postgres", + "destination": "/docs/production/postgres", + "permanent": true + }, + { + "source": "/postgres", + "destination": "/docs/postgres", + "permanent": true + }, + { + "source": "/production/how-reservation-works", + "destination": "/docs/production/how-reservation-works", + "permanent": true + }, + { + "source": "/production/migrations", + "destination": "/docs/production/migrations", + "permanent": true + }, + { + "source": "/production/recovery", + "destination": "/docs/production/recovery", + "permanent": true + }, + { + "source": "/production/receipt-integrity", + "destination": "/docs/production/receipt-integrity", + "permanent": true + }, + { + "source": "/production/soak", + "destination": "/docs/production/soak", + "permanent": true + }, + { + "source": "/production/operations", + "destination": "/docs/production/operations", + "permanent": true + }, + { + "source": "/mcp/overview", + "destination": "/docs/mcp/overview", + "permanent": true + }, + { + "source": "/mcp/gateway-in-5-minutes", + "destination": "/docs/mcp/gateway-in-5-minutes", + "permanent": true + }, + { + "source": "/mcp/approve-from-your-assistant", + "destination": "/docs/mcp/approve-from-your-assistant", + "permanent": true + }, + { + "source": "/mcp/use-the-docs-from-your-editor", + "destination": "/docs/mcp/use-the-docs-from-your-editor", + "permanent": true + }, + { + "source": "/concepts/action-and-hash", + "destination": "/docs/concepts/action-and-hash", + "permanent": true + }, + { + "source": "/concepts/decisions", + "destination": "/docs/concepts/decisions", + "permanent": true + }, + { + "source": "/concepts/approval-binding", + "destination": "/docs/concepts/approval-binding", + "permanent": true + }, + { + "source": "/concepts/effect-keys", + "destination": "/docs/concepts/effect-keys", + "permanent": true + }, + { + "source": "/concepts/outcomes-and-ambiguous", + "destination": "/docs/concepts/outcomes-and-ambiguous", + "permanent": true + }, + { + "source": "/concepts/receipts-and-evidence", + "destination": "/docs/concepts/receipts-and-evidence", + "permanent": true + }, + { + "source": "/concepts/authority-and-delegation", + "destination": "/docs/concepts/authority-and-delegation", + "permanent": true + }, + { + "source": "/concepts/observe-mode", + "destination": "/docs/concepts/observe-mode", + "permanent": true + }, + { + "source": "/concepts/fail-closed", + "destination": "/docs/concepts/fail-closed", + "permanent": true + }, + { + "source": "/guides/protect-a-function", + "destination": "/docs/guides/protect-a-function", + "permanent": true + }, + { + "source": "/guides/gateway-in-front-of-mcp", + "destination": "/docs/guides/gateway-in-front-of-mcp", + "permanent": true + }, + { + "source": "/guides/approvals-in-slack", + "destination": "/docs/guides/approvals-in-slack", + "permanent": true + }, + { + "source": "/guides/resolve-an-ambiguous-effect", + "destination": "/docs/guides/resolve-an-ambiguous-effect", + "permanent": true + }, + { + "source": "/guides/reconcile-automatically", + "destination": "/docs/guides/reconcile-automatically", + "permanent": true + }, + { + "source": "/guides/observe-to-enforce", + "destination": "/docs/guides/observe-to-enforce", + "permanent": true + }, + { + "source": "/guides/run-on-postgres", + "destination": "/docs/guides/run-on-postgres", + "permanent": true + }, + { + "source": "/guides/verify-in-ci", + "destination": "/docs/guides/verify-in-ci", + "permanent": true + }, + { + "source": "/guides/export-to-opentelemetry", + "destination": "/docs/guides/export-to-opentelemetry", + "permanent": true + }, + { + "source": "/guides/langgraph-adapter", + "destination": "/docs/guides/langgraph-adapter", + "permanent": true + }, + { + "source": "/guides/openai-agents-adapter", + "destination": "/docs/guides/openai-agents-adapter", + "permanent": true + }, + { + "source": "/cookbook/index", + "destination": "/docs/cookbook/index", + "permanent": true + }, + { + "source": "/cookbook/refund-agent", + "destination": "/docs/cookbook/refund-agent", + "permanent": true + }, + { + "source": "/cookbook/payout-maker-checker", + "destination": "/docs/cookbook/payout-maker-checker", + "permanent": true + }, + { + "source": "/cookbook/deploy-agent", + "destination": "/docs/cookbook/deploy-agent", + "permanent": true + }, + { + "source": "/cookbook/database-migration-agent", + "destination": "/docs/cookbook/database-migration-agent", + "permanent": true + }, + { + "source": "/cookbook/iam-agent", + "destination": "/docs/cookbook/iam-agent", + "permanent": true + }, + { + "source": "/cookbook/credential-rotation-agent", + "destination": "/docs/cookbook/credential-rotation-agent", + "permanent": true + }, + { + "source": "/cookbook/crm-update-agent", + "destination": "/docs/cookbook/crm-update-agent", + "permanent": true + }, + { + "source": "/cookbook/data-deletion-agent", + "destination": "/docs/cookbook/data-deletion-agent", + "permanent": true + }, + { + "source": "/cookbook/outbound-email-agent", + "destination": "/docs/cookbook/outbound-email-agent", + "permanent": true + }, + { + "source": "/cookbook/customer-notification-agent", + "destination": "/docs/cookbook/customer-notification-agent", + "permanent": true + }, + { + "source": "/cookbook/manager-and-worker", + "destination": "/docs/cookbook/manager-and-worker", + "permanent": true + }, + { + "source": "/cookbook/protect-an-mcp-server", + "destination": "/docs/cookbook/protect-an-mcp-server", + "permanent": true + }, + { + "source": "/cookbook/langgraph-interrupt", + "destination": "/docs/cookbook/langgraph-interrupt", + "permanent": true + }, + { + "source": "/cookbook/openai-agents-tool-approval", + "destination": "/docs/cookbook/openai-agents-tool-approval", + "permanent": true + }, + { + "source": "/cookbook/slack-approvals", + "destination": "/docs/cookbook/slack-approvals", + "permanent": true + }, + { + "source": "/cookbook/receipts-to-opentelemetry", + "destination": "/docs/cookbook/receipts-to-opentelemetry", + "permanent": true + }, + { + "source": "/cookbook/observe-then-enforce", + "destination": "/docs/cookbook/observe-then-enforce", + "permanent": true + }, + { + "source": "/cookbook/resolve-an-ambiguous-effect", + "destination": "/docs/cookbook/resolve-an-ambiguous-effect", + "permanent": true + }, + { + "source": "/cookbook/reconcile-against-the-remote", + "destination": "/docs/cookbook/reconcile-against-the-remote", + "permanent": true + }, + { + "source": "/cookbook/verify-in-github-actions", + "destination": "/docs/cookbook/verify-in-github-actions", + "permanent": true + }, + { + "source": "/cookbook/sqlite-to-postgres", + "destination": "/docs/cookbook/sqlite-to-postgres", + "permanent": true + }, + { + "source": "/compare/framework-hitl", + "destination": "/docs/compare/framework-hitl", + "permanent": true + }, + { + "source": "/compare/guardrail-libraries", + "destination": "/docs/compare/guardrail-libraries", + "permanent": true + }, + { + "source": "/compare/governance-toolkits", + "destination": "/docs/compare/governance-toolkits", + "permanent": true + }, + { + "source": "/compare/durable-workflows", + "destination": "/docs/compare/durable-workflows", + "permanent": true + }, + { + "source": "/compare/idempotency-keys", + "destination": "/docs/compare/idempotency-keys", + "permanent": true + }, + { + "source": "/faq", + "destination": "/docs/faq", + "permanent": true + }, + { + "source": "/verify/get-the-badge", + "destination": "/docs/verify/get-the-badge", + "permanent": true + }, + { + "source": "/study/does-your-framework-double-execute", + "destination": "/docs/study/does-your-framework-double-execute", + "permanent": true + }, + { + "source": "/reference/policy-yaml", + "destination": "/docs/reference/policy-yaml", + "permanent": true + }, + { + "source": "/reference/authority-yaml", + "destination": "/docs/reference/authority-yaml", + "permanent": true + }, + { + "source": "/reference/cli", + "destination": "/docs/reference/cli", + "permanent": true + }, + { + "source": "/reference/receipt-and-event-schemas", + "destination": "/docs/reference/receipt-and-event-schemas", + "permanent": true + }, + { + "source": "/reference/exit-codes", + "destination": "/docs/reference/exit-codes", + "permanent": true + }, + { + "source": "/reference/errors", + "destination": "/docs/reference/errors", + "permanent": true + }, + { + "source": "/reference/api/index", + "destination": "/docs/reference/api/index", + "permanent": true + }, + { + "source": "/reference/api/Action", + "destination": "/docs/reference/api/Action", + "permanent": true + }, + { + "source": "/reference/api/ActionDenied", + "destination": "/docs/reference/api/ActionDenied", + "permanent": true + }, + { + "source": "/reference/api/AmbiguousEffect", + "destination": "/docs/reference/api/AmbiguousEffect", + "permanent": true + }, + { + "source": "/reference/api/Approval", + "destination": "/docs/reference/api/Approval", + "permanent": true + }, + { + "source": "/reference/api/ApprovalAnswer", + "destination": "/docs/reference/api/ApprovalAnswer", + "permanent": true + }, + { + "source": "/reference/api/ApprovalMismatch", + "destination": "/docs/reference/api/ApprovalMismatch", + "permanent": true + }, + { + "source": "/reference/api/ApprovalProvider", + "destination": "/docs/reference/api/ApprovalProvider", + "permanent": true + }, + { + "source": "/reference/api/ApprovalRequest", + "destination": "/docs/reference/api/ApprovalRequest", + "permanent": true + }, + { + "source": "/reference/api/ApprovalRequired", + "destination": "/docs/reference/api/ApprovalRequired", + "permanent": true + }, + { + "source": "/reference/api/ApprovalTimeout", + "destination": "/docs/reference/api/ApprovalTimeout", + "permanent": true + }, + { + "source": "/reference/api/Authority", + "destination": "/docs/reference/api/Authority", + "permanent": true + }, + { + "source": "/reference/api/AuthorityDenied", + "destination": "/docs/reference/api/AuthorityDenied", + "permanent": true + }, + { + "source": "/reference/api/AuthorityEscalation", + "destination": "/docs/reference/api/AuthorityEscalation", + "permanent": true + }, + { + "source": "/reference/api/AuthorityResult", + "destination": "/docs/reference/api/AuthorityResult", + "permanent": true + }, + { + "source": "/reference/api/CTRLRunError", + "destination": "/docs/reference/api/CTRLRunError", + "permanent": true + }, + { + "source": "/reference/api/Condition", + "destination": "/docs/reference/api/Condition", + "permanent": true + }, + { + "source": "/reference/api/Control", + "destination": "/docs/reference/api/Control", + "permanent": true + }, + { + "source": "/reference/api/Decision", + "destination": "/docs/reference/api/Decision", + "permanent": true + }, + { + "source": "/reference/api/Delegation", + "destination": "/docs/reference/api/Delegation", + "permanent": true + }, + { + "source": "/reference/api/DelegationRecord", + "destination": "/docs/reference/api/DelegationRecord", + "permanent": true + }, + { + "source": "/reference/api/DuplicateEffect", + "destination": "/docs/reference/api/DuplicateEffect", + "permanent": true + }, + { + "source": "/reference/api/EffectKeyError", + "destination": "/docs/reference/api/EffectKeyError", + "permanent": true + }, + { + "source": "/reference/api/EffectRecord", + "destination": "/docs/reference/api/EffectRecord", + "permanent": true + }, + { + "source": "/reference/api/EffectState", + "destination": "/docs/reference/api/EffectState", + "permanent": true + }, + { + "source": "/reference/api/Event", + "destination": "/docs/reference/api/Event", + "permanent": true + }, + { + "source": "/reference/api/EventSink", + "destination": "/docs/reference/api/EventSink", + "permanent": true + }, + { + "source": "/reference/api/FrameworkInterrupt", + "destination": "/docs/reference/api/FrameworkInterrupt", + "permanent": true + }, + { + "source": "/reference/api/Grant", + "destination": "/docs/reference/api/Grant", + "permanent": true + }, + { + "source": "/reference/api/HeaderIdentityProvider", + "destination": "/docs/reference/api/HeaderIdentityProvider", + "permanent": true + }, + { + "source": "/reference/api/IdentityContext", + "destination": "/docs/reference/api/IdentityContext", + "permanent": true + }, + { + "source": "/reference/api/IdentityError", + "destination": "/docs/reference/api/IdentityError", + "permanent": true + }, + { + "source": "/reference/api/IdentityProvider", + "destination": "/docs/reference/api/IdentityProvider", + "permanent": true + }, + { + "source": "/reference/api/InMemoryStateStore", + "destination": "/docs/reference/api/InMemoryStateStore", + "permanent": true + }, + { + "source": "/reference/api/InterruptApprovalProvider", + "destination": "/docs/reference/api/InterruptApprovalProvider", + "permanent": true + }, + { + "source": "/reference/api/InvalidArgument", + "destination": "/docs/reference/api/InvalidArgument", + "permanent": true + }, + { + "source": "/reference/api/JSONLEventSink", + "destination": "/docs/reference/api/JSONLEventSink", + "permanent": true + }, + { + "source": "/reference/api/LocalApprovalProvider", + "destination": "/docs/reference/api/LocalApprovalProvider", + "permanent": true + }, + { + "source": "/reference/api/MissingDependency", + "destination": "/docs/reference/api/MissingDependency", + "permanent": true + }, + { + "source": "/reference/api/NotExecuted", + "destination": "/docs/reference/api/NotExecuted", + "permanent": true + }, + { + "source": "/reference/api/PendingApproval", + "destination": "/docs/reference/api/PendingApproval", + "permanent": true + }, + { + "source": "/reference/api/Policy", + "destination": "/docs/reference/api/Policy", + "permanent": true + }, + { + "source": "/reference/api/PolicyError", + "destination": "/docs/reference/api/PolicyError", + "permanent": true + }, + { + "source": "/reference/api/Principal", + "destination": "/docs/reference/api/Principal", + "permanent": true + }, + { + "source": "/reference/api/Receipt", + "destination": "/docs/reference/api/Receipt", + "permanent": true + }, + { + "source": "/reference/api/ReconcileOutcome", + "destination": "/docs/reference/api/ReconcileOutcome", + "permanent": true + }, + { + "source": "/reference/api/SQLiteStateStore", + "destination": "/docs/reference/api/SQLiteStateStore", + "permanent": true + }, + { + "source": "/reference/api/SchemaMismatch", + "destination": "/docs/reference/api/SchemaMismatch", + "permanent": true + }, + { + "source": "/reference/api/ScriptedApprovalProvider", + "destination": "/docs/reference/api/ScriptedApprovalProvider", + "permanent": true + }, + { + "source": "/reference/api/StateStore", + "destination": "/docs/reference/api/StateStore", + "permanent": true + }, + { + "source": "/reference/api/StaticIdentityProvider", + "destination": "/docs/reference/api/StaticIdentityProvider", + "permanent": true + }, + { + "source": "/reference/api/Subject", + "destination": "/docs/reference/api/Subject", + "permanent": true + }, + { + "source": "/reference/api/Suspended", + "destination": "/docs/reference/api/Suspended", + "permanent": true + }, + { + "source": "/reference/api/WebhookApprovalProvider", + "destination": "/docs/reference/api/WebhookApprovalProvider", + "permanent": true + }, + { + "source": "/reference/api/acs-AcsControlHook", + "destination": "/docs/reference/api/acs-AcsControlHook", + "permanent": true + }, + { + "source": "/reference/api/action_hash", + "destination": "/docs/reference/api/action_hash", + "permanent": true + }, + { + "source": "/reference/api/banner", + "destination": "/docs/reference/api/banner", + "permanent": true + }, + { + "source": "/reference/api/canonical_bytes", + "destination": "/docs/reference/api/canonical_bytes", + "permanent": true + }, + { + "source": "/reference/api/canonicalize", + "destination": "/docs/reference/api/canonicalize", + "permanent": true + }, + { + "source": "/reference/api/conformance-run", + "destination": "/docs/reference/api/conformance-run", + "permanent": true + }, + { + "source": "/reference/api/conformance-store-run", + "destination": "/docs/reference/api/conformance-store-run", + "permanent": true + }, + { + "source": "/reference/api/context", + "destination": "/docs/reference/api/context", + "permanent": true + }, + { + "source": "/reference/api/gateway-serve", + "destination": "/docs/reference/api/gateway-serve", + "permanent": true + }, + { + "source": "/reference/api/jwt_identity-JWTIdentityProvider", + "destination": "/docs/reference/api/jwt_identity-JWTIdentityProvider", + "permanent": true + }, + { + "source": "/reference/api/needs_approval", + "destination": "/docs/reference/api/needs_approval", + "permanent": true + }, + { + "source": "/reference/api/otel-OTelEventSink", + "destination": "/docs/reference/api/otel-OTelEventSink", + "permanent": true + }, + { + "source": "/reference/api/parse_conditions", + "destination": "/docs/reference/api/parse_conditions", + "permanent": true + }, + { + "source": "/reference/api/postgres-PostgresStateStore", + "destination": "/docs/reference/api/postgres-PostgresStateStore", + "permanent": true + }, + { + "source": "/reference/api/protect", + "destination": "/docs/reference/api/protect", + "permanent": true + }, + { + "source": "/reference/api/verify-run", + "destination": "/docs/reference/api/verify-run", + "permanent": true + }, + { + "source": "/reference/api/with_approval", + "destination": "/docs/reference/api/with_approval", + "permanent": true + }, + { + "source": "/ARCHITECTURE", + "destination": "/docs/ARCHITECTURE", + "permanent": true + }, + { + "source": "/architecture/specifications", + "destination": "/docs/architecture/specifications", + "permanent": true + }, + { + "source": "/security/verify-guarantees", + "destination": "/docs/security/verify-guarantees", + "permanent": true + }, + { + "source": "/security/receipt-chain", + "destination": "/docs/security/receipt-chain", + "permanent": true + }, + { + "source": "/security/disclosure", + "destination": "/docs/security/disclosure", + "permanent": true + }, + { + "source": "/THREAT_MODEL", + "destination": "/docs/THREAT_MODEL", + "permanent": true + }, + { + "source": "/how-this-is-built", + "destination": "/docs/how-this-is-built", + "permanent": true + }, + { + "source": "/OWASP-AGENTIC-TOP10", + "destination": "/docs/OWASP-AGENTIC-TOP10", + "permanent": true + }, + { + "source": "/ACS", + "destination": "/docs/ACS", + "permanent": true + }, + { + "source": "/adapters", + "destination": "/docs/adapters", + "permanent": true + }, + { + "source": "/authority", + "destination": "/docs/authority", + "permanent": true + }, + { + "source": "/verify", + "destination": "/docs/verify", + "permanent": true + }, + { + "source": "/CLAIMS", + "destination": "/docs/CLAIMS", + "permanent": true + }, + { + "source": "/ROADMAP", + "destination": "/docs/ROADMAP", + "permanent": true + } + ] } diff --git a/docs/docs.mdx b/docs/docs.mdx new file mode 100644 index 00000000..817cdc29 --- /dev/null +++ b/docs/docs.mdx @@ -0,0 +1,291 @@ +--- +title: "The execution safety layer for AI agents" +sidebarTitle: "Overview" +description: "The last check before an AI agent does something it can't undo. Autonomy belongs to the action, not the agent." +mode: "wide" +"og:title": "CTRLRun — the execution safety layer for AI agents" +"twitter:title": "CTRLRun — the execution safety layer for AI agents" +canonical: "https://ctrlrun.dev/docs" +--- + +CTRLRun is a Python library that sits between an agent's decision to act and the call that acts. +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. + +```bash +pip install ctrlrun && ctrlrun demo +``` + + + + No install. Approve €2,000, execute €5,000, lose a reply, retry: real refusals, in this tab. + + + One policy file, one decorator, one approval from the shell, three receipts. + + + +**Runs in production on a single file, or on Postgres across hosts.** SQLite is the default and +is production-grade on one host; Postgres is for many. Apache-2.0. + +## Protect one function + +CTRLRun wraps the call that has the consequence, and a YAML file says how much autonomy that +call gets. This is the whole integration for a function in your own process: + +```yaml runnable +schema: ctrlrun.policy/v2 + +actions: + stripe.refund: + effect: "refund:{payment_id}" + rules: + - when: { amount_gte: 0, amount_lte: 50000 } # up to €500: autonomous + decision: allow + - when: { amount_gte: 0, amount_lte: 500000 } # up to €5,000: a human decides + decision: approve + - decision: deny # above that: never +``` + +```python runnable +import ctrlrun + + +class Stripe: # stands in for the real client so this block runs offline + def refund(self, payment_id: str, amount: int) -> dict: + return {"status": "succeeded"} + + +stripe = Stripe() + + +@ctrlrun.protect("stripe.refund", effect="refund:{payment_id}") +def refund(payment_id: str, amount: int) -> dict: + return stripe.refund(payment_id, amount) + + +with ctrlrun.context(agent="refund-agent"): + refund(payment_id="txn_1", amount=10000) # €100: runs, and leaves a receipt + try: + refund(payment_id="txn_2", amount=200000) # €2,000: waits for a human + except ctrlrun.ApprovalRequired as pending: + print("a human decides:", pending.request_id) + else: + raise SystemExit("the €2,000 refund ran without a human; the policy is not in force") +``` + +What the same function does next, and what stops it: + +| The agent | CTRLRun | +|---|---| +| refunds €100 | runs it; one receipt | +| refunds €2,000 | raises `ApprovalRequired`; `ctrlrun approve ` from the shell lets it through | +| has €2,000 approved, executes €5,000 | `ApprovalMismatch`: the approval is bound to the action a human saw | +| refunds €20,000 | `ActionDenied`; no request is created | +| retries a refund whose reply was lost | `AmbiguousEffect`: the remote may have committed; a human or a reconcile hook decides | +| runs the same refund from two workers | one reserves `refund:txn_1`, the other gets `DuplicateEffect` | + +The refund is the first example because everyone understands it; the same file protects a +`kubectl delete`, an IAM grant, a record deletion or an outbound email, and the +[cookbook](/docs/cookbook/index) has each of those as a runnable recipe. + +## What the demo shows + +Five ways an agent action goes wrong, and what stops each one, in under a second with no network. +The first scenario is the one that explains the product: a refund commits at the remote, the +reply is lost, the agent retries, and the retry is refused. The customer was refunded once. + +```console +$ ctrlrun demo +CTRLRun demo — five ways an agent action goes wrong, and what stops it. +Policy: refunds up to €1,000 are autonomous, up to €10,000 need a human, above that are denied. + +1. Duplicate effect after a lost response + + refund €500 → remote commits → response lost → effect: AMBIGUOUS + agent retries the same refund + ✗ BLOCKED — effect may already have committed; blind retry refused + remote refund calls: 1 + only a human moves it on: ctrlrun resolve refund:txn_1 --committed|--failed +``` + +The other four are approval mutation, two agents racing for one effect, approval replay, and an +agent trying to act outside what was delegated to it. [Try it in your browser](/docs/try-it) runs +the same demo without an install and lets you break one refund yourself, or read the full transcript in the +[repository README](https://github.com/CTRLRun/ctrlrun#what-ctrlrun-demo-shows). + +## What it does + +{/* generated from docs/capabilities.yaml (mdx) — edit the YAML, never this grid */} + + + An approval is bound to the exact action; a mutated or replayed one is refused. Since v0.1. + + + One logical effect happens at most once, across threads, processes and hosts. Since v0.1. + + + An unknown outcome is AMBIGUOUS, never FAILED, and blocks a blind retry. Since v0.1. + + + An unknown action, a missing policy or a missing principal is denied. Since v0.1. + + + With authority on, every principal needs a grant, and delegation cannot widen one. Since v0.3. + + + Every executed action leaves a portable JSON receipt of who, what and outcome. Since v0.1. + + + + + + One YAML file decides allow, approve or deny per action and argument. Since v0.1. + + + Approve, deny, resolve, inspect and count from the shell, against any store. Since v0.1. + + + Every guarantee in front of an MCP tool server, with no agent changes. Since v0.2. + + + A reconcile hook asks the remote what happened and resolves an AMBIGUOUS effect. Since v0.2. + + + Approval requests go to a webhook, such as Slack, and the answer comes back. Since v0.2. + + + One span per action, one span event per step; argument values are opt-in. Since v0.2. + + + A principal comes from a verified header or JWT; CTRLRun issues nothing. Since v0.3. + + + A principal narrows its own grant at runtime; one revocation cuts the chain. Since v0.3. + + + Records what enforcement would have blocked, blocks nothing, and counts it. Since v0.3. + + + Runs the guarantee catalogue against your policy and store; N/A is not a pass. Since v0.4. + + + A GitHub Action and a badge that means the declared guarantees pass. Since v0.4. + + + An approval routed through the framework's own interrupt; never a second path. Since v0.5. + + + SQLite on one host, Postgres across hosts, the same guarantees either way. Since v0.6. + + + The same store on Postgres, graded by the suite written for SQLite. Since v0.6. + + + Migrations run at open, forward only, and an unknown schema is refused. Since v0.6. + + + A dead worker's effect stays AMBIGUOUS until a human or a hook resolves it. Since v0.6. + + + Each receipt carries the hash of the one before; alteration is detected and named. Since v0.6. + + + Every receipt names the policy hash and version that decided it. Since v0.6. + + + Name the house controls an action satisfies, and receipts cite them. Since v0.6. + + + Label arguments by data class and condition a rule on the labels present. Since v0.6. + + + +{/* end generated */} + +## Three ways in + +| You have | Use | Needs | +|---|---|---| +| Python in this process: a raw model call, a LangChain tool, a hand-rolled loop, a cron job | the `@protect` decorator | nothing beyond `pip install ctrlrun` | +| Tools behind an MCP server, in any language | the gateway, `ctrlrun gateway` | `pip install "ctrlrun[gateway]"` | +| A framework with its own approval interrupt, and a place where humans already answer | an adapter | the framework to have a human-in-the-loop primitive | + +Most readers need the decorator. An adapter buys exactly one thing, routing an approval through +the framework's own interrupt, and a framework with no such primitive does not need one. +[Choosing between them](/docs/get-started/choosing) has the decision table. + +## Where it stands + +{/* generated from the suite, pyproject and the soak (mdx) — run the generator */} +- **Version 0.6.1**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later. +- **4,404 tests**, every version specified before it was written and every requirement mutation-tested. +- **11 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. +- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. +- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). +- **Each receipt carries the hash of the one before it**, so an alteration is detected and named. +- **Apache-2.0**, and the enforcement kernel stays open source. Releases carry PyPI provenance attestations from GitHub Actions. + +**Not yet:** + +- No external security audit. (planned for v0.8 or v0.9) +- No third-party review of the kernel. (every review so far was run inside this project) +- No sector packs. (the policy templates are starting points, not a product) +{/* end generated */} + +## Start here + + + + Protect one function end to end and read the receipt. Ten minutes. + + + One refund you can break six ways, and the five demo scenarios, on the released wheel. + + + Refunds, deploys, IAM, deletions, email, MCP, LangGraph: each a recipe that runs. + + + + + + Decorator, gateway, adapter: what each covers and what each needs. + + + The gateway in front of any MCP server, and this site as an MCP server. + + + Which store, what a lost `COMMIT` does, what survives a crash, and what to watch. + + + + + + The five principles, in 700 words. The page people link to. + + + The idea that explains the product: a timeout is not a failure. + + + +## Ask your coding tool + +This site is an MCP server. Add it to Cursor or any MCP client that takes an `mcpServers` +entry, and the assistant answers from these pages rather than from memory: + +```json +{ + "mcpServers": { + "ctrlrun-docs": { "type": "http", "url": "https://ctrlrun.dev/mcp" } + } +} +``` + +The server exposes one tool, a search across this documentation. When the site moves to its own +domain the URL moves with it; the current one is always in this block. + +## Next + +- [Why](/docs/why): what CTRLRun believes and why. +- [Install](/docs/get-started/install): what `pip install ctrlrun` puts on your machine, and what it does not. +- [How this is built](/docs/how-this-is-built): the discipline behind the guarantees. diff --git a/docs/ACS.md b/docs/docs/ACS.md similarity index 100% rename from docs/ACS.md rename to docs/docs/ACS.md diff --git a/docs/ARCHITECTURE.md b/docs/docs/ARCHITECTURE.md similarity index 99% rename from docs/ARCHITECTURE.md rename to docs/docs/ARCHITECTURE.md index ad03b7d2..a773bd3f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/docs/ARCHITECTURE.md @@ -239,4 +239,4 @@ refused at load (§4.7). Stable from v0.1 onward: the four public concepts, the action canonical form (versioned), the effect state machine, fail-closed defaults, the executor outcome mapping. -Expected to change: policy language (providers), StateStore backends, approval providers, receipt fields (additive only). See [`ROADMAP.md`](ROADMAP.md). +Expected to change: policy language (providers), StateStore backends, approval providers, receipt fields (additive only). See [`ROADMAP.md`](/docs/ROADMAP). diff --git a/docs/CLAIMS.md b/docs/docs/CLAIMS.md similarity index 97% rename from docs/CLAIMS.md rename to docs/docs/CLAIMS.md index c25832ea..50854a49 100644 --- a/docs/CLAIMS.md +++ b/docs/docs/CLAIMS.md @@ -28,7 +28,7 @@ by its quoted claim, and `tests/test_docs_audit.py` fails if a named row is not | "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/index.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` | +| "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 @@ -113,9 +113,9 @@ by its quoted claim, and `tests/test_docs_audit.py` fails if a named row is not | "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/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/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/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` | +| 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` | @@ -124,7 +124,7 @@ 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/verify.md`, "What it does not mean"; `THREAT_MODEL.md`, "Known v0.4 limitations" | +| 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` | @@ -190,7 +190,7 @@ The README also makes negative claims. They matter as much as the positive ones. | "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/verify.md`, "What it does not mean"; `THREAT_MODEL.md`, "Known v0.4 limitations" | +| "`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 | @@ -214,7 +214,7 @@ restating the code; the ones that are new to the site carry their own code and p | 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_home_page_carries_the_fixed_copy_and_the_generated_grid` asserts the configuration line is present | +| `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` | diff --git a/docs/OWASP-AGENTIC-TOP10.md b/docs/docs/OWASP-AGENTIC-TOP10.md similarity index 98% rename from docs/OWASP-AGENTIC-TOP10.md rename to docs/docs/OWASP-AGENTIC-TOP10.md index 5402c1b3..32d447fb 100644 --- a/docs/OWASP-AGENTIC-TOP10.md +++ b/docs/docs/OWASP-AGENTIC-TOP10.md @@ -85,7 +85,7 @@ The half that makes the table above credible. One honest sentence each; nothing | `ASI04:2026` | Agentic Supply Chain Vulnerabilities | Out of scope. CTRLRun never inspects a package, a model, a tool registry or an MCP server's provenance; it decides actions, and a poisoned dependency reaches it as an ordinary caller. | | `ASI05:2026` | Unexpected Code Execution | Out of scope. Nothing here sandboxes an interpreter or constrains what a process may run. CTRLRun sits between an agent and one remote effect, not between an agent and its own runtime. | | `ASI06:2026` | Memory & Context Poisoning | Out of scope, and deliberately so: CTRLRun never reads a model's memory, its context or its prompt. It sees a proposed action and its arguments, which is the point at which a poisoned context has already become a concrete request. | -| `ASI07:2026` | Insecure Inter-Agent Communication | Not yet. Authority does not propagate across agent hops in this release — a grant is evaluated where the action is proposed, and there is no A2A model. `docs/ROADMAP.md` puts that in v0.7; until then, an agent handing work to another agent is outside what these guarantees say anything about. | +| `ASI07:2026` | Insecure Inter-Agent Communication | Not yet. Authority does not propagate across agent hops in this release — a grant is evaluated where the action is proposed, and there is no A2A model. `docs/docs/ROADMAP.md` puts that in v0.7; until then, an agent handing work to another agent is outside what these guarantees say anything about. | And the two entries where the mapping above is **partial**, with the part that is not covered stated here rather than left implied: @@ -103,7 +103,7 @@ The mapping is only worth what the tests behind it are worth. `ctrlrun verify` r against a configuration and reports which of them that configuration can exercise at all — **not applicable is not a pass**, so a mapping row whose guarantee your policy cannot exercise shows up as `N/A` with the reason rather than as a green tick. See -[`docs/verify.md`](verify.md). +[`docs/docs/verify.md`](/docs/verify). Each guarantee also descends from an acceptance test in `docs/SPEC-v0.1.md §7`, `docs/SPEC-v0.2.md §10` or `docs/SPEC-v0.3.md §10`, named in the registry and carried into diff --git a/docs/ROADMAP.md b/docs/docs/ROADMAP.md similarity index 97% rename from docs/ROADMAP.md rename to docs/docs/ROADMAP.md index bdb46ec3..80ef2166 100644 --- a/docs/ROADMAP.md +++ b/docs/docs/ROADMAP.md @@ -36,7 +36,7 @@ one arrived that it did not list: - The **OWASP ACS adapter** was a v0.3 standards line. Reading the v0.1.0 schemas showed a stable enough interface to build against, so it shipped here — with no compliance claim, and - `docs/ACS.md` recording where the standard is silent. + `docs/docs/ACS.md` recording where the standard is silent. - **`Suspended` / `Control.resume`** were not on any milestone. MCP elicitation (§6.9) needs a reservation held across a round trip the kernel does not control, and so does an advisory hook model like ACS. It is public API now, frozen in `SPEC-v0.2.md` §11. @@ -44,7 +44,7 @@ one arrived that it did not list: a tool call has no decorator to carry an effect template. - **`EventSink`** replaced the store's file writing, which the v0.1 kernel had owned. -Standards: OpenTelemetry export (code), MCP gateway. The OWASP ACS adapter shipped in v0.2 (see `docs/ACS.md`); "ACS-compatible" is still unearned and waits on an ACS conformance suite to measure against. +Standards: OpenTelemetry export (code), MCP gateway. The OWASP ACS adapter shipped in v0.2 (see `docs/docs/ACS.md`); "ACS-compatible" is still unearned and waits on an ACS conformance suite to measure against. ## v0.3 — Authority ✅ shipped @@ -90,7 +90,7 @@ requirement for the same reason: a self-reported name cannot be an authorization Exit: every acceptance test in `SPEC-v0.4.md §8` passes, and every one in v0.1, v0.2 and v0.3 still does. `ctrlrun verify` against `examples/authority/payments.yaml` reports 11/11; against `examples/policies/payments.yaml`, 6/6 with five not applicable — the N/A rule dogfooded rather than described. -Standards: first mapping doc — `docs/OWASP-AGENTIC-TOP10.md`, each guarantee mapped to the OWASP Top 10 for Agentic Applications entries it mitigates, and the four entries CTRLRun does not address listed by name. A reading of somebody else's taxonomy, and it says so on its first line. +Standards: first mapping doc — `docs/docs/OWASP-AGENTIC-TOP10.md`, each guarantee mapped to the OWASP Top 10 for Agentic Applications entries it mitigates, and the four entries CTRLRun does not address listed by name. A reading of somebody else's taxonomy, and it says so on its first line. ## v0.5 — Adapter contract (Released 2026-09-05) diff --git a/docs/THREAT_MODEL.md b/docs/docs/THREAT_MODEL.md similarity index 99% rename from docs/THREAT_MODEL.md rename to docs/docs/THREAT_MODEL.md index d6c4b42b..46c2ebea 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/docs/THREAT_MODEL.md @@ -83,7 +83,7 @@ they entitled to?* Everything above still holds; these are the threats the secon `ctrlrun verify` runs the kernel's own failure scenarios against an operator's configuration and reports what passed, what failed, and what could not be tested at all. The list of what it cannot see matters more than the feature does, so it is here as well as in -[`docs/verify.md`](verify.md) — verify sees **the configuration, not the code**. +[`docs/docs/verify.md`](/docs/verify) — verify sees **the configuration, not the code**. - **Not the operator's executors.** The function behind `@protect` is never called. The `NotExecuted` integration bug above is invisible here, because verify supplies its own diff --git a/docs/adapters.md b/docs/docs/adapters.md similarity index 100% rename from docs/adapters.md rename to docs/docs/adapters.md diff --git a/docs/architecture/specifications.mdx b/docs/docs/architecture/specifications.mdx similarity index 82% rename from docs/architecture/specifications.mdx rename to docs/docs/architecture/specifications.mdx index a62fc578..1fad20a4 100644 --- a/docs/architecture/specifications.mdx +++ b/docs/docs/architecture/specifications.mdx @@ -38,14 +38,14 @@ before it was right. | You want | Read | |---|---| -| what a thing is, in one page | [Concepts](/concepts/action-and-hash) | -| how to do something | [Guides](/guides/protect-a-function) | -| every key, flag, field and error | [Reference](/reference/policy-yaml) | -| what is guaranteed and what is not | [What verify guarantees](/security/verify-guarantees), [Threat model](/THREAT_MODEL) | -| how the guarantees are held up | [How this is built](/how-this-is-built) | -| every README sentence, mapped to its code and test | [Claims](/CLAIMS) | +| what a thing is, in one page | [Concepts](/docs/concepts/action-and-hash) | +| how to do something | [Guides](/docs/guides/protect-a-function) | +| every key, flag, field and error | [Reference](/docs/reference/policy-yaml) | +| what is guaranteed and what is not | [What verify guarantees](/docs/security/verify-guarantees), [Threat model](/docs/THREAT_MODEL) | +| how the guarantees are held up | [How this is built](/docs/how-this-is-built) | +| every README sentence, mapped to its code and test | [Claims](/docs/CLAIMS) | ## Next -- [Architecture](/ARCHITECTURE): how the kernel is shaped, and why. -- [How this is built](/how-this-is-built) · [Get started](/get-started/quickstart) · [Why](/why). +- [Architecture](/docs/ARCHITECTURE): how the kernel is shaped, and why. +- [How this is built](/docs/how-this-is-built) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/authority.md b/docs/docs/authority.md similarity index 100% rename from docs/authority.md rename to docs/docs/authority.md diff --git a/docs/compare/durable-workflows.mdx b/docs/docs/compare/durable-workflows.mdx similarity index 92% rename from docs/compare/durable-workflows.mdx rename to docs/docs/compare/durable-workflows.mdx index 3ab4085e..4fd0188c 100644 --- a/docs/compare/durable-workflows.mdx +++ b/docs/docs/compare/durable-workflows.mdx @@ -51,6 +51,6 @@ says so. ## Next -- [Effect keys](/concepts/effect-keys) · [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous). -- [Compare: idempotency keys](/compare/idempotency-keys). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Effect keys](/docs/concepts/effect-keys) · [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous). +- [Compare: idempotency keys](/docs/compare/idempotency-keys). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/compare/framework-hitl.mdx b/docs/docs/compare/framework-hitl.mdx similarity index 91% rename from docs/compare/framework-hitl.mdx rename to docs/docs/compare/framework-hitl.mdx index 202f8961..52e1b5d9 100644 --- a/docs/compare/framework-hitl.mdx +++ b/docs/docs/compare/framework-hitl.mdx @@ -50,6 +50,6 @@ adapter's page says which it is, in that word. ## Next -- [Three ways in](/get-started/three-ways-in): when an adapter is the answer and when it is not. -- [Use the LangGraph adapter](/guides/langgraph-adapter) · [Use the OpenAI Agents SDK adapter](/guides/openai-agents-adapter). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Three ways in](/docs/get-started/three-ways-in): when an adapter is the answer and when it is not. +- [Use the LangGraph adapter](/docs/guides/langgraph-adapter) · [Use the OpenAI Agents SDK adapter](/docs/guides/openai-agents-adapter). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/compare/governance-toolkits.mdx b/docs/docs/compare/governance-toolkits.mdx similarity index 90% rename from docs/compare/governance-toolkits.mdx rename to docs/docs/compare/governance-toolkits.mdx index 7df3c191..5d690908 100644 --- a/docs/compare/governance-toolkits.mdx +++ b/docs/docs/compare/governance-toolkits.mdx @@ -47,6 +47,6 @@ make. ## Next -- [Receipts and evidence](/concepts/receipts-and-evidence) · [Policy YAML reference](/reference/policy-yaml). -- [How this is built](/how-this-is-built): what the guarantees rest on. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Receipts and evidence](/docs/concepts/receipts-and-evidence) · [Policy YAML reference](/docs/reference/policy-yaml). +- [How this is built](/docs/how-this-is-built): what the guarantees rest on. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/compare/guardrail-libraries.mdx b/docs/docs/compare/guardrail-libraries.mdx similarity index 93% rename from docs/compare/guardrail-libraries.mdx rename to docs/docs/compare/guardrail-libraries.mdx index 7850a626..dffdecfe 100644 --- a/docs/compare/guardrail-libraries.mdx +++ b/docs/docs/compare/guardrail-libraries.mdx @@ -50,6 +50,6 @@ tell you whether the completion was rude. ## Next -- [Why](/why): the boundary CTRLRun owns. -- [Fail closed](/concepts/fail-closed) · [Effect keys](/concepts/effect-keys). -- [Get started](/get-started/quickstart). +- [Why](/docs/why): the boundary CTRLRun owns. +- [Fail closed](/docs/concepts/fail-closed) · [Effect keys](/docs/concepts/effect-keys). +- [Get started](/docs/get-started/quickstart). diff --git a/docs/compare/idempotency-keys.mdx b/docs/docs/compare/idempotency-keys.mdx similarity index 94% rename from docs/compare/idempotency-keys.mdx rename to docs/docs/compare/idempotency-keys.mdx index bd9a557c..06a6d696 100644 --- a/docs/compare/idempotency-keys.mdx +++ b/docs/docs/compare/idempotency-keys.mdx @@ -61,6 +61,6 @@ what was recorded. ## Next -- [Effect keys](/concepts/effect-keys): the definitional page. -- [Compare: durable workflows](/compare/durable-workflows). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Effect keys](/docs/concepts/effect-keys): the definitional page. +- [Compare: durable workflows](/docs/compare/durable-workflows). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/concepts/action-and-hash.mdx b/docs/docs/concepts/action-and-hash.mdx similarity index 91% rename from docs/concepts/action-and-hash.mdx rename to docs/docs/concepts/action-and-hash.mdx index a6af9313..144f25d1 100644 --- a/docs/concepts/action-and-hash.mdx +++ b/docs/docs/concepts/action-and-hash.mdx @@ -50,6 +50,6 @@ The hash also says nothing about whether the arguments are sensible; the policy ## Next -- [Approval binding](/concepts/approval-binding): what the hash is for. -- [Effect keys](/concepts/effect-keys): the other identity, for the consequence. -- [Why](/why) · [Get started](/get-started/quickstart). +- [Approval binding](/docs/concepts/approval-binding): what the hash is for. +- [Effect keys](/docs/concepts/effect-keys): the other identity, for the consequence. +- [Why](/docs/why) · [Get started](/docs/get-started/quickstart). diff --git a/docs/concepts/approval-binding.mdx b/docs/docs/concepts/approval-binding.mdx similarity index 91% rename from docs/concepts/approval-binding.mdx rename to docs/docs/concepts/approval-binding.mdx index d64aeaad..2bd86def 100644 --- a/docs/concepts/approval-binding.mdx +++ b/docs/docs/concepts/approval-binding.mdx @@ -57,6 +57,6 @@ invalidated so it cannot outlive the action it was for. ## Next -- [Action and hash](/concepts/action-and-hash): what the approval binds to. -- [Effect keys](/concepts/effect-keys): the reservation it is consumed with. -- [Approve in Slack](/guides/approvals-in-slack) · [Get started](/get-started/quickstart) · [Why](/why). +- [Action and hash](/docs/concepts/action-and-hash): what the approval binds to. +- [Effect keys](/docs/concepts/effect-keys): the reservation it is consumed with. +- [Approve in Slack](/docs/guides/approvals-in-slack) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/concepts/authority-and-delegation.mdx b/docs/docs/concepts/authority-and-delegation.mdx similarity index 93% rename from docs/concepts/authority-and-delegation.mdx rename to docs/docs/concepts/authority-and-delegation.mdx index c2d2dc8a..68c476a0 100644 --- a/docs/concepts/authority-and-delegation.mdx +++ b/docs/docs/concepts/authority-and-delegation.mdx @@ -82,6 +82,6 @@ is worth exactly what the thing setting the header is worth. ## Next -- [Authority YAML reference](/reference/authority-yaml). -- [Decisions](/concepts/decisions): the axis authority sits beside. -- [Authority and delegation, in plain language](/authority) · [Get started](/get-started/quickstart) · [Why](/why). +- [Authority YAML reference](/docs/reference/authority-yaml). +- [Decisions](/docs/concepts/decisions): the axis authority sits beside. +- [Authority and delegation, in plain language](/docs/authority) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/concepts/decisions.mdx b/docs/docs/concepts/decisions.mdx similarity index 84% rename from docs/concepts/decisions.mdx rename to docs/docs/concepts/decisions.mdx index 606c7602..9888bb20 100644 --- a/docs/concepts/decisions.mdx +++ b/docs/docs/concepts/decisions.mdx @@ -36,7 +36,7 @@ waits for a person. The policy sees the action's name and its arguments and nothing else. It cannot see the principal: `agent_eq`, `user_eq` and every other principal-addressing condition is refused when the file loads, not silently matched. That is deliberate and it is the second principle on the -[Why](/why) page: how much autonomy `iam.grant_role` has is the same for everyone; who may +[Why](/docs/why) page: how much autonomy `iam.grant_role` has is the same for everyone; who may propose it at all is the job of authority. ## The guarantee it supports @@ -54,6 +54,6 @@ or end `AMBIGUOUS` because the remote did not answer. ## Next -- [Approval binding](/concepts/approval-binding): what `approve` sets in motion. -- [Authority and delegation](/concepts/authority-and-delegation): the axis the policy cannot see. -- [Policy YAML reference](/reference/policy-yaml) · [Get started](/get-started/quickstart). +- [Approval binding](/docs/concepts/approval-binding): what `approve` sets in motion. +- [Authority and delegation](/docs/concepts/authority-and-delegation): the axis the policy cannot see. +- [Policy YAML reference](/docs/reference/policy-yaml) · [Get started](/docs/get-started/quickstart). diff --git a/docs/concepts/effect-keys.mdx b/docs/docs/concepts/effect-keys.mdx similarity index 92% rename from docs/concepts/effect-keys.mdx rename to docs/docs/concepts/effect-keys.mdx index 2121d709..aa9de2ad 100644 --- a/docs/concepts/effect-keys.mdx +++ b/docs/docs/concepts/effect-keys.mdx @@ -68,6 +68,6 @@ entirely is not reserved, which the threat model lists as out of scope. ## Next -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous): what happens when the reply is lost. -- [Compare: idempotency keys](/compare/idempotency-keys). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous): what happens when the reply is lost. +- [Compare: idempotency keys](/docs/compare/idempotency-keys). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/concepts/fail-closed.mdx b/docs/docs/concepts/fail-closed.mdx similarity index 91% rename from docs/concepts/fail-closed.mdx rename to docs/docs/concepts/fail-closed.mdx index 4129f26e..f69251e9 100644 --- a/docs/concepts/fail-closed.mdx +++ b/docs/docs/concepts/fail-closed.mdx @@ -48,6 +48,6 @@ those are in the threat model as limits, not as vulnerabilities. ## Next -- [Decisions](/concepts/decisions): the three that can be reached. -- [Errors reference](/reference/errors): every exception and when it is raised. -- [Threat model](/THREAT_MODEL) · [Get started](/get-started/quickstart) · [Why](/why). +- [Decisions](/docs/concepts/decisions): the three that can be reached. +- [Errors reference](/docs/reference/errors): every exception and when it is raised. +- [Threat model](/docs/THREAT_MODEL) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/concepts/observe-mode.mdx b/docs/docs/concepts/observe-mode.mdx similarity index 92% rename from docs/concepts/observe-mode.mdx rename to docs/docs/concepts/observe-mode.mdx index 4b9eb144..022379ae 100644 --- a/docs/concepts/observe-mode.mdx +++ b/docs/docs/concepts/observe-mode.mdx @@ -66,6 +66,6 @@ on traffic you would run unprotected today, read `ctrlrun stats`, then switch th ## Next -- [Roll out observe, then enforce](/guides/observe-to-enforce). -- [Fail closed](/concepts/fail-closed): what enforce mode refuses. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Roll out observe, then enforce](/docs/guides/observe-to-enforce). +- [Fail closed](/docs/concepts/fail-closed): what enforce mode refuses. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/concepts/outcomes-and-ambiguous.mdx b/docs/docs/concepts/outcomes-and-ambiguous.mdx similarity index 94% rename from docs/concepts/outcomes-and-ambiguous.mdx rename to docs/docs/concepts/outcomes-and-ambiguous.mdx index 11fab67e..0e5a8912 100644 --- a/docs/concepts/outcomes-and-ambiguous.mdx +++ b/docs/docs/concepts/outcomes-and-ambiguous.mdx @@ -99,6 +99,6 @@ integration bug the threat model names as the most dangerous one available. ## Next -- [Resolve an AMBIGUOUS effect](/guides/resolve-an-ambiguous-effect). -- [Reconcile automatically](/guides/reconcile-automatically). -- [Effect keys](/concepts/effect-keys) · [Get started](/get-started/quickstart) · [Why](/why). +- [Resolve an AMBIGUOUS effect](/docs/guides/resolve-an-ambiguous-effect). +- [Reconcile automatically](/docs/guides/reconcile-automatically). +- [Effect keys](/docs/concepts/effect-keys) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/concepts/receipts-and-evidence.mdx b/docs/docs/concepts/receipts-and-evidence.mdx similarity index 90% rename from docs/concepts/receipts-and-evidence.mdx rename to docs/docs/concepts/receipts-and-evidence.mdx index 98faa5b1..38732dff 100644 --- a/docs/concepts/receipts-and-evidence.mdx +++ b/docs/docs/concepts/receipts-and-evidence.mdx @@ -61,7 +61,7 @@ Each receipt carries `seq` and `prev_hash`, the hash of the receipt before it. A receipt, a deletion from the middle, a reordering: each is detected and named by `seq` with `ctrlrun receipts --verify-chain`. The chain detects alteration. It does not prove authorship: receipts are not signed, and an administrator who can rewrite every row including the chain head -can rewrite history undetected. [The receipt chain](/security/receipt-chain) says exactly what +can rewrite history undetected. [The receipt chain](/docs/security/receipt-chain) says exactly what it covers and what it does not. ## The guarantee it supports @@ -79,6 +79,6 @@ design. ## Next -- [The receipt chain](/security/receipt-chain). -- [Export to OpenTelemetry](/guides/export-to-opentelemetry). -- [Receipt and event schemas](/reference/receipt-and-event-schemas) · [Get started](/get-started/quickstart) · [Why](/why). +- [The receipt chain](/docs/security/receipt-chain). +- [Export to OpenTelemetry](/docs/guides/export-to-opentelemetry). +- [Receipt and event schemas](/docs/reference/receipt-and-event-schemas) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/credential-rotation-agent.mdx b/docs/docs/cookbook/credential-rotation-agent.mdx similarity index 94% rename from docs/cookbook/credential-rotation-agent.mdx rename to docs/docs/cookbook/credential-rotation-agent.mdx index 9ba4c64a..3cb49f3f 100644 --- a/docs/cookbook/credential-rotation-agent.mdx +++ b/docs/docs/cookbook/credential-rotation-agent.mdx @@ -125,5 +125,5 @@ pip install ctrlrun && python main.py ## Next -- [An IAM agent that can grant read but never admin](/cookbook/iam-agent). -- [Resolve an AMBIGUOUS effect](/guides/resolve-an-ambiguous-effect) · [Get started](/get-started/quickstart) · [Why](/why). +- [An IAM agent that can grant read but never admin](/docs/cookbook/iam-agent). +- [Resolve an AMBIGUOUS effect](/docs/guides/resolve-an-ambiguous-effect) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/crm-update-agent.mdx b/docs/docs/cookbook/crm-update-agent.mdx similarity index 95% rename from docs/cookbook/crm-update-agent.mdx rename to docs/docs/cookbook/crm-update-agent.mdx index 4e4007ef..ba6b36f0 100644 --- a/docs/cookbook/crm-update-agent.mdx +++ b/docs/docs/cookbook/crm-update-agent.mdx @@ -148,5 +148,5 @@ pip install ctrlrun && python main.py ## Next -- [A data-deletion agent under a retention rule](/cookbook/data-deletion-agent). -- [Effect keys](/concepts/effect-keys) · [Get started](/get-started/quickstart) · [Why](/why). +- [A data-deletion agent under a retention rule](/docs/cookbook/data-deletion-agent). +- [Effect keys](/docs/concepts/effect-keys) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/customer-notification-agent.mdx b/docs/docs/cookbook/customer-notification-agent.mdx similarity index 95% rename from docs/cookbook/customer-notification-agent.mdx rename to docs/docs/cookbook/customer-notification-agent.mdx index e5bd35b2..ba377b49 100644 --- a/docs/cookbook/customer-notification-agent.mdx +++ b/docs/docs/cookbook/customer-notification-agent.mdx @@ -123,5 +123,5 @@ pip install ctrlrun && python main.py ## Next -- [An outbound-email agent](/cookbook/outbound-email-agent). -- [Effect keys](/concepts/effect-keys) · [Get started](/get-started/quickstart) · [Why](/why). +- [An outbound-email agent](/docs/cookbook/outbound-email-agent). +- [Effect keys](/docs/concepts/effect-keys) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/data-deletion-agent.mdx b/docs/docs/cookbook/data-deletion-agent.mdx similarity index 96% rename from docs/cookbook/data-deletion-agent.mdx rename to docs/docs/cookbook/data-deletion-agent.mdx index 5e78e157..88065246 100644 --- a/docs/cookbook/data-deletion-agent.mdx +++ b/docs/docs/cookbook/data-deletion-agent.mdx @@ -132,5 +132,5 @@ pip install ctrlrun && python main.py ## Next -- [A CRM-update agent](/cookbook/crm-update-agent). -- [Decisions](/concepts/decisions) · [Get started](/get-started/quickstart) · [Why](/why). +- [A CRM-update agent](/docs/cookbook/crm-update-agent). +- [Decisions](/docs/concepts/decisions) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/database-migration-agent.mdx b/docs/docs/cookbook/database-migration-agent.mdx similarity index 95% rename from docs/cookbook/database-migration-agent.mdx rename to docs/docs/cookbook/database-migration-agent.mdx index 036f968f..b80215a3 100644 --- a/docs/cookbook/database-migration-agent.mdx +++ b/docs/docs/cookbook/database-migration-agent.mdx @@ -132,5 +132,5 @@ pip install ctrlrun && python main.py ## Next -- [A deploy agent](/cookbook/deploy-agent). -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous) · [Get started](/get-started/quickstart) · [Why](/why). +- [A deploy agent](/docs/cookbook/deploy-agent). +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/deploy-agent.mdx b/docs/docs/cookbook/deploy-agent.mdx similarity index 95% rename from docs/cookbook/deploy-agent.mdx rename to docs/docs/cookbook/deploy-agent.mdx index 3311487d..be3d90d9 100644 --- a/docs/cookbook/deploy-agent.mdx +++ b/docs/docs/cookbook/deploy-agent.mdx @@ -135,5 +135,5 @@ pip install ctrlrun && python main.py ## Next -- [A database-migration agent](/cookbook/database-migration-agent). -- [Effect keys](/concepts/effect-keys) · [Get started](/get-started/quickstart) · [Why](/why). +- [A database-migration agent](/docs/cookbook/database-migration-agent). +- [Effect keys](/docs/concepts/effect-keys) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/iam-agent.mdx b/docs/docs/cookbook/iam-agent.mdx similarity index 95% rename from docs/cookbook/iam-agent.mdx rename to docs/docs/cookbook/iam-agent.mdx index 66d96d6f..e12440a3 100644 --- a/docs/cookbook/iam-agent.mdx +++ b/docs/docs/cookbook/iam-agent.mdx @@ -132,5 +132,5 @@ pip install ctrlrun && python main.py ## Next -- [A credential-rotation agent](/cookbook/credential-rotation-agent). -- [Approval binding](/concepts/approval-binding) · [Get started](/get-started/quickstart) · [Why](/why). +- [A credential-rotation agent](/docs/cookbook/credential-rotation-agent). +- [Approval binding](/docs/concepts/approval-binding) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/docs/cookbook/index.mdx b/docs/docs/cookbook/index.mdx new file mode 100644 index 00000000..e0791c5b --- /dev/null +++ b/docs/docs/cookbook/index.mdx @@ -0,0 +1,65 @@ +--- +title: "Cookbook" +description: "One recipe per real situation: the policy, the code with a fake remote, what the agent sees when refused or asked, the receipt." +--- + +Each recipe is a situation an agent is put in, across money, infrastructure, permissions, +records and communications, and shows the policy, the code against a stand-in remote, what the +agent sees when it is refused or asked, the receipt, and what to do when an effect's outcome is +unknown. Every runnable block runs offline in this repository's CI, and every recipe with code +is also a directory under `examples/cookbook/` extracted from its page. To run one instead of +reading it: + +```bash +git clone https://github.com/CTRLRun/ctrlrun && cd ctrlrun/examples/cookbook/refund-agent +pip install ctrlrun && python main.py +``` + +## Money + +- [A refund agent with amount tiers](/docs/cookbook/refund-agent) +- [A payout agent with maker/checker via delegation](/docs/cookbook/payout-maker-checker) + +## Infrastructure + +- [A deploy agent](/docs/cookbook/deploy-agent) +- [A database-migration agent](/docs/cookbook/database-migration-agent) + +## Permissions + +- [An IAM agent that can grant read but never admin](/docs/cookbook/iam-agent) +- [A credential-rotation agent](/docs/cookbook/credential-rotation-agent) + +## Records + +- [A CRM-update agent](/docs/cookbook/crm-update-agent) +- [A data-deletion agent under a retention rule](/docs/cookbook/data-deletion-agent) + +## Communications + +- [An outbound-email agent with external-recipient approval](/docs/cookbook/outbound-email-agent) +- [A customer-notification agent](/docs/cookbook/customer-notification-agent) + +## Multi-agent + +- [A manager agent delegating bounded authority to a worker](/docs/cookbook/manager-and-worker) + +## Integrations + +- [Protect an existing MCP server in five minutes](/docs/cookbook/protect-an-mcp-server) +- [LangGraph with interrupt()](/docs/cookbook/langgraph-interrupt) +- [OpenAI Agents SDK tool approval](/docs/cookbook/openai-agents-tool-approval) +- [Approvals in Slack via webhook](/docs/cookbook/slack-approvals) +- [Receipts into OpenTelemetry](/docs/cookbook/receipts-to-opentelemetry) + +## Operations + +- [Observe for a week, then enforce](/docs/cookbook/observe-then-enforce) +- [Resolve an ambiguous effect](/docs/cookbook/resolve-an-ambiguous-effect) +- [Reconcile against Stripe or Kubernetes automatically](/docs/cookbook/reconcile-against-the-remote) +- [Run verify in GitHub Actions](/docs/cookbook/verify-in-github-actions) +- [Move from SQLite to Postgres](/docs/cookbook/sqlite-to-postgres) + +## Next + +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/langgraph-interrupt.mdx b/docs/docs/cookbook/langgraph-interrupt.mdx similarity index 93% rename from docs/cookbook/langgraph-interrupt.mdx rename to docs/docs/cookbook/langgraph-interrupt.mdx index 1e60181c..daaaad63 100644 --- a/docs/cookbook/langgraph-interrupt.mdx +++ b/docs/docs/cookbook/langgraph-interrupt.mdx @@ -95,5 +95,5 @@ needs a new interrupt. ## Next -- [Use the LangGraph adapter](/guides/langgraph-adapter): prevention versus attribution, and where LangGraph shows through. -- [Approval binding](/concepts/approval-binding) · [Get started](/get-started/quickstart) · [Why](/why). +- [Use the LangGraph adapter](/docs/guides/langgraph-adapter): prevention versus attribution, and where LangGraph shows through. +- [Approval binding](/docs/concepts/approval-binding) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/manager-and-worker.mdx b/docs/docs/cookbook/manager-and-worker.mdx similarity index 96% rename from docs/cookbook/manager-and-worker.mdx rename to docs/docs/cookbook/manager-and-worker.mdx index 6d77cab7..e99fa872 100644 --- a/docs/cookbook/manager-and-worker.mdx +++ b/docs/docs/cookbook/manager-and-worker.mdx @@ -189,5 +189,5 @@ pip install ctrlrun && python main.py ## Next -- [A payout agent with maker/checker](/cookbook/payout-maker-checker). -- [Authority and delegation](/concepts/authority-and-delegation) · [Get started](/get-started/quickstart) · [Why](/why). +- [A payout agent with maker/checker](/docs/cookbook/payout-maker-checker). +- [Authority and delegation](/docs/concepts/authority-and-delegation) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/observe-then-enforce.mdx b/docs/docs/cookbook/observe-then-enforce.mdx similarity index 95% rename from docs/cookbook/observe-then-enforce.mdx rename to docs/docs/cookbook/observe-then-enforce.mdx index 0bce231b..db2b0bee 100644 --- a/docs/cookbook/observe-then-enforce.mdx +++ b/docs/docs/cookbook/observe-then-enforce.mdx @@ -132,5 +132,5 @@ pip install ctrlrun && python main.py ## Next -- [Roll out observe, then enforce](/guides/observe-to-enforce): the week, the numbers and the switch. -- [Observe mode](/concepts/observe-mode) · [Get started](/get-started/quickstart) · [Why](/why). +- [Roll out observe, then enforce](/docs/guides/observe-to-enforce): the week, the numbers and the switch. +- [Observe mode](/docs/concepts/observe-mode) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/openai-agents-tool-approval.mdx b/docs/docs/cookbook/openai-agents-tool-approval.mdx similarity index 93% rename from docs/cookbook/openai-agents-tool-approval.mdx rename to docs/docs/cookbook/openai-agents-tool-approval.mdx index 549e4177..21414b3b 100644 --- a/docs/cookbook/openai-agents-tool-approval.mdx +++ b/docs/docs/cookbook/openai-agents-tool-approval.mdx @@ -86,5 +86,5 @@ the retry is refused with `AmbiguousEffect`, which `gate.run` returns as itself; ## Next -- [Use the OpenAI Agents SDK adapter](/guides/openai-agents-adapter): where the SDK shows through, and `ApprovalNotAsked`. -- [Three ways in](/get-started/three-ways-in) · [Get started](/get-started/quickstart) · [Why](/why). +- [Use the OpenAI Agents SDK adapter](/docs/guides/openai-agents-adapter): where the SDK shows through, and `ApprovalNotAsked`. +- [Three ways in](/docs/get-started/three-ways-in) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/outbound-email-agent.mdx b/docs/docs/cookbook/outbound-email-agent.mdx similarity index 95% rename from docs/cookbook/outbound-email-agent.mdx rename to docs/docs/cookbook/outbound-email-agent.mdx index 97b8e9c1..5552d168 100644 --- a/docs/cookbook/outbound-email-agent.mdx +++ b/docs/docs/cookbook/outbound-email-agent.mdx @@ -131,5 +131,5 @@ pip install ctrlrun && python main.py ## Next -- [A customer-notification agent](/cookbook/customer-notification-agent). -- [Approval binding](/concepts/approval-binding) · [Get started](/get-started/quickstart) · [Why](/why). +- [A customer-notification agent](/docs/cookbook/customer-notification-agent). +- [Approval binding](/docs/concepts/approval-binding) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/payout-maker-checker.mdx b/docs/docs/cookbook/payout-maker-checker.mdx similarity index 96% rename from docs/cookbook/payout-maker-checker.mdx rename to docs/docs/cookbook/payout-maker-checker.mdx index c17d82a6..c48e2b14 100644 --- a/docs/cookbook/payout-maker-checker.mdx +++ b/docs/docs/cookbook/payout-maker-checker.mdx @@ -207,5 +207,5 @@ pip install ctrlrun && python main.py ## Next -- [A manager agent delegating to a worker](/cookbook/manager-and-worker). -- [Authority and delegation](/concepts/authority-and-delegation) · [Get started](/get-started/quickstart) · [Why](/why). +- [A manager agent delegating to a worker](/docs/cookbook/manager-and-worker). +- [Authority and delegation](/docs/concepts/authority-and-delegation) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/protect-an-mcp-server.mdx b/docs/docs/cookbook/protect-an-mcp-server.mdx similarity index 93% rename from docs/cookbook/protect-an-mcp-server.mdx rename to docs/docs/cookbook/protect-an-mcp-server.mdx index 5f7bdf02..25b55e5e 100644 --- a/docs/cookbook/protect-an-mcp-server.mdx +++ b/docs/docs/cookbook/protect-an-mcp-server.mdx @@ -6,7 +6,7 @@ description: "Put the gateway in front of an MCP server you already run: name it You run an MCP server and an agent that calls it. In production the gateway is a process between them, started with one command; here the same gateway object is driven in process against a stand-in upstream, so the recipe runs offline and shows exactly what the agent gets -back. [The gateway in five minutes](/mcp/gateway-in-5-minutes) has the production commands. +back. [The gateway in five minutes](/docs/mcp/gateway-in-5-minutes) has the production commands. ## The policy @@ -170,5 +170,5 @@ pip install ctrlrun && python main.py ## Next -- [The gateway in five minutes](/mcp/gateway-in-5-minutes): the production commands and the full code table. -- [Put the gateway in front of MCP](/guides/gateway-in-front-of-mcp) · [Get started](/get-started/quickstart) · [Why](/why). +- [The gateway in five minutes](/docs/mcp/gateway-in-5-minutes): the production commands and the full code table. +- [Put the gateway in front of MCP](/docs/guides/gateway-in-front-of-mcp) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/receipts-to-opentelemetry.mdx b/docs/docs/cookbook/receipts-to-opentelemetry.mdx similarity index 94% rename from docs/cookbook/receipts-to-opentelemetry.mdx rename to docs/docs/cookbook/receipts-to-opentelemetry.mdx index 0298bb54..38aa2314 100644 --- a/docs/cookbook/receipts-to-opentelemetry.mdx +++ b/docs/docs/cookbook/receipts-to-opentelemetry.mdx @@ -124,5 +124,5 @@ pip install ctrlrun && python main.py ## Next -- [Export to OpenTelemetry](/guides/export-to-opentelemetry): the OTLP exporter and the gateway's `--otel`. -- [Receipts and evidence](/concepts/receipts-and-evidence) · [Get started](/get-started/quickstart) · [Why](/why). +- [Export to OpenTelemetry](/docs/guides/export-to-opentelemetry): the OTLP exporter and the gateway's `--otel`. +- [Receipts and evidence](/docs/concepts/receipts-and-evidence) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/reconcile-against-the-remote.mdx b/docs/docs/cookbook/reconcile-against-the-remote.mdx similarity index 95% rename from docs/cookbook/reconcile-against-the-remote.mdx rename to docs/docs/cookbook/reconcile-against-the-remote.mdx index 9fb6d52d..8f0b8685 100644 --- a/docs/cookbook/reconcile-against-the-remote.mdx +++ b/docs/docs/cookbook/reconcile-against-the-remote.mdx @@ -153,5 +153,5 @@ pip install ctrlrun && python main.py ## Next -- [Reconcile automatically](/guides/reconcile-automatically): the three answers and when to run the hook. -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous) · [Get started](/get-started/quickstart) · [Why](/why). +- [Reconcile automatically](/docs/guides/reconcile-automatically): the three answers and when to run the hook. +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/refund-agent.mdx b/docs/docs/cookbook/refund-agent.mdx similarity index 93% rename from docs/cookbook/refund-agent.mdx rename to docs/docs/cookbook/refund-agent.mdx index 77f96d65..1d0b8461 100644 --- a/docs/cookbook/refund-agent.mdx +++ b/docs/docs/cookbook/refund-agent.mdx @@ -119,7 +119,7 @@ Four receipts: `allow/committed`, `approve/committed` with the approver, `deny/d A refund whose reply was lost is `AMBIGUOUS` and a retry is refused. Look up the payment in the Stripe dashboard, then `ctrlrun resolve refund:txn_N --committed` or `--failed`. A `reconcile` hook that queries `stripe.Refund.list(payment_intent=...)` does the same automatically: -[Reconcile against the remote](/cookbook/reconcile-against-the-remote). +[Reconcile against the remote](/docs/cookbook/reconcile-against-the-remote). ## Run it @@ -133,5 +133,5 @@ pip install ctrlrun && python main.py ## Next -- [A payout agent with maker/checker](/cookbook/payout-maker-checker): the same money, two people. -- [Approval binding](/concepts/approval-binding) · [Get started](/get-started/quickstart) · [Why](/why). +- [A payout agent with maker/checker](/docs/cookbook/payout-maker-checker): the same money, two people. +- [Approval binding](/docs/concepts/approval-binding) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/resolve-an-ambiguous-effect.mdx b/docs/docs/cookbook/resolve-an-ambiguous-effect.mdx similarity index 94% rename from docs/cookbook/resolve-an-ambiguous-effect.mdx rename to docs/docs/cookbook/resolve-an-ambiguous-effect.mdx index 671f21bc..1c951424 100644 --- a/docs/cookbook/resolve-an-ambiguous-effect.mdx +++ b/docs/docs/cookbook/resolve-an-ambiguous-effect.mdx @@ -132,5 +132,5 @@ pip install ctrlrun && python main.py ## Next -- [Reconcile against the remote](/cookbook/reconcile-against-the-remote): the same answer from a hook. -- [Resolve an AMBIGUOUS effect](/guides/resolve-an-ambiguous-effect) · [Get started](/get-started/quickstart) · [Why](/why). +- [Reconcile against the remote](/docs/cookbook/reconcile-against-the-remote): the same answer from a hook. +- [Resolve an AMBIGUOUS effect](/docs/guides/resolve-an-ambiguous-effect) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/slack-approvals.mdx b/docs/docs/cookbook/slack-approvals.mdx similarity index 95% rename from docs/cookbook/slack-approvals.mdx rename to docs/docs/cookbook/slack-approvals.mdx index 9682fc82..1bdbc495 100644 --- a/docs/cookbook/slack-approvals.mdx +++ b/docs/docs/cookbook/slack-approvals.mdx @@ -138,5 +138,5 @@ pip install ctrlrun && python main.py ## Next -- [Approve in Slack](/guides/approvals-in-slack): the outbound half, the payload and the flags. -- [Approval binding](/concepts/approval-binding) · [Get started](/get-started/quickstart) · [Why](/why). +- [Approve in Slack](/docs/guides/approvals-in-slack): the outbound half, the payload and the flags. +- [Approval binding](/docs/concepts/approval-binding) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/sqlite-to-postgres.mdx b/docs/docs/cookbook/sqlite-to-postgres.mdx similarity index 94% rename from docs/cookbook/sqlite-to-postgres.mdx rename to docs/docs/cookbook/sqlite-to-postgres.mdx index e8999e28..0cb023cc 100644 --- a/docs/cookbook/sqlite-to-postgres.mdx +++ b/docs/docs/cookbook/sqlite-to-postgres.mdx @@ -115,5 +115,5 @@ pip install ctrlrun && python main.py ## Next -- [Run on Postgres](/guides/run-on-postgres): the schema, the grants, failover. -- [Effect keys](/concepts/effect-keys) · [Get started](/get-started/quickstart) · [Why](/why). +- [Run on Postgres](/docs/guides/run-on-postgres): the schema, the grants, failover. +- [Effect keys](/docs/concepts/effect-keys) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/cookbook/verify-in-github-actions.mdx b/docs/docs/cookbook/verify-in-github-actions.mdx similarity index 94% rename from docs/cookbook/verify-in-github-actions.mdx rename to docs/docs/cookbook/verify-in-github-actions.mdx index 742f6a4f..5c025d1c 100644 --- a/docs/cookbook/verify-in-github-actions.mdx +++ b/docs/docs/cookbook/verify-in-github-actions.mdx @@ -104,7 +104,7 @@ applicable guarantee passed; 1 a failure; 2 a refused or unusable configuration, Verify's G5 and G10 make an ambiguous effect on purpose, in the scratch store, and assert that a blind retry is refused. Your store is never opened, so nothing here can leave a real effect ambiguous. An `AMBIGUOUS` in your own store is the agent's, and the -[resolve recipe](/cookbook/resolve-an-ambiguous-effect) is for it. +[resolve recipe](/docs/cookbook/resolve-an-ambiguous-effect) is for it. ## Run it @@ -118,5 +118,5 @@ pip install ctrlrun && bash run.sh ## Next -- [Verify in CI](/guides/verify-in-ci): inputs, outputs and publishing the badge. -- [Exit codes](/reference/exit-codes) · [Get started](/get-started/quickstart) · [Why](/why). +- [Verify in CI](/docs/guides/verify-in-ci): inputs, outputs and publishing the badge. +- [Exit codes](/docs/reference/exit-codes) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/demos/index.mdx b/docs/docs/demos/index.mdx similarity index 77% rename from docs/demos/index.mdx rename to docs/docs/demos/index.mdx index 5494d683..9674205a 100644 --- a/docs/demos/index.mdx +++ b/docs/docs/demos/index.mdx @@ -8,11 +8,11 @@ Every demo here runs real Python in your tab. Pyodide loads, micropip installs t sent anywhere, and every refusal you read is the library's own. - + One protected refund under one policy. Change the amount, lose the reply, approve one amount and execute another, and read what refused you. - + A letter to a physician is drafted, reviewed and signed. New evidence lands, the recommendation flips, and the send is refused. @@ -29,6 +29,6 @@ on the first press and cache after that; a laptop offline can run the same thing ## Next -- [Why](/why) — what these demos are demonstrating, in 700 words -- [Get started](/get-started/quickstart) — the same guarantees around your own function -- [Cookbook](/cookbook/index) — nineteen wirings you can copy +- [Why](/docs/why) — what these demos are demonstrating, in 700 words +- [Get started](/docs/get-started/quickstart) — the same guarantees around your own function +- [Cookbook](/docs/cookbook/index) — nineteen wirings you can copy diff --git a/docs/demos/medical-affairs.mdx b/docs/docs/demos/medical-affairs.mdx similarity index 91% rename from docs/demos/medical-affairs.mdx rename to docs/docs/demos/medical-affairs.mdx index b46f1195..e5974c95 100644 --- a/docs/demos/medical-affairs.mdx +++ b/docs/docs/demos/medical-affairs.mdx @@ -62,16 +62,16 @@ page is medical information about a real medicine. ## What refused you Revision A says no dose adjustment is needed. The reviewer reads that letter and signs for it, -and the approval is bound to [the hash of that exact action](/concepts/approval-binding) — its +and the approval is bound to [the hash of that exact action](/docs/concepts/approval-binding) — its name, its arguments, its recommendation sentence, its reference list. Then a newer study supersedes reference 2 and the pipeline redrafts. Revision B recommends the opposite. The signature is still there, still valid, still unexpired, and it authorizes nothing, because the letter it covers no longer exists. The two side buttons are the other two failures. Filing a safety case with the reply lost leaves -the effect [AMBIGUOUS rather than failed](/concepts/outcomes-and-ambiguous), so the retry is +the effect [AMBIGUOUS rather than failed](/docs/concepts/outcomes-and-ambiguous), so the retry is refused and the case is filed once. An unapproved use is refused by the policy at any size, and -an action nobody wrote down is refused for [not being in it](/concepts/fail-closed). +an action nobody wrote down is refused for [not being in it](/docs/concepts/fail-closed). ```yaml schema: ctrlrun.policy/v2 @@ -106,6 +106,6 @@ from the same wheel `pip install ctrlrun` gives you. ## Next -- [Why](/why) — the 700-word version of the problem underneath this one -- [Get started](/get-started/quickstart) — the same guarantees around your own function -- [Approval binding](/concepts/approval-binding) — what an approval is bound to, and why +- [Why](/docs/why) — the 700-word version of the problem underneath this one +- [Get started](/docs/get-started/quickstart) — the same guarantees around your own function +- [Approval binding](/docs/concepts/approval-binding) — what an approval is bound to, and why diff --git a/docs/faq.mdx b/docs/docs/faq.mdx similarity index 90% rename from docs/faq.mdx rename to docs/docs/faq.mdx index fdf59cbd..967fd7a5 100644 --- a/docs/faq.mdx +++ b/docs/docs/faq.mdx @@ -11,14 +11,14 @@ The fourteen questions that come up first, answered in under eighty words each. It runs in production on a single file or on Postgres across hosts, and every guarantee is graded by one suite against both stores. What it has not had: an external security audit, a third-party review of the kernel, and a soak of the length the roadmap asks for. Those are - listed on [Run it in production](/production/index) rather than left for you to find. + listed on [Run it in production](/docs/production/index) rather than left for you to find. On one host, yes. `BEGIN IMMEDIATE` is a write lock on the file, so one effect executes once across threads and across OS processes on that machine, and the conformance suite grades SQLite case for case beside Postgres. Move when a second host must write to the store, not - before. [The choice, in one table](/production/postgres). + before. [The choice, in one table](/docs/production/postgres). @@ -27,7 +27,7 @@ The fourteen questions that come up first, answered in under eighty words each. remote it touches, is bound to the approval and the receipt, and refuses a retry when the outcome is unknown rather than resending and hoping. Where a remote does support idempotency keys, pass the effect key as one and get both. - [More](/compare/idempotency-keys). + [More](/docs/compare/idempotency-keys). @@ -35,28 +35,28 @@ The fourteen questions that come up first, answered in under eighty words each. succeed; CTRLRun decides whether a consequential effect is authorized and refuses to retry an unknown outcome. Their advice, make activities idempotent, is the work CTRLRun does for you and enforces in the store. Run the workflow in the engine and decorate the activity - that acts. [More](/compare/durable-workflows). + that acts. [More](/docs/compare/durable-workflows). Probably not. `@protect` covers anything in your Python process, and the gateway covers anything behind an MCP server in any language. An adapter buys exactly one thing: routing an approval through the framework's own interrupt so a human answers where they already answer. - A framework with no such primitive needs none. [More](/get-started/three-ways-in). + A framework with no such primitive needs none. [More](/docs/get-started/three-ways-in). No, and nothing can be against systems it does not control. CTRLRun guarantees it will not knowingly execute the same logical effect twice, and never treats an unknown outcome as a failure. The remote is the only thing that knows what the remote did; when nobody knows, - CTRLRun says so and stops. [More](/concepts/outcomes-and-ambiguous). + CTRLRun says so and stops. [More](/docs/concepts/outcomes-and-ambiguous). The effect becomes `AMBIGUOUS`, never `FAILED`, and a retry against it is refused. Only `NotExecuted`, raised by your executor when it knows the remote did nothing, means failed. A human runs `ctrlrun resolve --committed` or `--failed` after asking the remote, or a - `reconcile` hook asks for them. [More](/concepts/outcomes-and-ambiguous). + `reconcile` hook asks for them. [More](/docs/concepts/outcomes-and-ambiguous). @@ -64,7 +64,7 @@ The fourteen questions that come up first, answered in under eighty words each. enforcement is out of scope. Two things narrow it. The gateway sits between the agent and its tools, where the agent has no choice; and CTRLRun is never a tool the agent decides to call, because a check the agent opts into is not a check. - [Threat model](/THREAT_MODEL). + [Threat model](/docs/THREAT_MODEL). @@ -78,35 +78,35 @@ The fourteen questions that come up first, answered in under eighty words each. The approval request expires at its TTL, fifteen minutes by default, and a waiting call raises `ApprovalTimeout` with nothing executed. Raise the TTL for slow approvals, or do not wait: `ApprovalRequired` carries the request id, so an agent can surface it and come back - later with `with_approval(request_id)`. [More](/concepts/approval-binding). + later with `with_approval(request_id)`. [More](/docs/concepts/approval-binding). Either. On one host the store is a SQLite file and reservation is atomic across processes through `BEGIN IMMEDIATE`. Across hosts, `pip install "ctrlrun[postgres]"` and change the store: a unique index on the effect key and compare-and-set updates give the same guarantee, - graded by the suite written for SQLite. [More](/guides/run-on-postgres). + graded by the suite written for SQLite. [More](/docs/guides/run-on-postgres). One executed action: who proposed it, the action and its canonical arguments, the decision and why, the approval and approver, the effect key, the outcome, the timestamps, the hash and declared version of the policy that decided it, and the hash of the receipt before it. - Portable JSON, one object per line. [More](/reference/receipt-and-event-schemas). + Portable JSON, one object per line. [More](/docs/reference/receipt-and-event-schemas). No. Each receipt carries the hash of the one before, so an edit, a deletion from the middle or a reordering is detected and named by `seq`. It does not prove who wrote a receipt, receipts are not signed, and it does not survive an administrator who can rewrite every row - including the chain head. [More](/security/receipt-chain). + including the chain head. [More](/docs/security/receipt-chain). A compromised host or process. A malicious administrator with write access to the store. A lying remote. A compromised approver. An executor that raises `NotExecuted` after the remote acted, which `ctrlrun verify` cannot see. Data exfiltration through reads. Authority across - an agent-to-agent hop. [Threat model](/THREAT_MODEL). + an agent-to-agent hop. [Threat model](/docs/THREAT_MODEL). @@ -135,5 +135,5 @@ The fourteen questions that come up first, answered in under eighty words each. ## Next -- [Why](/why) · [Get started](/get-started/quickstart). -- [Compare](/compare/idempotency-keys): the five comparisons in full. +- [Why](/docs/why) · [Get started](/docs/get-started/quickstart). +- [Compare](/docs/compare/idempotency-keys): the five comparisons in full. diff --git a/docs/get-started/choosing.mdx b/docs/docs/get-started/choosing.mdx similarity index 92% rename from docs/get-started/choosing.mdx rename to docs/docs/get-started/choosing.mdx index d6f92098..af0bf9a2 100644 --- a/docs/get-started/choosing.mdx +++ b/docs/docs/get-started/choosing.mdx @@ -35,7 +35,7 @@ For a single host, the whole of the default configuration is a policy file and a | an identity provider | `ctrlrun.context(agent=...)` names the principal; a header or JWT provider replaces it when the process is fronted by something that authenticates | When the store has to be shared across hosts, that is the one thing that changes: -[Run on Postgres](/guides/run-on-postgres). +[Run on Postgres](/docs/guides/run-on-postgres). ## What changes nothing @@ -45,6 +45,6 @@ one refused by the decorator, and `ctrlrun verify` checks the configuration behi ## Next -- [Protect your first action](/get-started/quickstart), for the decorator. -- [Put the gateway in front of MCP](/guides/gateway-in-front-of-mcp). -- [Why](/why). +- [Protect your first action](/docs/get-started/quickstart), for the decorator. +- [Put the gateway in front of MCP](/docs/guides/gateway-in-front-of-mcp). +- [Why](/docs/why). diff --git a/docs/get-started/install.mdx b/docs/docs/get-started/install.mdx similarity index 88% rename from docs/get-started/install.mdx rename to docs/docs/get-started/install.mdx index 3e12c475..176076ec 100644 --- a/docs/get-started/install.mdx +++ b/docs/docs/get-started/install.mdx @@ -41,7 +41,7 @@ pip install "ctrlrun[gateway,postgres]" Adapters are separate distributions on their own version lines: `ctrlrun-langgraph` and `ctrlrun-openai-agents`. Most readers do not need one; see -[Three ways in](/get-started/three-ways-in). +[Three ways in](/docs/get-started/three-ways-in). ## Check it works @@ -54,6 +54,6 @@ The demo runs five scenarios in process with no network and writes its evidence ## Next -- [Protect your first action](/get-started/quickstart): one function end to end. -- [Choosing between the three ways in](/get-started/choosing). -- [Why](/why): what the library believes. +- [Protect your first action](/docs/get-started/quickstart): one function end to end. +- [Choosing between the three ways in](/docs/get-started/choosing). +- [Why](/docs/why): what the library believes. diff --git a/docs/get-started/quickstart.mdx b/docs/docs/get-started/quickstart.mdx similarity index 93% rename from docs/get-started/quickstart.mdx rename to docs/docs/get-started/quickstart.mdx index 6eb40595..da69ff4b 100644 --- a/docs/get-started/quickstart.mdx +++ b/docs/docs/get-started/quickstart.mdx @@ -1,6 +1,7 @@ --- title: "Protect your first action" description: "Protect one function end to end in ten minutes: write a policy, decorate the call, watch a small refund run and a large one wait for a human." +canonical: "https://ctrlrun.dev/docs/get-started/quickstart" --- In about ten minutes you will write a policy, protect a refund function, run one refund @@ -9,7 +10,7 @@ call refused, and read the receipts. Every block on this page runs offline again and the outputs shown are from a real run. **Prerequisites:** `pip install ctrlrun`, Python 3.11 or later, and an empty directory. If you -would rather see it before you type it, [Try it in your browser](/try-it) runs the five demo +would rather see it before you type it, [Try it in your browser](/docs/try-it) runs the five demo scenarios with no install. @@ -166,7 +167,7 @@ scenarios with no install. - **Receipts.** Everything above is in the evidence log, in order. What you did not see is a lost reply. That is the case CTRLRun exists for, and -[Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous) is where to read it next. +[Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous) is where to read it next. ## If it didn't work @@ -179,7 +180,7 @@ What you did not see is a lost reply. That is the case CTRLRun exists for, and ## Next -- [Cookbook](/cookbook/index): the same shape for a deploy, an IAM grant, a deletion, an email, an MCP server. -- [Three ways in](/get-started/three-ways-in): decorator, gateway, adapter. -- [Effect keys](/concepts/effect-keys): what the `effect=` template names, and why it is not a request id. -- [Why](/why). +- [Cookbook](/docs/cookbook/index): the same shape for a deploy, an IAM grant, a deletion, an email, an MCP server. +- [Three ways in](/docs/get-started/three-ways-in): decorator, gateway, adapter. +- [Effect keys](/docs/concepts/effect-keys): what the `effect=` template names, and why it is not a request id. +- [Why](/docs/why). diff --git a/docs/get-started/three-ways-in.mdx b/docs/docs/get-started/three-ways-in.mdx similarity index 90% rename from docs/get-started/three-ways-in.mdx rename to docs/docs/get-started/three-ways-in.mdx index 7123778b..3526de19 100644 --- a/docs/get-started/three-ways-in.mdx +++ b/docs/docs/get-started/three-ways-in.mdx @@ -31,7 +31,7 @@ with ctrlrun.context(agent="deploy-agent"): ``` `ApprovalRequired`, `ActionDenied`, `DuplicateEffect` and `AmbiguousEffect` are raised as -themselves, before the function body runs. [Protect a function](/guides/protect-a-function) is +themselves, before the function body runs. [Protect a function](/docs/guides/protect-a-function) is the full guide. ## The gateway @@ -53,7 +53,7 @@ ctrlrun gateway --upstream http://localhost:8000/mcp --alias acme --principal re Tools become actions named `mcp..`. A tool call has no decorator to carry its effect template, so the template is declared in the policy, and the gateway prints on the line that starts it every action that has none. -[Put the gateway in front of MCP](/guides/gateway-in-front-of-mcp) is the guide. +[Put the gateway in front of MCP](/docs/guides/gateway-in-front-of-mcp) is the guide. ## Adapters @@ -78,6 +78,6 @@ need one. The decorator already covers it. ## Next -- [Choosing between them](/get-started/choosing): the decision table. -- [Use the LangGraph adapter](/guides/langgraph-adapter) · [Use the OpenAI Agents SDK adapter](/guides/openai-agents-adapter). -- [Why](/why). +- [Choosing between them](/docs/get-started/choosing): the decision table. +- [Use the LangGraph adapter](/docs/guides/langgraph-adapter) · [Use the OpenAI Agents SDK adapter](/docs/guides/openai-agents-adapter). +- [Why](/docs/why). diff --git a/docs/guides/approvals-in-slack.mdx b/docs/docs/guides/approvals-in-slack.mdx similarity index 96% rename from docs/guides/approvals-in-slack.mdx rename to docs/docs/guides/approvals-in-slack.mdx index a20df614..5be8233b 100644 --- a/docs/guides/approvals-in-slack.mdx +++ b/docs/docs/guides/approvals-in-slack.mdx @@ -138,6 +138,6 @@ short backoff, and an undeliverable request is logged and left `pending` for `ct ## Next -- [Approval binding](/concepts/approval-binding): what the answer authorizes. -- [Put the gateway in front of MCP](/guides/gateway-in-front-of-mcp). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Approval binding](/docs/concepts/approval-binding): what the answer authorizes. +- [Put the gateway in front of MCP](/docs/guides/gateway-in-front-of-mcp). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/export-to-opentelemetry.mdx b/docs/docs/guides/export-to-opentelemetry.mdx similarity index 94% rename from docs/guides/export-to-opentelemetry.mdx rename to docs/docs/guides/export-to-opentelemetry.mdx index a0be768e..5f5a952a 100644 --- a/docs/guides/export-to-opentelemetry.mdx +++ b/docs/docs/guides/export-to-opentelemetry.mdx @@ -105,6 +105,6 @@ already look at traces. Deleting a trace deletes nothing CTRLRun relies on. ## Next -- [Receipts and evidence](/concepts/receipts-and-evidence). -- [Receipt and event schemas](/reference/receipt-and-event-schemas): the event names above. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Receipts and evidence](/docs/concepts/receipts-and-evidence). +- [Receipt and event schemas](/docs/reference/receipt-and-event-schemas): the event names above. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/gateway-in-front-of-mcp.mdx b/docs/docs/guides/gateway-in-front-of-mcp.mdx similarity index 96% rename from docs/guides/gateway-in-front-of-mcp.mdx rename to docs/docs/guides/gateway-in-front-of-mcp.mdx index f9f54453..299a21a1 100644 --- a/docs/guides/gateway-in-front-of-mcp.mdx +++ b/docs/docs/guides/gateway-in-front-of-mcp.mdx @@ -136,6 +136,6 @@ validated, never trusted. ## Next -- [Approve in Slack](/guides/approvals-in-slack): where the request id goes. -- [Choosing between the three ways in](/get-started/choosing). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Approve in Slack](/docs/guides/approvals-in-slack): where the request id goes. +- [Choosing between the three ways in](/docs/get-started/choosing). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/langgraph-adapter.mdx b/docs/docs/guides/langgraph-adapter.mdx similarity index 94% rename from docs/guides/langgraph-adapter.mdx rename to docs/docs/guides/langgraph-adapter.mdx index 1a6e1c0b..99a018e0 100644 --- a/docs/guides/langgraph-adapter.mdx +++ b/docs/docs/guides/langgraph-adapter.mdx @@ -106,6 +106,6 @@ them against a real LangGraph install in this repository's CI. ## Next -- [Use the OpenAI Agents SDK adapter](/guides/openai-agents-adapter): the other shape, and why its binding is attribution. -- [Approval binding](/concepts/approval-binding). -- [The adapter's README](https://github.com/CTRLRun/ctrlrun/blob/main/adapters/langgraph/README.md) · [Get started](/get-started/quickstart) · [Why](/why). +- [Use the OpenAI Agents SDK adapter](/docs/guides/openai-agents-adapter): the other shape, and why its binding is attribution. +- [Approval binding](/docs/concepts/approval-binding). +- [The adapter's README](https://github.com/CTRLRun/ctrlrun/blob/main/adapters/langgraph/README.md) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/observe-to-enforce.mdx b/docs/docs/guides/observe-to-enforce.mdx similarity index 96% rename from docs/guides/observe-to-enforce.mdx rename to docs/docs/guides/observe-to-enforce.mdx index c13a59d9..85857e1b 100644 --- a/docs/guides/observe-to-enforce.mdx +++ b/docs/docs/guides/observe-to-enforce.mdx @@ -140,6 +140,6 @@ mode, for the same reason: a human's no must not stop what observe mode promises ## Next -- [Observe mode](/concepts/observe-mode). -- [Fail closed](/concepts/fail-closed): what enforce mode refuses. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Observe mode](/docs/concepts/observe-mode). +- [Fail closed](/docs/concepts/fail-closed): what enforce mode refuses. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/openai-agents-adapter.mdx b/docs/docs/guides/openai-agents-adapter.mdx similarity index 95% rename from docs/guides/openai-agents-adapter.mdx rename to docs/docs/guides/openai-agents-adapter.mdx index 371eb5f6..6846241a 100644 --- a/docs/guides/openai-agents-adapter.mdx +++ b/docs/docs/guides/openai-agents-adapter.mdx @@ -101,6 +101,6 @@ blocks are the adapter's own example, run against a real SDK install in this rep ## Next -- [Use the LangGraph adapter](/guides/langgraph-adapter): the prevention shape. -- [Three ways in](/get-started/three-ways-in). -- [The adapter's README](https://github.com/CTRLRun/ctrlrun/blob/main/adapters/openai-agents/README.md) · [Get started](/get-started/quickstart) · [Why](/why). +- [Use the LangGraph adapter](/docs/guides/langgraph-adapter): the prevention shape. +- [Three ways in](/docs/get-started/three-ways-in). +- [The adapter's README](https://github.com/CTRLRun/ctrlrun/blob/main/adapters/openai-agents/README.md) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/protect-a-function.mdx b/docs/docs/guides/protect-a-function.mdx similarity index 93% rename from docs/guides/protect-a-function.mdx rename to docs/docs/guides/protect-a-function.mdx index 3e8cb492..404c3c63 100644 --- a/docs/guides/protect-a-function.mdx +++ b/docs/docs/guides/protect-a-function.mdx @@ -118,7 +118,7 @@ below runs offline; the deploy tool is a stand-in that records calls. Every call that reaches a decision has a receipt, denied ones included, and every effect key has a state. A call waiting on a human has an approval request and no receipt yet. The pending approvals are what `ctrlrun approve ` answers; the - [quickstart](/get-started/quickstart) walks through presenting one. + [quickstart](/docs/get-started/quickstart) walks through presenting one. @@ -131,7 +131,7 @@ below runs offline; the deploy tool is a stand-in that records calls. | `resource=` | a template for the resource, part of the hash and what a grant's `resources:` matches | | `wait=True` | block on the approval provider instead of raising `ApprovalRequired`; what an adapter or a webhook provider needs | | `lease=` | how long the reservation is held for a slow call; past it the effect is `AMBIGUOUS`, never released | -| `reconcile=` | a function that asks the remote what happened to an effect key; [Reconcile automatically](/guides/reconcile-automatically) | +| `reconcile=` | a function that asks the remote what happened to an effect key; [Reconcile automatically](/docs/guides/reconcile-automatically) | | `control=` | a `Control` you built, instead of the one discovered from `ctrlrun.yaml` | Template syntax is checked at decoration time, so a typo fails at import rather than mid-run. @@ -149,5 +149,5 @@ reserved subject. ## Next -- [Put the gateway in front of MCP](/guides/gateway-in-front-of-mcp): the same guarantees with no code change. -- [Effect keys](/concepts/effect-keys) · [Get started](/get-started/quickstart) · [Why](/why). +- [Put the gateway in front of MCP](/docs/guides/gateway-in-front-of-mcp): the same guarantees with no code change. +- [Effect keys](/docs/concepts/effect-keys) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/reconcile-automatically.mdx b/docs/docs/guides/reconcile-automatically.mdx similarity index 94% rename from docs/guides/reconcile-automatically.mdx rename to docs/docs/guides/reconcile-automatically.mdx index 015b4267..df645a0e 100644 --- a/docs/guides/reconcile-automatically.mdx +++ b/docs/docs/guides/reconcile-automatically.mdx @@ -115,6 +115,6 @@ second execution. ## Next -- [Resolve an AMBIGUOUS effect](/guides/resolve-an-ambiguous-effect): the human path. -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Resolve an AMBIGUOUS effect](/docs/guides/resolve-an-ambiguous-effect): the human path. +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/resolve-an-ambiguous-effect.mdx b/docs/docs/guides/resolve-an-ambiguous-effect.mdx similarity index 92% rename from docs/guides/resolve-an-ambiguous-effect.mdx rename to docs/docs/guides/resolve-an-ambiguous-effect.mdx index 0e0e026b..cf5ba7bd 100644 --- a/docs/guides/resolve-an-ambiguous-effect.mdx +++ b/docs/docs/guides/resolve-an-ambiguous-effect.mdx @@ -96,7 +96,7 @@ commits and then loses the reply. If the remote can be asked programmatically, a `reconcile` hook asks it for you and moves the record the same way, with the same rule that it moves only in the direction the answer points: -[Reconcile automatically](/guides/reconcile-automatically). +[Reconcile automatically](/docs/guides/reconcile-automatically). ## If it didn't work @@ -108,6 +108,6 @@ record the same way, with the same rule that it moves only in the direction the ## Next -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous): why there is a third outcome. -- [Reconcile automatically](/guides/reconcile-automatically). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous): why there is a third outcome. +- [Reconcile automatically](/docs/guides/reconcile-automatically). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/run-on-postgres.mdx b/docs/docs/guides/run-on-postgres.mdx similarity index 94% rename from docs/guides/run-on-postgres.mdx rename to docs/docs/guides/run-on-postgres.mdx index 2b08931f..f5a01ebd 100644 --- a/docs/guides/run-on-postgres.mdx +++ b/docs/docs/guides/run-on-postgres.mdx @@ -106,6 +106,6 @@ payment are one winner and ninety-nine refusals. ## Next -- [Effect keys](/concepts/effect-keys): what the unique index protects. -- [The operator's Postgres page](/postgres): grants, failover and what the store does not do for you, in full. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Effect keys](/docs/concepts/effect-keys): what the unique index protects. +- [The operator's Postgres page](/docs/postgres): grants, failover and what the store does not do for you, in full. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/guides/verify-in-ci.mdx b/docs/docs/guides/verify-in-ci.mdx similarity index 95% rename from docs/guides/verify-in-ci.mdx rename to docs/docs/guides/verify-in-ci.mdx index 70b69547..79ac64e2 100644 --- a/docs/guides/verify-in-ci.mdx +++ b/docs/docs/guides/verify-in-ci.mdx @@ -106,7 +106,7 @@ guarantees pass. `contents: write` and asking every user for that is a bad trade for a tool about least privilege. Publish it yourself from a job that runs only on pushes to your default branch, with `contents: write` on that job alone, and point Shields' endpoint badge at the raw file. - [Get the badge](/verify/get-the-badge) has the copy-paste workflow. + [Get the badge](/docs/verify/get-the-badge) has the copy-paste workflow. @@ -130,6 +130,6 @@ acted is invisible to it. The badge does not mean secure, safe, compliant, certi ## Next -- [Exit codes](/reference/exit-codes). -- [What the badge means](/verify): the guarantee catalogue in full. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Exit codes](/docs/reference/exit-codes). +- [What the badge means](/docs/verify): the guarantee catalogue in full. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/how-this-is-built.md b/docs/docs/how-this-is-built.md similarity index 93% rename from docs/how-this-is-built.md rename to docs/docs/how-this-is-built.md index 5e08d73c..494367bd 100644 --- a/docs/how-this-is-built.md +++ b/docs/docs/how-this-is-built.md @@ -36,7 +36,7 @@ The numbers that are recorded: |---|---|---| | v0.2 | The mutation tables found roughly thirty-five gaps, almost all of one of four shapes: subsumed guards, orphaned handlers, negative tests against behaviour the library refuses anyway, and windows not actually reproduced | `CONTRIBUTING.md`, "The four shapes of a false green" | | v0.3 | A self-review of the specification found four defects; an independent review of the same two sections found two authorization holes the author had missed, both visible only from a file the spec did not mention | `CHANGELOG.md`, 0.3.0 | -| v0.5 | An independent review of the adapter contract found five defects that would each have produced an insecure or unimplementable adapter. Three further reviews and a third adapter written against the contract alone found five authorization defects in one reference adapter before it shipped, all of the same shape | `CHANGELOG.md`, 0.5.0; `docs/adapters.md`, the fourteen questions | +| v0.5 | An independent review of the adapter contract found five defects that would each have produced an insecure or unimplementable adapter. Three further reviews and a third adapter written against the contract alone found five authorization defects in one reference adapter before it shipped, all of the same shape | `CHANGELOG.md`, 0.5.0; `docs/docs/adapters.md`, the fourteen questions | | v0.6 | An independent review of the store work found twenty-one defects, four of them blockers, three of which were invisible from the diff and visible only from the shipped code | `CHANGELOG.md`, 0.6.0 | The suite today is 1,704 test functions, 3,944 cases with parametrisation, run on two Python @@ -54,7 +54,7 @@ prevention is renamed everywhere it was called a defence. ## Every claim maps to a test -`docs/CLAIMS.md` maps every sentence in the README to the code that implements it and the +`docs/docs/CLAIMS.md` maps every sentence in the README to the code that implements it and the test that proves it. A sentence with no row is cut; a row whose test disappears takes its sentence with it. A test resolves every line number in the table against the line it cites and fails if the named symbol is not there. It found nine stale references the first time it ran, @@ -71,7 +71,7 @@ review so far was run inside this project, by sessions that did not write the co review but that follow the same specifications and the same rules. An external audit is on the roadmap for v0.8 to v0.9. Until it happens, the evidence for the guarantees is the suite, the mutation tables, the review records in the changelog, and `ctrlrun verify` against your own -configuration. Read `docs/THREAT_MODEL.md` for what the guarantees do not cover. +configuration. Read `docs/docs/THREAT_MODEL.md` for what the guarantees do not cover. ## AI coding agents, and the constraints that make that safe @@ -103,6 +103,6 @@ same distributions to a GitHub Release with the tag's changelog entry. ## Next -- [`docs/THREAT_MODEL.md`](THREAT_MODEL.md): what the guarantees do not cover. -- [`docs/verify.md`](verify.md): running the guarantees against your own configuration. +- [`docs/docs/THREAT_MODEL.md`](/docs/THREAT_MODEL): what the guarantees do not cover. +- [`docs/docs/verify.md`](/docs/verify): running the guarantees against your own configuration. - [`CONTRIBUTING.md`](https://github.com/CTRLRun/ctrlrun/blob/main/CONTRIBUTING.md): the rules, in the form a contributor follows them. diff --git a/docs/mcp/approve-from-your-assistant.mdx b/docs/docs/mcp/approve-from-your-assistant.mdx similarity index 96% rename from docs/mcp/approve-from-your-assistant.mdx rename to docs/docs/mcp/approve-from-your-assistant.mdx index 415039b3..8f73d044 100644 --- a/docs/mcp/approve-from-your-assistant.mdx +++ b/docs/docs/mcp/approve-from-your-assistant.mdx @@ -117,5 +117,5 @@ The agent then runs the refund it was waiting on, and the receipt says who let i ## Next -- [CTRLRun and MCP](/mcp/overview) · [The gateway in five minutes](/mcp/gateway-in-5-minutes). -- [Get started](/get-started/quickstart) · [Why](/why). +- [CTRLRun and MCP](/docs/mcp/overview) · [The gateway in five minutes](/docs/mcp/gateway-in-5-minutes). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/mcp/gateway-in-5-minutes.mdx b/docs/docs/mcp/gateway-in-5-minutes.mdx similarity index 96% rename from docs/mcp/gateway-in-5-minutes.mdx rename to docs/docs/mcp/gateway-in-5-minutes.mdx index d1b617b4..2e960a44 100644 --- a/docs/mcp/gateway-in-5-minutes.mdx +++ b/docs/docs/mcp/gateway-in-5-minutes.mdx @@ -126,5 +126,5 @@ accepts `2025-11-25`, `2025-06-18` and `2025-03-26`. ## Next -- [Put the gateway in front of MCP](/guides/gateway-in-front-of-mcp): the longer guide, with the lost-reply case. -- [CTRLRun and MCP](/mcp/overview) · [Get started](/get-started/quickstart) · [Why](/why). +- [Put the gateway in front of MCP](/docs/guides/gateway-in-front-of-mcp): the longer guide, with the lost-reply case. +- [CTRLRun and MCP](/docs/mcp/overview) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/mcp/overview.mdx b/docs/docs/mcp/overview.mdx similarity index 87% rename from docs/mcp/overview.mdx rename to docs/docs/mcp/overview.mdx index 8b6fd0de..b8116aa5 100644 --- a/docs/mcp/overview.mdx +++ b/docs/docs/mcp/overview.mdx @@ -29,7 +29,7 @@ after agent ──▶ CTRLRun gateway ──▶ MCP server - Every call that **reaches a decision** leaves a receipt, denied ones included. A call still waiting on a human has not been decided yet and has none until it is. - Measure first with observe mode, in the same policy file. -[The gateway in five minutes](/mcp/gateway-in-5-minutes) is the page for someone who already +[The gateway in five minutes](/docs/mcp/gateway-in-5-minutes) is the page for someone who already runs an MCP server. ## Learning: this site is an MCP server @@ -37,7 +37,7 @@ runs an MCP server. Every page here is reachable through an MCP server hosted with the site, with one tool, a search across the documentation. Add it to Cursor, VS Code or any MCP client and the assistant answers from these pages rather than from memory. -[Use the docs from your editor](/mcp/use-the-docs-from-your-editor) has the configuration. +[Use the docs from your editor](/docs/mcp/use-the-docs-from-your-editor) has the configuration. ## Discovery: the registries @@ -69,7 +69,7 @@ approver's assistant ──▶ ctrlrun mcp-operator ──▶ the same appro - It binds loopback and has no flag that changes that, because its read tools answer without a credential. -[Approve from your assistant](/mcp/approve-from-your-assistant) is the page with the +[Approve from your assistant](/docs/mcp/approve-from-your-assistant) is the page with the configuration and a transcript. ## What MCP does not change @@ -82,5 +82,5 @@ gateway is in the path whether the agent likes it or not. ## Next -- [The gateway in five minutes](/mcp/gateway-in-5-minutes) · [Approve from your assistant](/mcp/approve-from-your-assistant). -- [Three ways in](/get-started/three-ways-in) · [Get started](/get-started/quickstart) · [Why](/why). +- [The gateway in five minutes](/docs/mcp/gateway-in-5-minutes) · [Approve from your assistant](/docs/mcp/approve-from-your-assistant). +- [Three ways in](/docs/get-started/three-ways-in) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/mcp/use-the-docs-from-your-editor.mdx b/docs/docs/mcp/use-the-docs-from-your-editor.mdx similarity index 79% rename from docs/mcp/use-the-docs-from-your-editor.mdx rename to docs/docs/mcp/use-the-docs-from-your-editor.mdx index 794ab52a..4ddd01c8 100644 --- a/docs/mcp/use-the-docs-from-your-editor.mdx +++ b/docs/docs/mcp/use-the-docs-from-your-editor.mdx @@ -35,15 +35,15 @@ Both shapes are the ones Mintlify's documentation gives for a hosted docs server ## Three questions it can now answer - *What happens in CTRLRun when a tool call times out?* The assistant finds - [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous) and answers that a timeout is + [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous) and answers that a timeout is `AMBIGUOUS`, not `FAILED`, and that a retry is refused until a human or a reconcile hook resolves it. - *How do I put an approval in front of one MCP tool but not another?* It finds the - [policy reference](/reference/policy-yaml) and the - [gateway page](/mcp/gateway-in-5-minutes): one entry per tool, `approve` on one, `allow` on + [policy reference](/docs/reference/policy-yaml) and the + [gateway page](/docs/mcp/gateway-in-5-minutes): one entry per tool, `approve` on one, `allow` on the other. - *Does the receipt chain prove who wrote a receipt?* It finds - [Receipts and evidence](/concepts/receipts-and-evidence) and answers no: the chain detects + [Receipts and evidence](/docs/concepts/receipts-and-evidence) and answers no: the chain detects alteration and is not a signature. ## What it is not @@ -55,5 +55,5 @@ agent and its tools and not a tool the agent picks. ## Next -- [CTRLRun and MCP](/mcp/overview). -- [Get started](/get-started/quickstart) · [Why](/why). +- [CTRLRun and MCP](/docs/mcp/overview). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/not-only-agents.mdx b/docs/docs/not-only-agents.mdx similarity index 89% rename from docs/not-only-agents.mdx rename to docs/docs/not-only-agents.mdx index 9a6e6de2..827763b7 100644 --- a/docs/not-only-agents.mdx +++ b/docs/docs/not-only-agents.mdx @@ -36,7 +36,7 @@ handler runs a second time with the same payload. An `Idempotency-Key` the handl does not help: the second delivery is a different process, and it generates the key again. What is needed is a name for the consequence that both deliveries compute identically — an -[effect key](/concepts/effect-keys) — and one atomic reservation of it. +[effect key](/docs/concepts/effect-keys) — and one atomic reservation of it. ```bash python examples/without-an-agent/redelivered-webhook/main.py @@ -62,7 +62,7 @@ python examples/without-an-agent/lost-merge/main.py Two things, and neither is the retry. An agent chooses the arguments, so an approval that is not bound to the exact arguments a person saw approves something nobody read — which is why -[approval binding](/concepts/approval-binding) exists. And an agent that fails at one route will +[approval binding](/docs/concepts/approval-binding) exists. And an agent that fails at one route will try another, which is the second half of the merge example: the defence that matters there is not a better retry, it is a policy that is a list of what is permitted. @@ -78,6 +78,6 @@ outcome is never a failure — and the other four are there when you want them. ## Next -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous): why a timeout is not a failure. -- [Effect keys](/concepts/effect-keys) · [Compare: idempotency keys](/compare/idempotency-keys). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous): why a timeout is not a failure. +- [Effect keys](/docs/concepts/effect-keys) · [Compare: idempotency keys](/docs/compare/idempotency-keys). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/postgres.md b/docs/docs/postgres.md similarity index 99% rename from docs/postgres.md rename to docs/docs/postgres.md index 89da015f..af2223c1 100644 --- a/docs/postgres.md +++ b/docs/docs/postgres.md @@ -235,7 +235,7 @@ What that means in practice: The soak in `research/soak/` is not a measurement of this. It publishes a duration and an action count, on one host with four threads against a database on the same machine, and it names no -throughput figure on purpose — [what it does not establish](/production/soak) says why. Size this +throughput figure on purpose — [what it does not establish](/docs/production/soak) says why. Size this against your own hardware. ## Verifying the chain @@ -290,7 +290,7 @@ them is true: — an edited row, a deletion from the middle, a reordering — and that is all it detects. Receipts are not signed, alteration is not authorship, and the chain is not tamper-proof against an administrator with write access to every row including the chain head. - `docs/THREAT_MODEL.md` states what remains open. + `docs/docs/THREAT_MODEL.md` states what remains open. - **It does not make a read replica safe to use.** See above. - **It is not a queue, a scheduler or a workflow engine.** It records decisions and outcomes for actions somebody else is executing. diff --git a/docs/production/how-reservation-works.mdx b/docs/docs/production/how-reservation-works.mdx similarity index 93% rename from docs/production/how-reservation-works.mdx rename to docs/docs/production/how-reservation-works.mdx index 1fa95036..07fdc981 100644 --- a/docs/production/how-reservation-works.mdx +++ b/docs/docs/production/how-reservation-works.mdx @@ -52,7 +52,7 @@ failure into an unknown one. - **It does not tell you what the remote did.** Reservation is about the store. An executor that raised something other than `NotExecuted` leaves an `AMBIGUOUS` effect that no query settles. - **It does not reclaim a key.** A lease that lapsed becomes `AMBIGUOUS` when the next contender - arrives. [Nothing sweeps](/production/recovery). + arrives. [Nothing sweeps](/docs/production/recovery). - **A failed re-read costs availability.** The key stays reserved by an attempt that will never run, its lease lapses, and a person answers. That is the trade: it never costs a second execution. @@ -75,6 +75,6 @@ between two hosts** is not, and this project claims it nowhere. ## Next -- [Recovery after a crash](/production/recovery): what a restarted process may conclude. -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous): the other unknown, the one no query settles. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Recovery after a crash](/docs/production/recovery): what a restarted process may conclude. +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous): the other unknown, the one no query settles. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/production/index.mdx b/docs/docs/production/index.mdx similarity index 81% rename from docs/production/index.mdx rename to docs/docs/production/index.mdx index 5b995f59..439004bf 100644 --- a/docs/production/index.mdx +++ b/docs/docs/production/index.mdx @@ -16,23 +16,23 @@ need. `test_the_first_line_of_the_section_says_which_store_and_why` asserts the | If you are asking | Read | |---|---| -| Which store, and what does each cost me? | [SQLite or Postgres](/production/postgres) | -| What happens when the database write is lost mid-`COMMIT`? | [How reservation works](/production/how-reservation-works) | -| What happens when I deploy a new version over an old database? | [Migrations](/production/migrations) | -| The process died holding a key. Now what? | [Recovery after a crash](/production/recovery) | -| Somebody edited a receipt. Would I know? | [Receipt integrity](/production/receipt-integrity) | -| Has this been run under load? | [The soak](/production/soak) | -| What do I watch, and what do I page on? | [Operations](/production/operations) | +| Which store, and what does each cost me? | [SQLite or Postgres](/docs/production/postgres) | +| What happens when the database write is lost mid-`COMMIT`? | [How reservation works](/docs/production/how-reservation-works) | +| What happens when I deploy a new version over an old database? | [Migrations](/docs/production/migrations) | +| The process died holding a key. Now what? | [Recovery after a crash](/docs/production/recovery) | +| Somebody edited a receipt. Would I know? | [Receipt integrity](/docs/production/receipt-integrity) | +| Has this been run under load? | [The soak](/docs/production/soak) | +| What do I watch, and what do I page on? | [Operations](/docs/production/operations) | ## Where it stands {/* generated from the suite, pyproject and the soak (full) — run the generator */} - **Version 0.6.1**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later. -- **4,404 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/how-this-is-built). -- **11 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/security/verify-guarantees). -- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/production/postgres). -- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/production/soak). -- **Each receipt carries the hash of the one before it**, so an alteration is detected and named. [Read more](/production/receipt-integrity). +- **4,404 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). +- **11 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/docs/security/verify-guarantees). +- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/docs/production/postgres). +- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/docs/production/soak). +- **Each receipt carries the hash of the one before it**, so an alteration is detected and named. [Read more](/docs/production/receipt-integrity). - **Apache-2.0**, and the enforcement kernel stays open source. Releases carry PyPI provenance attestations from GitHub Actions. **Not yet:** @@ -69,7 +69,7 @@ questions about the store. - **It does not run anything for you.** There is no daemon, no scheduler and no background thread. Nothing sweeps expired leases, nothing retries on your behalf, and a restarted process - repairs nothing. See [recovery](/production/recovery) for why that is deliberate. + repairs nothing. See [recovery](/docs/production/recovery) for why that is deliberate. - **It does not make an external system idempotent.** CTRLRun refuses to knowingly act twice. Whether the remote acted is a fact only the remote holds. - **It does not roll anything back.** It is not a transaction manager and it never pretends a @@ -84,6 +84,6 @@ and destroys. It never opens the store you run on. ## Next -- [SQLite or Postgres](/production/postgres): the choice, and what each one costs. -- [How reservation works](/production/how-reservation-works): the lost `COMMIT`, in two rows. -- [Get started](/get-started/quickstart) · [Why](/why). +- [SQLite or Postgres](/docs/production/postgres): the choice, and what each one costs. +- [How reservation works](/docs/production/how-reservation-works): the lost `COMMIT`, in two rows. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/production/migrations.mdx b/docs/docs/production/migrations.mdx similarity index 95% rename from docs/production/migrations.mdx rename to docs/docs/production/migrations.mdx index 85931cc2..a14b95a7 100644 --- a/docs/production/migrations.mdx +++ b/docs/docs/production/migrations.mdx @@ -76,6 +76,6 @@ for the flag that does not exist. ## Next -- [SQLite or Postgres](/production/postgres): where the schema lives. -- [Operations](/production/operations): what to check after a deploy. -- [Get started](/get-started/quickstart) · [Why](/why). +- [SQLite or Postgres](/docs/production/postgres): where the schema lives. +- [Operations](/docs/production/operations): what to check after a deploy. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/production/operations.mdx b/docs/docs/production/operations.mdx similarity index 91% rename from docs/production/operations.mdx rename to docs/docs/production/operations.mdx index 5e06e375..32dabaec 100644 --- a/docs/production/operations.mdx +++ b/docs/docs/production/operations.mdx @@ -21,7 +21,7 @@ ctrlrun stats # what the receipts say | Effects in an unknown outcome | `ctrlrun effects --state ambiguous` | somebody must say what happened. This is the page-a-human number | | `executing (lease expired)` | `ctrlrun effects` | a holder went away and nobody has contended the key since. Not yet a problem, and not self-healing | | An approval granted long ago and never consumed | `ctrlrun inspect ` | a human answered and the agent never came back. There is no command that lists these: an action still awaiting one has no receipt, and `ctrlrun stats` counts receipts | -| A chain break, by name | `ctrlrun receipts --verify-chain` | see [receipt integrity](/production/receipt-integrity) | +| A chain break, by name | `ctrlrun receipts --verify-chain` | see [receipt integrity](/docs/production/receipt-integrity) | | A `WARNING` on the `ctrlrun.postgres` logger with a `branch` attribute | your logs | a `COMMIT` was lost and the re-read decided what to do. The attribute says which branch ran, including the two that end in a refusal | | The store raising rather than deciding | your exception handler | the database is unreachable. Work stops; it is never quietly permitted | @@ -42,7 +42,7 @@ and there will not be one. - **Verify the chain after a restore**, and after any maintenance that wrote to the database directly. - **Take the backup before the first new process starts** on a deploy, because migrations run at - open and there is [no way back](/production/migrations). + open and there is [no way back](/docs/production/migrations). - **Size the lease to your slowest executor**, not to your average one. It defaults to five minutes and is meant to be raised. A lease shorter than the work it covers turns healthy slow actions into unknown outcomes. @@ -69,6 +69,6 @@ command list so a new one cannot appear unnoticed. ## Next -- [Recovery after a crash](/production/recovery): why nothing sweeps. -- [Reconcile automatically](/guides/reconcile-automatically): the hook that answers without a person. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Recovery after a crash](/docs/production/recovery): why nothing sweeps. +- [Reconcile automatically](/docs/guides/reconcile-automatically): the hook that answers without a person. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/production/postgres.mdx b/docs/docs/production/postgres.mdx similarity index 86% rename from docs/production/postgres.mdx rename to docs/docs/production/postgres.mdx index 3ee4eaf8..994a5f53 100644 --- a/docs/production/postgres.mdx +++ b/docs/docs/production/postgres.mdx @@ -37,8 +37,8 @@ store = PostgresStateStore("postgresql://ctrlrun@db.internal:5432/ctrlrun", sche You create the schema; the store does not. Its encoding must be UTF-8 and anything else is refused at open, and migrations run at open, forward only, with no flag that suppresses them. -[Run on Postgres](/guides/run-on-postgres) is the step-by-step — the two SQL statements, the -grants, where to put the password. [The Postgres reference](/postgres) has connection strings, +[Run on Postgres](/docs/guides/run-on-postgres) is the step-by-step — the two SQL statements, the +grants, where to put the password. [The Postgres reference](/docs/postgres) has connection strings, pooling, failover and the throughput ceiling, and says why each refusal is where it is. ## What does not change @@ -52,7 +52,7 @@ SQLite before Postgres existed — which is the point of running one suite rathe - **Postgres does not buy you a stronger guarantee.** It buys the same guarantee across more machines. A deployment that did not need the second machine gains nothing and pays for a server. - **Neither store makes a remote idempotent.** The reservation stops a second *attempt*; what the - remote already did is [a different question](/concepts/outcomes-and-ambiguous). + remote already did is [a different question](/docs/concepts/outcomes-and-ambiguous). - **The store never fails open when the database is unreachable.** It raises, and the caller sees the store's exception rather than an outcome. An unreachable database stops work; it does not quietly permit it. @@ -63,7 +63,7 @@ SQLite before Postgres existed — which is the point of running one suite rathe no shared memory, no shared file locks, which is the part `BEGIN IMMEDIATE` was relying on and a second host removes. A **network partition between hosts** is not exercised by anything in this repository, and the mechanism's independence from a shared file is an argument, not a - measurement. [How reservation works](/production/how-reservation-works) says the same in the + measurement. [How reservation works](/docs/production/how-reservation-works) says the same in the place it cites the tests. **Verified by** `T141` and `T154` — every case of the store conformance suite against SQLite, the @@ -75,6 +75,6 @@ backend you configured, in a scratch store verify creates and destroys rather th ## Next -- [How reservation works](/production/how-reservation-works): what happens when a `COMMIT` is lost. -- [Migrations](/production/migrations): what a new binary does to an old database. -- [Get started](/get-started/quickstart) · [Why](/why). +- [How reservation works](/docs/production/how-reservation-works): what happens when a `COMMIT` is lost. +- [Migrations](/docs/production/migrations): what a new binary does to an old database. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/production/receipt-integrity.mdx b/docs/docs/production/receipt-integrity.mdx similarity index 90% rename from docs/production/receipt-integrity.mdx rename to docs/docs/production/receipt-integrity.mdx index 07524601..e3a13842 100644 --- a/docs/production/receipt-integrity.mdx +++ b/docs/docs/production/receipt-integrity.mdx @@ -52,8 +52,8 @@ exception you must handle, and a log with no hole in it. - **It does not tell you who wrote a receipt.** Alteration is not authorship, it does not survive an administrator who can rewrite every row including the head, and it vouches for nothing that - was never recorded. [The receipt chain](/security/receipt-chain) is where those limits are - argued and [the threat model](/THREAT_MODEL) says what stays open; this page is what to run and + was never recorded. [The receipt chain](/docs/security/receipt-chain) is where those limits are + argued and [the threat model](/docs/THREAT_MODEL) says what stays open; this page is what to run and what to do with the answer. - **It does not tell you a break was malicious.** A restore from backup, a hand-run `UPDATE` and a retention job all produce the same names. The report says what changed, not why. @@ -71,6 +71,6 @@ control. ## Next -- [The receipt chain](/security/receipt-chain): what it proves, and what it does not. -- [Operations](/production/operations): where this check belongs in a schedule. -- [Get started](/get-started/quickstart) · [Why](/why). +- [The receipt chain](/docs/security/receipt-chain): what it proves, and what it does not. +- [Operations](/docs/production/operations): where this check belongs in a schedule. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/production/recovery.mdx b/docs/docs/production/recovery.mdx similarity index 94% rename from docs/production/recovery.mdx rename to docs/docs/production/recovery.mdx index 8f1219f9..0e3e1721 100644 --- a/docs/production/recovery.mdx +++ b/docs/docs/production/recovery.mdx @@ -73,6 +73,6 @@ blind retry on your store's backend, in a scratch store of its own. ## Next -- [Resolve an AMBIGUOUS effect](/guides/resolve-an-ambiguous-effect): the commands, step by step. -- [Operations](/production/operations): what to watch so you find these before a customer does. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Resolve an AMBIGUOUS effect](/docs/guides/resolve-an-ambiguous-effect): the commands, step by step. +- [Operations](/docs/production/operations): what to watch so you find these before a customer does. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/production/soak.mdx b/docs/docs/production/soak.mdx similarity index 90% rename from docs/production/soak.mdx rename to docs/docs/production/soak.mdx index 308fff27..e945f3cc 100644 --- a/docs/production/soak.mdx +++ b/docs/docs/production/soak.mdx @@ -65,13 +65,13 @@ stopped asking for one is not the same as a run that no longer has one. - **It is not a throughput figure you can plan against.** A policy denial writes a receipt like any other outcome, so nearly every attempt above went through the one-row chain head — but on one host, with four threads, against a database on - the same machine. [Postgres](/postgres) describes that ceiling; this run does not + the same machine. [Postgres](/docs/postgres) describes that ceiling; this run does not size it for your hardware. - **It does not exercise a partition or a second host.** That is the cross-host - suite, and [how reservation works](/production/how-reservation-works) says which + suite, and [how reservation works](/docs/production/how-reservation-works) says which of those were actually run. - **It says nothing about the receipt chain's integrity.** - [Receipt integrity](/production/receipt-integrity) is that. + [Receipt integrity](/docs/production/receipt-integrity) is that. ## Run it yourself @@ -94,6 +94,6 @@ expired lease does and does not free. ## Next -- [Run it in production](/production/index): the section this belongs to. -- [Recovery after a crash](/production/recovery): what survives a restart, and what does not. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Run it in production](/docs/production/index): the section this belongs to. +- [Recovery after a crash](/docs/production/recovery): what survives a restart, and what does not. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Action.mdx b/docs/docs/reference/api/Action.mdx similarity index 87% rename from docs/reference/api/Action.mdx rename to docs/docs/reference/api/Action.mdx index 11f38106..250b3b67 100644 --- a/docs/reference/api/Action.mdx +++ b/docs/docs/reference/api/Action.mdx @@ -24,5 +24,5 @@ their `action_id` matches (SPEC-v0.1 §2.1). Identical content shares an `action ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/ActionDenied.mdx b/docs/docs/reference/api/ActionDenied.mdx similarity index 83% rename from docs/reference/api/ActionDenied.mdx rename to docs/docs/reference/api/ActionDenied.mdx index e7898373..b31afb37 100644 --- a/docs/reference/api/ActionDenied.mdx +++ b/docs/docs/reference/api/ActionDenied.mdx @@ -21,5 +21,5 @@ The action may not run. `reason` says why, e.g. `unknown_action` (SPEC-v0.1 §3. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/AmbiguousEffect.mdx b/docs/docs/reference/api/AmbiguousEffect.mdx similarity index 87% rename from docs/reference/api/AmbiguousEffect.mdx rename to docs/docs/reference/api/AmbiguousEffect.mdx index 884c555d..1f83ece7 100644 --- a/docs/reference/api/AmbiguousEffect.mdx +++ b/docs/docs/reference/api/AmbiguousEffect.mdx @@ -25,5 +25,5 @@ until `ctrlrun resolve` says which it was. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Approval.mdx b/docs/docs/reference/api/Approval.mdx similarity index 84% rename from docs/reference/api/Approval.mdx rename to docs/docs/reference/api/Approval.mdx index 73b7d525..c4a5cd0b 100644 --- a/docs/reference/api/Approval.mdx +++ b/docs/docs/reference/api/Approval.mdx @@ -23,5 +23,5 @@ A human's grant, bound to one `action_hash` (SPEC-v0.1 §4.1). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/ApprovalAnswer.mdx b/docs/docs/reference/api/ApprovalAnswer.mdx similarity index 92% rename from docs/reference/api/ApprovalAnswer.mdx rename to docs/docs/reference/api/ApprovalAnswer.mdx index 266e2652..e4e51891 100644 --- a/docs/reference/api/ApprovalAnswer.mdx +++ b/docs/docs/reference/api/ApprovalAnswer.mdx @@ -33,5 +33,5 @@ trivially pass, which is manufacturing the check. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/ApprovalMismatch.mdx b/docs/docs/reference/api/ApprovalMismatch.mdx similarity index 86% rename from docs/reference/api/ApprovalMismatch.mdx rename to docs/docs/reference/api/ApprovalMismatch.mdx index 41ff37e5..18858785 100644 --- a/docs/reference/api/ApprovalMismatch.mdx +++ b/docs/docs/reference/api/ApprovalMismatch.mdx @@ -24,5 +24,5 @@ The presented approval does not authorize this action (SPEC-v0.1 §4.2). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/ApprovalProvider.mdx b/docs/docs/reference/api/ApprovalProvider.mdx similarity index 87% rename from docs/reference/api/ApprovalProvider.mdx rename to docs/docs/reference/api/ApprovalProvider.mdx index 03370241..aa836270 100644 --- a/docs/reference/api/ApprovalProvider.mdx +++ b/docs/docs/reference/api/ApprovalProvider.mdx @@ -25,5 +25,5 @@ shape; it checks method names only, which is why the static check matters more. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/ApprovalRequest.mdx b/docs/docs/reference/api/ApprovalRequest.mdx similarity index 84% rename from docs/reference/api/ApprovalRequest.mdx rename to docs/docs/reference/api/ApprovalRequest.mdx index 005aa9af..3ae214fc 100644 --- a/docs/reference/api/ApprovalRequest.mdx +++ b/docs/docs/reference/api/ApprovalRequest.mdx @@ -21,5 +21,5 @@ A pending question for a human: may this exact action run? (SPEC-v0.1 §4.1) ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/ApprovalRequired.mdx b/docs/docs/reference/api/ApprovalRequired.mdx similarity index 86% rename from docs/reference/api/ApprovalRequired.mdx rename to docs/docs/reference/api/ApprovalRequired.mdx index 30934759..8be768e8 100644 --- a/docs/reference/api/ApprovalRequired.mdx +++ b/docs/docs/reference/api/ApprovalRequired.mdx @@ -24,5 +24,5 @@ Raised instead of blocking, so an agent loop can surface the request and come ba ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/ApprovalTimeout.mdx b/docs/docs/reference/api/ApprovalTimeout.mdx similarity index 82% rename from docs/reference/api/ApprovalTimeout.mdx rename to docs/docs/reference/api/ApprovalTimeout.mdx index 7d452b1e..0000a964 100644 --- a/docs/reference/api/ApprovalTimeout.mdx +++ b/docs/docs/reference/api/ApprovalTimeout.mdx @@ -21,5 +21,5 @@ Nobody answered the approval request in time (SPEC-v0.1 §4.3). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Authority.mdx b/docs/docs/reference/api/Authority.mdx similarity index 86% rename from docs/reference/api/Authority.mdx rename to docs/docs/reference/api/Authority.mdx index 6d41eb0f..e4f5df24 100644 --- a/docs/reference/api/Authority.mdx +++ b/docs/docs/reference/api/Authority.mdx @@ -24,5 +24,5 @@ no event, and has no other side effect. `Control` performs every write (§4.8). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/AuthorityDenied.mdx b/docs/docs/reference/api/AuthorityDenied.mdx similarity index 90% rename from docs/reference/api/AuthorityDenied.mdx rename to docs/docs/reference/api/AuthorityDenied.mdx index 3b974e19..9fd97c37 100644 --- a/docs/reference/api/AuthorityDenied.mdx +++ b/docs/docs/reference/api/AuthorityDenied.mdx @@ -28,5 +28,5 @@ grant is not evidence. The id travels in `grant_id`. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/AuthorityEscalation.mdx b/docs/docs/reference/api/AuthorityEscalation.mdx similarity index 89% rename from docs/reference/api/AuthorityEscalation.mdx rename to docs/docs/reference/api/AuthorityEscalation.mdx index 6de85f9a..02819548 100644 --- a/docs/reference/api/AuthorityEscalation.mdx +++ b/docs/docs/reference/api/AuthorityEscalation.mdx @@ -26,5 +26,5 @@ reasons. The two are never used interchangeably. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/AuthorityResult.mdx b/docs/docs/reference/api/AuthorityResult.mdx similarity index 91% rename from docs/reference/api/AuthorityResult.mdx rename to docs/docs/reference/api/AuthorityResult.mdx index 44bc8c31..43c9b607 100644 --- a/docs/reference/api/AuthorityResult.mdx +++ b/docs/docs/reference/api/AuthorityResult.mdx @@ -27,5 +27,5 @@ in evidence and no test could tell which had run. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/CTRLRunError.mdx b/docs/docs/reference/api/CTRLRunError.mdx similarity index 78% rename from docs/reference/api/CTRLRunError.mdx rename to docs/docs/reference/api/CTRLRunError.mdx index eba6d14f..dd146c4d 100644 --- a/docs/reference/api/CTRLRunError.mdx +++ b/docs/docs/reference/api/CTRLRunError.mdx @@ -20,5 +20,5 @@ Base class for every error raised by CTRLRun. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Condition.mdx b/docs/docs/reference/api/Condition.mdx similarity index 87% rename from docs/reference/api/Condition.mdx rename to docs/docs/reference/api/Condition.mdx index d0ea1593..6a3d6934 100644 --- a/docs/reference/api/Condition.mdx +++ b/docs/docs/reference/api/Condition.mdx @@ -25,5 +25,5 @@ start comparing equal to `1`. `key` is the raw condition key the author wrote. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Control.mdx b/docs/docs/reference/api/Control.mdx similarity index 89% rename from docs/reference/api/Control.mdx rename to docs/docs/reference/api/Control.mdx index 9bf7cb93..fe8696c3 100644 --- a/docs/reference/api/Control.mdx +++ b/docs/docs/reference/api/Control.mdx @@ -24,5 +24,5 @@ executor, and records a receipt and events for whatever happened. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Decision.mdx b/docs/docs/reference/api/Decision.mdx similarity index 83% rename from docs/reference/api/Decision.mdx rename to docs/docs/reference/api/Decision.mdx index ecfb8b69..aec0b55b 100644 --- a/docs/reference/api/Decision.mdx +++ b/docs/docs/reference/api/Decision.mdx @@ -25,5 +25,5 @@ What may happen to an action: exactly three outcomes in v0.1 (SPEC-v0.1 §3.3). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Delegation.mdx b/docs/docs/reference/api/Delegation.mdx similarity index 89% rename from docs/reference/api/Delegation.mdx rename to docs/docs/reference/api/Delegation.mdx index 69b8b671..abd449d0 100644 --- a/docs/reference/api/Delegation.mdx +++ b/docs/docs/reference/api/Delegation.mdx @@ -26,5 +26,5 @@ takes either (§5.2). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/DelegationRecord.mdx b/docs/docs/reference/api/DelegationRecord.mdx similarity index 91% rename from docs/reference/api/DelegationRecord.mdx rename to docs/docs/reference/api/DelegationRecord.mdx index 7cc0e640..59367df3 100644 --- a/docs/reference/api/DelegationRecord.mdx +++ b/docs/docs/reference/api/DelegationRecord.mdx @@ -30,5 +30,5 @@ chain. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/DuplicateEffect.mdx b/docs/docs/reference/api/DuplicateEffect.mdx similarity index 86% rename from docs/reference/api/DuplicateEffect.mdx rename to docs/docs/reference/api/DuplicateEffect.mdx index c0b9a908..5aa4c66a 100644 --- a/docs/reference/api/DuplicateEffect.mdx +++ b/docs/docs/reference/api/DuplicateEffect.mdx @@ -24,5 +24,5 @@ holds a live reservation on the key. Neither permits a second execution. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/EffectKeyError.mdx b/docs/docs/reference/api/EffectKeyError.mdx similarity index 84% rename from docs/reference/api/EffectKeyError.mdx rename to docs/docs/reference/api/EffectKeyError.mdx index 2ce2504f..f43411bc 100644 --- a/docs/reference/api/EffectKeyError.mdx +++ b/docs/docs/reference/api/EffectKeyError.mdx @@ -23,5 +23,5 @@ logical effect cannot be identified cannot be protected against duplication. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/EffectRecord.mdx b/docs/docs/reference/api/EffectRecord.mdx similarity index 85% rename from docs/reference/api/EffectRecord.mdx rename to docs/docs/reference/api/EffectRecord.mdx index f503309c..86c432af 100644 --- a/docs/reference/api/EffectRecord.mdx +++ b/docs/docs/reference/api/EffectRecord.mdx @@ -21,5 +21,5 @@ What a StateStore holds for one effect key (ARCHITECTURE §5). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/EffectState.mdx b/docs/docs/reference/api/EffectState.mdx similarity index 85% rename from docs/reference/api/EffectState.mdx rename to docs/docs/reference/api/EffectState.mdx index fdcea5d5..8f277ec6 100644 --- a/docs/reference/api/EffectState.mdx +++ b/docs/docs/reference/api/EffectState.mdx @@ -30,5 +30,5 @@ moves a record out of it. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Event.mdx b/docs/docs/reference/api/Event.mdx similarity index 89% rename from docs/reference/api/Event.mdx rename to docs/docs/reference/api/Event.mdx index 9e5e7e7b..172bd82c 100644 --- a/docs/reference/api/Event.mdx +++ b/docs/docs/reference/api/Event.mdx @@ -28,5 +28,5 @@ a field every reader takes to name a real proposal. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/EventSink.mdx b/docs/docs/reference/api/EventSink.mdx similarity index 90% rename from docs/reference/api/EventSink.mdx rename to docs/docs/reference/api/EventSink.mdx index e840585d..f3a9b504 100644 --- a/docs/reference/api/EventSink.mdx +++ b/docs/docs/reference/api/EventSink.mdx @@ -32,5 +32,5 @@ kernel: `Control` catches every `Exception` and carries on (§4.2). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/FrameworkInterrupt.mdx b/docs/docs/reference/api/FrameworkInterrupt.mdx similarity index 90% rename from docs/reference/api/FrameworkInterrupt.mdx rename to docs/docs/reference/api/FrameworkInterrupt.mdx index 7888b93b..4bcb7d08 100644 --- a/docs/reference/api/FrameworkInterrupt.mdx +++ b/docs/docs/reference/api/FrameworkInterrupt.mdx @@ -31,5 +31,5 @@ interrupt puts a refusal in the evidence log that nobody made. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Grant.mdx b/docs/docs/reference/api/Grant.mdx similarity index 90% rename from docs/reference/api/Grant.mdx rename to docs/docs/reference/api/Grant.mdx index 02aac5bf..b5c24cec 100644 --- a/docs/reference/api/Grant.mdx +++ b/docs/docs/reference/api/Grant.mdx @@ -27,5 +27,5 @@ validated. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/HeaderIdentityProvider.mdx b/docs/docs/reference/api/HeaderIdentityProvider.mdx similarity index 91% rename from docs/reference/api/HeaderIdentityProvider.mdx rename to docs/docs/reference/api/HeaderIdentityProvider.mdx index 678214fe..fbc75a18 100644 --- a/docs/reference/api/HeaderIdentityProvider.mdx +++ b/docs/docs/reference/api/HeaderIdentityProvider.mdx @@ -31,5 +31,5 @@ verified data, which is the one thing this module must not do. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/IdentityContext.mdx b/docs/docs/reference/api/IdentityContext.mdx similarity index 90% rename from docs/reference/api/IdentityContext.mdx rename to docs/docs/reference/api/IdentityContext.mdx index a0da0cb6..edeccca9 100644 --- a/docs/reference/api/IdentityContext.mdx +++ b/docs/docs/reference/api/IdentityContext.mdx @@ -29,5 +29,5 @@ custom provider can implement "look up the token for this agent" without a secon ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/IdentityError.mdx b/docs/docs/reference/api/IdentityError.mdx similarity index 88% rename from docs/reference/api/IdentityError.mdx rename to docs/docs/reference/api/IdentityError.mdx index 7b148b06..37a929d0 100644 --- a/docs/reference/api/IdentityError.mdx +++ b/docs/docs/reference/api/IdentityError.mdx @@ -26,5 +26,5 @@ which includes an expired `Principal` reaching `Control.execute` (§2.3). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/IdentityProvider.mdx b/docs/docs/reference/api/IdentityProvider.mdx similarity index 90% rename from docs/reference/api/IdentityProvider.mdx rename to docs/docs/reference/api/IdentityProvider.mdx index 2f81d04a..ecf7821c 100644 --- a/docs/reference/api/IdentityProvider.mdx +++ b/docs/docs/reference/api/IdentityProvider.mdx @@ -30,5 +30,5 @@ logged and re-raised as one with the original chained. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/InMemoryStateStore.mdx b/docs/docs/reference/api/InMemoryStateStore.mdx similarity index 88% rename from docs/reference/api/InMemoryStateStore.mdx rename to docs/docs/reference/api/InMemoryStateStore.mdx index 7199ffc1..6a4039e8 100644 --- a/docs/reference/api/InMemoryStateStore.mdx +++ b/docs/docs/reference/api/InMemoryStateStore.mdx @@ -27,5 +27,5 @@ covers each whole check-and-write. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/InterruptApprovalProvider.mdx b/docs/docs/reference/api/InterruptApprovalProvider.mdx similarity index 92% rename from docs/reference/api/InterruptApprovalProvider.mdx rename to docs/docs/reference/api/InterruptApprovalProvider.mdx index 0edbca02..c67da085 100644 --- a/docs/reference/api/InterruptApprovalProvider.mdx +++ b/docs/docs/reference/api/InterruptApprovalProvider.mdx @@ -37,5 +37,5 @@ chosen there, by the person who deployed it. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/InvalidArgument.mdx b/docs/docs/reference/api/InvalidArgument.mdx similarity index 84% rename from docs/reference/api/InvalidArgument.mdx rename to docs/docs/reference/api/InvalidArgument.mdx index b8255a37..ad8090b3 100644 --- a/docs/reference/api/InvalidArgument.mdx +++ b/docs/docs/reference/api/InvalidArgument.mdx @@ -24,5 +24,5 @@ an effect nobody reserved. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/JSONLEventSink.mdx b/docs/docs/reference/api/JSONLEventSink.mdx similarity index 91% rename from docs/reference/api/JSONLEventSink.mdx rename to docs/docs/reference/api/JSONLEventSink.mdx index bd76c223..fe87e816 100644 --- a/docs/reference/api/JSONLEventSink.mdx +++ b/docs/docs/reference/api/JSONLEventSink.mdx @@ -32,5 +32,5 @@ The files it writes, and where, are unchanged. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/LocalApprovalProvider.mdx b/docs/docs/reference/api/LocalApprovalProvider.mdx similarity index 88% rename from docs/reference/api/LocalApprovalProvider.mdx rename to docs/docs/reference/api/LocalApprovalProvider.mdx index ec877503..6b644c53 100644 --- a/docs/reference/api/LocalApprovalProvider.mdx +++ b/docs/docs/reference/api/LocalApprovalProvider.mdx @@ -25,5 +25,5 @@ in `ApprovalTimeout` rather than a blocked agent. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/MissingDependency.mdx b/docs/docs/reference/api/MissingDependency.mdx similarity index 86% rename from docs/reference/api/MissingDependency.mdx rename to docs/docs/reference/api/MissingDependency.mdx index d5c55559..e99e0b6e 100644 --- a/docs/reference/api/MissingDependency.mdx +++ b/docs/docs/reference/api/MissingDependency.mdx @@ -25,5 +25,5 @@ is missing and the command that installs it. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/NotExecuted.mdx b/docs/docs/reference/api/NotExecuted.mdx similarity index 84% rename from docs/reference/api/NotExecuted.mdx rename to docs/docs/reference/api/NotExecuted.mdx index 429884d5..5250328b 100644 --- a/docs/reference/api/NotExecuted.mdx +++ b/docs/docs/reference/api/NotExecuted.mdx @@ -23,5 +23,5 @@ Every other exception is an `AMBIGUOUS` outcome. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/PendingApproval.mdx b/docs/docs/reference/api/PendingApproval.mdx similarity index 91% rename from docs/reference/api/PendingApproval.mdx rename to docs/docs/reference/api/PendingApproval.mdx index 26ebf27a..a8aaa3ba 100644 --- a/docs/reference/api/PendingApproval.mdx +++ b/docs/docs/reference/api/PendingApproval.mdx @@ -29,5 +29,5 @@ no constructor, keyword or callback anywhere on this surface that puts one back ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Policy.mdx b/docs/docs/reference/api/Policy.mdx similarity index 88% rename from docs/reference/api/Policy.mdx rename to docs/docs/reference/api/Policy.mdx index 67651546..e6b47f8d 100644 --- a/docs/reference/api/Policy.mdx +++ b/docs/docs/reference/api/Policy.mdx @@ -24,5 +24,5 @@ empty permissive policy (SPEC-v0.1 §3.4). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/PolicyError.mdx b/docs/docs/reference/api/PolicyError.mdx similarity index 81% rename from docs/reference/api/PolicyError.mdx rename to docs/docs/reference/api/PolicyError.mdx index d5e44122..8f88305e 100644 --- a/docs/reference/api/PolicyError.mdx +++ b/docs/docs/reference/api/PolicyError.mdx @@ -20,5 +20,5 @@ The policy is missing, unreadable, or malformed. Raised at load time (SPEC-v0.1 ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Principal.mdx b/docs/docs/reference/api/Principal.mdx similarity index 88% rename from docs/reference/api/Principal.mdx rename to docs/docs/reference/api/Principal.mdx index d7fe8528..f7b584f0 100644 --- a/docs/reference/api/Principal.mdx +++ b/docs/docs/reference/api/Principal.mdx @@ -26,5 +26,5 @@ see and no agent could fix. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Receipt.mdx b/docs/docs/reference/api/Receipt.mdx similarity index 90% rename from docs/reference/api/Receipt.mdx rename to docs/docs/reference/api/Receipt.mdx index 09ba07c9..8ebc0998 100644 --- a/docs/reference/api/Receipt.mdx +++ b/docs/docs/reference/api/Receipt.mdx @@ -21,5 +21,5 @@ Portable evidence of one action that reached a terminal state (SPEC-v0.1 §6.1). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/ReconcileOutcome.mdx b/docs/docs/reference/api/ReconcileOutcome.mdx similarity index 87% rename from docs/reference/api/ReconcileOutcome.mdx rename to docs/docs/reference/api/ReconcileOutcome.mdx index dd955644..38a17766 100644 --- a/docs/reference/api/ReconcileOutcome.mdx +++ b/docs/docs/reference/api/ReconcileOutcome.mdx @@ -25,5 +25,5 @@ thing besides a human permitted to move one out of `AMBIGUOUS`. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/SQLiteStateStore.mdx b/docs/docs/reference/api/SQLiteStateStore.mdx similarity index 90% rename from docs/reference/api/SQLiteStateStore.mdx rename to docs/docs/reference/api/SQLiteStateStore.mdx index 0fc5503f..49769d88 100644 --- a/docs/reference/api/SQLiteStateStore.mdx +++ b/docs/docs/reference/api/SQLiteStateStore.mdx @@ -29,5 +29,5 @@ processes open the same file, which is the point. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/SchemaMismatch.mdx b/docs/docs/reference/api/SchemaMismatch.mdx similarity index 89% rename from docs/reference/api/SchemaMismatch.mdx rename to docs/docs/reference/api/SchemaMismatch.mdx index 406ee7af..5a0b1c30 100644 --- a/docs/reference/api/SchemaMismatch.mdx +++ b/docs/docs/reference/api/SchemaMismatch.mdx @@ -26,5 +26,5 @@ both would put a schema problem behind a wiring bug. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/ScriptedApprovalProvider.mdx b/docs/docs/reference/api/ScriptedApprovalProvider.mdx similarity index 91% rename from docs/reference/api/ScriptedApprovalProvider.mdx rename to docs/docs/reference/api/ScriptedApprovalProvider.mdx index b8fe8f70..d998e9ef 100644 --- a/docs/reference/api/ScriptedApprovalProvider.mdx +++ b/docs/docs/reference/api/ScriptedApprovalProvider.mdx @@ -30,5 +30,5 @@ real provider would have refused (SPEC-v0.1 §4.3). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/StateStore.mdx b/docs/docs/reference/api/StateStore.mdx similarity index 95% rename from docs/reference/api/StateStore.mdx rename to docs/docs/reference/api/StateStore.mdx index 5c3ae28c..2603dc9b 100644 --- a/docs/reference/api/StateStore.mdx +++ b/docs/docs/reference/api/StateStore.mdx @@ -47,5 +47,5 @@ Durable state behind a `Control` (SPEC-v0.1 §5.3): approvals, effects, evidence ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/StaticIdentityProvider.mdx b/docs/docs/reference/api/StaticIdentityProvider.mdx similarity index 89% rename from docs/reference/api/StaticIdentityProvider.mdx rename to docs/docs/reference/api/StaticIdentityProvider.mdx index e63bdd78..3e21f014 100644 --- a/docs/reference/api/StaticIdentityProvider.mdx +++ b/docs/docs/reference/api/StaticIdentityProvider.mdx @@ -25,5 +25,5 @@ operator who needs to see this needs to see it once, at the point they chose it. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Subject.mdx b/docs/docs/reference/api/Subject.mdx similarity index 87% rename from docs/reference/api/Subject.mdx rename to docs/docs/reference/api/Subject.mdx index 8c5d16ed..f6501e82 100644 --- a/docs/reference/api/Subject.mdx +++ b/docs/docs/reference/api/Subject.mdx @@ -25,5 +25,5 @@ powers. "Any agent" is spelled `Subject(agent="*")`, which is greppable. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/Suspended.mdx b/docs/docs/reference/api/Suspended.mdx similarity index 89% rename from docs/reference/api/Suspended.mdx rename to docs/docs/reference/api/Suspended.mdx index 4d7b72f8..ab9fdf19 100644 --- a/docs/reference/api/Suspended.mdx +++ b/docs/docs/reference/api/Suspended.mdx @@ -29,5 +29,5 @@ never parses it, and only ever compares it with `hmac.compare_digest`. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/WebhookApprovalProvider.mdx b/docs/docs/reference/api/WebhookApprovalProvider.mdx similarity index 92% rename from docs/reference/api/WebhookApprovalProvider.mdx rename to docs/docs/reference/api/WebhookApprovalProvider.mdx index 7085e4bd..ac7777e3 100644 --- a/docs/reference/api/WebhookApprovalProvider.mdx +++ b/docs/docs/reference/api/WebhookApprovalProvider.mdx @@ -30,5 +30,5 @@ either way, which is the only outcome that is safe when nobody was told. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/acs-AcsControlHook.mdx b/docs/docs/reference/api/acs-AcsControlHook.mdx similarity index 90% rename from docs/reference/api/acs-AcsControlHook.mdx rename to docs/docs/reference/api/acs-AcsControlHook.mdx index 93b953bc..e7988ef3 100644 --- a/docs/reference/api/acs-AcsControlHook.mdx +++ b/docs/docs/reference/api/acs-AcsControlHook.mdx @@ -27,5 +27,5 @@ stable string and two providers exposing the same tool name stay distinguishable ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/action_hash.mdx b/docs/docs/reference/api/action_hash.mdx similarity index 81% rename from docs/reference/api/action_hash.mdx rename to docs/docs/reference/api/action_hash.mdx index 65d62390..0ef1302c 100644 --- a/docs/reference/api/action_hash.mdx +++ b/docs/docs/reference/api/action_hash.mdx @@ -20,5 +20,5 @@ Return the action hash used to bind approvals to an exact action (SPEC-v0.1 §2. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/banner.mdx b/docs/docs/reference/api/banner.mdx similarity index 90% rename from docs/reference/api/banner.mdx rename to docs/docs/reference/api/banner.mdx index 37d75b30..cfff3ec4 100644 --- a/docs/reference/api/banner.mdx +++ b/docs/docs/reference/api/banner.mdx @@ -30,5 +30,5 @@ forget. A no-op under `mode: enforce`. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/canonical_bytes.mdx b/docs/docs/reference/api/canonical_bytes.mdx similarity index 92% rename from docs/reference/api/canonical_bytes.mdx rename to docs/docs/reference/api/canonical_bytes.mdx index abe195ac..61d88833 100644 --- a/docs/reference/api/canonical_bytes.mdx +++ b/docs/docs/reference/api/canonical_bytes.mdx @@ -33,5 +33,5 @@ silently encoded `0.1` would make two hosts with different libm disagree about a ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/canonicalize.mdx b/docs/docs/reference/api/canonicalize.mdx similarity index 86% rename from docs/reference/api/canonicalize.mdx rename to docs/docs/reference/api/canonicalize.mdx index 92147712..5dcccd50 100644 --- a/docs/reference/api/canonicalize.mdx +++ b/docs/docs/reference/api/canonicalize.mdx @@ -24,5 +24,5 @@ rather than two (SPEC-v0.6 §6.2). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/conformance-run.mdx b/docs/docs/reference/api/conformance-run.mdx similarity index 92% rename from docs/reference/api/conformance-run.mdx rename to docs/docs/reference/api/conformance-run.mdx index b8426646..ded0281a 100644 --- a/docs/reference/api/conformance-run.mdx +++ b/docs/docs/reference/api/conformance-run.mdx @@ -32,5 +32,5 @@ never as a crashed run: one broken suite must not be able to hide the eight that ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/conformance-store-run.mdx b/docs/docs/reference/api/conformance-store-run.mdx similarity index 92% rename from docs/reference/api/conformance-store-run.mdx rename to docs/docs/reference/api/conformance-store-run.mdx index f8c2c827..abef2f5e 100644 --- a/docs/reference/api/conformance-store-run.mdx +++ b/docs/docs/reference/api/conformance-store-run.mdx @@ -33,5 +33,5 @@ crashed run: one broken case must not hide the twenty that worked. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/context.mdx b/docs/docs/reference/api/context.mdx similarity index 90% rename from docs/reference/api/context.mdx rename to docs/docs/reference/api/context.mdx index 21781d3f..75f3a0f9 100644 --- a/docs/reference/api/context.mdx +++ b/docs/docs/reference/api/context.mdx @@ -32,5 +32,5 @@ an identity: the provider wins where it answers (§3.2). ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/gateway-serve.mdx b/docs/docs/reference/api/gateway-serve.mdx similarity index 89% rename from docs/reference/api/gateway-serve.mdx rename to docs/docs/reference/api/gateway-serve.mdx index 3a90e90a..4798cb78 100644 --- a/docs/reference/api/gateway-serve.mdx +++ b/docs/docs/reference/api/gateway-serve.mdx @@ -28,5 +28,5 @@ gateway and a decorator-based worker sharing a policy share reservations (§6.1) ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/docs/reference/api/index.mdx b/docs/docs/reference/api/index.mdx new file mode 100644 index 00000000..a31bfcdd --- /dev/null +++ b/docs/docs/reference/api/index.mdx @@ -0,0 +1,88 @@ +--- +title: "Python API" +description: "Every frozen public name of the ctrlrun package and its extras, one page each, rendered from the docstrings." +--- + +{/* generated by tools/docs_audit/render_api.py from the docstrings — edit the docstring, never this page */} + +The names below are the ones the specifications freeze: `ctrlrun.__all__`, the entry +points of the extras, and `ctrlrun.verify.run`. Each page is rendered from the docstring; +a name with no docstring fails a test, so every page has one. + +| Name | Kind | Summary | +|---|---|---| +| [`ctrlrun.Action`](/docs/reference/api/Action) | class | A proposed agent action: what, with which arguments, by whom, on what. | +| [`ctrlrun.ActionDenied`](/docs/reference/api/ActionDenied) | class | The action may not run. `reason` says why, e.g. `unknown_action` (SPEC-v0.1 §3.4). | +| [`ctrlrun.AmbiguousEffect`](/docs/reference/api/AmbiguousEffect) | class | The outcome of this effect is unknown; only a human may resolve it (SPEC-v0.1 §5.4). | +| [`ctrlrun.Approval`](/docs/reference/api/Approval) | class | A human's grant, bound to one `action_hash` (SPEC-v0.1 §4.1). | +| [`ctrlrun.ApprovalAnswer`](/docs/reference/api/ApprovalAnswer) | class | A human's answer, and who gave it (SPEC-v0.5 §2.2, §3.4). | +| [`ctrlrun.ApprovalMismatch`](/docs/reference/api/ApprovalMismatch) | class | The presented approval does not authorize this action (SPEC-v0.1 §4.2). | +| [`ctrlrun.ApprovalProvider`](/docs/reference/api/ApprovalProvider) | class | How a human is asked, and how the answer comes back (SPEC-v0.1 §4.3). | +| [`ctrlrun.ApprovalRequest`](/docs/reference/api/ApprovalRequest) | class | A pending question for a human: may this exact action run? (SPEC-v0.1 §4.1) | +| [`ctrlrun.ApprovalRequired`](/docs/reference/api/ApprovalRequired) | class | The action needs a human. `request_id` is what `ctrlrun approve` takes (SPEC §4.3). | +| [`ctrlrun.ApprovalTimeout`](/docs/reference/api/ApprovalTimeout) | class | Nobody answered the approval request in time (SPEC-v0.1 §4.3). | +| [`ctrlrun.Authority`](/docs/reference/api/Authority) | class | The `authority:` section, loaded and evaluable (SPEC-v0.3 §4). | +| [`ctrlrun.AuthorityDenied`](/docs/reference/api/AuthorityDenied) | class | The principal holds no grant that covers this action (SPEC-v0.3 §4.3). | +| [`ctrlrun.AuthorityEscalation`](/docs/reference/api/AuthorityEscalation) | class | A delegation that may not exist: it is not contained in its parent (SPEC-v0.3 §5.3). | +| [`ctrlrun.AuthorityResult`](/docs/reference/api/AuthorityResult) | class | What the authority axis decided, and which grant it decided on (§4.8). | +| [`ctrlrun.CTRLRunError`](/docs/reference/api/CTRLRunError) | class | Base class for every error raised by CTRLRun. | +| [`ctrlrun.Condition`](/docs/reference/api/Condition) | class | One `_: operand` test against an action's arguments (SPEC-v0.1 §3.2). | +| [`ctrlrun.Control`](/docs/reference/api/Control) | class | Policy, state and evidence composed around a single action (SPEC-v0.1 §8). | +| [`ctrlrun.Decision`](/docs/reference/api/Decision) | class | What may happen to an action: exactly three outcomes in v0.1 (SPEC-v0.1 §3.3). | +| [`ctrlrun.Delegation`](/docs/reference/api/Delegation) | class | A grant created at runtime by a principal who already holds one (SPEC-v0.3 §5.1). | +| [`ctrlrun.DelegationRecord`](/docs/reference/api/DelegationRecord) | class | One row of the `delegations` table (SPEC-v0.3 §5.2). | +| [`ctrlrun.DuplicateEffect`](/docs/reference/api/DuplicateEffect) | class | This logical effect already happened, or is happening now (SPEC-v0.1 §5.4). | +| [`ctrlrun.EffectKeyError`](/docs/reference/api/EffectKeyError) | class | An effect template cannot be resolved to a key (SPEC-v0.1 §5.1). | +| [`ctrlrun.EffectRecord`](/docs/reference/api/EffectRecord) | class | What a StateStore holds for one effect key (ARCHITECTURE §5). | +| [`ctrlrun.EffectState`](/docs/reference/api/EffectState) | class | Where a logical effect stands (SPEC-v0.1 §5.2). | +| [`ctrlrun.Event`](/docs/reference/api/Event) | class | One ordered step in the life of an action (SPEC-v0.1 §6.2). | +| [`ctrlrun.EventSink`](/docs/reference/api/EventSink) | class | Somewhere a copy of every `Event` and `Receipt` goes (SPEC-v0.2 §4.1). | +| [`ctrlrun.FrameworkInterrupt`](/docs/reference/api/FrameworkInterrupt) | class | One framework's human-in-the-loop primitive, and nothing else (SPEC-v0.5 §2.1). | +| [`ctrlrun.Grant`](/docs/reference/api/Grant) | class | One permission: this subject may propose these actions, under these limits (§4.2). | +| [`ctrlrun.HeaderIdentityProvider`](/docs/reference/api/HeaderIdentityProvider) | class | The principal named by a trusted HTTP header (§3.3). | +| [`ctrlrun.IdentityContext`](/docs/reference/api/IdentityContext) | class | What a provider is told about the call it is resolving a principal for (§3.1). | +| [`ctrlrun.IdentityError`](/docs/reference/api/IdentityError) | class | A credential was offered and rejected (SPEC-v0.3 §3.2). | +| [`ctrlrun.IdentityProvider`](/docs/reference/api/IdentityProvider) | class | Resolves the principal for one action (SPEC-v0.3 §3.1). | +| [`ctrlrun.InMemoryStateStore`](/docs/reference/api/InMemoryStateStore) | class | Everything held in process memory: for tests and `ctrlrun demo`. | +| [`ctrlrun.InterruptApprovalProvider`](/docs/reference/api/InterruptApprovalProvider) | class | An `ApprovalProvider` whose `wait()` routes through a framework's own primitive. | +| [`ctrlrun.InvalidArgument`](/docs/reference/api/InvalidArgument) | class | An argument cannot be accepted as given. | +| [`ctrlrun.JSONLEventSink`](/docs/reference/api/JSONLEventSink) | class | The JSONL half of the evidence: two append-only files in one directory (SPEC §6). | +| [`ctrlrun.LocalApprovalProvider`](/docs/reference/api/LocalApprovalProvider) | class | Requests go to the StateStore; `wait()` polls it (SPEC-v0.1 §4.3). | +| [`ctrlrun.MissingDependency`](/docs/reference/api/MissingDependency) | class | An optional extra is not installed (SPEC-v0.2 §1.1, §11). | +| [`ctrlrun.NotExecuted`](/docs/reference/api/NotExecuted) | class | Raised by an executor to assert the remote side did nothing (SPEC-v0.1 §5.5). | +| [`ctrlrun.PendingApproval`](/docs/reference/api/PendingApproval) | class | What the framework's interrupt is handed, and the only thing it is (SPEC-v0.5 §2.3). | +| [`ctrlrun.Policy`](/docs/reference/api/Policy) | class | Action-level autonomy policy: which actions may run, and under which conditions. | +| [`ctrlrun.PolicyError`](/docs/reference/api/PolicyError) | class | The policy is missing, unreadable, or malformed. Raised at load time (SPEC-v0.1 §3.4). | +| [`ctrlrun.Principal`](/docs/reference/api/Principal) | class | Who is acting: an agent, optionally on behalf of a human. | +| [`ctrlrun.Receipt`](/docs/reference/api/Receipt) | class | Portable evidence of one action that reached a terminal state (SPEC-v0.1 §6.1). | +| [`ctrlrun.ReconcileOutcome`](/docs/reference/api/ReconcileOutcome) | attribute | What a `reconcile` hook may answer about an effect key (SPEC-v0.2 §2). | +| [`ctrlrun.SQLiteStateStore`](/docs/reference/api/SQLiteStateStore) | class | Approvals, effects and evidence in one SQLite file (ARCHITECTURE §5). | +| [`ctrlrun.SchemaMismatch`](/docs/reference/api/SchemaMismatch) | class | A store met a database it does not recognise, in either direction (SPEC-v0.6 §3.3). | +| [`ctrlrun.ScriptedApprovalProvider`](/docs/reference/api/ScriptedApprovalProvider) | class | A human replaced by a fixed script: for tests and `ctrlrun demo` (SPEC-v0.1 §4.3). | +| [`ctrlrun.StateStore`](/docs/reference/api/StateStore) | class | Durable state behind a `Control` (SPEC-v0.1 §5.3): approvals, effects, evidence. | +| [`ctrlrun.StaticIdentityProvider`](/docs/reference/api/StaticIdentityProvider) | class | A fixed principal, for development, tests and single-tenant demonstrations (§3.3). | +| [`ctrlrun.Subject`](/docs/reference/api/Subject) | class | Who a grant is addressed to: an agent pattern, a user pattern, or both (§4.2). | +| [`ctrlrun.Suspended`](/docs/reference/api/Suspended) | class | Raised by an executor: the remote asked for something before it will finish. | +| [`ctrlrun.WebhookApprovalProvider`](/docs/reference/api/WebhookApprovalProvider) | class | Notify a human system on `APPROVAL_REQUESTED`, and let it answer (SPEC-v0.2 §7). | +| [`ctrlrun.action_hash`](/docs/reference/api/action_hash) | function | Return the action hash used to bind approvals to an exact action (SPEC-v0.1 §2.3). | +| [`ctrlrun.banner`](/docs/reference/api/banner) | function | Log SPEC-v0.3 §6.5's observe banner, once per `Control`. An adapter MUST call it (§3.6). | +| [`ctrlrun.canonical_bytes`](/docs/reference/api/canonical_bytes) | function | The canonical form of an arbitrary mapping: UTF-8 JSON, sorted keys, no whitespace. | +| [`ctrlrun.canonicalize`](/docs/reference/api/canonicalize) | function | Return the canonical form of an Action: UTF-8 JSON, sorted keys, no whitespace. | +| [`ctrlrun.context`](/docs/reference/api/context) | function | Bind the principal for calls made inside the block. | +| [`ctrlrun.needs_approval`](/docs/reference/api/needs_approval) | function | Does this call need a human? For a framework that asks before it invokes (SPEC-v0.5 §3.5). | +| [`ctrlrun.parse_conditions`](/docs/reference/api/parse_conditions) | function | Parse a `when:`-shaped mapping into conditions, keyed by the raw condition key. | +| [`ctrlrun.protect`](/docs/reference/api/protect) | function | Bind a function to an action name: every call becomes a decided, recorded Action. | +| [`ctrlrun.with_approval`](/docs/reference/api/with_approval) | function | Present a granted approval to the calls made inside the block (SPEC-v0.1 §4.3). | +| [`ctrlrun.postgres.PostgresStateStore`](/docs/reference/api/postgres-PostgresStateStore) | class | Approvals, effects and evidence in a Postgres schema (SPEC-v0.6 §4). | +| [`ctrlrun.otel.OTelEventSink`](/docs/reference/api/otel-OTelEventSink) | class | Export every `Event` and `Receipt` as OpenTelemetry spans (SPEC-v0.2 §8). | +| [`ctrlrun.jwt_identity.JWTIdentityProvider`](/docs/reference/api/jwt_identity-JWTIdentityProvider) | class | Verify a bearer JWT and map its verified claims onto a `Principal` (SPEC-v0.3 §3.4). | +| [`ctrlrun.acs.AcsControlHook`](/docs/reference/api/acs-AcsControlHook) | class | Answer ACS `steps/*` hooks with CTRLRun's decisions and outcomes. | +| [`ctrlrun.gateway.serve`](/docs/reference/api/gateway-serve) | function | Run a gateway in front of one upstream MCP server (SPEC-v0.2 §6.1). | +| [`ctrlrun.verify.run`](/docs/reference/api/verify-run) | function | Run the applicable guarantees against this configuration and report (§9.1). | +| [`ctrlrun.conformance.run`](/docs/reference/api/conformance-run) | function | Drive every suite through `adapter` and report what each came to (SPEC-v0.5 §5). | +| [`ctrlrun.conformance.store.run`](/docs/reference/api/conformance-store-run) | function | Drive every case against `backend` and report what each came to (SPEC-v0.6 §2). | + +## Next + +- [CLI reference](/docs/reference/cli). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/jwt_identity-JWTIdentityProvider.mdx b/docs/docs/reference/api/jwt_identity-JWTIdentityProvider.mdx similarity index 93% rename from docs/reference/api/jwt_identity-JWTIdentityProvider.mdx rename to docs/docs/reference/api/jwt_identity-JWTIdentityProvider.mdx index 61ceca38..251e1550 100644 --- a/docs/reference/api/jwt_identity-JWTIdentityProvider.mdx +++ b/docs/docs/reference/api/jwt_identity-JWTIdentityProvider.mdx @@ -32,5 +32,5 @@ negative test against behaviour the library refuses anyway. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/needs_approval.mdx b/docs/docs/reference/api/needs_approval.mdx similarity index 94% rename from docs/reference/api/needs_approval.mdx rename to docs/docs/reference/api/needs_approval.mdx index f6153af3..9a47a88c 100644 --- a/docs/reference/api/needs_approval.mdx +++ b/docs/docs/reference/api/needs_approval.mdx @@ -44,5 +44,5 @@ skipped it would evaluate a different action from the one that runs. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/otel-OTelEventSink.mdx b/docs/docs/reference/api/otel-OTelEventSink.mdx similarity index 89% rename from docs/reference/api/otel-OTelEventSink.mdx rename to docs/docs/reference/api/otel-OTelEventSink.mdx index 5f67c9ad..6338d0c5 100644 --- a/docs/reference/api/otel-OTelEventSink.mdx +++ b/docs/docs/reference/api/otel-OTelEventSink.mdx @@ -28,5 +28,5 @@ unended — stated, not solved. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/parse_conditions.mdx b/docs/docs/reference/api/parse_conditions.mdx similarity index 91% rename from docs/reference/api/parse_conditions.mdx rename to docs/docs/reference/api/parse_conditions.mdx index c24b35da..02ee7d28 100644 --- a/docs/reference/api/parse_conditions.mdx +++ b/docs/docs/reference/api/parse_conditions.mdx @@ -29,5 +29,5 @@ data label"* out of v0.6 rather than letting it in through a shared parser. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/postgres-PostgresStateStore.mdx b/docs/docs/reference/api/postgres-PostgresStateStore.mdx similarity index 94% rename from docs/reference/api/postgres-PostgresStateStore.mdx rename to docs/docs/reference/api/postgres-PostgresStateStore.mdx index 931027d7..6a3e9260 100644 --- a/docs/reference/api/postgres-PostgresStateStore.mdx +++ b/docs/docs/reference/api/postgres-PostgresStateStore.mdx @@ -41,5 +41,5 @@ advisory locks, and it is a property worth keeping deliberately rather than by l ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/protect.mdx b/docs/docs/reference/api/protect.mdx similarity index 93% rename from docs/reference/api/protect.mdx rename to docs/docs/reference/api/protect.mdx index 74a8c263..6d385eeb 100644 --- a/docs/reference/api/protect.mdx +++ b/docs/docs/reference/api/protect.mdx @@ -34,5 +34,5 @@ produces an `AMBIGUOUS` outcome, rather than only when one blocks a later attemp ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/verify-run.mdx b/docs/docs/reference/api/verify-run.mdx similarity index 90% rename from docs/reference/api/verify-run.mdx rename to docs/docs/reference/api/verify-run.mdx index 68ac80db..9a8b618c 100644 --- a/docs/reference/api/verify-run.mdx +++ b/docs/docs/reference/api/verify-run.mdx @@ -27,5 +27,5 @@ exception**. The operator's store is not opened, not read and not created (§3.5 ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/api/with_approval.mdx b/docs/docs/reference/api/with_approval.mdx similarity index 85% rename from docs/reference/api/with_approval.mdx rename to docs/docs/reference/api/with_approval.mdx index ffe08447..e5e0e585 100644 --- a/docs/reference/api/with_approval.mdx +++ b/docs/docs/reference/api/with_approval.mdx @@ -23,5 +23,5 @@ only until it expires. Presenting it is an offer, not a decision. ## Next -- [Python API index](/reference/api/index). -- [Get started](/get-started/quickstart) · [Why](/why). +- [Python API index](/docs/reference/api/index). +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/authority-yaml.mdx b/docs/docs/reference/authority-yaml.mdx similarity index 95% rename from docs/reference/authority-yaml.mdx rename to docs/docs/reference/authority-yaml.mdx index 9bde5d6c..8d3c73f1 100644 --- a/docs/reference/authority-yaml.mdx +++ b/docs/docs/reference/authority-yaml.mdx @@ -103,6 +103,6 @@ expiry are live, an edited file takes effect when the process next loads it. ## Next -- [Authority and delegation](/concepts/authority-and-delegation): the concept. -- [Policy YAML reference](/reference/policy-yaml): the other axis. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Authority and delegation](/docs/concepts/authority-and-delegation): the concept. +- [Policy YAML reference](/docs/reference/policy-yaml): the other axis. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/cli.mdx b/docs/docs/reference/cli.mdx similarity index 98% rename from docs/reference/cli.mdx rename to docs/docs/reference/cli.mdx index d4813242..3f7eb2fd 100644 --- a/docs/reference/cli.mdx +++ b/docs/docs/reference/cli.mdx @@ -406,6 +406,6 @@ Options: ## Next -- [Exit codes](/reference/exit-codes): what each command's exit status means. -- [Resolve an AMBIGUOUS effect](/guides/resolve-an-ambiguous-effect): `effects`, `resolve` and `inspect` in use. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Exit codes](/docs/reference/exit-codes): what each command's exit status means. +- [Resolve an AMBIGUOUS effect](/docs/guides/resolve-an-ambiguous-effect): `effects`, `resolve` and `inspect` in use. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/errors.mdx b/docs/docs/reference/errors.mdx similarity index 96% rename from docs/reference/errors.mdx rename to docs/docs/reference/errors.mdx index 85839f51..17e8b9ba 100644 --- a/docs/reference/errors.mdx +++ b/docs/docs/reference/errors.mdx @@ -159,6 +159,6 @@ is missing and the command that installs it. ## Next -- [Fail closed](/concepts/fail-closed): the table of what is refused, and where. -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous): `NotExecuted` and why it is the dangerous one. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Fail closed](/docs/concepts/fail-closed): the table of what is refused, and where. +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous): `NotExecuted` and why it is the dangerous one. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/exit-codes.mdx b/docs/docs/reference/exit-codes.mdx similarity index 92% rename from docs/reference/exit-codes.mdx rename to docs/docs/reference/exit-codes.mdx index 7a4a6042..8c45417a 100644 --- a/docs/reference/exit-codes.mdx +++ b/docs/docs/reference/exit-codes.mdx @@ -53,6 +53,6 @@ not load. ## Next -- [CLI reference](/reference/cli). -- [Errors](/reference/errors): the exception behind each exit 1. -- [Verify in CI](/guides/verify-in-ci) · [Get started](/get-started/quickstart) · [Why](/why). +- [CLI reference](/docs/reference/cli). +- [Errors](/docs/reference/errors): the exception behind each exit 1. +- [Verify in CI](/docs/guides/verify-in-ci) · [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/policy-yaml.mdx b/docs/docs/reference/policy-yaml.mdx similarity index 96% rename from docs/reference/policy-yaml.mdx rename to docs/docs/reference/policy-yaml.mdx index 26d0d9a2..5bc9197c 100644 --- a/docs/reference/policy-yaml.mdx +++ b/docs/docs/reference/policy-yaml.mdx @@ -17,7 +17,7 @@ every key and operator the loader accepts. | `actions` | map of action name → entry | v1 | **error** | the whole list of what an agent may do; an action not in it is denied | | `mode` | `enforce` or `observe` | v3 | `enforce` | top level only; refused anywhere else | | `environment` | string | v3 | `production`, unless `$CTRLRUN_ENVIRONMENT` or the gateway's `--environment` says otherwise | part of every action's hash and of authority matching; never read off the wire | -| `authority` | section | v3 | none: the document behaves as v2, with no authority check | the moment it exists, every principal needs a grant. See the [authority reference](/reference/authority-yaml) | +| `authority` | section | v3 | none: the document behaves as v2, with no authority check | the moment it exists, every principal needs a grant. See the [authority reference](/docs/reference/authority-yaml) | | `version` | string | v4 | none recorded | your label for this document, carried on every receipt as `policy_version`; never authoritative, the hash is | | `controls` | map of id → entry with `title` and `source` | v4 | empty | the registry an action or a rule may cite; `source` is cited on receipts and never interpreted; a cited id the registry does not define is a load error | @@ -135,6 +135,6 @@ misread it. ## Next -- [Authority YAML reference](/reference/authority-yaml). -- [Decisions](/concepts/decisions): how a rule is matched. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Authority YAML reference](/docs/reference/authority-yaml). +- [Decisions](/docs/concepts/decisions): how a rule is matched. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/reference/receipt-and-event-schemas.mdx b/docs/docs/reference/receipt-and-event-schemas.mdx similarity index 94% rename from docs/reference/receipt-and-event-schemas.mdx rename to docs/docs/reference/receipt-and-event-schemas.mdx index f2abbb5b..4bafaa0b 100644 --- a/docs/reference/receipt-and-event-schemas.mdx +++ b/docs/docs/reference/receipt-and-event-schemas.mdx @@ -110,6 +110,6 @@ configuration with an `authority:` section. ## Next -- [Receipts and evidence](/concepts/receipts-and-evidence): what a receipt is for. -- [The receipt chain](/security/receipt-chain): what `seq`, `prev_hash` and `hash` detect. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Receipts and evidence](/docs/concepts/receipts-and-evidence): what a receipt is for. +- [The receipt chain](/docs/security/receipt-chain): what `seq`, `prev_hash` and `hash` detect. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/security/disclosure.mdx b/docs/docs/security/disclosure.mdx similarity index 84% rename from docs/security/disclosure.mdx rename to docs/docs/security/disclosure.mdx index 463076a8..f9707462 100644 --- a/docs/security/disclosure.mdx +++ b/docs/docs/security/disclosure.mdx @@ -26,7 +26,7 @@ these is a security issue rather than a bug: ## What is a documented limit -These are stated in the [threat model](/THREAT_MODEL) and are not vulnerabilities, though if you +These are stated in the [threat model](/docs/THREAT_MODEL) and are not vulnerabilities, though if you think one is stated too generously, say so: - A compromised process, host or Python environment. @@ -43,7 +43,7 @@ An assessment, then a fix with a test that fails before it and passes after, the a fix is warranted you are credited in the release notes unless you ask not to be. Where the finding is about a claim rather than the code, the claim changes: a sentence that promised more than the code does is a defect in this project's terms, and -[how this is built](/how-this-is-built) explains why. +[how this is built](/docs/how-this-is-built) explains why. ## Supported versions @@ -51,6 +51,6 @@ CTRLRun is pre-1.0 and only the latest release receives fixes. ## Next -- [Threat model](/THREAT_MODEL) · [The receipt chain](/security/receipt-chain). -- [How this is built](/how-this-is-built): what has and has not been reviewed. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Threat model](/docs/THREAT_MODEL) · [The receipt chain](/docs/security/receipt-chain). +- [How this is built](/docs/how-this-is-built): what has and has not been reviewed. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/security/receipt-chain.mdx b/docs/docs/security/receipt-chain.mdx similarity index 88% rename from docs/security/receipt-chain.mdx rename to docs/docs/security/receipt-chain.mdx index 19d2a2d5..d18ee41e 100644 --- a/docs/security/receipt-chain.mdx +++ b/docs/docs/security/receipt-chain.mdx @@ -21,7 +21,7 @@ Six named breaks: `content_altered` where a receipt no longer hashes to its stor two, `missing` where there is a gap, `head_mismatch` where the head does not match the last receipt, and `unchained` for a receipt written before the chain existed. Each names the `seq`, so a reader can go and look at that row rather than being told the log is bad, and -[Receipt integrity](/production/receipt-integrity) says what to do about each. +[Receipt integrity](/docs/production/receipt-integrity) says what to do about each. ## What it does not prove @@ -58,12 +58,12 @@ not the lock. If you need evidence of origin rather than of integrity, sign or a receipts outside the database yourself; they are portable JSON, one object per line, for exactly that reason. -[Receipt integrity in practice](/production/receipt-integrity) is the runbook: when to run the +[Receipt integrity in practice](/docs/production/receipt-integrity) is the runbook: when to run the check, what each of the six names means for your store, and what an unchained count does to a scheduled job. ## Next -- [Receipts and evidence](/concepts/receipts-and-evidence): what a receipt holds. -- [Threat model](/THREAT_MODEL): what remains open, in full. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Receipts and evidence](/docs/concepts/receipts-and-evidence): what a receipt holds. +- [Threat model](/docs/THREAT_MODEL): what remains open, in full. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/security/verify-guarantees.mdx b/docs/docs/security/verify-guarantees.mdx similarity index 93% rename from docs/security/verify-guarantees.mdx rename to docs/docs/security/verify-guarantees.mdx index cd8bf73d..c33afec4 100644 --- a/docs/security/verify-guarantees.mdx +++ b/docs/docs/security/verify-guarantees.mdx @@ -63,6 +63,6 @@ existed, the thing verified would not be the thing that ships. ## Next -- [Verify in CI](/guides/verify-in-ci) · [Get the badge](/verify/get-the-badge). -- [The verify page](/verify): the operator's reference, in full. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Verify in CI](/docs/guides/verify-in-ci) · [Get the badge](/docs/verify/get-the-badge). +- [The verify page](/docs/verify): the operator's reference, in full. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/study/does-your-framework-double-execute.mdx b/docs/docs/study/does-your-framework-double-execute.mdx similarity index 94% rename from docs/study/does-your-framework-double-execute.mdx rename to docs/docs/study/does-your-framework-double-execute.mdx index 4e930e42..2f1281bf 100644 --- a/docs/study/does-your-framework-double-execute.mdx +++ b/docs/docs/study/does-your-framework-double-execute.mdx @@ -83,6 +83,6 @@ read, and what a run does not establish. ## Next -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous): what a lost reply is, and why a retry is refused. -- [Effect keys](/concepts/effect-keys): the guard the frameworks above are running without. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous): what a lost reply is, and why a retry is refused. +- [Effect keys](/docs/concepts/effect-keys): the guard the frameworks above are running without. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/try-it.mdx b/docs/docs/try-it.mdx similarity index 95% rename from docs/try-it.mdx rename to docs/docs/try-it.mdx index 5f3e9d53..4c8e4f9a 100644 --- a/docs/try-it.mdx +++ b/docs/docs/try-it.mdx @@ -105,12 +105,12 @@ actions: 5. **`txn_4`**, **€500**, with *lose the reply* ticked — the remote commits, the reply is lost, the effect is `AMBIGUOUS`. Untick and press *Refund* again: `AmbiguousEffect`, the blind retry is refused, and `remote refund calls` is still 1. That is the case this library exists - for: [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous). + for: [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous). 6. Back to **`txn_1`**, **€500** — `DuplicateEffect`. One logical effect, - [one reservation](/concepts/effect-keys). + [one reservation](/docs/concepts/effect-keys). Every refusal above is the exception your own code would catch, with the reason it carries. -[Approval binding](/concepts/approval-binding) and [Decisions](/concepts/decisions) are the two +[Approval binding](/docs/concepts/approval-binding) and [Decisions](/docs/concepts/decisions) are the two pages behind steps 2 to 4. ## The five scenarios, end to end @@ -263,6 +263,6 @@ this page failing, not the library. ## Next -- [Protect your first action](/get-started/quickstart): the same thing, in your own process. -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous): what scenario 1 means. -- [Get started](/get-started/install) · [Why](/why). +- [Protect your first action](/docs/get-started/quickstart): the same thing, in your own process. +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous): what scenario 1 means. +- [Get started](/docs/get-started/install) · [Why](/docs/why). diff --git a/docs/verify.md b/docs/docs/verify.md similarity index 98% rename from docs/verify.md rename to docs/docs/verify.md index c84d1df2..71fb53b2 100644 --- a/docs/verify.md +++ b/docs/docs/verify.md @@ -338,7 +338,7 @@ behind it is worth, and the run is in the workflow log. Then the badge is: ```markdown -[![CTRLRun](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/badges/verify-badge.json)](docs/verify.md#what-the-badge-means) +[![CTRLRun](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/badges/verify-badge.json)](docs/docs/verify.md#what-the-badge-means) ``` It renders as **CTRLRun verified N/M**, where `N` is passes and `M` is **applicable** @@ -353,6 +353,6 @@ A partial run (`--only`) and a run that exited 2 or 3 write **no badge at all**. ## Related - [`SPEC-v0.4.md`](https://github.com/CTRLRun/ctrlrun/blob/main/docs/SPEC-v0.4.md) — the contract this implements, guarantee by guarantee. -- [`OWASP-AGENTIC-TOP10.md`](OWASP-AGENTIC-TOP10.md) — a reading of somebody else's taxonomy +- [`OWASP-AGENTIC-TOP10.md`](/docs/OWASP-AGENTIC-TOP10) — a reading of somebody else's taxonomy against these guarantees, with the entries CTRLRun does not address listed by name. -- [`THREAT_MODEL.md`](THREAT_MODEL.md) — what fail-closed means here, and what is out of scope. +- [`THREAT_MODEL.md`](/docs/THREAT_MODEL) — what fail-closed means here, and what is out of scope. diff --git a/docs/verify/get-the-badge.mdx b/docs/docs/verify/get-the-badge.mdx similarity index 93% rename from docs/verify/get-the-badge.mdx rename to docs/docs/verify/get-the-badge.mdx index 114f3acb..1c547632 100644 --- a/docs/verify/get-the-badge.mdx +++ b/docs/docs/verify/get-the-badge.mdx @@ -79,7 +79,7 @@ proved, and updates itself. ```markdown - [![CTRLRun verified](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/badges/verify-badge.json)](https://github.com/CTRLRun/ctrlrun/blob/main/docs/verify.md#what-the-badge-means) + [![CTRLRun verified](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/badges/verify-badge.json)](https://github.com/CTRLRun/ctrlrun/blob/main/docs/docs/verify.md#what-the-badge-means) ``` Link it to what the badge means, as above. A badge nobody can click through to is a claim @@ -119,6 +119,6 @@ with `--only` writes no badge either. ## Next -- [Verify in CI](/guides/verify-in-ci): the inputs, the outputs and the two report shapes. -- [What verify guarantees](/security/verify-guarantees): the eleven, and the four things verify cannot see. -- [Get started](/get-started/quickstart) · [Why](/why). +- [Verify in CI](/docs/guides/verify-in-ci): the inputs, the outputs and the two report shapes. +- [What verify guarantees](/docs/security/verify-guarantees): the eleven, and the four things verify cannot see. +- [Get started](/docs/get-started/quickstart) · [Why](/docs/why). diff --git a/docs/why.mdx b/docs/docs/why.mdx similarity index 93% rename from docs/why.mdx rename to docs/docs/why.mdx index d2101bae..ce76622a 100644 --- a/docs/why.mdx +++ b/docs/docs/why.mdx @@ -74,6 +74,6 @@ guarantees against your own configuration and reports what it could not check. ## Next -- [How this is built](/how-this-is-built): the discipline, the numbers, and what has not been done yet. -- [Get started](/get-started/quickstart): protect one function, end to end. -- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous): the first principle, in code. +- [How this is built](/docs/how-this-is-built): the discipline, the numbers, and what has not been done yet. +- [Get started](/docs/get-started/quickstart): protect one function, end to end. +- [Outcomes and AMBIGUOUS](/docs/concepts/outcomes-and-ambiguous): the first principle, in code. diff --git a/docs/generated/badges.readme.md b/docs/generated/badges.readme.md index 97f0c6c0..920799e4 100644 --- a/docs/generated/badges.readme.md +++ b/docs/generated/badges.readme.md @@ -5,8 +5,8 @@ Docs CI CodeQL - Tests - CTRLRun verified + Tests + CTRLRun verified OpenSSF Scorecard Ruff Checked with mypy --strict diff --git a/docs/generated/capabilities.mdx b/docs/generated/capabilities.mdx index b1036d10..4d309b52 100644 --- a/docs/generated/capabilities.mdx +++ b/docs/generated/capabilities.mdx @@ -1,84 +1,84 @@ {/* generated from docs/capabilities.yaml (mdx) — edit the YAML, never this grid */} - + An approval is bound to the exact action; a mutated or replayed one is refused. Since v0.1. - + One logical effect happens at most once, across threads, processes and hosts. Since v0.1. - + An unknown outcome is AMBIGUOUS, never FAILED, and blocks a blind retry. Since v0.1. - + An unknown action, a missing policy or a missing principal is denied. Since v0.1. - + With authority on, every principal needs a grant, and delegation cannot widen one. Since v0.3. - + Every executed action leaves a portable JSON receipt of who, what and outcome. Since v0.1. - + One YAML file decides allow, approve or deny per action and argument. Since v0.1. - + Approve, deny, resolve, inspect and count from the shell, against any store. Since v0.1. - + Every guarantee in front of an MCP tool server, with no agent changes. Since v0.2. - + A reconcile hook asks the remote what happened and resolves an AMBIGUOUS effect. Since v0.2. - + Approval requests go to a webhook, such as Slack, and the answer comes back. Since v0.2. - + One span per action, one span event per step; argument values are opt-in. Since v0.2. - + A principal comes from a verified header or JWT; CTRLRun issues nothing. Since v0.3. - + A principal narrows its own grant at runtime; one revocation cuts the chain. Since v0.3. - + Records what enforcement would have blocked, blocks nothing, and counts it. Since v0.3. - + Runs the guarantee catalogue against your policy and store; N/A is not a pass. Since v0.4. - + A GitHub Action and a badge that means the declared guarantees pass. Since v0.4. - + An approval routed through the framework's own interrupt; never a second path. Since v0.5. - + SQLite on one host, Postgres across hosts, the same guarantees either way. Since v0.6. - + The same store on Postgres, graded by the suite written for SQLite. Since v0.6. - + Migrations run at open, forward only, and an unknown schema is refused. Since v0.6. - + A dead worker's effect stays AMBIGUOUS until a human or a hook resolves it. Since v0.6. - + Each receipt carries the hash of the one before; alteration is detected and named. Since v0.6. - + Every receipt names the policy hash and version that decided it. Since v0.6. - + Name the house controls an action satisfies, and receipts cite them. Since v0.6. - + Label arguments by data class and condition a rule on the labels present. Since v0.6. diff --git a/docs/generated/readiness.full.mdx b/docs/generated/readiness.full.mdx index 9bfac3a8..8a071aef 100644 --- a/docs/generated/readiness.full.mdx +++ b/docs/generated/readiness.full.mdx @@ -1,10 +1,10 @@ {/* generated from the suite, pyproject and the soak (full) — run the generator */} - **Version 0.6.1**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later. -- **4,404 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/how-this-is-built). -- **11 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/security/verify-guarantees). -- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/production/postgres). -- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/production/soak). -- **Each receipt carries the hash of the one before it**, so an alteration is detected and named. [Read more](/production/receipt-integrity). +- **4,404 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). +- **11 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/docs/security/verify-guarantees). +- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/docs/production/postgres). +- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/docs/production/soak). +- **Each receipt carries the hash of the one before it**, so an alteration is detected and named. [Read more](/docs/production/receipt-integrity). - **Apache-2.0**, and the enforcement kernel stays open source. Releases carry PyPI provenance attestations from GitHub Actions. **Not yet:** diff --git a/docs/generated/readiness.mdx b/docs/generated/readiness.mdx index 8ab45a37..bdbfb541 100644 --- a/docs/generated/readiness.mdx +++ b/docs/generated/readiness.mdx @@ -3,7 +3,7 @@ - **4,404 tests**, every version specified before it was written and every requirement mutation-tested. - **11 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. -- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/production/soak). +- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). - **Each receipt carries the hash of the one before it**, so an alteration is detected and named. - **Apache-2.0**, and the enforcement kernel stays open source. Releases carry PyPI provenance attestations from GitHub Actions. diff --git a/docs/generated/readiness.readme.md b/docs/generated/readiness.readme.md index ecf7e93c..20b0d92f 100644 --- a/docs/generated/readiness.readme.md +++ b/docs/generated/readiness.readme.md @@ -3,7 +3,7 @@ - **4,404 tests**, every version specified before it was written and every requirement mutation-tested. - **11 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. -- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/production/soak). +- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). - **Each receipt carries the hash of the one before it**, so an alteration is detected and named. - **Apache-2.0**, and the enforcement kernel stays open source. Releases carry PyPI provenance attestations from GitHub Actions. diff --git a/docs/index.mdx b/docs/index.mdx index 8e1ec428..41c9a515 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,290 +1,56 @@ --- -title: "The execution safety layer for AI agents" -sidebarTitle: "Home" -description: "The last check before an AI agent does something it can't undo. Autonomy belongs to the action, not the agent." -mode: "wide" -"og:title": "CTRLRun — the execution safety layer for AI agents" -"twitter:title": "CTRLRun — the execution safety layer for AI agents" +title: "Execution safety for AI agents" +sidebarTitle: "CTRLRun" +description: "Control AI agent actions before they affect real systems. Prevent duplicate actions, require exact approval, and stop unsafe retries." +mode: "custom" +"og:title": "CTRLRun — Execution safety for AI agents" +"twitter:title": "CTRLRun — Execution safety for AI agents" +canonical: "https://ctrlrun.dev/" +"og:url": "https://ctrlrun.dev/" --- -CTRLRun is a Python library that sits between an agent's decision to act and the call that acts. -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. - -```bash -pip install ctrlrun && ctrlrun demo -``` - - - - No install. Approve €2,000, execute €5,000, lose a reply, retry: real refusals, in this tab. - - - One policy file, one decorator, one approval from the shell, three receipts. - - - -**Runs in production on a single file, or on Postgres across hosts.** SQLite is the default and -is production-grade on one host; Postgres is for many. Apache-2.0. - -## Protect one function - -CTRLRun wraps the call that has the consequence, and a YAML file says how much autonomy that -call gets. This is the whole integration for a function in your own process: - -```yaml runnable -schema: ctrlrun.policy/v2 - -actions: - stripe.refund: - effect: "refund:{payment_id}" - rules: - - when: { amount_gte: 0, amount_lte: 50000 } # up to €500: autonomous - decision: allow - - when: { amount_gte: 0, amount_lte: 500000 } # up to €5,000: a human decides - decision: approve - - decision: deny # above that: never -``` - -```python runnable -import ctrlrun - - -class Stripe: # stands in for the real client so this block runs offline - def refund(self, payment_id: str, amount: int) -> dict: - return {"status": "succeeded"} - - -stripe = Stripe() - - -@ctrlrun.protect("stripe.refund", effect="refund:{payment_id}") -def refund(payment_id: str, amount: int) -> dict: - return stripe.refund(payment_id, amount) - - -with ctrlrun.context(agent="refund-agent"): - refund(payment_id="txn_1", amount=10000) # €100: runs, and leaves a receipt - try: - refund(payment_id="txn_2", amount=200000) # €2,000: waits for a human - except ctrlrun.ApprovalRequired as pending: - print("a human decides:", pending.request_id) - else: - raise SystemExit("the €2,000 refund ran without a human; the policy is not in force") -``` - -What the same function does next, and what stops it: - -| The agent | CTRLRun | -|---|---| -| refunds €100 | runs it; one receipt | -| refunds €2,000 | raises `ApprovalRequired`; `ctrlrun approve ` from the shell lets it through | -| has €2,000 approved, executes €5,000 | `ApprovalMismatch`: the approval is bound to the action a human saw | -| refunds €20,000 | `ActionDenied`; no request is created | -| retries a refund whose reply was lost | `AmbiguousEffect`: the remote may have committed; a human or a reconcile hook decides | -| runs the same refund from two workers | one reserves `refund:txn_1`, the other gets `DuplicateEffect` | - -The refund is the first example because everyone understands it; the same file protects a -`kubectl delete`, an IAM grant, a record deletion or an outbound email, and the -[cookbook](/cookbook/index) has each of those as a runnable recipe. - -## What the demo shows - -Five ways an agent action goes wrong, and what stops each one, in under a second with no network. -The first scenario is the one that explains the product: a refund commits at the remote, the -reply is lost, the agent retries, and the retry is refused. The customer was refunded once. - -```console -$ ctrlrun demo -CTRLRun demo — five ways an agent action goes wrong, and what stops it. -Policy: refunds up to €1,000 are autonomous, up to €10,000 need a human, above that are denied. - -1. Duplicate effect after a lost response - - refund €500 → remote commits → response lost → effect: AMBIGUOUS - agent retries the same refund - ✗ BLOCKED — effect may already have committed; blind retry refused - remote refund calls: 1 - only a human moves it on: ctrlrun resolve refund:txn_1 --committed|--failed -``` - -The other four are approval mutation, two agents racing for one effect, approval replay, and an -agent trying to act outside what was delegated to it. [Try it in your browser](/try-it) runs -the same demo without an install and lets you break one refund yourself, or read the full transcript in the -[repository README](https://github.com/CTRLRun/ctrlrun#what-ctrlrun-demo-shows). - -## What it does - -{/* generated from docs/capabilities.yaml (mdx) — edit the YAML, never this grid */} - - - An approval is bound to the exact action; a mutated or replayed one is refused. Since v0.1. - - - One logical effect happens at most once, across threads, processes and hosts. Since v0.1. - - - An unknown outcome is AMBIGUOUS, never FAILED, and blocks a blind retry. Since v0.1. - - - An unknown action, a missing policy or a missing principal is denied. Since v0.1. - - - With authority on, every principal needs a grant, and delegation cannot widen one. Since v0.3. - - - Every executed action leaves a portable JSON receipt of who, what and outcome. Since v0.1. - - - - - - One YAML file decides allow, approve or deny per action and argument. Since v0.1. - - - Approve, deny, resolve, inspect and count from the shell, against any store. Since v0.1. - - - Every guarantee in front of an MCP tool server, with no agent changes. Since v0.2. - - - A reconcile hook asks the remote what happened and resolves an AMBIGUOUS effect. Since v0.2. - - - Approval requests go to a webhook, such as Slack, and the answer comes back. Since v0.2. - - - One span per action, one span event per step; argument values are opt-in. Since v0.2. - - - A principal comes from a verified header or JWT; CTRLRun issues nothing. Since v0.3. - - - A principal narrows its own grant at runtime; one revocation cuts the chain. Since v0.3. - - - Records what enforcement would have blocked, blocks nothing, and counts it. Since v0.3. - - - Runs the guarantee catalogue against your policy and store; N/A is not a pass. Since v0.4. - - - A GitHub Action and a badge that means the declared guarantees pass. Since v0.4. - - - An approval routed through the framework's own interrupt; never a second path. Since v0.5. - - - SQLite on one host, Postgres across hosts, the same guarantees either way. Since v0.6. - - - The same store on Postgres, graded by the suite written for SQLite. Since v0.6. - - - Migrations run at open, forward only, and an unknown schema is refused. Since v0.6. - - - A dead worker's effect stays AMBIGUOUS until a human or a hook resolves it. Since v0.6. - - - Each receipt carries the hash of the one before; alteration is detected and named. Since v0.6. - - - Every receipt names the policy hash and version that decided it. Since v0.6. - - - Name the house controls an action satisfies, and receipts cite them. Since v0.6. - - - Label arguments by data class and condition a rule on the labels present. Since v0.6. - - - -{/* end generated */} - -## Three ways in - -| You have | Use | Needs | -|---|---|---| -| Python in this process: a raw model call, a LangChain tool, a hand-rolled loop, a cron job | the `@protect` decorator | nothing beyond `pip install ctrlrun` | -| Tools behind an MCP server, in any language | the gateway, `ctrlrun gateway` | `pip install "ctrlrun[gateway]"` | -| A framework with its own approval interrupt, and a place where humans already answer | an adapter | the framework to have a human-in-the-loop primitive | - -Most readers need the decorator. An adapter buys exactly one thing, routing an approval through -the framework's own interrupt, and a framework with no such primitive does not need one. -[Choosing between them](/get-started/choosing) has the decision table. - -## Where it stands - -{/* generated from the suite, pyproject and the soak (mdx) — run the generator */} -- **Version 0.6.1**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later. -- **4,404 tests**, every version specified before it was written and every requirement mutation-tested. -- **11 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. -- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. -- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/production/soak). -- **Each receipt carries the hash of the one before it**, so an alteration is detected and named. -- **Apache-2.0**, and the enforcement kernel stays open source. Releases carry PyPI provenance attestations from GitHub Actions. - -**Not yet:** - -- No external security audit. (planned for v0.8 or v0.9) -- No third-party review of the kernel. (every review so far was run inside this project) -- No sector packs. (the policy templates are starting points, not a product) -{/* end generated */} - -## Start here - - - - Protect one function end to end and read the receipt. Ten minutes. - - - One refund you can break six ways, and the five demo scenarios, on the released wheel. - - - Refunds, deploys, IAM, deletions, email, MCP, LangGraph: each a recipe that runs. - - - - - - Decorator, gateway, adapter: what each covers and what each needs. - - - The gateway in front of any MCP server, and this site as an MCP server. - - - Which store, what a lost `COMMIT` does, what survives a crash, and what to watch. - - - - - - The five principles, in 700 words. The page people link to. - - - The idea that explains the product: a timeout is not a failure. - - - -## Ask your coding tool - -This site is an MCP server. Add it to Cursor or any MCP client that takes an `mcpServers` -entry, and the assistant answers from these pages rather than from memory: - -```json -{ - "mcpServers": { - "ctrlrun-docs": { "type": "http", "url": "https://ctrlrun.dev/mcp" } - } -} -``` - -The server exposes one tool, a search across this documentation. When the site moves to its own -domain the URL moves with it; the current one is always in this block. - -## Next - -- [Why](/why): what CTRLRun believes and why. -- [Install](/get-started/install): what `pip install ctrlrun` puts on your machine, and what it does not. -- [How this is built](/how-this-is-built): the discipline behind the guarantees. +import { ScenarioExplorer } from "/snippets/scenario-explorer.jsx"; + +
+
+

CONTROL THE ACTION. KEEP THE AUTONOMY.

+

Execution safety
for AI agents.

+

Let agents act. Keep control of what happens next.

+

CTRLRun checks consequential actions before they reach real systems. Stop duplicate actions, require human approval, and pause unsafe retries.

+ +

Open source. Your agent. Your systems. Your rules.

+
+ +
+
01 / PROPOSEAI agentDecides what to do
+ +
02 / CHECKCTRLRun_The control point before execution
  • Is this allowed?
  • Is this exactly what was approved?
  • Has this already happened?
  • Is it safe to continue?
+ +
03 / ACTReal systemMove money. Change access.
Send email. Deploy software.
+
+ +
+

ONE LOST RESPONSE. TWO REFUNDS.

“Try again” can cost $500.

An agent refunds $500. The provider processes it, but the response is lost. The agent thinks it failed.

+
WITHOUT AN EXECUTION CHECK

Retry the refund another $500 may go out.

WITH CTRLRUN

Pause the retry. Confirm what happened before trying again.

+
+ +
+

MAKE IT RELEVANT TO YOU

Your industry. Your action.

No login. No API key. No setup.

+ +
+ +
+

FOR THE PEOPLE BUILDING IT

Start at the function
that takes action.

Wrap the call. Define your rules. Keep a record of what happened.

Python, agent frameworks, or an MCP gateway.
The execution boundary stays the same.

Start integrating
+
pip install ctrlrun
{"import ctrlrun\n\n@ctrlrun.protect(\n    \"stripe.refund\",\n    effect=\"refund:{payment_id}\"\n)\ndef refund(payment_id, amount):\n    ..."}

Illustrative wrapper. The quickstart adds your policy, identity, and approval flow.

+
+ +
+

WHEN THE ACTIONS ARE REAL

Protect my agent.

Have an agent taking real actions? Get engineering help deciding what can run automatically, what needs approval, and when to stop and confirm.

Paid architecture reviews, integration sprints, and ongoing production support. CTRLRun OSS stays free.

+
+ +
Give agents autonomy.
Keep control of their actions.
+
diff --git a/docs/protect-my-agent.mdx b/docs/protect-my-agent.mdx new file mode 100644 index 00000000..f6f116ab --- /dev/null +++ b/docs/protect-my-agent.mdx @@ -0,0 +1,10 @@ +--- +title: "Protect my agent" +description: "Get a focused execution safety review, a production integration sprint, or ongoing engineering support for your AI agents." +canonical: "https://ctrlrun.dev/protect-my-agent" +mode: "custom" +--- + +import { ArchitectureReview } from "/snippets/architecture-review.jsx"; + +
← CTRLRun

PRODUCTION AGENTS. ENGINEERING HELP.

Protect my agent.

Put execution controls where your architecture needs them.

A focused review of your agent’s actions, permissions, approvals, and retry behavior. Leave with an action map: what can run, what needs a person, and what must wait for confirmation.

Execution Safety Review

A paid architecture review of tool boundaries, approval flows, duplicate execution, uncertain outcomes, and action records. A practical control map and prioritized implementation plan.

Production Integration Sprint

Hands-on integration, policy design, approval workflows, persistence, retry handling, reconciliation, and production testing. Scope agreed around your architecture.

Ongoing Production Support

A recurring engagement for new actions, policy changes, upgrades, incident analysis, and rollout reviews.

Building it yourself? Start integrating with CTRLRun OSS →

diff --git a/docs/reference/api/index.mdx b/docs/reference/api/index.mdx deleted file mode 100644 index 9032bba1..00000000 --- a/docs/reference/api/index.mdx +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: "Python API" -description: "Every frozen public name of the ctrlrun package and its extras, one page each, rendered from the docstrings." ---- - -{/* generated by tools/docs_audit/render_api.py from the docstrings — edit the docstring, never this page */} - -The names below are the ones the specifications freeze: `ctrlrun.__all__`, the entry -points of the extras, and `ctrlrun.verify.run`. Each page is rendered from the docstring; -a name with no docstring fails a test, so every page has one. - -| Name | Kind | Summary | -|---|---|---| -| [`ctrlrun.Action`](/reference/api/Action) | class | A proposed agent action: what, with which arguments, by whom, on what. | -| [`ctrlrun.ActionDenied`](/reference/api/ActionDenied) | class | The action may not run. `reason` says why, e.g. `unknown_action` (SPEC-v0.1 §3.4). | -| [`ctrlrun.AmbiguousEffect`](/reference/api/AmbiguousEffect) | class | The outcome of this effect is unknown; only a human may resolve it (SPEC-v0.1 §5.4). | -| [`ctrlrun.Approval`](/reference/api/Approval) | class | A human's grant, bound to one `action_hash` (SPEC-v0.1 §4.1). | -| [`ctrlrun.ApprovalAnswer`](/reference/api/ApprovalAnswer) | class | A human's answer, and who gave it (SPEC-v0.5 §2.2, §3.4). | -| [`ctrlrun.ApprovalMismatch`](/reference/api/ApprovalMismatch) | class | The presented approval does not authorize this action (SPEC-v0.1 §4.2). | -| [`ctrlrun.ApprovalProvider`](/reference/api/ApprovalProvider) | class | How a human is asked, and how the answer comes back (SPEC-v0.1 §4.3). | -| [`ctrlrun.ApprovalRequest`](/reference/api/ApprovalRequest) | class | A pending question for a human: may this exact action run? (SPEC-v0.1 §4.1) | -| [`ctrlrun.ApprovalRequired`](/reference/api/ApprovalRequired) | class | The action needs a human. `request_id` is what `ctrlrun approve` takes (SPEC §4.3). | -| [`ctrlrun.ApprovalTimeout`](/reference/api/ApprovalTimeout) | class | Nobody answered the approval request in time (SPEC-v0.1 §4.3). | -| [`ctrlrun.Authority`](/reference/api/Authority) | class | The `authority:` section, loaded and evaluable (SPEC-v0.3 §4). | -| [`ctrlrun.AuthorityDenied`](/reference/api/AuthorityDenied) | class | The principal holds no grant that covers this action (SPEC-v0.3 §4.3). | -| [`ctrlrun.AuthorityEscalation`](/reference/api/AuthorityEscalation) | class | A delegation that may not exist: it is not contained in its parent (SPEC-v0.3 §5.3). | -| [`ctrlrun.AuthorityResult`](/reference/api/AuthorityResult) | class | What the authority axis decided, and which grant it decided on (§4.8). | -| [`ctrlrun.CTRLRunError`](/reference/api/CTRLRunError) | class | Base class for every error raised by CTRLRun. | -| [`ctrlrun.Condition`](/reference/api/Condition) | class | One `_: operand` test against an action's arguments (SPEC-v0.1 §3.2). | -| [`ctrlrun.Control`](/reference/api/Control) | class | Policy, state and evidence composed around a single action (SPEC-v0.1 §8). | -| [`ctrlrun.Decision`](/reference/api/Decision) | class | What may happen to an action: exactly three outcomes in v0.1 (SPEC-v0.1 §3.3). | -| [`ctrlrun.Delegation`](/reference/api/Delegation) | class | A grant created at runtime by a principal who already holds one (SPEC-v0.3 §5.1). | -| [`ctrlrun.DelegationRecord`](/reference/api/DelegationRecord) | class | One row of the `delegations` table (SPEC-v0.3 §5.2). | -| [`ctrlrun.DuplicateEffect`](/reference/api/DuplicateEffect) | class | This logical effect already happened, or is happening now (SPEC-v0.1 §5.4). | -| [`ctrlrun.EffectKeyError`](/reference/api/EffectKeyError) | class | An effect template cannot be resolved to a key (SPEC-v0.1 §5.1). | -| [`ctrlrun.EffectRecord`](/reference/api/EffectRecord) | class | What a StateStore holds for one effect key (ARCHITECTURE §5). | -| [`ctrlrun.EffectState`](/reference/api/EffectState) | class | Where a logical effect stands (SPEC-v0.1 §5.2). | -| [`ctrlrun.Event`](/reference/api/Event) | class | One ordered step in the life of an action (SPEC-v0.1 §6.2). | -| [`ctrlrun.EventSink`](/reference/api/EventSink) | class | Somewhere a copy of every `Event` and `Receipt` goes (SPEC-v0.2 §4.1). | -| [`ctrlrun.FrameworkInterrupt`](/reference/api/FrameworkInterrupt) | class | One framework's human-in-the-loop primitive, and nothing else (SPEC-v0.5 §2.1). | -| [`ctrlrun.Grant`](/reference/api/Grant) | class | One permission: this subject may propose these actions, under these limits (§4.2). | -| [`ctrlrun.HeaderIdentityProvider`](/reference/api/HeaderIdentityProvider) | class | The principal named by a trusted HTTP header (§3.3). | -| [`ctrlrun.IdentityContext`](/reference/api/IdentityContext) | class | What a provider is told about the call it is resolving a principal for (§3.1). | -| [`ctrlrun.IdentityError`](/reference/api/IdentityError) | class | A credential was offered and rejected (SPEC-v0.3 §3.2). | -| [`ctrlrun.IdentityProvider`](/reference/api/IdentityProvider) | class | Resolves the principal for one action (SPEC-v0.3 §3.1). | -| [`ctrlrun.InMemoryStateStore`](/reference/api/InMemoryStateStore) | class | Everything held in process memory: for tests and `ctrlrun demo`. | -| [`ctrlrun.InterruptApprovalProvider`](/reference/api/InterruptApprovalProvider) | class | An `ApprovalProvider` whose `wait()` routes through a framework's own primitive. | -| [`ctrlrun.InvalidArgument`](/reference/api/InvalidArgument) | class | An argument cannot be accepted as given. | -| [`ctrlrun.JSONLEventSink`](/reference/api/JSONLEventSink) | class | The JSONL half of the evidence: two append-only files in one directory (SPEC §6). | -| [`ctrlrun.LocalApprovalProvider`](/reference/api/LocalApprovalProvider) | class | Requests go to the StateStore; `wait()` polls it (SPEC-v0.1 §4.3). | -| [`ctrlrun.MissingDependency`](/reference/api/MissingDependency) | class | An optional extra is not installed (SPEC-v0.2 §1.1, §11). | -| [`ctrlrun.NotExecuted`](/reference/api/NotExecuted) | class | Raised by an executor to assert the remote side did nothing (SPEC-v0.1 §5.5). | -| [`ctrlrun.PendingApproval`](/reference/api/PendingApproval) | class | What the framework's interrupt is handed, and the only thing it is (SPEC-v0.5 §2.3). | -| [`ctrlrun.Policy`](/reference/api/Policy) | class | Action-level autonomy policy: which actions may run, and under which conditions. | -| [`ctrlrun.PolicyError`](/reference/api/PolicyError) | class | The policy is missing, unreadable, or malformed. Raised at load time (SPEC-v0.1 §3.4). | -| [`ctrlrun.Principal`](/reference/api/Principal) | class | Who is acting: an agent, optionally on behalf of a human. | -| [`ctrlrun.Receipt`](/reference/api/Receipt) | class | Portable evidence of one action that reached a terminal state (SPEC-v0.1 §6.1). | -| [`ctrlrun.ReconcileOutcome`](/reference/api/ReconcileOutcome) | attribute | What a `reconcile` hook may answer about an effect key (SPEC-v0.2 §2). | -| [`ctrlrun.SQLiteStateStore`](/reference/api/SQLiteStateStore) | class | Approvals, effects and evidence in one SQLite file (ARCHITECTURE §5). | -| [`ctrlrun.SchemaMismatch`](/reference/api/SchemaMismatch) | class | A store met a database it does not recognise, in either direction (SPEC-v0.6 §3.3). | -| [`ctrlrun.ScriptedApprovalProvider`](/reference/api/ScriptedApprovalProvider) | class | A human replaced by a fixed script: for tests and `ctrlrun demo` (SPEC-v0.1 §4.3). | -| [`ctrlrun.StateStore`](/reference/api/StateStore) | class | Durable state behind a `Control` (SPEC-v0.1 §5.3): approvals, effects, evidence. | -| [`ctrlrun.StaticIdentityProvider`](/reference/api/StaticIdentityProvider) | class | A fixed principal, for development, tests and single-tenant demonstrations (§3.3). | -| [`ctrlrun.Subject`](/reference/api/Subject) | class | Who a grant is addressed to: an agent pattern, a user pattern, or both (§4.2). | -| [`ctrlrun.Suspended`](/reference/api/Suspended) | class | Raised by an executor: the remote asked for something before it will finish. | -| [`ctrlrun.WebhookApprovalProvider`](/reference/api/WebhookApprovalProvider) | class | Notify a human system on `APPROVAL_REQUESTED`, and let it answer (SPEC-v0.2 §7). | -| [`ctrlrun.action_hash`](/reference/api/action_hash) | function | Return the action hash used to bind approvals to an exact action (SPEC-v0.1 §2.3). | -| [`ctrlrun.banner`](/reference/api/banner) | function | Log SPEC-v0.3 §6.5's observe banner, once per `Control`. An adapter MUST call it (§3.6). | -| [`ctrlrun.canonical_bytes`](/reference/api/canonical_bytes) | function | The canonical form of an arbitrary mapping: UTF-8 JSON, sorted keys, no whitespace. | -| [`ctrlrun.canonicalize`](/reference/api/canonicalize) | function | Return the canonical form of an Action: UTF-8 JSON, sorted keys, no whitespace. | -| [`ctrlrun.context`](/reference/api/context) | function | Bind the principal for calls made inside the block. | -| [`ctrlrun.needs_approval`](/reference/api/needs_approval) | function | Does this call need a human? For a framework that asks before it invokes (SPEC-v0.5 §3.5). | -| [`ctrlrun.parse_conditions`](/reference/api/parse_conditions) | function | Parse a `when:`-shaped mapping into conditions, keyed by the raw condition key. | -| [`ctrlrun.protect`](/reference/api/protect) | function | Bind a function to an action name: every call becomes a decided, recorded Action. | -| [`ctrlrun.with_approval`](/reference/api/with_approval) | function | Present a granted approval to the calls made inside the block (SPEC-v0.1 §4.3). | -| [`ctrlrun.postgres.PostgresStateStore`](/reference/api/postgres-PostgresStateStore) | class | Approvals, effects and evidence in a Postgres schema (SPEC-v0.6 §4). | -| [`ctrlrun.otel.OTelEventSink`](/reference/api/otel-OTelEventSink) | class | Export every `Event` and `Receipt` as OpenTelemetry spans (SPEC-v0.2 §8). | -| [`ctrlrun.jwt_identity.JWTIdentityProvider`](/reference/api/jwt_identity-JWTIdentityProvider) | class | Verify a bearer JWT and map its verified claims onto a `Principal` (SPEC-v0.3 §3.4). | -| [`ctrlrun.acs.AcsControlHook`](/reference/api/acs-AcsControlHook) | class | Answer ACS `steps/*` hooks with CTRLRun's decisions and outcomes. | -| [`ctrlrun.gateway.serve`](/reference/api/gateway-serve) | function | Run a gateway in front of one upstream MCP server (SPEC-v0.2 §6.1). | -| [`ctrlrun.verify.run`](/reference/api/verify-run) | function | Run the applicable guarantees against this configuration and report (§9.1). | -| [`ctrlrun.conformance.run`](/reference/api/conformance-run) | function | Drive every suite through `adapter` and report what each came to (SPEC-v0.5 §5). | -| [`ctrlrun.conformance.store.run`](/reference/api/conformance-store-run) | function | Drive every case against `backend` and report what each came to (SPEC-v0.6 §2). | - -## Next - -- [CLI reference](/reference/cli). -- [Get started](/get-started/quickstart) · [Why](/why). diff --git a/docs/risk-check.mdx b/docs/risk-check.mdx new file mode 100644 index 00000000..117d9ec4 --- /dev/null +++ b/docs/risk-check.mdx @@ -0,0 +1,10 @@ +--- +title: "Agent Execution Risk Check" +description: "Six questions to find where your agent architecture needs execution controls. A free check with practical next steps." +canonical: "https://ctrlrun.dev/risk-check" +mode: "custom" +--- + +import { RiskCheck } from "/snippets/risk-check.jsx"; + +
← CTRLRun

SIX QUESTIONS. A CLEARER EXECUTION BOUNDARY.

Agent Execution
Risk Check.

Find the gaps before your agent takes the next action.

Your answers stay in this browser. No email needed to see your result.

diff --git a/docs/snippets/architecture-review.jsx b/docs/snippets/architecture-review.jsx new file mode 100644 index 00000000..7bc7033a --- /dev/null +++ b/docs/snippets/architecture-review.jsx @@ -0,0 +1,67 @@ +export const ArchitectureReview = () => { + const [domain, setDomain] = useState(''); + const [risk, setRisk] = useState(''); + const endpoint = 'https://ctrlrun-review-form.vercel.app/api/review'; + const [emailAddress, setEmailAddress] = useState(''); + const [website, setWebsite] = useState(''); + const [sending, setSending] = useState(false); + const [sent, setSent] = useState(false); + const [error, setError] = useState(''); + const requestId = useRef(null); + const sendingRef = useRef(false); + const [company, setCompany] = useState(''); + const [purpose, setPurpose] = useState(''); + const [actions, setActions] = useState(''); + const [status, setStatus] = useState('Building'); + const [concerns, setConcerns] = useState([]); + const [started, setStarted] = useState(false); + const [prepared, setPrepared] = useState(false); + const [copied, setCopied] = useState(false); + const reviewRef = useRef(null); + const track = name => window.dispatchEvent(new CustomEvent('ctrlrun:conversion', { detail: { name } })); + useEffect(() => { + const params = new URLSearchParams(window.location.search); + setDomain((params.get('domain') || '').slice(0, 80)); + const level = params.get('risk'); + if (['High', 'Needs review', 'Lower indicated risk', 'Planning'].includes(level)) { + const count = Math.max(0, Math.min(5, Number(params.get('patterns')) || 0)); + const unknowns = Math.max(0, Math.min(5, Number(params.get('unknowns')) || 0)); + setRisk(level + ' — ' + count + ' patterns, ' + unknowns + ' unknowns (self-reported)'); + } + }, []); + useEffect(() => { if (prepared && reviewRef.current) reviewRef.current.focus(); }, [prepared]); + const brief = ['Architecture review request', '', 'Company: ' + company, 'Reply email: ' + emailAddress, domain && 'Domain: ' + domain, 'Agent purpose: ' + purpose, 'Actions it can execute: ' + actions, 'Production status: ' + status, 'Primary concerns: ' + (concerns.join(', ') || 'Discuss during review'), risk && 'Execution risk check: ' + risk].filter(line => line !== false).join('\n'); + const email = 'mailto:contact@arpanghoshal.com?subject=' + encodeURIComponent('CTRLRun architecture review — ' + company) + '&body=' + encodeURIComponent(brief); + const sendReview = async () => { + if (sendingRef.current || sent) return; + sendingRef.current = true; setSending(true); setError(''); + try { + const response = await fetch(endpoint, { + method: 'POST', credentials: 'omit', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ company, email: emailAddress, purpose, actions, status, concerns, domain, risk, website, requestId: requestId.current }), + signal: AbortSignal.timeout(15000) + }); + const data = await response.json(); + if (!response.ok || data.ok !== true || typeof data.id !== 'string') throw new Error(data.error || 'We could not confirm submission. Please retry or email us directly.'); + setSent(true); track('architecture_review_form_submitted'); + } catch (failure) { + setError(failure.name === 'TimeoutError' || failure.name === 'TypeError' ? 'We could not confirm submission. You can retry the same request safely, or email us directly.' : failure.message); + } finally { sendingRef.current = false; setSending(false); } + }; + return
+

Tell us where your agent acts.

+ {domain &&

Architecture review for {domain}

} + {risk &&

Risk check: {risk}

} +
{ if (!started) { setStarted(true); track('architecture_review_form_started'); } }} onChange={() => { setPrepared(false); setCopied(false); setSent(false); setError(''); requestId.current = null; }} onSubmit={event => { event.preventDefault(); if (!requestId.current) requestId.current = crypto.randomUUID(); setPrepared(true); track('architecture_review_form_prepared'); }}> +
+ + +