Skip to content

Commit e19ae67

Browse files
docs(deployment): name both Connect-an-Agent doors in the OS_MCP_STDIO_API_KEY row (#18959)
Fixes #18143 Clause-②: no ## The remainder — one line, one file This card named **four** sites. PR #18573 landed three of them; `content/docs/ai/connect-mcp.mdx` belongs to #17648. What was left is the fourth: the `OS_MCP_STDIO_API_KEY` row in `content/docs/deployment/environment-variables.mdx`, located **by content**, not by the line number the card quotes. | | the cell | |:--|:--| | before | … Mint one from **Setup → Connect an Agent** (or `POST /api/v1/keys`). … | | after | … Mint one from the **Connect an Agent** page — **Account → Developer** for any signed-in user, **Setup → Connect an Agent** for platform admins — or `POST /api/v1/keys`. … | One line in, one line out. It is a table cell in a long Markdown table, so the two-door sentence is compressed to fit: pipe count unchanged (5), row count unchanged (133 `OS_` rows), still a single line. ## Why the old cell was wrong `SETUP_APP` declares `requiredPermissions: ['setup.access']`, and a permissionless principal gets `403 PERMISSION_DENIED` on `/api/v1/meta/apps/setup`. A **direct minting instruction** naming only the Setup door therefore tells a non-admin to take a path they cannot take. Ruling #16746 (decision batch #85) delivers the page to them through a `navigationContributions` entry in the **`account`** app — app `account`, group `grp_account_developer` (label **Developer**), item `nav_connect_agent` (label **Connect an Agent**), package id `com.objectstack.account`. The Setup entry **stays** for admins, deliberately. So the fix is **name both doors**, ⛔ not replace Setup with Account — the shape PR #18142 and PR #18573 established. The wording here is copied from the two sibling pages rather than invented as a fourth spelling: - `content/docs/api/index.mdx:68-69` — "…from the **Connect an Agent** page in the Console — **Account → Developer** for any signed-in user, **Setup → Connect an Agent** for platform admins." - `content/docs/getting-started/build-with-claude-code.mdx:435-436` — "…lives on the **Connect an Agent** page: **Account → Developer** for any signed-in user, **Setup → Connect an Agent** for platform admins." ## Post-condition probe — written BEFORE the edit, and deliberately NOT "Setup goes to 0" An earlier round's first probe was "`Setup → Connect an Agent` must go to 0 in this file". That probe is **wrong for this card**: the correct end state keeps the Setup door named, so it would read a correct landing as a half-done one. The post-conditions here are about the **Account door appearing alongside**. Every count is taken on a **whitespace-flattened** file, so wrapped prose cannot give a false zero, and every zero is paired with a control from the same population that must hit. | # | reading (flattened) | before | after | post-condition | |:--|:--|--:|--:|:--| | A | this file, `Account → Developer` | 0 | **1** | ≥ 1 — the Account door appears | | B | this file, `Setup → Connect an Agent` | 1 | **1** | ≥ 1 — Setup **stays** named, for admins | | C | CONTROL, this file, `Connect an Agent` unprefixed | 1 | 2 | nonzero both sides — the reader has a pulse | | D | table integrity: `OS_` rows / pipes in the row / lines for that key | 133 / 5 / 1 | 133 / 5 / 1 | unchanged, single line | | E | CORPUS CONTROL over `content/docs/**/*.mdx` (404 files), `Connect an Agent` unprefixed | 10 | 11 | nonzero — the corpus reader has a pulse | Corpus-level close-out: the Setup door is still named in exactly **4** files (unchanged by design), and **every one of the 4 now also names the Account door** — carriers naming the Setup door but not the Account door: **0**. | carrier | `Setup → Connect an Agent` | `Account → Developer` | |:--|--:|--:| | `content/docs/ai/connect-mcp.mdx` | 1 | 1 | | `content/docs/api/index.mdx` | 1 | 1 | | `content/docs/deployment/environment-variables.mdx` | 1 | 1 | | `content/docs/getting-started/build-with-claude-code.mdx` | 1 | 1 | ## Serial constraint — re-measured at hunk level, and it does not bite PR #18420 (draft, untouched since 2026-09-17T16:16Z) is the only open PR touching this file. Read from its diff: its **only** hunk in this file is `@@ -87,7 +87,7 @@`, the `OS_AUTOMATION_SCHEDULED_WORK_ENABLED` row. This PR changes the row at `:260`. **173 lines apart**, far outside git's three-line context ⇒ no textual conflict. Nothing in #18420 was touched or coordinated. ## Verification Gate families derived in this worktree from the real change set, not from a hand-written list: `node scripts/pm/dispatch-gates.mjs --commands` (change set: 1 path vs merge base `46559f61c`). - **39 derived families, 39 run, all `exit 0`.** Reconciled with exit codes recorded: `dispatch-gates --repo objectstack-ai/objectstack --ran` ⇒ "39 derived famil(ies) accounted for — 39 run, 0 NOT-MEASURED (a DERIVED zero — all 39 recorded an exit code and none of them is 3)". - Four of them first returned `PREREQUISITE NOT MET` (`exit 3` ×3, plus `check:skill-examples` exit 1 on an unbuilt `client-react` dist) — **not findings**. After `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --filter=@objectstack/client-react --filter=@objectstack/client` (exit 0) all four re-ran at `exit 0`: `check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples`, `check:docs-transcript-drift`. - `pnpm --filter @objectstack/spec build` ran first (exit 0), so `check:docs` read a current tree. - That derivation is **not** a complete account of CI — the artifact-roster, wide-population, pending-changeset and path-scheduled families sit outside it, as the tool says of itself. - Control characters: `grep -naP` over the edited file finds none (exit 1), with a planted positive control proving the reader fires (exit 0, hit). `pnpm check:nul-bytes` exit 0. ### `pnpm lint` — a **proven narrowing**, not a skipped run The repo-wide scan is CI's run. Three pieces of evidence that narrowing excluded nothing: 1. **Population, read from eslint's own config:** every `files:` glob in `eslint.config.mjs` enumerates code extensions (`ts,tsx,mts,cts,js,jsx,mjs,cjs`); the string `mdx` occurs **0** times in that config. `.mdx` is not in the linted population at all. 2. **File count, read from `--format json`:** eslint over the changed file returns **0 results**; the positive control (`scripts/check-nul-bytes.mjs`) returns **1 result** — the reader resolves files and reports. 3. **Invariance for untouched files:** the config enables no type-aware linting for any file (its own header: "this repo runs one `eslint.config.mjs`, which never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file"), so this diff cannot move any untouched file's verdict. ### Changeset: `skip-changeset`, measured Nothing published moves. - 83 tracked manifests; **70** declare `files[]` (the control: the reader resolves `files[]` arrays — e.g. `@objectstack/spec` ⇒ `dist`, `json-schema`, `liveness`, `prompts`, `llms.txt`, `README.md`, `src/**/*.zod.ts`, `CHANGELOG.md`, `api-surface`, `spec-changes.json`). Entries reaching `content/docs/**`: **0**. - Symbol grep over the **2138** files those `files[]` entries actually resolve to: `Mint one from` ⇒ **0**, `Account → Developer` ⇒ **0**; positive control `objectstack` ⇒ **1971** files, so the reader reaches published bytes. - The only consumer of `content/docs/`, `@objectstack/docs` (`apps/docs`), is `private: true` and declares no `files[]`. - The one published manifest whose text mentions `content/docs` (`@objectstack/plugin-webhooks`) does so in its `description` prose about a different page; its `files[]` is `dist`, `README.md`, `CHANGELOG.md`. ## Acceptance notes Out of scope, noted and **not** filed: - The card's four deliberately excluded carriers (`docs/adr/0101-…:104`, `docs/qa/platform-checklist/areas/ai.json:206`, two `.changeset/*.md`) are dated records, left untouched. - This same file carries `Setup → Settings` and `Setup → Authentication`, and the corpus carries 27 other `Setup → X` phrases (Access Control, People, SSO Providers, Datasources, Approvals …). Those name genuinely admin-only surfaces addressed to admins — the Connect-an-Agent defect exists precisely because that one page is **also** delivered to non-admins through the `account` app, which is not true of the others. No defect, and the successor question has an answer: **successor: none** — no PR or reader is routed to them by this change. --- _Generated by [Claude Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_ --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent d8fac9a commit e19ae67

1 file changed

Lines changed: 1 addition & 1 deletion

File tree

‎content/docs/deployment/environment-variables.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ OS_MCP_STDIO_ENABLED=true OS_MCP_STDIO_API_KEY=osk_... os start # start the lo
257257
|:---|:---|:---|:---|
258258
| `OS_MCP_SERVER_ENABLED` | boolean | `true` | The MCP **HTTP** surface (`/api/v1/mcp`) is a core capability and defaults **on**. Set `false` to disable it (endpoint 404s, the Connect-an-Agent page disappears). |
259259
| `OS_MCP_STDIO_ENABLED` | boolean | `false` | Auto-start the long-lived **stdio** transport at boot. Opt-in and **stricter** than the HTTP surface. **Requires `OS_MCP_STDIO_API_KEY`** — stdio runs as that key's identity with RLS/FLS/tenant applied; if the key is missing or invalid, boot **fails closed** (stdio refuses to start). See ADR-0101. |
260-
| `OS_MCP_STDIO_API_KEY` | string | — | The `osk_...` API key the **stdio** transport runs as. Resolved through the same verify chain as the HTTP/REST surfaces, so reads are scoped to that identity's permissions. Mint one from **Setup → Connect an Agent** (or `POST /api/v1/keys`). For full authority, mint a key on a platform-admin or dedicated **service** identity — there is deliberately no `system`/unscoped bypass. |
260+
| `OS_MCP_STDIO_API_KEY` | string | — | The `osk_...` API key the **stdio** transport runs as. Resolved through the same verify chain as the HTTP/REST surfaces, so reads are scoped to that identity's permissions. Mint one from the **Connect an Agent** page — **Account → Developer** for any signed-in user, **Setup → Connect an Agent** for platform admins — or `POST /api/v1/keys`. For full authority, mint a key on a platform-admin or dedicated **service** identity — there is deliberately no `system`/unscoped bypass. |
261261
| `OS_MCP_SERVER_NAME` | string | `objectstack` | Server name advertised to MCP clients. |
262262
| `OS_MCP_SERVER_TRANSPORT` | enum | `stdio` | `stdio` \| `http`. Use `http` (Streamable HTTP) for a remote client; `stdio` for a local one. |
263263

0 commit comments

Comments
 (0)