fix(docs): name the project API key correctly in managed quickstarts (refs OSS-1029)

Three defects, all in the credential this branch renames.

Twelve integration quickstarts read `CPK_INTELLIGENCE_API_KEY=your_license_key`,
eleven of them under "The runtime reads the license key from step 1". The project
API key and the self-hosted license token are different credentials with
different lifetimes, and ENT-1151 exists to take the license token out of managed
setup -- so a reader who goes looking for a license key to paste finds a dead end
on the very page meant to connect them. Now `cpk-...`, and "reads the project API
key from step 1".

The placeholder prefix was wrong in the other direction on five pages, and newly
pinned that way by a test: `cpk_...`, with `cpk-...` asserted absent. A
provisioned key is `cpk-<projectId>_<short>_<long>` -- see the `cpk-` keyPrefix
in Intelligence's `apps/app-api/src/api-keys.ts` and the `parseApiKeyToken`
fixtures. No key the platform issues starts with `cpk_`, so the placeholder
taught a reader to distrust their own key. Both assertions are flipped.

The new copy guard scans every MDX page rather than listing the twelve, so a page
added next month is covered the day it lands. It reports the offending file and
value, which is how the twelve above were enumerated.

Finally, the retired-name boundary check is extracted to an exported
`retiredNameReference` and unit-tested. It is the load-bearing half of that rule
and it fails in one direction only: the canonical name ends with the retired one,
so a plain substring match reports all ~250 correct sites and the guard gets
switched off. The repo-wide scan cannot cover this -- it can say "clean", not
that the boundary is what made it clean, and it goes green either way once the
last old name is gone.

Verified: guard script exit 0; guard tests 20 passed; managed-starter-docs 10
passed (was 9); oxfmt and oxlint clean on the three changed TypeScript files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Taylor
2026-08-28 13:08:55 -05:00
committed by Maximiliano Korp
parent fe3a2f05a4
commit d768d0612c
20 changed files with 155 additions and 37 deletions
@@ -3,8 +3,65 @@ import {
findViolations,
managedUrlEnvFileAssignment,
managedUrlFallback,
retiredNameReference,
} from "../validate-intelligence-env-names.js";
/**
* The retired-name rule greps a literal and then decides whether the hit is the
* retired name itself. That second step is load-bearing and easy to get wrong in
* one direction: the canonical `CPK_INTELLIGENCE_API_KEY` *ends with* the
* retired name, so a plain substring match reports every correct site in the
* repository — 250-odd of them — and a guard that fires on everything is turned
* off within a day.
*
* Unit-tested rather than left to the repo-wide scan, because the scan can only
* say "clean". It cannot say the boundary is what made it clean, and it goes
* green either way the moment the last old name is gone.
*/
describe("retiredNameReference", () => {
const RETIRED = ["INTELLIGENCE", "API", "KEY"].join("_");
it("flags the retired name in a code read", () => {
expect(
retiredNameReference(
RETIRED,
" apiKey: process.env." + RETIRED + ' ?? "",',
),
).toBe(true);
});
it("flags it at the start of a line, as an env example assigns it", () => {
expect(retiredNameReference(RETIRED, RETIRED + "=cpk-...")).toBe(true);
});
it("flags it in backticked prose, where a reader copies it from", () => {
expect(
retiredNameReference(RETIRED, "Set `" + RETIRED + "` in `.env`."),
).toBe(true);
});
it("allows the canonical name, which merely ends with the retired one", () => {
expect(
retiredNameReference(
RETIRED,
' apiKey: process.env.CPK_INTELLIGENCE_API_KEY ?? "",',
),
).toBe(false);
});
it("allows the COPILOTKIT_ form, which its own entry reports", () => {
expect(
retiredNameReference(RETIRED, " COPILOTKIT_" + RETIRED + "=cpk-..."),
).toBe(false);
});
it("allows a longer variable that merely starts with the retired name", () => {
expect(retiredNameReference(RETIRED, RETIRED + "_LEGACY=cpk-...")).toBe(
false,
);
});
});
/**
* `CopilotKitIntelligence` resolves `apiUrl`/`wsUrl` to the managed hosts when
* they are omitted, so supplying any fallback for the two env vars that feed
+20 -4
View File
@@ -209,6 +209,25 @@ export function managedUrlEnvFileAssignment(text: string): string | null {
return null;
}
/**
* Whether a line names `name` itself rather than a longer variable that merely
* contains it.
*
* The grep that finds candidates matches a fixed string, so it also matches
* every variable the retired name is a substring of — and the canonical
* `CPK_INTELLIGENCE_API_KEY` ends with the retired project-key name, while
* `COPILOTKIT_INTELLIGENCE_API_KEY` contains it and has its own {@link RETIRED}
* entry. Without this boundary the rule reports every correct site in the
* repository, which is the one failure mode that gets a guard switched off.
*
* @param name - The retired variable name being looked for.
* @param text - One line of source, prose, or an env file.
* @returns `true` when the line carries that exact name.
*/
export function retiredNameReference(name: string, text: string): boolean {
return new RegExp(String.raw`(?<![A-Z0-9_])${name}(?![A-Z0-9_])`).test(text);
}
interface Violation {
file: string;
line: number;
@@ -252,12 +271,9 @@ export function findViolations(): Violation[] {
const violations: Violation[] = [];
for (const name of RETIRED) {
const exactName = new RegExp(
String.raw`(?<![A-Z0-9_])${name}(?![A-Z0-9_])`,
);
for (const hit of grepRepo(name)) {
if (hit.file === "scripts/validate-intelligence-env-names.ts") continue;
if (!exactName.test(hit.text)) continue;
if (!retiredNameReference(name, hit.text)) continue;
violations.push({
file: hit.file,
line: hit.line,
@@ -347,11 +347,11 @@ import OpenInspectorStep from "@/snippets/shared/inspector/open-inspector-step.m
export const POST = handler;
```
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -264,11 +264,11 @@ Before you begin, you'll need the following:
export const POST = handler;
```
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -107,7 +107,7 @@ Copy the generated values into your shell before importing:
```bash title="Terminal"
export INTELLIGENCE_API_URL="https://..."
export CPK_INTELLIGENCE_API_KEY="cpk_..."
export CPK_INTELLIGENCE_API_KEY="cpk-..."
```
`COPILOTKIT_API_KEY` is also accepted for the key. You can pass the destination directly with `--api-url` and `--api-key` instead.
@@ -347,11 +347,11 @@ import OpenInspectorStep from "@/snippets/shared/inspector/open-inspector-step.m
export const POST = handler;
```
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -225,11 +225,11 @@ Before you begin, you'll need the following:
export const POST = handler;
```
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -323,11 +323,11 @@ Before you begin, you'll need the following:
export const POST = handler;
```
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -129,11 +129,11 @@ Before you begin, you'll need the following:
export const POST = handler;
```
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -341,11 +341,11 @@ Before you begin, you'll need the following:
</Tab>
</Tabs>
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -430,11 +430,11 @@ Before you begin, you'll need the following:
</Tab>
</Tabs>
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -75,7 +75,7 @@ Copy the generated values into your shell before importing:
```bash title="Terminal"
export INTELLIGENCE_API_URL="https://..."
export CPK_INTELLIGENCE_API_KEY="cpk_..."
export CPK_INTELLIGENCE_API_KEY="cpk-..."
```
`COPILOTKIT_API_KEY` is also accepted for the key. You can pass the destination directly with `--api-url` and `--api-key` instead.
@@ -272,11 +272,11 @@ Before you begin, you'll need the following:
export const POST = handler;
```
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -291,7 +291,7 @@ Before you begin, you'll need the following:
It reads them here, not in the Mastra project:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
MASTRA_BASE_URL=http://127.0.0.1:4111
```
@@ -347,11 +347,11 @@ Before you begin, you'll need the following:
export const POST = handler;
```
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -231,11 +231,11 @@ Before you begin, you'll need the following:
export const POST = handler;
```
The runtime reads the license key from step 1. Add it to the app that serves
The runtime reads the project API key from step 1. Add it to the app that serves
this route:
```plaintext title=".env.local"
CPK_INTELLIGENCE_API_KEY=your_license_key
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="info" title="Running without the Intelligence Platform?">
@@ -29,7 +29,7 @@ You can also copy a key from the
[cloud-hosted dashboard](/premium/managed-intelligence-platform).
```bash title=".env"
CPK_INTELLIGENCE_API_KEY=cpk_...
CPK_INTELLIGENCE_API_KEY=cpk-...
```
<Callout type="warn">
@@ -87,7 +87,7 @@ Use the CLI when you want to start a new app, import historical ADK or LangGraph
```bash title=".env"
INTELLIGENCE_API_URL=https://...
INTELLIGENCE_GATEWAY_WS_URL=wss://...
CPK_INTELLIGENCE_API_KEY=cpk_...
CPK_INTELLIGENCE_API_KEY=cpk-...
CPK_TELEMETRY_ID=...
SL_ENABLED=true
```
@@ -147,7 +147,7 @@ Before the real import, export the destination values from that `.env`:
```bash title="Terminal"
export INTELLIGENCE_API_URL="https://..."
export CPK_INTELLIGENCE_API_KEY="cpk_..."
export CPK_INTELLIGENCE_API_KEY="cpk-..."
```
The importer reads `--api-url` and `--api-key` or the current process environment. It does not load `.env` or `.copilotkit/project.json` automatically. `COPILOTKIT_API_KEY` is also accepted for the key.
@@ -88,7 +88,7 @@ It does not import agent state snapshots, framework transport noise, LangSmith t
```bash title="Terminal"
export INTELLIGENCE_API_URL="https://..."
export CPK_INTELLIGENCE_API_KEY="cpk_..."
export CPK_INTELLIGENCE_API_KEY="cpk-..."
```
`COPILOTKIT_API_KEY` is also accepted for the key. You can pass the same values directly with `--api-url` and `--api-key` instead.
@@ -139,6 +139,13 @@ test("documents the managed CLI credential without an offline license token", ()
}
});
/**
* A provisioned project key is `cpk-<projectId>_<short>_<long>` see
* `keyPrefix: \`cpk-${projectId}\`` in Intelligence's `apps/app-api/src/api-keys.ts`
* and the `parseApiKeyToken` fixtures. `cpk_...` matches no key the platform
* issues, so a reader comparing the placeholder against their own key sees a
* mismatch where there is none.
*/
test("uses the managed API key prefix in thread import examples", () => {
const sources = readSources([
"docs/integrations/adk/threads-import.mdx",
@@ -148,8 +155,8 @@ test("uses the managed API key prefix in thread import examples", () => {
]);
for (const source of sources) {
expect(source).toContain('CPK_INTELLIGENCE_API_KEY="cpk_..."');
expect(source).not.toContain('CPK_INTELLIGENCE_API_KEY="cpk-..."');
expect(source).toContain('CPK_INTELLIGENCE_API_KEY="cpk-..."');
expect(source).not.toContain('CPK_INTELLIGENCE_API_KEY="cpk_..."');
}
});
@@ -163,6 +170,44 @@ test("reads the CLI-managed key name in Runtime wiring guides", () => {
}
const connectRuntime = sources[1];
expect(connectRuntime).toContain("CPK_INTELLIGENCE_API_KEY=cpk_...");
expect(connectRuntime).not.toContain("CPK_INTELLIGENCE_API_KEY=cpk-...");
expect(connectRuntime).toContain("CPK_INTELLIGENCE_API_KEY=cpk-...");
expect(connectRuntime).not.toContain("CPK_INTELLIGENCE_API_KEY=cpk_...");
});
/**
* ENT-1151 removed the license token from managed setup, but twelve integration
* quickstarts still handed the reader `CPK_INTELLIGENCE_API_KEY=your_license_key`
* under "The runtime reads the license key from step 1" a license key named as
* the value of the project API key, on the credential the PRD exists to isolate
* (OSS-1029). The two are different credentials with different lifetimes, and a
* reader who goes looking for a license key to paste finds a dead end.
*
* Scanned rather than listed: a page added next month is covered the day it
* lands, not the day someone remembers this test.
*/
test("never names a license key as the value of the project API key", () => {
const offenders: string[] = [];
for (const file of mdxFilesIn(CONTENT_DIR)) {
const text = fs.readFileSync(file, "utf8");
const relative = path.relative(CONTENT_DIR, file);
for (const [, value] of text.matchAll(/CPK_INTELLIGENCE_API_KEY=(\S+)/g)) {
if (/license/i.test(value!)) offenders.push(`${relative} (${value})`);
}
if (/reads the license key/i.test(text)) {
offenders.push(`${relative} (prose: "reads the license key")`);
}
}
expect(offenders).toEqual([]);
});
/** Every MDX page under `dir`, recursively. */
function mdxFilesIn(dir: string): string[] {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) return mdxFilesIn(full);
return entry.name.endsWith(".mdx") ? [full] : [];
});
}