diff --git a/README.md b/README.md index c541276..45e9c8c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This repository is the Mintlify site root, so a file's path here is the URL it s | Path | What it is | |---|---| | `docs.json` | Site configuration and the whole navigation tree | -| `index.mdx`, `execution-boundary.mdx`, `risk-check.mdx`, `protect-my-agent.mdx` | The top-level pages | +| `index.mdx`, `execution-boundary.mdx` | The top-level pages | | `docs/` | Every documentation page: guides, concepts, cookbook, reference, architecture | | `snippets/`, `images/`, `style.css`, `*.js` | Components, media and the browser demos | | `capabilities.yaml` | The capability matrix's source. Edit the YAML, never a rendered table | @@ -88,7 +88,8 @@ Apache-2.0, the same as the library. See [LICENSE](LICENSE). action, the five checks and the refusal each one raises, so the prose beside it stays short. - `docs.mdx` serves `/docs`: the technical overview. - `docs/` is every technical page, published under `/docs/...`. -- `risk-check.mdx` and `protect-my-agent.mdx` are custom-mode product pages. +- `execution-boundary.mdx` is a custom-mode page. The two commercial pages it sat beside, + `risk-check.mdx` and `protect-my-agent.mdx`, were removed when the site became technical only. - `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 layout. @@ -143,7 +144,7 @@ fallback stays available, and a browser retry reuses its request id, so success once the server confirms Resend accepted the message. `website-events.js` and the components emit `ctrlrun:conversion` events for page visits, CTA -clicks, selections, scenario outcomes, risk-check completion and review submission. No +clicks, selections, scenario outcomes and the launch-updates signup. No analytics provider is configured: these are integration hooks and not stored analytics. Event payloads exclude contact details and free-text form contents. diff --git a/SEO.md b/SEO.md index f3e5c78..c5f2690 100644 --- a/SEO.md +++ b/SEO.md @@ -153,5 +153,3 @@ not needed; the assistants that do read `llms.txt` get Mintlify's. |---|---| | `index` | stop wrong, restricted, or malicious AI agent actions · control AI agents running in your platform · works with agents you can and can't modify · any AI agent whatsapp slack claude code cursor codex chatgpt | | `execution-boundary` | what stops an agent action · AI agent execution boundary · how an agent execution boundary is adopted | -| `risk-check` | AI agent execution risk assessment | -| `protect-my-agent` | AI agent execution boundary, connection coverage and ctrlrun Pro/Enterprise governance for businesses | diff --git a/STYLE.md b/STYLE.md index 232d114..72334e7 100644 --- a/STYLE.md +++ b/STYLE.md @@ -103,4 +103,4 @@ 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. +The `/` and `/execution-boundary` 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/assets/verify-website.cjs b/assets/verify-website.cjs index 919f0f2..27bea12 100644 --- a/assets/verify-website.cjs +++ b/assets/verify-website.cjs @@ -58,52 +58,24 @@ module.exports = async function verifyWebsite(page, base = 'http://localhost:300 } await page.setViewportSize({ width: 1280, height: 900 }); - 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 request →' }).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 request →' }).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 request →' }).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' }) }); + // The home page's one form. `/risk-check` and `/protect-my-agent` were checked here until + // v0.12; both pages were removed when the site became technical only, and the walkthroughs + // that drove their multi-step forms went with them. What is left is the single signup. + await page.goto(base + '/'); + await page.locator('#updates').scrollIntoViewIfNeeded(); + const signups = []; + await page.route('https://ctrlrun-review-form.vercel.app/api/interest', async route => { + signups.push(route.request().postDataJSON()); + await route.fulfill({ status: 200, contentType: 'application/json', headers: { 'Access-Control-Allow-Origin': '*' }, body: JSON.stringify({ ok: true, id: 'test-email' }) }); }); - 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.locator('.cr-updates-form input[type=email]').fill('reader@example.com'); + await page.getByRole('button', { name: 'Keep me posted →' }).click(); + await page.locator('.cr-updates-done').waitFor(); + assert(signups.length === 1 && signups[0].intent === 'launch-updates', 'The signup posts the launch-updates intent'); + assert(signups[0].email === 'reader@example.com', 'The address reaches the endpoint'); + assert(!('company' in signups[0]) || !signups[0].company, 'The signup asks for nothing but an address'); + await page.unroute('https://ctrlrun-review-form.vercel.app/api/interest'); + assert(await page.locator('text=/Pro|Enterprise|Pricing/i').count() === 0, 'No commercial copy on the home page'); 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(); diff --git a/docs.json b/docs.json index 2a3554c..215a0fc 100644 --- a/docs.json +++ b/docs.json @@ -24,10 +24,6 @@ "href": "/docs", "label": "Docs" }, - { - "href": "/#pro-and-enterprise", - "label": "Free, Pro, Enterprise" - }, { "href": "https://github.com/CTRLRun/ctrlrun", "label": "GitHub" @@ -35,8 +31,8 @@ ], "primary": { "type": "button", - "label": "Protect my agent", - "href": "/protect-my-agent" + "label": "Protect your first action", + "href": "/docs/get-started/quickstart" } }, "navigation": { @@ -1231,6 +1227,16 @@ "source": "/ROADMAP", "destination": "/docs/ROADMAP", "permanent": true + }, + { + "source": "/protect-my-agent", + "destination": "/", + "permanent": true + }, + { + "source": "/risk-check", + "destination": "/", + "permanent": true } ] } diff --git a/docs.mdx b/docs.mdx index 08271f6..4cdf822 100644 --- a/docs.mdx +++ b/docs.mdx @@ -219,7 +219,7 @@ the framework's own interrupt, and a framework with no such primitive does not n {/* generated from the suite, pyproject and the soak (mdx) — run the generator */} - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). diff --git a/docs/production/index.mdx b/docs/production/index.mdx index ad555ed..52c2cca 100644 --- a/docs/production/index.mdx +++ b/docs/production/index.mdx @@ -28,7 +28,7 @@ need. `test_the_first_line_of_the_section_says_which_store_and_why` asserts the {/* generated from the suite, pyproject and the soak (full) — run the generator */} - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/docs/security/verify-guarantees). - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/docs/production/postgres). - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/docs/production/soak). diff --git a/generated/readiness.full.mdx b/generated/readiness.full.mdx index 7ec9139..d2e1208 100644 --- a/generated/readiness.full.mdx +++ b/generated/readiness.full.mdx @@ -1,6 +1,6 @@ {/* generated from the suite, pyproject and the soak (full) — run the generator */} - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/docs/security/verify-guarantees). - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/docs/production/postgres). - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/docs/production/soak). diff --git a/generated/readiness.json b/generated/readiness.json index 4eb42e5..ecf8fa7 100644 --- a/generated/readiness.json +++ b/generated/readiness.json @@ -18,6 +18,6 @@ "positive_control": true, "unexplained": 0 }, - "tests": 6232, + "tests": 6220, "version": "0.11.0" } diff --git a/generated/readiness.mdx b/generated/readiness.mdx index c84c27b..5926c59 100644 --- a/generated/readiness.mdx +++ b/generated/readiness.mdx @@ -1,6 +1,6 @@ {/* generated from the suite, pyproject and the soak (mdx) — run the generator */} - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). diff --git a/generated/readiness.readme.md b/generated/readiness.readme.md index ddde915..1cc45e8 100644 --- a/generated/readiness.readme.md +++ b/generated/readiness.readme.md @@ -1,6 +1,6 @@ - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). diff --git a/index.mdx b/index.mdx index d6d67e3..0428a41 100644 --- a/index.mdx +++ b/index.mdx @@ -11,7 +11,7 @@ canonical: "https://ctrlrun.dev/" import { HomeSlides } from "/snippets/home-slides.jsx"; import { HowDiagram } from "/snippets/how-diagram.jsx"; -import { CommercialTiers } from "/snippets/commercial-tiers.jsx"; +import { LaunchUpdates } from "/snippets/launch-updates.jsx";
Every action is checked against your rules before it runs. Allowed actions go through. Sensitive ones wait for a person. Forbidden ones are blocked.
Open source · one line, any action{'@ctrlrun.protect("your.action")'}
Interactive walkthrough: follow one agent action through every check →
-FREE, PRO, ENTERPRISE
-Free. Run it yourself.
-The boundary itself, at no cost. No account, no card, no call with us. Every rule that decides is code you can read, and every action leaves a receipt you keep. Free to use and free to change, under the Apache-2.0 licence.
-Who does the work Your team, on your machines.
- Install it -Integrate, analyze, protect. One dashboard.
-Connect your agents and workflows and see what each connection covers. Search every action, decision and outcome across your agents. Manage policies and approvals from one place, test a rule in observe mode, then turn enforcement on. Built on the open-source foundation, run by us.
-Who does the work Your team, on our dashboard.
-The same product, shaped to your company.
-Everything in Pro, with CTRLRun engineers who design, integrate and maintain the controls your business needs: custom policies and approval chains, connectors to your internal systems, your deployment options, the reports you ask for. Scoped to your requirements, delivered with your team.
-Who does the work Our engineers, with your team.
-WHAT COMES NEXT
+CTRLRun today is a library you install and wire in yourself. What we are building next takes the same boundary and makes it something a team runs without writing the wiring. Leave an address and we will tell you when it is ready. Nothing else, and no one else gets it.
+FOR BUSINESSES RUNNING AGENTS IN PRODUCTION
-CTRLRun sits between the agents you already run and the systems they act on.
-Your agents keep their code and their framework. CTRLRun wraps only the calls that change something: money, infrastructure, permissions, records, messages. Whatever that boundary needs in production, we build and run.
- -You deploy the agents. The controls around them become our job.
- -A paid architecture review of tool boundaries, approval flows, duplicate execution, uncertain outcomes and action records. You leave with an execution-control map and a prioritized plan: what can run automatically, what needs a person, and what must stop until the outcome is known.
Hands-on integration, policy design, approval workflows, persistence, retry handling, reconciliation and production testing. Rollout is per workflow: pick one, agree the rules and who approves what, run it in observe mode to see what would have been blocked, then turn enforcement on.
Connect your agents and workflows and see what each connection covers. Search every action, decision and outcome across your agents. Manage policies and approvals from one place, test a rule in observe mode, then turn enforcement on. Built on the open-source foundation, run by us. In development. Request early access ↗
Everything in Pro, with CTRLRun engineers who design, integrate and maintain the controls your business needs: custom policies and approval chains, connectors to your internal systems, your deployment options, the reports you ask for. Scoped to your requirements, delivered with your team. This is what the review above starts.
A recurring engagement for new actions, policy changes, upgrades, incident analysis and rollout reviews.
Building it yourself? ctrlrun Open Source is free. Install it →
-SIX QUESTIONS. A CLEARER EXECUTION BOUNDARY.
Find the gaps before your agent takes the next action.
Your answers stay in this browser. No email needed to see your result.
{tier.done}
} - {!sent && !open && ( - - )} - {!sent && open && ( - - )} -You are on the list. We will write to that address once, when it is ready.
; + + return ( + + ); +}; diff --git a/snippets/risk-check.jsx b/snippets/risk-check.jsx deleted file mode 100644 index b94b791..0000000 --- a/snippets/risk-check.jsx +++ /dev/null @@ -1,51 +0,0 @@ -export const RiskCheck = () => { - const [actions, setActions] = useState([]); - const [answers, setAnswers] = useState(['', '', '', '', '']); - const [started, setStarted] = useState(false); - const [result, setResult] = useState(null); - const resultRef = useRef(null); - const actionOptions = ['Move money', 'Delete data', 'Send external communications', 'Modify permissions', 'Deploy infrastructure', 'Modify business records', 'None of these yet']; - const questions = [ - 'Can actions be retried automatically?', - 'Can a provider complete an action before your agent receives confirmation?', - 'Are approvals tied to the exact action and parameters?', - 'Can concurrent workers trigger the same business action?', - 'Can you reconstruct why a specific action executed?' - ]; - const patterns = [ - { label: 'Automatic retries', advice: 'Identify the same business action across retries and prevent a second execution.', href: '/docs/concepts/effect-keys' }, - { label: 'Missing provider confirmation', advice: 'Treat a missing response as uncertain. Confirm the original outcome before retrying.', href: '/docs/concepts/outcomes-and-ambiguous' }, - { label: 'Approval can drift from the action', advice: 'Tie approval to the exact amount, target, and other parameters. Require a new approval when they change.', href: '/docs/concepts/approval-binding' }, - { label: 'Concurrent execution', advice: 'Make workers share an execution record so only one can begin the same business action.', href: '/docs/production/how-reservation-works' }, - { label: 'Missing action evidence', advice: 'Record who requested the action, the rule applied, the approval, and the final outcome.', href: '/docs/concepts/receipts-and-evidence' } - ]; - const track = (name, extra = {}) => window.dispatchEvent(new CustomEvent('ctrlrun:conversion', { detail: { name, ...extra } })); - const begin = () => { if (!started) { setStarted(true); track('risk_check_started'); } setResult(null); }; - const check = event => { - event.preventDefault(); - const risky = answers.map((answer, index) => answer === ([2, 4].includes(index) ? 'No' : 'Yes')); - const count = risky.filter(Boolean).length; - const unknowns = answers.filter(answer => answer === 'Unsure').length; - const active = !actions.includes('None of these yet'); - const level = !active ? 'Planning' : count >= 3 ? 'High' : count > 0 || unknowns > 0 ? 'Needs review' : 'Lower indicated risk'; - setResult({ count, unknowns, risky, active, level }); - track('risk_check_completed', { risk_level: level, pattern_count: count, unknown_count: unknowns }); - }; - useEffect(() => { if (result && resultRef.current) resultRef.current.focus(); }, [result]); - returnYOUR EXECUTION RISK CHECK
{result.active ? 'Your answers identify ' + result.count + ' execution-risk pattern' + (result.count === 1 ? '' : 's') + ' CTRLRun is designed to address.' : 'You have not selected a consequential action yet. Use these questions before granting agents permission to act.'}
- {result.unknowns > 0 &&{result.unknowns} answer{result.unknowns === 1 ? ' needs' : 's need'} confirmation. Uncertainty is a reason to inspect your architecture, not proof that a control is missing.
} -{pattern.advice} Implementation guide →
Your answers indicate fewer of these patterns. Validate the controls with concurrency, changed-approval, and lost-response tests before rollout.
} -A planning aid based on your answers, not an audit or a safety certification. High means at least three indicated patterns; “Needs review” means one or more patterns or unknowns.
- track('protect_clicked')}>Get a safety review ↗ -