diff --git a/changelog.mdx b/changelog.mdx
index a47438a..8bc1732 100644
--- a/changelog.mdx
+++ b/changelog.mdx
@@ -14,7 +14,7 @@ Dated notes on what the products ship. Each entry names a change the app or the
- **`/goal`** — persisted long-horizon objectives with `goal.json` on the session, an 8-turn budget, evidence-based completion, and a composer chip — merged to the CLI main branch in [CortexLM/cli #54](https://github.com/CortexLM/cli/pull/54). It ships with the next tagged release; `cortex upgrade` will pick it up. See [Goals](/cli/goal).
- **One approval path** — the permission prompt is the locked inline numbered picker (run once · always allow in this project · edit command · decline), merged in [#53](https://github.com/CortexLM/cli/pull/53). See [Modes and permissions](/cli/modes-and-permissions).
-- The Designer runtime pack — headless renders of every TUI state at 120×40 — is what the [CLI pages](/cli) now show. The README demo on a green macOS desktop is regenerated from the same chrome.
+- The CLI runtime pack — headless renders of every TUI state at 120×40 — is what the [CLI pages](/cli) now show. The README demo on a green macOS desktop is regenerated from the same chrome.
- Release: [v0.1.10 on GitHub](https://github.com/CortexLM/cli/releases/tag/v0.1.10). Update with `cortex upgrade`.
diff --git a/cli/index.mdx b/cli/index.mdx
index bcca9c0..b5091a6 100644
--- a/cli/index.mdx
+++ b/cli/index.mdx
@@ -58,24 +58,6 @@ cortex
First session, `AGENTS.md`, choosing autonomy.
-
- Timeline rows, the composer, status line, queueing follow-ups, interrupting.
-
-
- Everything after `/` — model, mode, permissions, plan, goal, sessions, MCP, diagnostics.
-
-
- Agent / Plan / Ask, Spec, approvals, sandbox, the `permission` table, effort.
-
-
- `/goal` — a persisted long-horizon objective that survives compaction and resume.
-
-
- Resume, rewind, fork, export, import, share, lock, clean up.
-
-
- Every binding, grouped by where it applies — plus the Ctrl+X overlay.
-
`cortex exec` and `cortex run` in scripts, GitHub Actions, GitLab CI.
@@ -85,11 +67,22 @@ cortex
MCP servers, skills, agents and subagents, hooks, plugins, themes.
-
- Outage copy, hosts that refuse, the agent that will not edit, MCP, quotas.
-
+## In the terminal
+
+The rest of the CLI reference lives in the sidebar under this tab:
+
+| Page | What it covers |
+| --- | --- |
+| [The TUI](/cli/tui) | Timeline rows, the composer, status line, queueing follow-ups, interrupting |
+| [Slash commands](/cli/slash-commands) | Everything after `/` — model, mode, permissions, plan, goal, sessions, MCP, diagnostics |
+| [Modes and permissions](/cli/modes-and-permissions) | Agent / Plan / Ask, Spec, approvals, sandbox, the `permission` table, effort |
+| [Goals](/cli/goal) | `/goal` — a persisted long-horizon objective that survives compaction and resume |
+| [Sessions](/cli/sessions) | Resume, rewind, fork, export, import, share, lock, clean up |
+| [Keyboard shortcuts](/cli/keyboard) | Every binding, grouped by where it applies — plus the Ctrl+X overlay |
+| [CLI troubleshooting](/cli/troubleshooting) | Outage copy, hosts that refuse, the agent that will not edit, MCP, quotas |
+
## Conventions on these pages
- Commands are written `cortex …`. The installed binary is `Cortex`; the installer adds `cortex` and `agent` symlinks, and both spellings work on case-insensitive filesystems.
diff --git a/getting-started/quickstart.mdx b/getting-started/quickstart.mdx
index 7ee2863..a3fb143 100644
--- a/getting-started/quickstart.mdx
+++ b/getting-started/quickstart.mdx
@@ -39,15 +39,6 @@ Cortex is one account across four products: **Chat**, **Code**, **Bot**, and **D
Choose **Sign in to save your work** in the sidebar footer. A signed-in account keeps chats, projects, Library files, plans, and Bot routines across devices, and unlocks Code and Bot. See [Accounts](/getting-started/accounts).
-
- | Surface | First useful thing | Guide |
- | --- | --- | --- |
- | **Chat** | Create a project with standing instructions | [Chat quickstart](/chat/quickstart) |
- | **Code** | Connect GitHub and start a Cloud session on a repository | [Code quickstart](/code/quickstart) |
- | **Bot** | Create a bot and open its computer | [Bot quickstart](/bot/quickstart) |
- | **CLI** | Install, sign in, run `cortex` in a project | [CLI quickstart](/cli/quickstart) |
- | **Design** | Host a canvas from Chat or Code, then open it on desktop | [Design](/design) |
-
## Install the desktop app or the CLI
diff --git a/getting-started/settings.mdx b/getting-started/settings.mdx
index 00ec2e8..1dbcc0a 100644
--- a/getting-started/settings.mdx
+++ b/getting-started/settings.mdx
@@ -59,17 +59,18 @@ Model availability depends on your plan. During peak hours new chats may fall ba
## Data & privacy
-| Control | Default | Notes |
+Where each control lives. What each one stores, and what it means, is on
+[Security and privacy](/security/overview).
+
+| Control | Where | Notes |
| --- | --- | --- |
-| **Save chat history** | On | Conversations are kept in your account so you can pick them up on any device. *Coming soon* as a toggle. |
-| **Improve the model** | Off | When on, a sample of your conversations may be reviewed to train future models. Your chats are never used without this setting. *Coming soon.* |
-| **Necessary cookies** | Always on | Session, language, and this preference. Cortex cannot work without them. |
-| **Optional analytics** | Off | Stays off until you allow it. Nothing in the current build loads an optional tracker. |
-| **Memory** | — | Review or delete individual memories. See [Memory](/chat/memory). |
-| **Two-factor authentication** | — | A signed-in account uses an authenticator app to confirm export and deletion. |
-| **Your data** | — | **Export my data** and **Delete all data**. Deletion takes effect within 30 days. |
-
-See [Security](/security/overview) for what Cortex stores and where code runs.
+| **Save chat history** | Data & privacy | On by default. *Coming soon* as a toggle. |
+| **Improve the model** | Data & privacy | Off by default. Your chats are never used without this setting. *Coming soon.* |
+| **Necessary cookies** | Data & privacy | Session, language, and this preference. Cortex cannot work without them. |
+| **Optional analytics** | Data & privacy | Stays off until you allow it. Nothing in the current build loads an optional tracker. |
+| **Memory** | Data & privacy | Review or delete individual memories. See [Memory](/chat/memory). |
+| **Two-factor authentication** | Data & privacy | An authenticator app confirms export and deletion. |
+| **Export my data / Delete all data** | Data & privacy | Deletion takes effect within 30 days. |
## Plan & billing
diff --git a/images/cli/README.md b/images/cli/README.md
index 1d167e9..edb8c20 100644
--- a/images/cli/README.md
+++ b/images/cli/README.md
@@ -9,7 +9,7 @@ exactly one page; no two files share a sha256.
`docs/media/intro.gif` — the README demo: the signed lock TUI composited onto a
photographed macOS desktop. Generated, not a live recording. Used on `cli/index`.
-## `runtime/` — Designer cli pack
+## `runtime/` — CLI runtime pack
Headless `MockTerminal` renders of the live session chrome, vendored byte-for-byte
from CortexLM/cli tip `4d41ef362481ec816ac395e0e64fe6a54327de14`
diff --git a/scripts/check-docs-site.mjs b/scripts/check-docs-site.mjs
index f7447c2..6345605 100644
--- a/scripts/check-docs-site.mjs
+++ b/scripts/check-docs-site.mjs
@@ -201,6 +201,35 @@ function assertNavPagesExist(parsed) {
}
}
+/**
+ * An MDX page no navigation entry reaches is dead weight: it still gets
+ * indexed, and still shows up in search — but no reader can navigate to it.
+ * This is how the old tree accumulated a second hub for every product
+ * (`chat/overview` beside `chat/index`) and pages whose only inbound links
+ * were from other orphans.
+ *
+ * The home page is reachable from the navbar's `/` link, which `pushNavSlug`
+ * drops as a non-slug, so it is exempted by name.
+ */
+function assertNoOrphanPages(docsJson) {
+ let parsed;
+ try {
+ parsed = JSON.parse(docsJson);
+ } catch {
+ return; // already reported as invalid JSON
+ }
+ const nav = new Set(navPages(parsed));
+ for (const file of walk(ROOT)) {
+ if (!file.endsWith('.mdx')) continue;
+ const slug = relative(ROOT, file).replace(/\.mdx$/, '');
+ if (slug === 'index') continue;
+ if (nav.has(slug) || nav.has(slug.replace(/\/index$/, ''))) continue;
+ fail(
+ `${slug}.mdx is not reachable from docs.json navigation (orphaned page)`,
+ );
+ }
+}
+
const AUTH_INTERNALS = [
{ pattern: /\/auth\//, label: '/auth/' },
{ pattern: /\/oauth\//, label: '/oauth/' },
@@ -210,6 +239,46 @@ const AUTH_INTERNALS = [
{ pattern: /client_secret/i, label: 'client secret' },
];
+/**
+ * Staging must never reach the public site again.
+ *
+ * `docs.cortex.foundation` is the public Mintlify site, and it once publicly
+ * served `/staging/*` pages plus a Staging collection. Deleting those files is
+ * not enough: a re-added `staging/…` page, a nav entry, or a redirect into one
+ * would put pre-prod copy back on the public domain. This holds the line
+ * mechanically, the same way the auth-internal check does.
+ */
+function assertNoStagingNav(docsJson) {
+ let parsed;
+ try {
+ parsed = JSON.parse(docsJson);
+ } catch {
+ return; // already reported as invalid JSON
+ }
+ const seen = new Set();
+ for (const slug of navPages(parsed)) {
+ if (!/(^|\/)staging(\/|$)/i.test(slug) || seen.has(slug)) continue;
+ seen.add(slug);
+ fail(`docs.json navigation must not publish a staging page (${slug})`);
+ }
+ const redirectSources = Array.isArray(parsed.redirects) ? parsed.redirects : [];
+ for (const rule of redirectSources) {
+ if (typeof rule?.destination === 'string' && /(^|\/)staging(\/|$)/i.test(rule.destination)) {
+ fail(`docs.json redirect ${rule.source} points at a staging page`);
+ }
+ }
+}
+
+function assertNoStagingContent(rel, text) {
+ if (!rel.endsWith('.mdx')) return;
+ if (/\/staging(\/|$)/.test(text)) {
+ fail(`${rel} links to a staging path (public docs have no staging mirror)`);
+ }
+ if (/\bstaging\b/i.test(text) && !/no staging|not staging|retired/i.test(text)) {
+ fail(`${rel} tells readers about staging (public docs are production only)`);
+ }
+}
+
function assertNoAuthInternals(rel, text) {
if (!rel.endsWith('.mdx')) return;
for (const { pattern, label } of AUTH_INTERNALS) {
@@ -310,6 +379,37 @@ for (const extra of pageCodes) {
}
}
+/**
+ * The catalog table in `problems/index.mdx` is hand-maintained while the pages
+ * beside it are generated in lockstep with the backend. That is the same decay
+ * this file exists to catch: a new code lands as a page, the catalog keeps
+ * listing the old set, and the index — the page a reader actually lands on —
+ * silently stops being complete.
+ */
+function assertCatalogCoversPages() {
+ const rel = 'problems/index.mdx';
+ if (!existsSync(join(ROOT, rel))) {
+ fail(`${rel} is missing (problem catalog index)`);
+ return;
+ }
+ const catalog = read(rel);
+ const listed = new Set(
+ [...catalog.matchAll(/\]\(\/problems\/([a-z_]+)\)/g)].map((m) => m[1]),
+ );
+ for (const code of pageCodes) {
+ if (!listed.has(code)) {
+ fail(`${rel} does not link \`${code}\`; every problem page belongs in the catalog`);
+ }
+ }
+ for (const code of listed) {
+ if (!pageCodes.has(code)) {
+ fail(`${rel} links \`${code}\`, which has no page at problems/${code}.mdx`);
+ }
+ }
+}
+
+assertCatalogCoversPages();
+
if (!existsSync(join(docsRoot, 'docs.json'))) {
fail('docs.json is missing (Mintlify site config)');
} else {
@@ -321,6 +421,8 @@ if (!existsSync(join(docsRoot, 'docs.json'))) {
fail('docs.json still names docs.cortex.sh');
}
assertDocsChrome(docsJson);
+ assertNoStagingNav(docsJson);
+ assertNoOrphanPages(docsJson);
}
for (const rel of FORBIDDEN_AUTH_PAGES) {
@@ -363,6 +465,7 @@ for (const file of docsFiles) {
);
}
assertNoAuthInternals(rel, text);
+ assertNoStagingContent(rel, text);
if (BACKEND === null) continue;
for (const path of documentedV1Paths(text)) {
const norm = normalizePath(path);
diff --git a/scripts/tests/check-docs-site.test.sh b/scripts/tests/check-docs-site.test.sh
index e3de33d..c6630cb 100755
--- a/scripts/tests/check-docs-site.test.sh
+++ b/scripts/tests/check-docs-site.test.sh
@@ -58,7 +58,7 @@ RS
"groups": [
{
"group": "Problems",
- "pages": ["problems/not_found", "problems/internal"]
+ "pages": ["problems/index", "problems/not_found", "problems/internal"]
}
]
}
@@ -79,6 +79,16 @@ title: "${code}"
| \`GET\` | \`/v1/conversations\` |
MDX
done
+ cat > "$dest/site/problems/index.mdx" <<'MDX'
+---
+title: "Problem catalog"
+---
+
+| Code | HTTP |
+| --- | --- |
+| [`not_found`](/problems/not_found) | 404 |
+| [`internal`](/problems/internal) | 500 |
+MDX
}
must_fail() {
@@ -318,7 +328,7 @@ cat > "$goodroot/site/docs.json" <<'JSON'
{
"group": "Problems",
"root": "problems/not_found",
- "pages": ["problems/internal"]
+ "pages": ["problems/internal", "problems/index"]
}
]
}
@@ -362,4 +372,124 @@ cat > "$missinghref/site/docs.json" <<'JSON'
JSON
must_fail "$missinghref" "missing-mintlify-page"
+# Staging must not return to the public site. docs.cortex.foundation served a
+# /staging collection publicly once; a re-added page, nav entry, or redirect
+# would put pre-prod copy back on the domain.
+stagingnav="$tmp/staging-nav"
+seed "$stagingnav"
+mkdir -p "$stagingnav/site/staging"
+cat > "$stagingnav/site/staging/index.mdx" <<'MDX'
+---
+title: "Index"
+---
+Staging mirror — unpublished drafts / pre-prod docs
+MDX
+cat > "$stagingnav/site/docs.json" <<'JSON'
+{
+ "name": "Cortex",
+ "logo": { "href": "https://docs.cortex.foundation" },
+ "navbar": {
+ "links": [
+ { "label": "Home", "href": "/" },
+ { "label": "Documentation", "href": "/problems/not_found" }
+ ]
+ },
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "Staging",
+ "groups": [
+ { "group": "Drafts", "pages": ["staging/index"] }
+ ]
+ }
+ ]
+ }
+}
+JSON
+must_fail "$stagingnav" "staging"
+
+# A redirect into a staging path is the same leak by another route.
+stagingredirect="$tmp/staging-redirect"
+seed "$stagingredirect"
+cat > "$stagingredirect/site/docs.json" <<'JSON'
+{
+ "name": "Cortex",
+ "logo": { "href": "https://docs.cortex.foundation" },
+ "navbar": {
+ "links": [
+ { "label": "Home", "href": "/" },
+ { "label": "Documentation", "href": "/problems/not_found" }
+ ]
+ },
+ "redirects": [
+ { "source": "/drafts", "destination": "/staging/index" }
+ ],
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "API",
+ "groups": [
+ { "group": "Problems", "pages": ["problems/not_found", "problems/internal"] }
+ ]
+ }
+ ]
+ }
+}
+JSON
+must_fail "$stagingredirect" "staging"
+
+# Copy that sends a reader to a staging host or path must fail even when the
+# page itself is fine.
+stagingcopy="$tmp/staging-copy"
+seed "$stagingcopy"
+printf '\nUse the staging environment at `/staging/api` for pre-prod keys.\n' \
+ >> "$stagingcopy/site/problems/not_found.mdx"
+must_fail "$stagingcopy" "staging"
+
+# A page no navigation entry reaches still gets indexed, but no reader can
+# navigate to it. That is how the tree accumulated a second hub for every
+# product.
+orphan="$tmp/orphan-page"
+seed "$orphan"
+cat > "$orphan/site/problems/leftover.mdx" <<'MDX'
+---
+title: "Leftover"
+---
+
+`type` is `https://docs.cortex.foundation/problems/leftover`.
+MDX
+must_fail "$orphan" "orphaned page"
+
+# The catalog index is hand-maintained while the pages beside it are generated
+# in lockstep with the backend. A page the catalog forgets is invisible to the
+# reader who lands on the index.
+drift="$tmp/catalog-drift"
+seed "$drift"
+cat > "$drift/site/problems/index.mdx" <<'MDX'
+---
+title: "Problem catalog"
+---
+
+| Code | HTTP |
+| --- | --- |
+| [`not_found`](/problems/not_found) | 404 |
+MDX
+must_fail "$drift" "does not link \`internal\`"
+
+# And the reverse: a catalog entry with no page behind it.
+dangling="$tmp/catalog-dangling"
+seed "$dangling"
+cat > "$dangling/site/problems/index.mdx" <<'MDX'
+---
+title: "Problem catalog"
+---
+
+| Code | HTTP |
+| --- | --- |
+| [`not_found`](/problems/not_found) | 404 |
+| [`internal`](/problems/internal) | 500 |
+| [`ghost`](/problems/ghost) | 500 |
+MDX
+must_fail "$dangling" "no page at problems/ghost.mdx"
+
echo "check-docs-site: ok"
diff --git a/security/overview.mdx b/security/overview.mdx
index ca25058..340a1cd 100644
--- a/security/overview.mdx
+++ b/security/overview.mdx
@@ -31,6 +31,9 @@ From **Settings → Data & privacy**, in the product's own words: your account d
| **Two-factor authentication** | — | A signed-in account uses an authenticator app to confirm export and deletion. |
| **Export my data / Delete all data** | — | Export everything held, or delete it permanently. Deletion takes effect within 30 days. |
+These live under **Settings → Data & privacy**; [Settings](/getting-started/settings)
+lists where each control is. The table above is what each one stores.
+
## Permissions
A guest cannot connect a plugin or MCP server, hold a GitHub grant, create a bot, or open Code's working UI. That is fail-closed by design: a guest session cannot be signed back into to revoke a grant later, so it is never allowed to create one.
diff --git a/status.mdx b/status.mdx
index b38cc13..fa004fa 100644
--- a/status.mdx
+++ b/status.mdx
@@ -8,17 +8,13 @@ keywords: ["uptime", "outage", "incident", "maintenance", "availability"]
-Cortex publishes live component health, incident history, and scheduled maintenance on the public status page.
+Live component health, incident history, and scheduled maintenance are published on **[status.cortex.foundation](https://status.cortex.foundation)**. Bookmark that page; this article explains what it covers and how it relates to product troubleshooting.
Open status.cortex.foundation
Troubleshooting
-
-The canonical status URL is **[status.cortex.foundation](https://status.cortex.foundation)**. Bookmark that page for outages and planned maintenance. This docs article explains what it covers and how it relates to product troubleshooting.
-
-
## What we report
| Component | What it covers |
@@ -33,8 +29,8 @@ Component names stay product-facing. Individual incidents may mark one or more o
## During an incident
-1. Check **[status.cortex.foundation](https://status.cortex.foundation)** for the current banner and component states.
-2. Subscribe on that page if you want email (or other) updates for the next change.
+1. Check the status page for the current banner and component states.
+2. Subscribe there if you want updates for the next change.
3. Keep any `request_id` from an error in the app — see [Troubleshooting](/getting-started/troubleshooting) and the [problem catalog](/problems).
User-facing error text names a Cortex surface, never a vendor. Status updates are the place for fleet-wide notices.
@@ -48,10 +44,6 @@ User-facing error text names a Cortex surface, never a vendor. Status updates ar
| [Security and privacy](/security/overview) | Data handling — not availability |
| [Changelog](/changelog) | What shipped — not outages |
-## Brand
-
-Status communications use Cortex green (`#1F4945`) and the Cortex logo. The status site is hosted under the `cortex.foundation` domain so it stays available when you need it most.
-
## Related
- [Troubleshooting](/getting-started/troubleshooting) — what a message in the product means.