Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down
2 changes: 0 additions & 2 deletions SEO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
2 changes: 1 addition & 1 deletion STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
62 changes: 17 additions & 45 deletions assets/verify-website.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
18 changes: 12 additions & 6 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,15 @@
"href": "/docs",
"label": "Docs"
},
{
"href": "/#pro-and-enterprise",
"label": "Free, Pro, Enterprise"
},
{
"href": "https://github.com/CTRLRun/ctrlrun",
"label": "GitHub"
}
],
"primary": {
"type": "button",
"label": "Protect my agent",
"href": "/protect-my-agent"
"label": "Protect your first action",
"href": "/docs/get-started/quickstart"
}
},
"navigation": {
Expand Down Expand Up @@ -1231,6 +1227,16 @@
"source": "/ROADMAP",
"destination": "/docs/ROADMAP",
"permanent": true
},
{
"source": "/protect-my-agent",
"destination": "/",
"permanent": true
},
{
"source": "/risk-check",
"destination": "/",
"permanent": true
}
]
}
2 changes: 1 addition & 1 deletion docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion docs/production/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion generated/readiness.full.mdx
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
2 changes: 1 addition & 1 deletion generated/readiness.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
"positive_control": true,
"unexplained": 0
},
"tests": 6232,
"tests": 6220,
"version": "0.11.0"
}
2 changes: 1 addition & 1 deletion generated/readiness.mdx
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
2 changes: 1 addition & 1 deletion generated/readiness.readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- generated from the suite, pyproject and the soak (readme) — 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).
Expand Down
Loading