mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
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:
committed by
Maximiliano Korp
parent
fe3a2f05a4
commit
d768d0612c
@@ -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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
+2
-2
@@ -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] : [];
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user