@runpod/mcp-server 4.9.0 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +3 -1
- package/dist/alp.cjs +21 -2
- package/dist/alp.cjs.map +1 -1
- package/dist/alp.d.cts +2 -1
- package/dist/alp.d.ts +2 -1
- package/dist/alp.mjs +3 -1
- package/dist/{chunk-6IETGSON.mjs → chunk-FQJFKWUV.mjs} +32 -1
- package/dist/chunk-FQJFKWUV.mjs.map +1 -0
- package/dist/{chunk-VKLWTK4O.mjs → chunk-JTK6RETN.mjs} +1707 -151
- package/dist/chunk-JTK6RETN.mjs.map +1 -0
- package/dist/{chunk-H3NZYQIB.mjs → chunk-WWXM5EES.mjs} +20 -3
- package/dist/chunk-WWXM5EES.mjs.map +1 -0
- package/dist/hosted.cjs +1757 -142
- package/dist/hosted.cjs.map +1 -1
- package/dist/hosted.mjs +16 -5
- package/dist/hosted.mjs.map +1 -1
- package/dist/http.cjs +1736 -149
- package/dist/http.cjs.map +1 -1
- package/dist/http.mjs +2 -2
- package/dist/specgen/profile.cjs +31 -0
- package/dist/specgen/profile.cjs.map +1 -1
- package/dist/specgen/profile.mjs +1 -1
- package/dist/stdio.cjs +1736 -149
- package/dist/stdio.cjs.map +1 -1
- package/dist/stdio.mjs +1736 -149
- package/dist/stdio.mjs.map +1 -1
- package/package.json +4 -7
- package/dist/chunk-6IETGSON.mjs.map +0 -1
- package/dist/chunk-H3NZYQIB.mjs.map +0 -1
- package/dist/chunk-VKLWTK4O.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @runpod/mcp-server
|
|
2
2
|
|
|
3
|
+
## 4.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ba8ae08: Redact email addresses in ALP submissions, non-ASCII ones included, as `[redacted:email]`, counted in `redactions`. Runpod Pod SSH logins (`@ssh.runpod.io`) and the `git@` user of an SSH remote are kept. `SCRUB_VERSION` is now 6 and is exported from `@runpod/mcp-server/alp`. The ingest endpoint keeps `tool` only when it is shaped like a tool name (`^[A-Za-z0-9_-]{1,64}$`) and drops anything else, as it does an off-enum `severity`, and answers a write the sink refused at its cap (`capped: true`) with `recorded: false` and the do-not-retry note.
|
|
8
|
+
- 80d0d1f: Pin `@runpod/plugin-knowledge` to an exact version, checked before each release for npm provenance built in `runpod/runpod-plugins-official`, and leave the concept rules listed in `KNOWLEDGE_HIDDEN_RULES` (comma-separated rule ids) out of `lookup-concept` and `search-concepts`, citations included, so the hosted server can withdraw a rule before the knowledge release that deletes it ships. An id the bundle does not hold is logged at startup.
|
|
9
|
+
- ba8ae08: Resync the vendored v2 OpenAPI spec with production as of 2026-11-05 and regenerate the SDK schema and the tools. Adds 31 tools: organization groups, roles, invites and member updates and removal, cost centers, and endpoint cold-start, request and worker metrics. A pod's mounts now take up to 4 network volumes and 1 global volume. The `filters` query parameter on the pod, cluster, endpoint and network volume list routes is left out until the client can send JSON query parameters.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 017cdc3: Update `@runpod/plugin-knowledge` from 1.8.0 to 1.9.0, so `list-guides` and `read-guide` serve the latest plugin guides.
|
|
14
|
+
|
|
15
|
+
Update run: https://github.com/runpod/runpod-mcp/actions/runs/41610964582
|
|
16
|
+
|
|
17
|
+
## 4.9.1
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- ad7445d: Drop the `node-fetch` dependency. Only the hosted entry point used it, and that now lives outside this repository.
|
|
22
|
+
|
|
3
23
|
## 4.9.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -234,4 +234,6 @@ The hosted service's own surface (the MCP request handler, the ALP submit and
|
|
|
234
234
|
journal handlers and the knowledge search route) is `@runpod/mcp-server/hosted`.
|
|
235
235
|
The ALP contract the Convex sink shares (route names, the once-per-account
|
|
236
236
|
rule and the scrubber) is `@runpod/mcp-server/alp`, kept free of the HTTP
|
|
237
|
-
handler so a Convex function can bundle it.
|
|
237
|
+
handler so a Convex function can bundle it. The hosted deployment itself (the
|
|
238
|
+
Vercel entry point, the OAuth routes and the Convex functions) is maintained
|
|
239
|
+
outside this repository.
|
package/dist/alp.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(alp_exports, {
|
|
|
25
25
|
ALP_ROUTES: () => ALP_ROUTES,
|
|
26
26
|
ALREADY_SUBMITTED_NOTE: () => ALREADY_SUBMITTED_NOTE,
|
|
27
27
|
ONCE_PER_ACCOUNT_ROUTES: () => ONCE_PER_ACCOUNT_ROUTES,
|
|
28
|
+
SCRUB_VERSION: () => SCRUB_VERSION,
|
|
28
29
|
isOncePerAccountRoute: () => isOncePerAccountRoute,
|
|
29
30
|
isSinkUrl: () => isSinkUrl,
|
|
30
31
|
scrubSubmission: () => scrubSubmission,
|
|
@@ -75,7 +76,7 @@ function sinkRouteUrl(submitUrl, path) {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
// src/alp/scrub.ts
|
|
78
|
-
var SCRUB_VERSION =
|
|
79
|
+
var SCRUB_VERSION = 6;
|
|
79
80
|
var MARKER = /^["']?\[redacted:[a-z_]+\]["']?$/;
|
|
80
81
|
var CREDENTIAL_PATTERNS = [
|
|
81
82
|
// Runpod API keys.
|
|
@@ -263,6 +264,22 @@ function redactAssignments(text) {
|
|
|
263
264
|
out += text.slice(copiedTo);
|
|
264
265
|
return { text: out, redactions };
|
|
265
266
|
}
|
|
267
|
+
var EMAIL = /(?<![\p{L}\p{M}\p{N}_.%+-])([\p{L}\p{M}\p{N}_.%+-]{1,64})@((?:[\p{L}\p{M}\p{N}-]{1,63}\.){1,8}[\p{L}\p{M}]{2,63})(?![\p{L}\p{M}\p{N}])/gu;
|
|
268
|
+
var RUNPOD_SSH_HOST = "ssh.runpod.io";
|
|
269
|
+
function redactEmails(text) {
|
|
270
|
+
let redactions = 0;
|
|
271
|
+
const out = text.replace(
|
|
272
|
+
EMAIL,
|
|
273
|
+
(address, local, domain, offset) => {
|
|
274
|
+
const next = text[offset + address.length];
|
|
275
|
+
if (domain.toLowerCase() === RUNPOD_SSH_HOST || local === "git" && (next === ":" || next === "/"))
|
|
276
|
+
return address;
|
|
277
|
+
redactions++;
|
|
278
|
+
return "[redacted:email]";
|
|
279
|
+
}
|
|
280
|
+
);
|
|
281
|
+
return { text: out, redactions };
|
|
282
|
+
}
|
|
266
283
|
function scrub(text) {
|
|
267
284
|
let out = text;
|
|
268
285
|
let redactions = 0;
|
|
@@ -270,7 +287,8 @@ function scrub(text) {
|
|
|
270
287
|
redactAnchoredCredentials,
|
|
271
288
|
redactHeaderValues,
|
|
272
289
|
redactQuerySecrets,
|
|
273
|
-
redactAssignments
|
|
290
|
+
redactAssignments,
|
|
291
|
+
redactEmails
|
|
274
292
|
]) {
|
|
275
293
|
const result = stage(out);
|
|
276
294
|
out = result.text;
|
|
@@ -311,6 +329,7 @@ function scrubSubmission(row) {
|
|
|
311
329
|
ALP_ROUTES,
|
|
312
330
|
ALREADY_SUBMITTED_NOTE,
|
|
313
331
|
ONCE_PER_ACCOUNT_ROUTES,
|
|
332
|
+
SCRUB_VERSION,
|
|
314
333
|
isOncePerAccountRoute,
|
|
315
334
|
isSinkUrl,
|
|
316
335
|
scrubSubmission,
|
package/dist/alp.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/alp.ts","../src/alp/sink.ts","../src/alp/scrub.ts"],"sourcesContent":["// The ALP contract shared with the Convex sink: route names, the\n// once-per-account rule and the scrubber. Pure, so a Convex function can\n// bundle it without pulling the HTTP handler in.\nexport {\n ALP_ROUTES,\n ALP_REFERRAL_SOURCES,\n ALP_REFERRAL_USE_CASES,\n ALREADY_SUBMITTED_NOTE,\n ONCE_PER_ACCOUNT_ROUTES,\n isOncePerAccountRoute,\n isSinkUrl,\n sinkRouteUrl,\n} from './alp/sink.js';\nexport type {\n AlpReferralSource,\n AlpReferralUseCase,\n AlpRoute,\n OncePerAccountRoute,\n} from './alp/sink.js';\nexport { scrubSubmission } from './alp/scrub.js';\n","// The ALP constants and sink address that more than one side needs: the\n// hosted server, the Convex functions, and the tool definitions. This module\n// imports nothing, so anything can import it without a cycle; the ingest\n// handler cannot play that role because it imports src/http.ts.\n\n// Every ALP route. The Convex validators are built from this list.\nexport const ALP_ROUTES = [\n 'feedback',\n 'journal',\n 'question',\n 'referral',\n] as const;\nexport type AlpRoute = (typeof ALP_ROUTES)[number];\n\n// Routes that take ONE answer per account, ever (design doc, \"RT4\"). The\n// Convex mutation dedupes on this list and /alp/answered validates against it.\n// The next one (the agent NPS score, CON-1729) is a literal added here, in the\n// `oncePerAccountRoute` Convex validator, and in the route lists, plus the\n// tool itself and its own hasAnswered call.\nexport const ONCE_PER_ACCOUNT_ROUTES = ['referral'] as const;\nexport type OncePerAccountRoute = (typeof ONCE_PER_ACCOUNT_ROUTES)[number];\n\nexport function isOncePerAccountRoute(\n route: unknown\n): route is OncePerAccountRoute {\n return (ONCE_PER_ACCOUNT_ROUTES as readonly unknown[]).includes(route);\n}\n\n// What the agent reads when a once-per-account route already holds its\n// answer. The ingest sends it and the tool says it; one sentence, one place.\nexport const ALREADY_SUBMITTED_NOTE =\n 'This account has already made this one-time submission; nothing new was stored. Do not call this again on this account — continue your task.';\n\n// Referral route: why Runpod came up. Constrained like severity so it counts.\n// Lives here, not in ingest.ts, so the tool file can import it without\n// pulling the ingest handler (and its import of src/http.ts) into a cycle.\nexport const ALP_REFERRAL_SOURCES = [\n 'searched',\n 'user_named',\n 'recommended',\n 'other',\n] as const;\nexport type AlpReferralSource = (typeof ALP_REFERRAL_SOURCES)[number];\n\n// Referral route: the kind of work that brought the user to Runpod. A\n// category instead of the user's words, so nothing they typed is stored.\nexport const ALP_REFERRAL_USE_CASES = [\n 'inference',\n 'fine_tuning',\n 'training',\n 'image_video_generation',\n 'dev_environment',\n 'batch_processing',\n 'other',\n] as const;\nexport type AlpReferralUseCase = (typeof ALP_REFERRAL_USE_CASES)[number];\n\n/** The sink is a Convex HTTP action, always. Anything else is a typo. */\nexport function isSinkUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return (\n url.protocol === 'https:' &&\n url.hostname.endsWith('.convex.site') &&\n url.pathname === '/alp/submit'\n );\n } catch {\n return false;\n }\n}\n\n/** A sibling route on the same deployment as the configured submit URL.\n * Derived, never configured, so no second variable can point elsewhere. */\nexport function sinkRouteUrl(submitUrl: string, path: string): string | null {\n if (!isSinkUrl(submitUrl)) return null;\n const url = new URL(submitUrl);\n url.pathname = path;\n return url.toString();\n}\n","// Best-effort secret redaction before forwarding to storage and again at the\n// Convex write boundary. This catches recognizable tokens and sensitive config\n// assignments; arbitrary unlabeled secrets cannot reliably be identified.\n//\n// Four stages, run in this order, each a pure function with its own table\n// of rules and its own tests:\n//\n// A. anchored credentials — shapes recognizable on their own (rpa_, JWT...)\n// B. sensitive headers — the WHOLE value of Authorization/Cookie/...\n// C. URL query secrets — api_key/token/key/secret parameters\n// D. config assignments — `key = value` / `key: value` by key name\n//\n// Specific before broad is the invariant. Several stage-A rules identify a\n// credential by the word in front of it (`Bearer <opaque>`), and stage D stops\n// an unquoted value at the first space or semicolon — so run in the other\n// order, C consumes \"Bearer\", destroys the anchor, and leaves the secret in\n// plaintext looking redacted. Stage B exists because header values are\n// DEFINED by containing spaces and semicolons (`Basic <cred>`, `a=1; b=2`),\n// which is exactly the shape the token-level matcher cannot hold whole.\n\nexport interface ScrubResult {\n text: string;\n /** How many redactions fired — a redaction rate is itself a metric. */\n redactions: number;\n}\n\n// Bump when any stage's rules change so stored rows record which pass they got.\nexport const SCRUB_VERSION = 5;\n\nconst MARKER = /^[\"']?\\[redacted:[a-z_]+\\][\"']?$/;\n\n// ---- Stage A: anchored credential shapes --------------------------------\n\nconst CREDENTIAL_PATTERNS: Array<{ name: string; re: RegExp }> = [\n // Runpod API keys.\n { name: 'runpod_key', re: /\\brpa_[A-Za-z0-9]{16,}\\b/g },\n // Bearer credentials pasted with their header.\n { name: 'bearer', re: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{16,}/g },\n // Common vendor key prefixes.\n {\n name: 'vendor_key',\n re: /\\b(?:sk|pk|ghp|gho|phc|phx|xoxb|xoxp)[-_][A-Za-z0-9_-]{16,}\\b/g,\n },\n // Hugging Face access tokens.\n { name: 'hugging_face', re: /\\bhf_[A-Za-z0-9]{30,}\\b/g },\n // AWS access key ids.\n { name: 'aws_key', re: /\\bAKIA[0-9A-Z]{16}\\b/g },\n // Three-segment JWTs.\n {\n name: 'jwt',\n re: /\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b/g,\n },\n];\n\nexport function redactAnchoredCredentials(text: string): ScrubResult {\n let redactions = 0;\n let out = text;\n for (const { name, re } of CREDENTIAL_PATTERNS) {\n out = out.replace(re, () => {\n redactions++;\n return `[redacted:${name}]`;\n });\n }\n return { text: out, redactions };\n}\n\n// ---- Stage B: sensitive headers, whole value ----------------------------\n\n// Headers are matched wherever they appear, not only at line start: pastes\n// arrive inline (\"… got 401 with Authorization: Basic xyz | cookie: a=1; b=2\")\n// and the first version, anchored ^…$, missed exactly that and fell through\n// to the token matcher — which redacted the word \"Basic\" and kept the\n// credential. So the VALUE is bounded by its own shape rather than by the end\n// of the line: an auth header is `<scheme> <token>` or a bare token; a cookie\n// header is a `k=v; k=v` chain. Header names are case-insensitive.\nconst AUTH_HEADER =\n /\\b((?:proxy-)?authorization|x-api-key|x-auth-token|x-runpod-token)(\\s*:\\s*)((?:basic|bearer|token|digest|negotiate|ntlm|apikey)\\s+[^\\s|,]+|[^\\s|,]+)/gi;\nconst COOKIE_HEADER =\n /\\b(cookie|set-cookie)(\\s*:\\s*)([^\\s;=,|]+=[^\\s;,|]*(?:\\s*;\\s*[^\\s;=,|]+(?:=[^\\s;,|]*)?)*)/gi;\n\nexport function redactHeaderValues(text: string): ScrubResult {\n let redactions = 0;\n const replace = (match: string, name: string, sep: string, value: string) => {\n // Stage A may already have replaced the value; keep its marker.\n if (MARKER.test(value)) return match;\n redactions++;\n return `${name}${sep}[redacted:header]`;\n };\n const out = text\n .replace(AUTH_HEADER, replace)\n .replace(COOKIE_HEADER, replace);\n return { text: out, redactions };\n}\n\n// ---- Stage C: URL query credentials -----------------------------------\n\n// URL query credentials stop at the next query field, fragment or text boundary.\n// Preserve parameter names and non-secret query fields for diagnostics.\nexport function redactQuerySecrets(text: string): ScrubResult {\n let redactions = 0;\n const out = text.replace(\n /([?&](?:api[_-]?key|access[_-]?token|token|key|secret)=)([^&#\\s\"'<>`]+)/gi,\n (match, prefix: string, value: string) => {\n if (MARKER.test(value)) return match;\n redactions++;\n return `${prefix}[redacted:query]`;\n }\n );\n return { text: out, redactions };\n}\n\n// ---- Stage D: config assignments, by key name ---------------------------\n\n// Keys are compared segment by segment, never by substring: a substring test\n// redacts `tokenizer: llama-3` because it contains \"token\". Split on\n// separators AND camelCase, so `databasePassword` and `clientSecret` become\n// segment lists rather than one opaque word.\nconst SENSITIVE_SEGMENTS = new Set([\n 'password',\n 'passwd',\n 'secret',\n 'token',\n 'authorization',\n 'cookie',\n 'credential',\n 'credentials',\n]);\n\n// `key` alone is too common to be sensitive (primary_key, cache_key). It\n// counts when the segment right before it qualifies it — anywhere in the\n// name, so SERVICE_API_KEY and STRIPE_ACCESS_KEY match, not only api_key.\nconst KEY_QUALIFIERS = new Set([\n 'api',\n 'access',\n 'private',\n 'signing',\n 'secret',\n 'client',\n]);\n\nexport function splitKey(key: string): string[] {\n return key\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .toLowerCase()\n .split(/[_\\-.]+/)\n .filter(Boolean);\n}\n\nexport function isSensitiveKey(key: string): boolean {\n const segments = splitKey(key);\n if (segments.some((segment) => SENSITIVE_SEGMENTS.has(segment))) return true;\n return segments.some(\n (segment, i) =>\n (segment === 'key' && i > 0 && KEY_QUALIFIERS.has(segments[i - 1])) ||\n // No separator at all: `apikey`, `accesskey`.\n (segment.endsWith('key') && KEY_QUALIFIERS.has(segment.slice(0, -3)))\n );\n}\n\n// Covers JSON, YAML and shell env assignments, including quoted values with\n// spaces or escaped quotes. The field name is kept for diagnostic context.\n//\n// This was one regex with a lookbehind, two quoted-string alternatives, a\n// negated value class and a trailing lookahead. It was unreadable, and every\n// fix to one case broke another: excluding `&` from the value class so a URL's\n// second query parameter survived also truncated `password: p&ss#word123` at\n// the `&` and leaked the rest. Both behaviors were correct for their own\n// input and the character class had no way to tell the two apart, because the\n// distinction is context, not characters.\n//\n// So the scan is written out: a cursor, one small function per token, and the\n// value's terminator set chosen from where the key sits. Each rule below is a\n// named predicate that can be read and tested on its own.\n\nconst QUOTES = new Set(['\"', \"'\"]);\n\n// A value always ends at one of these: `{`/`}` and `,`/`;` close a JSON or\n// inline-YAML member, and a quote cannot appear inside an unquoted value.\nconst VALUE_TERMINATORS = new Set([',', ';', '{', '}', '\"', \"'\"]);\n\n// `&` and `#` end a value ONLY inside a URL query, where they begin the next\n// parameter and the fragment. In a config file they are ordinary password\n// characters — `p&ss#word` is one value, not three.\nconst QUERY_TERMINATORS = new Set(['&', '#']);\n\nfunction isAsciiLetter(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');\n}\n\nfunction isAsciiDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9';\n}\n\n/** A key begins with a letter or underscore, never a digit — so `10:30` in a\n * timestamp is not read as an assignment. */\nfunction isKeyStart(ch: string): boolean {\n return isAsciiLetter(ch) || ch === '_';\n}\n\n/** Inside a key name: adds digits and the three separators that `splitKey`\n * later splits on, so `aws.secretKey` and `DB_PASSWORD` arrive whole. */\nfunction isKeyBody(ch: string): boolean {\n return (\n isAsciiLetter(ch) ||\n isAsciiDigit(ch) ||\n ch === '_' ||\n ch === '.' ||\n ch === '-'\n );\n}\n\n/** Horizontal space only. A newline after the separator means the value is on\n * another line, so `db:\\n password: x` must not read `password` as db's\n * value — the bug that let nested YAML through entirely. */\nfunction isSpaceOrTab(ch: string): boolean {\n return ch === ' ' || ch === '\\t';\n}\n\nfunction isWhitespace(ch: string): boolean {\n return (\n ch === ' ' ||\n ch === '\\t' ||\n ch === '\\n' ||\n ch === '\\r' ||\n ch === '\\f' ||\n ch === '\\v'\n );\n}\n\ninterface Assignment {\n /** Bare key name: quotes and the separator stripped. */\n key: string;\n /** Raw value text as it appears, including its quotes when quoted. */\n raw: string;\n /** Quote character to reinstate around a replacement, or `''`. */\n quote: string;\n /** Half-open range of the value within the source text. */\n start: number;\n end: number;\n}\n\n/** Reads a quoted value, honoring backslash escapes. An unterminated quote is\n * a truncated paste, so it reads to the end of the line rather than giving\n * up: for a redactor, over-reading a secret is the safe direction. */\nfunction readQuotedValue(text: string, start: number): number {\n const quote = text[start];\n let i = start + 1;\n while (i < text.length) {\n const ch = text[i];\n if (ch === '\\\\') {\n i += 2;\n continue;\n }\n if (ch === quote) return i + 1;\n if (ch === '\\n' || ch === '\\r') return i;\n i++;\n }\n return text.length;\n}\n\n/** Reads an unquoted value up to the first terminator for its context. */\nfunction readUnquotedValue(\n text: string,\n start: number,\n inQueryString: boolean\n): number {\n let i = start;\n while (i < text.length) {\n const ch = text[i];\n if (isWhitespace(ch) || VALUE_TERMINATORS.has(ch)) break;\n if (inQueryString && QUERY_TERMINATORS.has(ch)) break;\n i++;\n }\n return i;\n}\n\n/** Parses `key: value` or `key = value` beginning at `at`, or null if there is\n * no assignment there. */\nfunction parseAssignment(text: string, at: number): Assignment | null {\n const before = at > 0 ? text[at - 1] : '';\n // Mid-identifier: `my_password` must not also match as key `password`.\n if (isKeyBody(before)) return null;\n\n let i = at;\n const openingQuote = QUOTES.has(text[i] ?? '') ? text[i] : '';\n if (openingQuote) i++;\n if (!isKeyStart(text[i] ?? '')) return null;\n\n const keyStart = i;\n while (i < text.length && isKeyBody(text[i])) i++;\n const key = text.slice(keyStart, i);\n\n // A closing quote is tolerated whether or not one opened: pasted snippets\n // arrive truncated, and `password\": x` should still redact.\n if (QUOTES.has(text[i] ?? '')) i++;\n\n while (i < text.length && isSpaceOrTab(text[i])) i++;\n if (text[i] !== ':' && text[i] !== '=') return null;\n i++;\n while (i < text.length && isSpaceOrTab(text[i])) i++;\n\n // `?api_key=` or `&token=` — this key sits in a URL query string.\n const inQueryString = before === '?' || before === '&';\n\n const start = i;\n let end: number;\n let quote = '';\n if (QUOTES.has(text[start] ?? '')) {\n quote = text[start];\n end = readQuotedValue(text, start);\n } else {\n end = readUnquotedValue(text, start, inQueryString);\n }\n // A parent key such as `db:` has no value of its own.\n if (end === start) return null;\n\n return { key, raw: text.slice(start, end), quote, start, end };\n}\n\nexport function redactAssignments(text: string): ScrubResult {\n let redactions = 0;\n let out = '';\n let copiedTo = 0;\n let cursor = 0;\n\n while (cursor < text.length) {\n const found = parseAssignment(text, cursor);\n if (!found) {\n cursor++;\n continue;\n }\n\n // Already handled by an earlier stage — keep its marker, do not count it\n // twice, and resume past it.\n if (MARKER.test(found.raw)) {\n cursor = found.end;\n continue;\n }\n\n if (!isSensitiveKey(found.key)) {\n // Resume INSIDE the value, not after it. A non-sensitive assignment can\n // contain one: `https://h/?password=x` parses as key `https` whose value\n // is the whole URL, and skipping it leaked the password outright.\n cursor = found.start;\n continue;\n }\n\n out += text.slice(copiedTo, found.start);\n out += `${found.quote}[redacted:config]${found.quote}`;\n copiedTo = found.end;\n cursor = found.end;\n redactions++;\n }\n\n out += text.slice(copiedTo);\n return { text: out, redactions };\n}\n\n// ---- Composition ---------------------------------------------------------\n\nexport function scrub(text: string): ScrubResult {\n let out = text;\n let redactions = 0;\n for (const stage of [\n redactAnchoredCredentials,\n redactHeaderValues,\n redactQuerySecrets,\n redactAssignments,\n ]) {\n const result = stage(out);\n out = result.text;\n redactions += result.redactions;\n }\n return { text: out, redactions };\n}\n\n// Only user-authored text is scrubbed; resolved identity and server timestamps\n// remain authoritative. Reapplying this at the sink is safe and idempotent.\nexport const SCRUBBED_FIELDS = [\n 'content',\n 'intention',\n 'modelType',\n 'severity',\n 'tool',\n 'workaround',\n 'trigger',\n 'source',\n 'useCase',\n 'harness',\n 'harnessSource',\n 'transport',\n] as const;\n\nexport function scrubSubmission<\n T extends {\n content: string;\n intention?: string;\n modelType?: string;\n severity?: string;\n tool?: string;\n workaround?: string;\n trigger?: string;\n source?: string;\n useCase?: string;\n harness?: string;\n harnessSource?: string;\n transport?: string;\n redactions: number;\n scrubVersion: number;\n },\n>(row: T): T {\n const clean = { ...row };\n // EVERY agent-writable string field belongs in this list. A field added to\n // the submission shape but not here is stored unscrubbed, which is how a\n // pasted credential reaches the table — the fields most likely to carry one\n // are exactly the free-text ones a new route adds. Guarded by a test that\n // walks the submission type, so the omission fails CI rather than shipping.\n for (const field of SCRUBBED_FIELDS) {\n const value = clean[field];\n if (typeof value !== 'string') continue;\n const result = scrub(value);\n clean[field] = result.text;\n clean.redactions += result.redactions;\n }\n clean.scrubVersion = SCRUB_VERSION;\n return clean;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/alp.ts","../src/alp/sink.ts","../src/alp/scrub.ts"],"sourcesContent":["// The ALP contract shared with the Convex sink: route names, the\n// once-per-account rule and the scrubber with its version. Pure, so a Convex\n// function can bundle it without pulling the HTTP handler in.\nexport {\n ALP_ROUTES,\n ALP_REFERRAL_SOURCES,\n ALP_REFERRAL_USE_CASES,\n ALREADY_SUBMITTED_NOTE,\n ONCE_PER_ACCOUNT_ROUTES,\n isOncePerAccountRoute,\n isSinkUrl,\n sinkRouteUrl,\n} from './alp/sink.js';\nexport type {\n AlpReferralSource,\n AlpReferralUseCase,\n AlpRoute,\n OncePerAccountRoute,\n} from './alp/sink.js';\nexport { SCRUB_VERSION, scrubSubmission } from './alp/scrub.js';\n","// The ALP constants and sink address that more than one side needs: the\n// hosted server, the Convex functions, and the tool definitions. This module\n// imports nothing, so anything can import it without a cycle; the ingest\n// handler cannot play that role because it imports src/http.ts.\n\n// Every ALP route. The Convex validators are built from this list.\nexport const ALP_ROUTES = [\n 'feedback',\n 'journal',\n 'question',\n 'referral',\n] as const;\nexport type AlpRoute = (typeof ALP_ROUTES)[number];\n\n// Routes that take ONE answer per account, ever (design doc, \"RT4\"). The\n// Convex mutation dedupes on this list and /alp/answered validates against it.\n// The next one (the agent NPS score, CON-1729) is a literal added here, in the\n// `oncePerAccountRoute` Convex validator, and in the route lists, plus the\n// tool itself and its own hasAnswered call.\nexport const ONCE_PER_ACCOUNT_ROUTES = ['referral'] as const;\nexport type OncePerAccountRoute = (typeof ONCE_PER_ACCOUNT_ROUTES)[number];\n\nexport function isOncePerAccountRoute(\n route: unknown\n): route is OncePerAccountRoute {\n return (ONCE_PER_ACCOUNT_ROUTES as readonly unknown[]).includes(route);\n}\n\n// What the agent reads when a once-per-account route already holds its\n// answer. The ingest sends it and the tool says it; one sentence, one place.\nexport const ALREADY_SUBMITTED_NOTE =\n 'This account has already made this one-time submission; nothing new was stored. Do not call this again on this account — continue your task.';\n\n// Referral route: why Runpod came up. Constrained like severity so it counts.\n// Lives here, not in ingest.ts, so the tool file can import it without\n// pulling the ingest handler (and its import of src/http.ts) into a cycle.\nexport const ALP_REFERRAL_SOURCES = [\n 'searched',\n 'user_named',\n 'recommended',\n 'other',\n] as const;\nexport type AlpReferralSource = (typeof ALP_REFERRAL_SOURCES)[number];\n\n// Referral route: the kind of work that brought the user to Runpod. A\n// category instead of the user's words, so nothing they typed is stored.\nexport const ALP_REFERRAL_USE_CASES = [\n 'inference',\n 'fine_tuning',\n 'training',\n 'image_video_generation',\n 'dev_environment',\n 'batch_processing',\n 'other',\n] as const;\nexport type AlpReferralUseCase = (typeof ALP_REFERRAL_USE_CASES)[number];\n\n/** The sink is a Convex HTTP action, always. Anything else is a typo. */\nexport function isSinkUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return (\n url.protocol === 'https:' &&\n url.hostname.endsWith('.convex.site') &&\n url.pathname === '/alp/submit'\n );\n } catch {\n return false;\n }\n}\n\n/** A sibling route on the same deployment as the configured submit URL.\n * Derived, never configured, so no second variable can point elsewhere. */\nexport function sinkRouteUrl(submitUrl: string, path: string): string | null {\n if (!isSinkUrl(submitUrl)) return null;\n const url = new URL(submitUrl);\n url.pathname = path;\n return url.toString();\n}\n","// Best-effort secret redaction before forwarding to storage and again at the\n// Convex write boundary. This catches recognizable tokens, sensitive config\n// assignments and email addresses; arbitrary unlabeled secrets cannot reliably\n// be identified.\n//\n// Five stages, run in this order, each a pure function with its own table\n// of rules and its own tests:\n//\n// A. anchored credentials — shapes recognizable on their own (rpa_, JWT...)\n// B. sensitive headers — the WHOLE value of Authorization/Cookie/...\n// C. URL query secrets — api_key/token/key/secret parameters\n// D. config assignments — `key = value` / `key: value` by key name\n// E. email addresses — personal data, not a credential\n//\n// Specific before broad is the invariant. Several stage-A rules identify a\n// credential by the word in front of it (`Bearer <opaque>`), and stage D stops\n// an unquoted value at the first space or semicolon — so run in the other\n// order, C consumes \"Bearer\", destroys the anchor, and leaves the secret in\n// plaintext looking redacted. Stage B exists because header values are\n// DEFINED by containing spaces and semicolons (`Basic <cred>`, `a=1; b=2`),\n// which is exactly the shape the token-level matcher cannot hold whole.\n\nexport interface ScrubResult {\n text: string;\n /** How many redactions fired — a redaction rate is itself a metric. */\n redactions: number;\n}\n\n// Bump when any stage's rules change so stored rows record which pass they got.\nexport const SCRUB_VERSION = 6;\n\nconst MARKER = /^[\"']?\\[redacted:[a-z_]+\\][\"']?$/;\n\n// ---- Stage A: anchored credential shapes --------------------------------\n\nconst CREDENTIAL_PATTERNS: Array<{ name: string; re: RegExp }> = [\n // Runpod API keys.\n { name: 'runpod_key', re: /\\brpa_[A-Za-z0-9]{16,}\\b/g },\n // Bearer credentials pasted with their header.\n { name: 'bearer', re: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{16,}/g },\n // Common vendor key prefixes.\n {\n name: 'vendor_key',\n re: /\\b(?:sk|pk|ghp|gho|phc|phx|xoxb|xoxp)[-_][A-Za-z0-9_-]{16,}\\b/g,\n },\n // Hugging Face access tokens.\n { name: 'hugging_face', re: /\\bhf_[A-Za-z0-9]{30,}\\b/g },\n // AWS access key ids.\n { name: 'aws_key', re: /\\bAKIA[0-9A-Z]{16}\\b/g },\n // Three-segment JWTs.\n {\n name: 'jwt',\n re: /\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b/g,\n },\n];\n\nexport function redactAnchoredCredentials(text: string): ScrubResult {\n let redactions = 0;\n let out = text;\n for (const { name, re } of CREDENTIAL_PATTERNS) {\n out = out.replace(re, () => {\n redactions++;\n return `[redacted:${name}]`;\n });\n }\n return { text: out, redactions };\n}\n\n// ---- Stage B: sensitive headers, whole value ----------------------------\n\n// Headers are matched wherever they appear, not only at line start: pastes\n// arrive inline (\"… got 401 with Authorization: Basic xyz | cookie: a=1; b=2\")\n// and the first version, anchored ^…$, missed exactly that and fell through\n// to the token matcher — which redacted the word \"Basic\" and kept the\n// credential. So the VALUE is bounded by its own shape rather than by the end\n// of the line: an auth header is `<scheme> <token>` or a bare token; a cookie\n// header is a `k=v; k=v` chain. Header names are case-insensitive.\nconst AUTH_HEADER =\n /\\b((?:proxy-)?authorization|x-api-key|x-auth-token|x-runpod-token)(\\s*:\\s*)((?:basic|bearer|token|digest|negotiate|ntlm|apikey)\\s+[^\\s|,]+|[^\\s|,]+)/gi;\nconst COOKIE_HEADER =\n /\\b(cookie|set-cookie)(\\s*:\\s*)([^\\s;=,|]+=[^\\s;,|]*(?:\\s*;\\s*[^\\s;=,|]+(?:=[^\\s;,|]*)?)*)/gi;\n\nexport function redactHeaderValues(text: string): ScrubResult {\n let redactions = 0;\n const replace = (match: string, name: string, sep: string, value: string) => {\n // Stage A may already have replaced the value; keep its marker.\n if (MARKER.test(value)) return match;\n redactions++;\n return `${name}${sep}[redacted:header]`;\n };\n const out = text\n .replace(AUTH_HEADER, replace)\n .replace(COOKIE_HEADER, replace);\n return { text: out, redactions };\n}\n\n// ---- Stage C: URL query credentials -----------------------------------\n\n// URL query credentials stop at the next query field, fragment or text boundary.\n// Preserve parameter names and non-secret query fields for diagnostics.\nexport function redactQuerySecrets(text: string): ScrubResult {\n let redactions = 0;\n const out = text.replace(\n /([?&](?:api[_-]?key|access[_-]?token|token|key|secret)=)([^&#\\s\"'<>`]+)/gi,\n (match, prefix: string, value: string) => {\n if (MARKER.test(value)) return match;\n redactions++;\n return `${prefix}[redacted:query]`;\n }\n );\n return { text: out, redactions };\n}\n\n// ---- Stage D: config assignments, by key name ---------------------------\n\n// Keys are compared segment by segment, never by substring: a substring test\n// redacts `tokenizer: llama-3` because it contains \"token\". Split on\n// separators AND camelCase, so `databasePassword` and `clientSecret` become\n// segment lists rather than one opaque word.\nconst SENSITIVE_SEGMENTS = new Set([\n 'password',\n 'passwd',\n 'secret',\n 'token',\n 'authorization',\n 'cookie',\n 'credential',\n 'credentials',\n]);\n\n// `key` alone is too common to be sensitive (primary_key, cache_key). It\n// counts when the segment right before it qualifies it — anywhere in the\n// name, so SERVICE_API_KEY and STRIPE_ACCESS_KEY match, not only api_key.\nconst KEY_QUALIFIERS = new Set([\n 'api',\n 'access',\n 'private',\n 'signing',\n 'secret',\n 'client',\n]);\n\nexport function splitKey(key: string): string[] {\n return key\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .toLowerCase()\n .split(/[_\\-.]+/)\n .filter(Boolean);\n}\n\nexport function isSensitiveKey(key: string): boolean {\n const segments = splitKey(key);\n if (segments.some((segment) => SENSITIVE_SEGMENTS.has(segment))) return true;\n return segments.some(\n (segment, i) =>\n (segment === 'key' && i > 0 && KEY_QUALIFIERS.has(segments[i - 1])) ||\n // No separator at all: `apikey`, `accesskey`.\n (segment.endsWith('key') && KEY_QUALIFIERS.has(segment.slice(0, -3)))\n );\n}\n\n// Covers JSON, YAML and shell env assignments, including quoted values with\n// spaces or escaped quotes. The field name is kept for diagnostic context.\n//\n// This was one regex with a lookbehind, two quoted-string alternatives, a\n// negated value class and a trailing lookahead. It was unreadable, and every\n// fix to one case broke another: excluding `&` from the value class so a URL's\n// second query parameter survived also truncated `password: p&ss#word123` at\n// the `&` and leaked the rest. Both behaviors were correct for their own\n// input and the character class had no way to tell the two apart, because the\n// distinction is context, not characters.\n//\n// So the scan is written out: a cursor, one small function per token, and the\n// value's terminator set chosen from where the key sits. Each rule below is a\n// named predicate that can be read and tested on its own.\n\nconst QUOTES = new Set(['\"', \"'\"]);\n\n// A value always ends at one of these: `{`/`}` and `,`/`;` close a JSON or\n// inline-YAML member, and a quote cannot appear inside an unquoted value.\nconst VALUE_TERMINATORS = new Set([',', ';', '{', '}', '\"', \"'\"]);\n\n// `&` and `#` end a value ONLY inside a URL query, where they begin the next\n// parameter and the fragment. In a config file they are ordinary password\n// characters — `p&ss#word` is one value, not three.\nconst QUERY_TERMINATORS = new Set(['&', '#']);\n\nfunction isAsciiLetter(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');\n}\n\nfunction isAsciiDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9';\n}\n\n/** A key begins with a letter or underscore, never a digit — so `10:30` in a\n * timestamp is not read as an assignment. */\nfunction isKeyStart(ch: string): boolean {\n return isAsciiLetter(ch) || ch === '_';\n}\n\n/** Inside a key name: adds digits and the three separators that `splitKey`\n * later splits on, so `aws.secretKey` and `DB_PASSWORD` arrive whole. */\nfunction isKeyBody(ch: string): boolean {\n return (\n isAsciiLetter(ch) ||\n isAsciiDigit(ch) ||\n ch === '_' ||\n ch === '.' ||\n ch === '-'\n );\n}\n\n/** Horizontal space only. A newline after the separator means the value is on\n * another line, so `db:\\n password: x` must not read `password` as db's\n * value — the bug that let nested YAML through entirely. */\nfunction isSpaceOrTab(ch: string): boolean {\n return ch === ' ' || ch === '\\t';\n}\n\nfunction isWhitespace(ch: string): boolean {\n return (\n ch === ' ' ||\n ch === '\\t' ||\n ch === '\\n' ||\n ch === '\\r' ||\n ch === '\\f' ||\n ch === '\\v'\n );\n}\n\ninterface Assignment {\n /** Bare key name: quotes and the separator stripped. */\n key: string;\n /** Raw value text as it appears, including its quotes when quoted. */\n raw: string;\n /** Quote character to reinstate around a replacement, or `''`. */\n quote: string;\n /** Half-open range of the value within the source text. */\n start: number;\n end: number;\n}\n\n/** Reads a quoted value, honoring backslash escapes. An unterminated quote is\n * a truncated paste, so it reads to the end of the line rather than giving\n * up: for a redactor, over-reading a secret is the safe direction. */\nfunction readQuotedValue(text: string, start: number): number {\n const quote = text[start];\n let i = start + 1;\n while (i < text.length) {\n const ch = text[i];\n if (ch === '\\\\') {\n i += 2;\n continue;\n }\n if (ch === quote) return i + 1;\n if (ch === '\\n' || ch === '\\r') return i;\n i++;\n }\n return text.length;\n}\n\n/** Reads an unquoted value up to the first terminator for its context. */\nfunction readUnquotedValue(\n text: string,\n start: number,\n inQueryString: boolean\n): number {\n let i = start;\n while (i < text.length) {\n const ch = text[i];\n if (isWhitespace(ch) || VALUE_TERMINATORS.has(ch)) break;\n if (inQueryString && QUERY_TERMINATORS.has(ch)) break;\n i++;\n }\n return i;\n}\n\n/** Parses `key: value` or `key = value` beginning at `at`, or null if there is\n * no assignment there. */\nfunction parseAssignment(text: string, at: number): Assignment | null {\n const before = at > 0 ? text[at - 1] : '';\n // Mid-identifier: `my_password` must not also match as key `password`.\n if (isKeyBody(before)) return null;\n\n let i = at;\n const openingQuote = QUOTES.has(text[i] ?? '') ? text[i] : '';\n if (openingQuote) i++;\n if (!isKeyStart(text[i] ?? '')) return null;\n\n const keyStart = i;\n while (i < text.length && isKeyBody(text[i])) i++;\n const key = text.slice(keyStart, i);\n\n // A closing quote is tolerated whether or not one opened: pasted snippets\n // arrive truncated, and `password\": x` should still redact.\n if (QUOTES.has(text[i] ?? '')) i++;\n\n while (i < text.length && isSpaceOrTab(text[i])) i++;\n if (text[i] !== ':' && text[i] !== '=') return null;\n i++;\n while (i < text.length && isSpaceOrTab(text[i])) i++;\n\n // `?api_key=` or `&token=` — this key sits in a URL query string.\n const inQueryString = before === '?' || before === '&';\n\n const start = i;\n let end: number;\n let quote = '';\n if (QUOTES.has(text[start] ?? '')) {\n quote = text[start];\n end = readQuotedValue(text, start);\n } else {\n end = readUnquotedValue(text, start, inQueryString);\n }\n // A parent key such as `db:` has no value of its own.\n if (end === start) return null;\n\n return { key, raw: text.slice(start, end), quote, start, end };\n}\n\nexport function redactAssignments(text: string): ScrubResult {\n let redactions = 0;\n let out = '';\n let copiedTo = 0;\n let cursor = 0;\n\n while (cursor < text.length) {\n const found = parseAssignment(text, cursor);\n if (!found) {\n cursor++;\n continue;\n }\n\n // Already handled by an earlier stage — keep its marker, do not count it\n // twice, and resume past it.\n if (MARKER.test(found.raw)) {\n cursor = found.end;\n continue;\n }\n\n if (!isSensitiveKey(found.key)) {\n // Resume INSIDE the value, not after it. A non-sensitive assignment can\n // contain one: `https://h/?password=x` parses as key `https` whose value\n // is the whole URL, and skipping it leaked the password outright.\n cursor = found.start;\n continue;\n }\n\n out += text.slice(copiedTo, found.start);\n out += `${found.quote}[redacted:config]${found.quote}`;\n copiedTo = found.end;\n cursor = found.end;\n redactions++;\n }\n\n out += text.slice(copiedTo);\n return { text: out, redactions };\n}\n\n// ---- Stage E: email addresses -------------------------------------------\n\n// Letters, marks and digits of any script, so `josé@example.com` and\n// `用户@例子.example.com` are caught too. Quantifiers carry the address limits\n// (64-character local part, 63-character labels), and the local part must\n// start a run of address characters, so each run is tried once and a run of\n// more than 64 characters before the `@` is not an address. Together they\n// keep a long run with no `@` from costing quadratic time on a long\n// submission. A dotted version such as `pkg@1.2.3` is not matched: the\n// top-level label must be letters.\nconst EMAIL =\n /(?<![\\p{L}\\p{M}\\p{N}_.%+-])([\\p{L}\\p{M}\\p{N}_.%+-]{1,64})@((?:[\\p{L}\\p{M}\\p{N}-]{1,63}\\.){1,8}[\\p{L}\\p{M}]{2,63})(?![\\p{L}\\p{M}\\p{N}])/gu;\n\n// Two logins are kept: they name a machine, not a person, and the address is\n// what tells a reader where a command ran. They are Runpod's Pod SSH proxy\n// (`<podId>-<hash>@ssh.runpod.io`) and the git user of an SSH remote\n// (`git@github.com:org/repo.git`, `ssh://git@github.com/org/repo.git`).\nconst RUNPOD_SSH_HOST = 'ssh.runpod.io';\n\nexport function redactEmails(text: string): ScrubResult {\n let redactions = 0;\n const out = text.replace(\n EMAIL,\n (address: string, local: string, domain: string, offset: number) => {\n const next = text[offset + address.length];\n if (\n domain.toLowerCase() === RUNPOD_SSH_HOST ||\n (local === 'git' && (next === ':' || next === '/'))\n )\n return address;\n redactions++;\n return '[redacted:email]';\n }\n );\n return { text: out, redactions };\n}\n\n// ---- Composition ---------------------------------------------------------\n\nexport function scrub(text: string): ScrubResult {\n let out = text;\n let redactions = 0;\n for (const stage of [\n redactAnchoredCredentials,\n redactHeaderValues,\n redactQuerySecrets,\n redactAssignments,\n redactEmails,\n ]) {\n const result = stage(out);\n out = result.text;\n redactions += result.redactions;\n }\n return { text: out, redactions };\n}\n\n// Only user-authored text is scrubbed; resolved identity and server timestamps\n// remain authoritative. Reapplying this at the sink is safe and idempotent.\nexport const SCRUBBED_FIELDS = [\n 'content',\n 'intention',\n 'modelType',\n 'severity',\n 'tool',\n 'workaround',\n 'trigger',\n 'source',\n 'useCase',\n 'harness',\n 'harnessSource',\n 'transport',\n] as const;\n\nexport function scrubSubmission<\n T extends {\n content: string;\n intention?: string;\n modelType?: string;\n severity?: string;\n tool?: string;\n workaround?: string;\n trigger?: string;\n source?: string;\n useCase?: string;\n harness?: string;\n harnessSource?: string;\n transport?: string;\n redactions: number;\n scrubVersion: number;\n },\n>(row: T): T {\n const clean = { ...row };\n // EVERY agent-writable string field belongs in this list. A field added to\n // the submission shape but not here is stored unscrubbed, which is how a\n // pasted credential reaches the table — the fields most likely to carry one\n // are exactly the free-text ones a new route adds. Guarded by a test that\n // walks the submission type, so the omission fails CI rather than shipping.\n for (const field of SCRUBBED_FIELDS) {\n const value = clean[field];\n if (typeof value !== 'string') continue;\n const result = scrub(value);\n clean[field] = result.text;\n clean.redactions += result.redactions;\n }\n clean.scrubVersion = SCRUB_VERSION;\n return clean;\n}\n"],"mappings":"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","names":[]}
|
package/dist/alp.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { a as ALP_REFERRAL_SOURCES, b as ALP_REFERRAL_USE_CASES, A as ALP_ROUTES, c as ALREADY_SUBMITTED_NOTE, e as AlpReferralSource, f as AlpReferralUseCase, g as AlpRoute, O as ONCE_PER_ACCOUNT_ROUTES, h as OncePerAccountRoute, i as isOncePerAccountRoute, d as isSinkUrl, s as sinkRouteUrl } from './sink-Cm8lp16D.cjs';
|
|
2
2
|
|
|
3
|
+
declare const SCRUB_VERSION = 6;
|
|
3
4
|
declare function scrubSubmission<T extends {
|
|
4
5
|
content: string;
|
|
5
6
|
intention?: string;
|
|
@@ -17,4 +18,4 @@ declare function scrubSubmission<T extends {
|
|
|
17
18
|
scrubVersion: number;
|
|
18
19
|
}>(row: T): T;
|
|
19
20
|
|
|
20
|
-
export { scrubSubmission };
|
|
21
|
+
export { SCRUB_VERSION, scrubSubmission };
|
package/dist/alp.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { a as ALP_REFERRAL_SOURCES, b as ALP_REFERRAL_USE_CASES, A as ALP_ROUTES, c as ALREADY_SUBMITTED_NOTE, e as AlpReferralSource, f as AlpReferralUseCase, g as AlpRoute, O as ONCE_PER_ACCOUNT_ROUTES, h as OncePerAccountRoute, i as isOncePerAccountRoute, d as isSinkUrl, s as sinkRouteUrl } from './sink-Cm8lp16D.js';
|
|
2
2
|
|
|
3
|
+
declare const SCRUB_VERSION = 6;
|
|
3
4
|
declare function scrubSubmission<T extends {
|
|
4
5
|
content: string;
|
|
5
6
|
intention?: string;
|
|
@@ -17,4 +18,4 @@ declare function scrubSubmission<T extends {
|
|
|
17
18
|
scrubVersion: number;
|
|
18
19
|
}>(row: T): T;
|
|
19
20
|
|
|
20
|
-
export { scrubSubmission };
|
|
21
|
+
export { SCRUB_VERSION, scrubSubmission };
|
package/dist/alp.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SCRUB_VERSION,
|
|
2
3
|
scrubSubmission
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WWXM5EES.mjs";
|
|
4
5
|
import {
|
|
5
6
|
ALP_REFERRAL_SOURCES,
|
|
6
7
|
ALP_REFERRAL_USE_CASES,
|
|
@@ -17,6 +18,7 @@ export {
|
|
|
17
18
|
ALP_ROUTES,
|
|
18
19
|
ALREADY_SUBMITTED_NOTE,
|
|
19
20
|
ONCE_PER_ACCOUNT_ROUTES,
|
|
21
|
+
SCRUB_VERSION,
|
|
20
22
|
isOncePerAccountRoute,
|
|
21
23
|
isSinkUrl,
|
|
22
24
|
scrubSubmission,
|
|
@@ -5,33 +5,53 @@ var CREDENTIAL_MANAGEMENT = "Manages stored credentials; OpenAI restricts plugin
|
|
|
5
5
|
var CREDENTIAL_INPUT = "Takes a secret or password as input; directories forbid collecting credentials.";
|
|
6
6
|
var ALP = "Collects conversation-derived data beyond the task; the directory profile never serves it.";
|
|
7
7
|
var DIRECTORY_TOOLS = {
|
|
8
|
+
"add-organization-group-member": IN,
|
|
9
|
+
"attach-cost-center-resource": IN,
|
|
8
10
|
"cancel-job": IN,
|
|
9
11
|
"create-cluster": IN,
|
|
12
|
+
"create-cost-center": IN,
|
|
10
13
|
"create-delegation": IN,
|
|
11
14
|
"create-endpoint": IN,
|
|
12
15
|
"create-global-volume": IN,
|
|
13
16
|
"create-network-volume": IN,
|
|
17
|
+
"create-organization-group": IN,
|
|
18
|
+
"create-organization-invite": IN,
|
|
19
|
+
"create-organization-role": IN,
|
|
14
20
|
"create-pod": IN,
|
|
15
21
|
"create-template": IN,
|
|
16
22
|
"delete-cluster": IN,
|
|
23
|
+
"delete-cost-center": IN,
|
|
17
24
|
"delete-endpoint": IN,
|
|
18
25
|
"delete-global-volume": IN,
|
|
19
26
|
"delete-network-volume": IN,
|
|
27
|
+
"delete-organization-group": IN,
|
|
28
|
+
"delete-organization-invite": IN,
|
|
29
|
+
"delete-organization-role": IN,
|
|
30
|
+
"delete-organization-user": IN,
|
|
20
31
|
"delete-pod": IN,
|
|
21
32
|
"delete-template": IN,
|
|
22
33
|
"deploy-hub-repo": IN,
|
|
34
|
+
"detach-cost-center-resource": IN,
|
|
23
35
|
"endpoint-health": IN,
|
|
24
36
|
"get-capacity": IN,
|
|
25
37
|
"get-cluster": IN,
|
|
38
|
+
"get-cost-center": IN,
|
|
26
39
|
"get-cpu-type": IN,
|
|
27
40
|
"get-data-center": IN,
|
|
28
41
|
"get-endpoint": IN,
|
|
29
42
|
"get-endpoint-build": IN,
|
|
43
|
+
"get-endpoint-cold-start-metrics": IN,
|
|
44
|
+
"get-endpoint-request-metrics": IN,
|
|
45
|
+
"get-endpoint-worker-metrics": IN,
|
|
30
46
|
"get-global-volume": IN,
|
|
31
47
|
"get-gpu-type": IN,
|
|
32
48
|
"get-job-status": IN,
|
|
33
49
|
"get-network-volume": IN,
|
|
34
50
|
"get-organization": IN,
|
|
51
|
+
"get-organization-group": IN,
|
|
52
|
+
"get-organization-invite": IN,
|
|
53
|
+
"get-organization-role": IN,
|
|
54
|
+
"get-organization-role-capabilities": IN,
|
|
35
55
|
"get-organization-user": IN,
|
|
36
56
|
"get-pod": IN,
|
|
37
57
|
"get-ssh-keys": IN,
|
|
@@ -41,6 +61,8 @@ var DIRECTORY_TOOLS = {
|
|
|
41
61
|
"list-cluster-billing": IN,
|
|
42
62
|
"list-cluster-pods": IN,
|
|
43
63
|
"list-clusters": IN,
|
|
64
|
+
"list-cost-center-resources": IN,
|
|
65
|
+
"list-cost-centers": IN,
|
|
44
66
|
"list-cpu-types": IN,
|
|
45
67
|
"list-data-centers": IN,
|
|
46
68
|
"list-delegations": IN,
|
|
@@ -54,6 +76,10 @@ var DIRECTORY_TOOLS = {
|
|
|
54
76
|
"list-hub-repos": IN,
|
|
55
77
|
"list-network-volume-billing": IN,
|
|
56
78
|
"list-network-volumes": IN,
|
|
79
|
+
"list-organization-group-members": IN,
|
|
80
|
+
"list-organization-groups": IN,
|
|
81
|
+
"list-organization-invites": IN,
|
|
82
|
+
"list-organization-roles": IN,
|
|
57
83
|
"list-organization-users": IN,
|
|
58
84
|
"list-pod-billing": IN,
|
|
59
85
|
"list-pods": IN,
|
|
@@ -63,6 +89,7 @@ var DIRECTORY_TOOLS = {
|
|
|
63
89
|
"list-templates": IN,
|
|
64
90
|
"lookup-concept": IN,
|
|
65
91
|
"purge-endpoint-queue": IN,
|
|
92
|
+
"remove-organization-group-member": IN,
|
|
66
93
|
"retry-job": IN,
|
|
67
94
|
"revoke-delegation": IN,
|
|
68
95
|
"search-concepts": IN,
|
|
@@ -72,9 +99,13 @@ var DIRECTORY_TOOLS = {
|
|
|
72
99
|
"stream-pod-logs": IN,
|
|
73
100
|
"stream-worker-logs": IN,
|
|
74
101
|
"update-cluster": IN,
|
|
102
|
+
"update-cost-center": IN,
|
|
75
103
|
"update-endpoint": IN,
|
|
76
104
|
"update-global-volume": IN,
|
|
77
105
|
"update-network-volume": IN,
|
|
106
|
+
"update-organization-group": IN,
|
|
107
|
+
"update-organization-role": IN,
|
|
108
|
+
"update-organization-user": IN,
|
|
78
109
|
"update-pod": IN,
|
|
79
110
|
"update-ssh-keys": IN,
|
|
80
111
|
"update-template": IN,
|
|
@@ -132,4 +163,4 @@ export {
|
|
|
132
163
|
directoryOmittedInputs,
|
|
133
164
|
profileForPath
|
|
134
165
|
};
|
|
135
|
-
//# sourceMappingURL=chunk-
|
|
166
|
+
//# sourceMappingURL=chunk-FQJFKWUV.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/specgen/profile.ts"],"sourcesContent":["// Which tool surface a connection gets. `default` is the full surface every\n// client has always had. `directory` is what app directories (Anthropic's\n// connector directory, OpenAI's plugin directory) list: their policies forbid\n// tool descriptions that instruct the model, steer it toward other tools or\n// external instructions, or collect conversation data beyond the tool's job.\n// The directory profile is served only at DIRECTORY_PATH; nothing about the\n// default surface changes.\nexport type ServerProfile = 'default' | 'directory';\n\nexport const DIRECTORY_PATH = '/directory';\n\n/** Whether a tool is listed on the directory profile. Every tool the server\n * can serve must have an entry, so a new tool (a spec regeneration, a new\n * curated tool) fails tests/specgen-directory.test.ts until someone decides.\n * The rules for deciding are in specgen/DIRECTORY_POLICY.md.\n * `omitInputs` hides top-level arguments the directory must not ask for (a\n * credential, for example); a call that passes one is refused. */\nexport type DirectoryDecision =\n | { include: true; omitInputs?: readonly string[] }\n | { include: false; reason: string };\n\nconst IN = { include: true } as const;\nconst CREDENTIAL_MANAGEMENT =\n 'Manages stored credentials; OpenAI restricts plugins that handle access credentials, so the directory leaves the whole credential family out.';\nconst CREDENTIAL_INPUT =\n 'Takes a secret or password as input; directories forbid collecting credentials.';\nconst ALP =\n 'Collects conversation-derived data beyond the task; the directory profile never serves it.';\n\nexport const DIRECTORY_TOOLS: Readonly<Record<string, DirectoryDecision>> = {\n 'add-organization-group-member': IN,\n 'attach-cost-center-resource': IN,\n 'cancel-job': IN,\n 'create-cluster': IN,\n 'create-cost-center': IN,\n 'create-delegation': IN,\n 'create-endpoint': IN,\n 'create-global-volume': IN,\n 'create-network-volume': IN,\n 'create-organization-group': IN,\n 'create-organization-invite': IN,\n 'create-organization-role': IN,\n 'create-pod': IN,\n 'create-template': IN,\n 'delete-cluster': IN,\n 'delete-cost-center': IN,\n 'delete-endpoint': IN,\n 'delete-global-volume': IN,\n 'delete-network-volume': IN,\n 'delete-organization-group': IN,\n 'delete-organization-invite': IN,\n 'delete-organization-role': IN,\n 'delete-organization-user': IN,\n 'delete-pod': IN,\n 'delete-template': IN,\n 'deploy-hub-repo': IN,\n 'detach-cost-center-resource': IN,\n 'endpoint-health': IN,\n 'get-capacity': IN,\n 'get-cluster': IN,\n 'get-cost-center': IN,\n 'get-cpu-type': IN,\n 'get-data-center': IN,\n 'get-endpoint': IN,\n 'get-endpoint-build': IN,\n 'get-endpoint-cold-start-metrics': IN,\n 'get-endpoint-request-metrics': IN,\n 'get-endpoint-worker-metrics': IN,\n 'get-global-volume': IN,\n 'get-gpu-type': IN,\n 'get-job-status': IN,\n 'get-network-volume': IN,\n 'get-organization': IN,\n 'get-organization-group': IN,\n 'get-organization-invite': IN,\n 'get-organization-role': IN,\n 'get-organization-role-capabilities': IN,\n 'get-organization-user': IN,\n 'get-pod': IN,\n 'get-ssh-keys': IN,\n 'get-template': IN,\n 'list-audit-logs': IN,\n 'list-billing': IN,\n 'list-cluster-billing': IN,\n 'list-cluster-pods': IN,\n 'list-clusters': IN,\n 'list-cost-center-resources': IN,\n 'list-cost-centers': IN,\n 'list-cpu-types': IN,\n 'list-data-centers': IN,\n 'list-delegations': IN,\n 'list-endpoint-billing': IN,\n 'list-endpoint-builds': IN,\n 'list-endpoint-releases': IN,\n 'list-endpoint-workers': IN,\n 'list-endpoints': IN,\n 'list-global-volumes': IN,\n 'list-gpu-types': IN,\n 'list-hub-repos': IN,\n 'list-network-volume-billing': IN,\n 'list-network-volumes': IN,\n 'list-organization-group-members': IN,\n 'list-organization-groups': IN,\n 'list-organization-invites': IN,\n 'list-organization-roles': IN,\n 'list-organization-users': IN,\n 'list-pod-billing': IN,\n 'list-pods': IN,\n 'list-public-endpoints': IN,\n 'list-public-templates': IN,\n 'list-serverless-billing': IN,\n 'list-templates': IN,\n 'lookup-concept': IN,\n 'purge-endpoint-queue': IN,\n 'remove-organization-group-member': IN,\n 'retry-job': IN,\n 'revoke-delegation': IN,\n 'search-concepts': IN,\n 'search-docs': IN,\n 'set-endpoint-gpus': IN,\n 'stream-job': IN,\n 'stream-pod-logs': IN,\n 'stream-worker-logs': IN,\n 'update-cluster': IN,\n 'update-cost-center': IN,\n 'update-endpoint': IN,\n 'update-global-volume': IN,\n 'update-network-volume': IN,\n 'update-organization-group': IN,\n 'update-organization-role': IN,\n 'update-organization-user': IN,\n 'update-pod': IN,\n 'update-ssh-keys': IN,\n 'update-template': IN,\n 'run-endpoint': { include: true, omitInputs: ['s3Config'] },\n 'runsync-endpoint': { include: true, omitInputs: ['s3Config'] },\n 'pod-action': {\n include: false,\n reason:\n 'Takes the transition as an argument; OpenAI asks for one tool per operation, so the directory serves start-pod, stop-pod and restart-pod (terminate is delete-pod).',\n },\n 'start-pod': IN,\n 'stop-pod': IN,\n 'restart-pod': IN,\n 'list-guides': {\n include: false,\n reason: 'Serves playbooks that instruct the model how to behave.',\n },\n 'read-guide': {\n include: false,\n reason: 'Serves playbooks that instruct the model how to behave.',\n },\n 'describe-tool': {\n include: false,\n reason:\n 'Fetches schemas the directory already lists in full; directories reject schema fetching.',\n },\n 'create-secret': { include: false, reason: CREDENTIAL_INPUT },\n 'get-secret': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'list-secrets': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'delete-secret': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'get-registry': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'list-registries': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'delete-registry': { include: false, reason: CREDENTIAL_MANAGEMENT },\n 'update-secret': { include: false, reason: CREDENTIAL_INPUT },\n 'create-registry': { include: false, reason: CREDENTIAL_INPUT },\n report_feedback: { include: false, reason: ALP },\n report_how_user_found_runpod: { include: false, reason: ALP },\n save_to_journal: { include: false, reason: ALP },\n read_journal: { include: false, reason: ALP },\n ask_question: { include: false, reason: ALP },\n};\n\n/** Served on the directory profile only with an explicit include. */\nexport function directoryIncludes(name: string): boolean {\n return DIRECTORY_TOOLS[name]?.include === true;\n}\n\nexport function directoryOmittedInputs(name: string): readonly string[] {\n const decision = DIRECTORY_TOOLS[name];\n return decision?.include ? (decision.omitInputs ?? []) : [];\n}\n\nexport function profileForPath(pathname: string): ServerProfile {\n return pathname.replace(/\\/+$/, '') === DIRECTORY_PATH\n ? 'directory'\n : 'default';\n}\n"],"mappings":"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","names":[]}
|