modelrig 0.0.1 → 0.1.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/LICENSE +201 -0
- package/README.md +115 -8
- package/dist/adapters/anthropic.d.ts +46 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +202 -0
- package/dist/adapters/deepseek.d.ts +41 -0
- package/dist/adapters/deepseek.d.ts.map +1 -0
- package/dist/adapters/deepseek.js +163 -0
- package/dist/adapters/gemini.d.ts +44 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +222 -0
- package/dist/adapters/grok.d.ts +44 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +168 -0
- package/dist/adapters/openai-compat.d.ts +74 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -0
- package/dist/adapters/openai-compat.js +187 -0
- package/dist/adapters/openai.d.ts +49 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +222 -0
- package/dist/bakeoff/engine.d.ts +52 -0
- package/dist/bakeoff/engine.d.ts.map +1 -0
- package/dist/bakeoff/engine.js +163 -0
- package/dist/bakeoff/replay.d.ts +52 -0
- package/dist/bakeoff/replay.d.ts.map +1 -0
- package/dist/bakeoff/replay.js +94 -0
- package/dist/bakeoff/samples.d.ts +62 -0
- package/dist/bakeoff/samples.d.ts.map +1 -0
- package/dist/bakeoff/samples.js +128 -0
- package/dist/bakeoff/stats.d.ts +13 -0
- package/dist/bakeoff/stats.d.ts.map +1 -0
- package/dist/bakeoff/stats.js +48 -0
- package/dist/bakeoff/types.d.ts +37 -0
- package/dist/bakeoff/types.d.ts.map +1 -0
- package/dist/bakeoff/types.js +10 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +572 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +148 -0
- package/dist/feedback/coverage.d.ts +32 -0
- package/dist/feedback/coverage.d.ts.map +1 -0
- package/dist/feedback/coverage.js +95 -0
- package/dist/feedback/feedback.d.ts +35 -0
- package/dist/feedback/feedback.d.ts.map +1 -0
- package/dist/feedback/feedback.js +39 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +448 -0
- package/dist/init/finder.d.ts +79 -0
- package/dist/init/finder.d.ts.map +1 -0
- package/dist/init/finder.js +297 -0
- package/dist/init/run.d.ts +99 -0
- package/dist/init/run.d.ts.map +1 -0
- package/dist/init/run.js +281 -0
- package/dist/init/signatures.d.ts +39 -0
- package/dist/init/signatures.d.ts.map +1 -0
- package/dist/init/signatures.js +116 -0
- package/dist/matrix/aggregate.d.ts +89 -0
- package/dist/matrix/aggregate.d.ts.map +1 -0
- package/dist/matrix/aggregate.js +186 -0
- package/dist/query/grammar.d.ts +35 -0
- package/dist/query/grammar.d.ts.map +1 -0
- package/dist/query/grammar.js +279 -0
- package/dist/query/run.d.ts +8 -0
- package/dist/query/run.d.ts.map +1 -0
- package/dist/query/run.js +21 -0
- package/dist/registry/build.d.ts +126 -0
- package/dist/registry/build.d.ts.map +1 -0
- package/dist/registry/build.js +192 -0
- package/dist/registry/call-notes.d.ts +11 -0
- package/dist/registry/call-notes.d.ts.map +1 -0
- package/dist/registry/call-notes.js +72 -0
- package/dist/registry/claims.d.ts +63 -0
- package/dist/registry/claims.d.ts.map +1 -0
- package/dist/registry/claims.js +107 -0
- package/dist/registry/facts.d.ts +32 -0
- package/dist/registry/facts.d.ts.map +1 -0
- package/dist/registry/facts.js +120 -0
- package/dist/registry/fingerprint.d.ts +20 -0
- package/dist/registry/fingerprint.d.ts.map +1 -0
- package/dist/registry/fingerprint.js +150 -0
- package/dist/registry/parity.d.ts +26 -0
- package/dist/registry/parity.d.ts.map +1 -0
- package/dist/registry/parity.js +41 -0
- package/dist/registry/pricing.d.ts +19 -0
- package/dist/registry/pricing.d.ts.map +1 -0
- package/dist/registry/pricing.js +69 -0
- package/dist/registry/rows.d.ts +16 -0
- package/dist/registry/rows.d.ts.map +1 -0
- package/dist/registry/rows.js +57 -0
- package/dist/registry/seed.d.ts +43 -0
- package/dist/registry/seed.d.ts.map +1 -0
- package/dist/registry/seed.js +97 -0
- package/dist/registry/select.d.ts +68 -0
- package/dist/registry/select.d.ts.map +1 -0
- package/dist/registry/select.js +141 -0
- package/dist/registry/staleness.d.ts +18 -0
- package/dist/registry/staleness.d.ts.map +1 -0
- package/dist/registry/staleness.js +39 -0
- package/dist/routes/draft.d.ts +83 -0
- package/dist/routes/draft.d.ts.map +1 -0
- package/dist/routes/draft.js +135 -0
- package/dist/routes/loader.d.ts +26 -0
- package/dist/routes/loader.d.ts.map +1 -0
- package/dist/routes/loader.js +436 -0
- package/dist/routes/manifest.d.ts +24 -0
- package/dist/routes/manifest.d.ts.map +1 -0
- package/dist/routes/manifest.js +118 -0
- package/dist/routes/serveability.d.ts +52 -0
- package/dist/routes/serveability.d.ts.map +1 -0
- package/dist/routes/serveability.js +73 -0
- package/dist/routes/template.d.ts +18 -0
- package/dist/routes/template.d.ts.map +1 -0
- package/dist/routes/template.js +94 -0
- package/dist/run/cost.d.ts +26 -0
- package/dist/run/cost.d.ts.map +1 -0
- package/dist/run/cost.js +41 -0
- package/dist/run/envelope.d.ts +21 -0
- package/dist/run/envelope.d.ts.map +1 -0
- package/dist/run/envelope.js +45 -0
- package/dist/run/grounding.d.ts +56 -0
- package/dist/run/grounding.d.ts.map +1 -0
- package/dist/run/grounding.js +89 -0
- package/dist/run/repair.d.ts +36 -0
- package/dist/run/repair.d.ts.map +1 -0
- package/dist/run/repair.js +61 -0
- package/dist/run/resolve.d.ts +26 -0
- package/dist/run/resolve.d.ts.map +1 -0
- package/dist/run/resolve.js +81 -0
- package/dist/run/retry.d.ts +19 -0
- package/dist/run/retry.d.ts.map +1 -0
- package/dist/run/retry.js +50 -0
- package/dist/run/run.d.ts +41 -0
- package/dist/run/run.d.ts.map +1 -0
- package/dist/run/run.js +0 -0
- package/dist/run/validate.d.ts +15 -0
- package/dist/run/validate.d.ts.map +1 -0
- package/dist/run/validate.js +56 -0
- package/dist/savings/emit.d.ts +25 -0
- package/dist/savings/emit.d.ts.map +1 -0
- package/dist/savings/emit.js +75 -0
- package/dist/savings/odometer.d.ts +32 -0
- package/dist/savings/odometer.d.ts.map +1 -0
- package/dist/savings/odometer.js +70 -0
- package/dist/savings/swap-execute.d.ts +44 -0
- package/dist/savings/swap-execute.d.ts.map +1 -0
- package/dist/savings/swap-execute.js +87 -0
- package/dist/savings/swaps-client.d.ts +41 -0
- package/dist/savings/swaps-client.d.ts.map +1 -0
- package/dist/savings/swaps-client.js +81 -0
- package/dist/telemetry/capture.d.ts +28 -0
- package/dist/telemetry/capture.d.ts.map +1 -0
- package/dist/telemetry/capture.js +74 -0
- package/dist/telemetry/exporter.d.ts +62 -0
- package/dist/telemetry/exporter.d.ts.map +1 -0
- package/dist/telemetry/exporter.js +233 -0
- package/dist/telemetry/ingest-protocol.d.ts +161 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
- package/dist/telemetry/ingest-protocol.js +163 -0
- package/dist/telemetry/sinks.d.ts +54 -0
- package/dist/telemetry/sinks.d.ts.map +1 -0
- package/dist/telemetry/sinks.js +89 -0
- package/dist/telemetry/store.d.ts +38 -0
- package/dist/telemetry/store.d.ts.map +1 -0
- package/dist/telemetry/store.js +332 -0
- package/dist/telemetry/supabase-exporter.d.ts +64 -0
- package/dist/telemetry/supabase-exporter.d.ts.map +1 -0
- package/dist/telemetry/supabase-exporter.js +211 -0
- package/dist/tenancy/api-key-format.d.ts +67 -0
- package/dist/tenancy/api-key-format.d.ts.map +1 -0
- package/dist/tenancy/api-key-format.js +91 -0
- package/dist/tenancy/constants.d.ts +23 -0
- package/dist/tenancy/constants.d.ts.map +1 -0
- package/dist/tenancy/constants.js +25 -0
- package/dist/tenancy/scrub.d.ts +47 -0
- package/dist/tenancy/scrub.d.ts.map +1 -0
- package/dist/tenancy/scrub.js +85 -0
- package/dist/types/failures-teaching.d.ts +56 -0
- package/dist/types/failures-teaching.d.ts.map +1 -0
- package/dist/types/failures-teaching.js +177 -0
- package/dist/types/failures.d.ts +27 -0
- package/dist/types/failures.d.ts.map +1 -0
- package/dist/types/failures.js +41 -0
- package/dist/types/provider.d.ts +79 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/provider.js +20 -0
- package/dist/types/route.d.ts +127 -0
- package/dist/types/route.d.ts.map +1 -0
- package/dist/types/route.js +6 -0
- package/dist/types/telemetry.d.ts +89 -0
- package/dist/types/telemetry.d.ts.map +1 -0
- package/dist/types/telemetry.js +6 -0
- package/dist/watch/budget.d.ts +19 -0
- package/dist/watch/budget.d.ts.map +1 -0
- package/dist/watch/budget.js +35 -0
- package/dist/watch/evaluate.d.ts +66 -0
- package/dist/watch/evaluate.d.ts.map +1 -0
- package/dist/watch/evaluate.js +94 -0
- package/dist/watch/proposals.d.ts +73 -0
- package/dist/watch/proposals.d.ts.map +1 -0
- package/dist/watch/proposals.js +169 -0
- package/dist/watch/run-record.d.ts +64 -0
- package/dist/watch/run-record.d.ts.map +1 -0
- package/dist/watch/run-record.js +86 -0
- package/dist/watch/run-reported.d.ts +20 -0
- package/dist/watch/run-reported.d.ts.map +1 -0
- package/dist/watch/run-reported.js +53 -0
- package/dist/watch/run.d.ts +40 -0
- package/dist/watch/run.d.ts.map +1 -0
- package/dist/watch/run.js +44 -0
- package/dist/watch/state-store.d.ts +48 -0
- package/dist/watch/state-store.d.ts.map +1 -0
- package/dist/watch/state-store.js +86 -0
- package/dist/watch/state.d.ts +21 -0
- package/dist/watch/state.d.ts.map +1 -0
- package/dist/watch/state.js +93 -0
- package/package.json +50 -14
- package/index.js +0 -3
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The `rig_sk_` key FORMAT — Phase 6 WS2.
|
|
4
|
+
*
|
|
5
|
+
* Shared knowledge, deliberately: the SDK builds the header that presents a
|
|
6
|
+
* key, modelrig-server resolves one, and the oracle decides whether the value
|
|
7
|
+
* it was handed is a key at all. Three packages agreeing on a prefix by
|
|
8
|
+
* copying a string literal is how N1's "one name per concept" fails in
|
|
9
|
+
* practice, so the string lives here and they all import it.
|
|
10
|
+
*
|
|
11
|
+
* What is NOT here is MINTING. Generating a secret and writing its hash is the
|
|
12
|
+
* control plane's job; an SDK that could do it would imply a client could
|
|
13
|
+
* issue itself a credential, which is the shape of the thing this scheme
|
|
14
|
+
* exists to prevent. `mintApiKey` lives in modelrig-server.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.API_KEY_SCOPES = exports.API_KEY_BODY_MIN_LENGTH = exports.API_KEY_SECRET_BYTES = exports.API_KEY_PREFIX = void 0;
|
|
18
|
+
exports.hashApiKeySecret = hashApiKeySecret;
|
|
19
|
+
exports.looksLikeApiKey = looksLikeApiKey;
|
|
20
|
+
exports.redactApiKey = redactApiKey;
|
|
21
|
+
exports.parseScopes = parseScopes;
|
|
22
|
+
exports.hasScope = hasScope;
|
|
23
|
+
const node_crypto_1 = require("node:crypto");
|
|
24
|
+
/**
|
|
25
|
+
* The one place this prefix is spelled. Grep-visible on purpose: the
|
|
26
|
+
* guardrails C2 rule keys off it, and so does the exporter's sink selection.
|
|
27
|
+
*/
|
|
28
|
+
exports.API_KEY_PREFIX = "rig_sk_";
|
|
29
|
+
/** 32 bytes = 256 bits, as base64url. The minting side uses this to generate;
|
|
30
|
+
* this side uses its ENCODED length to recognise. See 00020's header for why
|
|
31
|
+
* that number carries the argument that a plain sha-256 at rest is sound. */
|
|
32
|
+
exports.API_KEY_SECRET_BYTES = 32;
|
|
33
|
+
/** base64url length of API_KEY_SECRET_BYTES — the minimum a real key's body has. */
|
|
34
|
+
exports.API_KEY_BODY_MIN_LENGTH = 43;
|
|
35
|
+
/**
|
|
36
|
+
* The granted authorities. Mirrors migration 00020's CHECK constraint — a
|
|
37
|
+
* test pins that the two agree, because a scope that exists here and not
|
|
38
|
+
* there is a key that cannot be written, and one that exists there and not
|
|
39
|
+
* here is an authority nothing enforces.
|
|
40
|
+
*/
|
|
41
|
+
exports.API_KEY_SCOPES = ["ingest", "query", "feedback", "decision", "oracle-act"];
|
|
42
|
+
/** sha256 of the WHOLE presented string, prefix included — so a caller cannot
|
|
43
|
+
* strip the prefix and land on a different row. */
|
|
44
|
+
function hashApiKeySecret(secret) {
|
|
45
|
+
return (0, node_crypto_1.createHash)("sha256").update(secret, "utf8").digest("hex");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Cheap shape check, used to decide which credential branch a bearer token
|
|
49
|
+
* takes before any I/O happens.
|
|
50
|
+
*
|
|
51
|
+
* The minimum body length matters: without it the bare string `rig_sk_` would
|
|
52
|
+
* take the api-key branch and cost a database round trip per garbage request,
|
|
53
|
+
* which is a free amplification primitive on an unauthenticated surface.
|
|
54
|
+
*/
|
|
55
|
+
function looksLikeApiKey(presented) {
|
|
56
|
+
if (!presented.startsWith(exports.API_KEY_PREFIX))
|
|
57
|
+
return false;
|
|
58
|
+
const body = presented.slice(exports.API_KEY_PREFIX.length);
|
|
59
|
+
return body.length >= exports.API_KEY_BODY_MIN_LENGTH && /^[A-Za-z0-9_-]+$/.test(body);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* What a key may be called in a log line, an error, or a boot banner.
|
|
63
|
+
*
|
|
64
|
+
* Returns the placeholder form for a non-key so that a caller who reaches for
|
|
65
|
+
* this with the wrong variable prints nothing useful rather than printing the
|
|
66
|
+
* whole thing. C2's leak-gate is the belt; this is the braces.
|
|
67
|
+
*/
|
|
68
|
+
function redactApiKey(presented) {
|
|
69
|
+
const suffix = looksLikeApiKey(presented) ? presented.slice(-4) : "????";
|
|
70
|
+
return `${exports.API_KEY_PREFIX}…${suffix}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Narrow an untrusted scope list to the known set (T7: a jsonb/array column
|
|
74
|
+
* is not proof of shape — narrow at the boundary).
|
|
75
|
+
*
|
|
76
|
+
* Unknown entries are DROPPED, not rejected: a row written by a future
|
|
77
|
+
* migration with a scope this build predates should still authorize the
|
|
78
|
+
* scopes this build understands, rather than failing closed on everything.
|
|
79
|
+
* Returns declaration order so two equal grant sets compare equal.
|
|
80
|
+
*/
|
|
81
|
+
function parseScopes(raw) {
|
|
82
|
+
if (!Array.isArray(raw))
|
|
83
|
+
return [];
|
|
84
|
+
const present = new Set(raw.filter((s) => typeof s === "string"));
|
|
85
|
+
return exports.API_KEY_SCOPES.filter((scope) => present.has(scope));
|
|
86
|
+
}
|
|
87
|
+
/** An empty grant set authorizes nothing — stated as code so it cannot be
|
|
88
|
+
* accidentally re-derived as "no scopes means unrestricted". */
|
|
89
|
+
function hasScope(granted, required) {
|
|
90
|
+
return granted.includes(required);
|
|
91
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenancy constants — Phase 6 WS1.
|
|
3
|
+
*
|
|
4
|
+
* Last Verified: 2026-08-04 against migration
|
|
5
|
+
* `supabase/migrations/00012_orgs_projects_memberships.sql` (S4: schema truth
|
|
6
|
+
* is the migration file; never assume an id, read the seed).
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The org that owns everything predating accounts, and the org a
|
|
10
|
+
* single-tenant deployment writes into.
|
|
11
|
+
*
|
|
12
|
+
* A fixed sentinel rather than a random uuid so that policies, migrations and
|
|
13
|
+
* code can all name it without a lookup. Seeded by 00012; a partial unique
|
|
14
|
+
* index on `orgs (is_house) where is_house` makes a second one impossible.
|
|
15
|
+
*
|
|
16
|
+
* On a SELF-HOSTED control plane this is simply "the customer's org" — their
|
|
17
|
+
* database has exactly one, and everything belongs to it. On the hosted plane
|
|
18
|
+
* it is ours, and WS2's `/v1/ingest` never uses it: there, org and project are
|
|
19
|
+
* resolved from the api key and stamped explicitly (T3 — tenancy is derived
|
|
20
|
+
* from the principal, never defaulted).
|
|
21
|
+
*/
|
|
22
|
+
export declare const HOUSE_ORG_ID = "00000000-0000-4000-8000-000000000001";
|
|
23
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/tenancy/constants.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,yCAAyC,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tenancy constants — Phase 6 WS1.
|
|
4
|
+
*
|
|
5
|
+
* Last Verified: 2026-08-04 against migration
|
|
6
|
+
* `supabase/migrations/00012_orgs_projects_memberships.sql` (S4: schema truth
|
|
7
|
+
* is the migration file; never assume an id, read the seed).
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.HOUSE_ORG_ID = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* The org that owns everything predating accounts, and the org a
|
|
13
|
+
* single-tenant deployment writes into.
|
|
14
|
+
*
|
|
15
|
+
* A fixed sentinel rather than a random uuid so that policies, migrations and
|
|
16
|
+
* code can all name it without a lookup. Seeded by 00012; a partial unique
|
|
17
|
+
* index on `orgs (is_house) where is_house` makes a second one impossible.
|
|
18
|
+
*
|
|
19
|
+
* On a SELF-HOSTED control plane this is simply "the customer's org" — their
|
|
20
|
+
* database has exactly one, and everything belongs to it. On the hosted plane
|
|
21
|
+
* it is ours, and WS2's `/v1/ingest` never uses it: there, org and project are
|
|
22
|
+
* resolved from the api key and stamped explicitly (T3 — tenancy is derived
|
|
23
|
+
* from the principal, never defaulted).
|
|
24
|
+
*/
|
|
25
|
+
exports.HOUSE_ORG_ID = "00000000-0000-4000-8000-000000000001";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one secret-scrubber every emission boundary shares — C2's leak gate.
|
|
3
|
+
*
|
|
4
|
+
* ── WHY THIS MOVED HERE (WS3) ────────────────────────────────────────────
|
|
5
|
+
* `scrubSecrets` was born in `watch/run-record.ts`, scoped to the six
|
|
6
|
+
* INFRASTRUCTURE prefixes a failing cron might spill (`sk_live_`, `whsec_`,
|
|
7
|
+
* `rnd_`, a Supabase JWT). WS3 changed the threat model: the BYOK vault now
|
|
8
|
+
* decrypts CUSTOMER PROVIDER keys into data-plane memory at call time, and a
|
|
9
|
+
* decrypted key that reaches an error message, a telemetry row, an export, or
|
|
10
|
+
* a log line is precisely the leak the vault exists to prevent. So the scrubber
|
|
11
|
+
* is promoted to shared, its prefix set widened to every provider family we
|
|
12
|
+
* might hold, and it is exported from the package so the server and the SDK run
|
|
13
|
+
* the SAME gate rather than two that drift.
|
|
14
|
+
*
|
|
15
|
+
* ── PREFIX-BASED, NOT ENTROPY-BASED, ON PURPOSE ──────────────────────────
|
|
16
|
+
* A high-entropy heuristic ("looks random enough") redacts request ids, git
|
|
17
|
+
* shas, and base64 payloads — and a scrubber that eats legitimate text gets
|
|
18
|
+
* muted within a week, which is worse than none. Anchoring on known credential
|
|
19
|
+
* prefixes matches a real key and almost nothing else. The cost is that a
|
|
20
|
+
* provider inventing a brand-new prefix is invisible until added here; that is
|
|
21
|
+
* a one-line change and the correct trade.
|
|
22
|
+
*
|
|
23
|
+
* ── A NAME IS NOT A LEAK ─────────────────────────────────────────────────
|
|
24
|
+
* These patterns match secret VALUES, never their variable names. The string
|
|
25
|
+
* `MODELRIG_SUPABASE_SERVICE_ROLE_KEY` in an error is a name and stays; only a
|
|
26
|
+
* value carrying a live prefix is redacted. `publish-safety.test.ts` relies on
|
|
27
|
+
* the same distinction.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Credential prefixes that mark a key-shaped VALUE. Grep-visible on purpose —
|
|
31
|
+
* `scripts/guardrails.ts` (C2) and this module are the two places the set is
|
|
32
|
+
* spelled, and a test pins that the vault's provider families all appear here.
|
|
33
|
+
*
|
|
34
|
+
* Order does not matter; each is tried independently.
|
|
35
|
+
*/
|
|
36
|
+
export declare const KEY_SHAPED_PREFIXES: readonly string[];
|
|
37
|
+
/**
|
|
38
|
+
* Replace every key-shaped substring with `[redacted]`, leaving all other text
|
|
39
|
+
* intact. Safe to run on any string headed out of the process — failure
|
|
40
|
+
* messages, telemetry notes, export rows, log lines.
|
|
41
|
+
*/
|
|
42
|
+
export declare function scrubSecrets(text: string): string;
|
|
43
|
+
/** True if the text contains anything key-shaped. The predicate behind the
|
|
44
|
+
* build-failing leak-gate test — cheaper to read than a diff of scrubbed vs
|
|
45
|
+
* original, and it says what it means. */
|
|
46
|
+
export declare function containsKeyShapedString(text: string): boolean;
|
|
47
|
+
//# sourceMappingURL=scrub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub.d.ts","sourceRoot":"","sources":["../../src/tenancy/scrub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAiBhD,CAAC;AAkBF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;0CAE0C;AAC1C,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7D"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The one secret-scrubber every emission boundary shares — C2's leak gate.
|
|
4
|
+
*
|
|
5
|
+
* ── WHY THIS MOVED HERE (WS3) ────────────────────────────────────────────
|
|
6
|
+
* `scrubSecrets` was born in `watch/run-record.ts`, scoped to the six
|
|
7
|
+
* INFRASTRUCTURE prefixes a failing cron might spill (`sk_live_`, `whsec_`,
|
|
8
|
+
* `rnd_`, a Supabase JWT). WS3 changed the threat model: the BYOK vault now
|
|
9
|
+
* decrypts CUSTOMER PROVIDER keys into data-plane memory at call time, and a
|
|
10
|
+
* decrypted key that reaches an error message, a telemetry row, an export, or
|
|
11
|
+
* a log line is precisely the leak the vault exists to prevent. So the scrubber
|
|
12
|
+
* is promoted to shared, its prefix set widened to every provider family we
|
|
13
|
+
* might hold, and it is exported from the package so the server and the SDK run
|
|
14
|
+
* the SAME gate rather than two that drift.
|
|
15
|
+
*
|
|
16
|
+
* ── PREFIX-BASED, NOT ENTROPY-BASED, ON PURPOSE ──────────────────────────
|
|
17
|
+
* A high-entropy heuristic ("looks random enough") redacts request ids, git
|
|
18
|
+
* shas, and base64 payloads — and a scrubber that eats legitimate text gets
|
|
19
|
+
* muted within a week, which is worse than none. Anchoring on known credential
|
|
20
|
+
* prefixes matches a real key and almost nothing else. The cost is that a
|
|
21
|
+
* provider inventing a brand-new prefix is invisible until added here; that is
|
|
22
|
+
* a one-line change and the correct trade.
|
|
23
|
+
*
|
|
24
|
+
* ── A NAME IS NOT A LEAK ─────────────────────────────────────────────────
|
|
25
|
+
* These patterns match secret VALUES, never their variable names. The string
|
|
26
|
+
* `MODELRIG_SUPABASE_SERVICE_ROLE_KEY` in an error is a name and stays; only a
|
|
27
|
+
* value carrying a live prefix is redacted. `publish-safety.test.ts` relies on
|
|
28
|
+
* the same distinction.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.KEY_SHAPED_PREFIXES = void 0;
|
|
32
|
+
exports.scrubSecrets = scrubSecrets;
|
|
33
|
+
exports.containsKeyShapedString = containsKeyShapedString;
|
|
34
|
+
/**
|
|
35
|
+
* Credential prefixes that mark a key-shaped VALUE. Grep-visible on purpose —
|
|
36
|
+
* `scripts/guardrails.ts` (C2) and this module are the two places the set is
|
|
37
|
+
* spelled, and a test pins that the vault's provider families all appear here.
|
|
38
|
+
*
|
|
39
|
+
* Order does not matter; each is tried independently.
|
|
40
|
+
*/
|
|
41
|
+
exports.KEY_SHAPED_PREFIXES = [
|
|
42
|
+
// Provider keys the BYOK vault can hold (signup-auth-boundary §BYOK).
|
|
43
|
+
"sk-", // OpenAI, Anthropic (sk-ant-), DeepSeek, most openai-compat
|
|
44
|
+
"AIza", // Google / Gemini
|
|
45
|
+
"xai-", // xAI / Grok
|
|
46
|
+
"fw_", // Fireworks
|
|
47
|
+
"tvly-", // Tavily
|
|
48
|
+
// ModelRig's own machine credential.
|
|
49
|
+
"rig_sk_",
|
|
50
|
+
// Platform / infrastructure secrets (the original run-record set).
|
|
51
|
+
"sk_live_",
|
|
52
|
+
"sk_test_",
|
|
53
|
+
"rk_live_", // Stripe restricted keys
|
|
54
|
+
"rk_test_",
|
|
55
|
+
"whsec_", // Stripe webhook signing
|
|
56
|
+
"rnd_", // Render API
|
|
57
|
+
"eyJ", // JWT (Supabase publishable / service-role) — base64url header
|
|
58
|
+
];
|
|
59
|
+
/** The body that must follow a prefix for it to count as a key: at least eight
|
|
60
|
+
* of the characters a base64url/hex secret is made of. Keeps `sk-` from eating
|
|
61
|
+
* the word "sk-" in prose while catching every real key, which is long. */
|
|
62
|
+
const KEY_BODY = "[A-Za-z0-9_\\-.]{8,}";
|
|
63
|
+
function escape(prefix) {
|
|
64
|
+
return prefix.replace(/[.*+?^${}()|[\]\\-]/g, "\\$&");
|
|
65
|
+
}
|
|
66
|
+
/** One combined pattern, rebuilt per call (regex objects carry `lastIndex`
|
|
67
|
+
* state with the `g` flag, so a shared instance is a footgun across callers). */
|
|
68
|
+
function keyPattern() {
|
|
69
|
+
const alt = exports.KEY_SHAPED_PREFIXES.map((p) => escape(p) + KEY_BODY).join("|");
|
|
70
|
+
return new RegExp(alt, "g");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Replace every key-shaped substring with `[redacted]`, leaving all other text
|
|
74
|
+
* intact. Safe to run on any string headed out of the process — failure
|
|
75
|
+
* messages, telemetry notes, export rows, log lines.
|
|
76
|
+
*/
|
|
77
|
+
function scrubSecrets(text) {
|
|
78
|
+
return text.replace(keyPattern(), "[redacted]");
|
|
79
|
+
}
|
|
80
|
+
/** True if the text contains anything key-shaped. The predicate behind the
|
|
81
|
+
* build-failing leak-gate test — cheaper to read than a diff of scrubbed vs
|
|
82
|
+
* original, and it says what it means. */
|
|
83
|
+
function containsKeyShapedString(text) {
|
|
84
|
+
return keyPattern().test(text);
|
|
85
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Errors that teach — Phase 5 WS3.
|
|
3
|
+
*
|
|
4
|
+
* ONE table, not prose scattered across call sites. Every failure class here
|
|
5
|
+
* answers three questions in the order a stuck engineer asks them:
|
|
6
|
+
*
|
|
7
|
+
* 1. what happened? → `what` (plain language, no jargon-first)
|
|
8
|
+
* 2. what do I do about it? → `fixHint` (an action, not a diagnosis)
|
|
9
|
+
* 3. where do I read more? → `docsUrl` (a deep link, not a homepage)
|
|
10
|
+
*
|
|
11
|
+
* And where the fix is MECHANICAL, a fourth: `suggestedChange` — a
|
|
12
|
+
* JSON-patch-shaped hint a coding agent can apply without inventing
|
|
13
|
+
* anything. Only classes whose remedy is genuinely a config edit carry one;
|
|
14
|
+
* inventing a patch for "the model refused" would be worse than silence.
|
|
15
|
+
*
|
|
16
|
+
* The completeness test in failures-teaching.test.ts fails the build if a
|
|
17
|
+
* new FailureClass is added without an entry here, so this table cannot
|
|
18
|
+
* silently fall behind the taxonomy.
|
|
19
|
+
*/
|
|
20
|
+
import type { FailureClass, RigFailure } from "./failures";
|
|
21
|
+
/** A JSON-Patch-shaped edit (RFC 6902 subset) against the route bundle. */
|
|
22
|
+
export interface SuggestedChange {
|
|
23
|
+
readonly op: "add" | "replace" | "remove";
|
|
24
|
+
/** JSON Pointer into the route bundle YAML, e.g. "/policy/retries/network". */
|
|
25
|
+
readonly path: string;
|
|
26
|
+
readonly value?: unknown;
|
|
27
|
+
/** Why this edit is the right one — shown beside the diff. */
|
|
28
|
+
readonly because: string;
|
|
29
|
+
}
|
|
30
|
+
export interface FailureTeaching {
|
|
31
|
+
readonly class: FailureClass;
|
|
32
|
+
/** One sentence: what this class MEANS, in the reader's terms. */
|
|
33
|
+
readonly what: string;
|
|
34
|
+
/** The default action to take. Overridden by a failure's own fixHint when
|
|
35
|
+
* the site knows something more specific (a provider name, a model). */
|
|
36
|
+
readonly fixHint: string;
|
|
37
|
+
readonly docsUrl: string;
|
|
38
|
+
/** Build a mechanical edit for this failure, when one exists. */
|
|
39
|
+
readonly suggestedChange?: (failure: RigFailure) => SuggestedChange | null;
|
|
40
|
+
}
|
|
41
|
+
export declare const FAILURE_TEACHING: Readonly<Record<FailureClass, FailureTeaching>>;
|
|
42
|
+
/** The rendered teaching for one failure: the site's own fixHint wins when
|
|
43
|
+
* it has one (it knows the provider and model), the table fills the rest. */
|
|
44
|
+
export interface TeachableFailure {
|
|
45
|
+
readonly class: FailureClass;
|
|
46
|
+
readonly message: string;
|
|
47
|
+
readonly what: string;
|
|
48
|
+
readonly fixHint: string;
|
|
49
|
+
readonly docsUrl: string;
|
|
50
|
+
readonly suggestedChange: SuggestedChange | null;
|
|
51
|
+
}
|
|
52
|
+
export declare function teach(failure: RigFailure): TeachableFailure;
|
|
53
|
+
/** Terminal-friendly rendering — used by the CLI. Kept here so the CLI and
|
|
54
|
+
* the server cannot drift into two different-looking errors. */
|
|
55
|
+
export declare function renderFailureForTerminal(failure: RigFailure): string;
|
|
56
|
+
//# sourceMappingURL=failures-teaching.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failures-teaching.d.ts","sourceRoot":"","sources":["../../src/types/failures-teaching.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3D,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC1C,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;4EACwE;IACxE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,eAAe,GAAG,IAAI,CAAC;CAC5E;AAID,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,CA+I5E,CAAC;AAiBF;6EAC6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;CAClD;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,gBAAgB,CAU3D;AAED;gEACgE;AAChE,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAapE"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Errors that teach — Phase 5 WS3.
|
|
4
|
+
*
|
|
5
|
+
* ONE table, not prose scattered across call sites. Every failure class here
|
|
6
|
+
* answers three questions in the order a stuck engineer asks them:
|
|
7
|
+
*
|
|
8
|
+
* 1. what happened? → `what` (plain language, no jargon-first)
|
|
9
|
+
* 2. what do I do about it? → `fixHint` (an action, not a diagnosis)
|
|
10
|
+
* 3. where do I read more? → `docsUrl` (a deep link, not a homepage)
|
|
11
|
+
*
|
|
12
|
+
* And where the fix is MECHANICAL, a fourth: `suggestedChange` — a
|
|
13
|
+
* JSON-patch-shaped hint a coding agent can apply without inventing
|
|
14
|
+
* anything. Only classes whose remedy is genuinely a config edit carry one;
|
|
15
|
+
* inventing a patch for "the model refused" would be worse than silence.
|
|
16
|
+
*
|
|
17
|
+
* The completeness test in failures-teaching.test.ts fails the build if a
|
|
18
|
+
* new FailureClass is added without an entry here, so this table cannot
|
|
19
|
+
* silently fall behind the taxonomy.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.FAILURE_TEACHING = void 0;
|
|
23
|
+
exports.teach = teach;
|
|
24
|
+
exports.renderFailureForTerminal = renderFailureForTerminal;
|
|
25
|
+
const DOCS_BASE = "https://modelrig.dev/docs/failures";
|
|
26
|
+
exports.FAILURE_TEACHING = {
|
|
27
|
+
content_invalid: {
|
|
28
|
+
class: "content_invalid",
|
|
29
|
+
what: "The model answered, but the answer did not match the schema your route requires — so it " +
|
|
30
|
+
"was rejected rather than passed downstream.",
|
|
31
|
+
fixHint: "Retry on this class is budgeted separately, so a flaky schema cannot eat your network " +
|
|
32
|
+
"retries. If it persists, the model likely cannot hold this shape: check the registry for " +
|
|
33
|
+
"a candidate with probed native structured output.",
|
|
34
|
+
docsUrl: `${DOCS_BASE}#content_invalid`,
|
|
35
|
+
// Mechanical: raise the content-retry budget. A repair rung is a bigger
|
|
36
|
+
// decision, so we suggest the small edit and name the bigger one in prose.
|
|
37
|
+
suggestedChange: () => ({
|
|
38
|
+
op: "replace",
|
|
39
|
+
path: "/policy/retries/content_invalid",
|
|
40
|
+
value: 3,
|
|
41
|
+
because: "A schema miss is usually transient at low retry counts; three attempts is the point " +
|
|
42
|
+
"past which the model is telling you it cannot hold this shape.",
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
capacity_shed: {
|
|
46
|
+
class: "capacity_shed",
|
|
47
|
+
what: "The provider shed your request rather than serving it — a 429, a 503, or a flex-tier " +
|
|
48
|
+
"rejection. Nothing was wrong with the request.",
|
|
49
|
+
fixHint: "Capacity retries back off automatically. If it happens constantly, you are asking for " +
|
|
50
|
+
"discounted sheddable capacity during peak: move the route off the flex tier, or add a " +
|
|
51
|
+
"candidate on another provider so a shed on one does not stall the route.",
|
|
52
|
+
docsUrl: `${DOCS_BASE}#capacity_shed`,
|
|
53
|
+
suggestedChange: () => ({
|
|
54
|
+
op: "replace",
|
|
55
|
+
path: "/policy/tier",
|
|
56
|
+
value: "standard",
|
|
57
|
+
because: "Flex capacity is discounted because it is sheddable. If sheds are routine on this " +
|
|
58
|
+
"route, standard tier is the cheaper choice once retries are counted.",
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
network: {
|
|
62
|
+
class: "network",
|
|
63
|
+
what: "The request never completed at the transport level — DNS, TLS, a dropped connection, or " +
|
|
64
|
+
"an error we could not classify any more precisely.",
|
|
65
|
+
fixHint: "These retry on their own budget. A persistent one is usually local: check egress, proxy " +
|
|
66
|
+
"settings, and whether the provider is up before changing anything in the route.",
|
|
67
|
+
docsUrl: `${DOCS_BASE}#network`,
|
|
68
|
+
},
|
|
69
|
+
refusal: {
|
|
70
|
+
class: "refusal",
|
|
71
|
+
what: "The model declined the task itself — this is an answer, not a fault.",
|
|
72
|
+
fixHint: "Retrying a refusal usually earns another refusal. Read the prompt as the model saw it: " +
|
|
73
|
+
"the input often contains something the model's policy will not touch. A different " +
|
|
74
|
+
"candidate may answer, but check the prompt first.",
|
|
75
|
+
docsUrl: `${DOCS_BASE}#refusal`,
|
|
76
|
+
// Deliberately no suggestedChange: no config edit fixes a refusal, and a
|
|
77
|
+
// patch that pretends otherwise would send someone down the wrong path.
|
|
78
|
+
},
|
|
79
|
+
cache_invalid: {
|
|
80
|
+
class: "cache_invalid",
|
|
81
|
+
what: "The provider-side cache or cached context this call relied on expired or was rejected, " +
|
|
82
|
+
"so the discount it was counting on is gone.",
|
|
83
|
+
fixHint: "Re-running usually repopulates the cache. If it recurs, your cached prefix is probably " +
|
|
84
|
+
"changing between calls — anything varying near the START of the prompt defeats prefix " +
|
|
85
|
+
"caching, so move volatile values to the end.",
|
|
86
|
+
docsUrl: `${DOCS_BASE}#cache_invalid`,
|
|
87
|
+
},
|
|
88
|
+
timeout: {
|
|
89
|
+
class: "timeout",
|
|
90
|
+
what: "The call passed the route's wall-clock deadline before the model finished.",
|
|
91
|
+
fixHint: "Either the deadline is too tight for this task or the model is slower than assumed. " +
|
|
92
|
+
"Check the latency column for this model before raising the timeout — a timeout raised to " +
|
|
93
|
+
"cover a slow model just makes a slow route.",
|
|
94
|
+
docsUrl: `${DOCS_BASE}#timeout`,
|
|
95
|
+
suggestedChange: () => ({
|
|
96
|
+
op: "replace",
|
|
97
|
+
path: "/policy/timeout_ms",
|
|
98
|
+
value: 300000,
|
|
99
|
+
because: "Long structured generations routinely exceed a 2-minute default. Five minutes is the " +
|
|
100
|
+
"ceiling used by the pipeline routes that produce full reports.",
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
config_auth: {
|
|
104
|
+
class: "config_auth",
|
|
105
|
+
what: "The provider rejected the credentials, or there were none to send. This is never " +
|
|
106
|
+
"retried — retrying a dead key just spends time.",
|
|
107
|
+
fixHint: "Set the provider's API key in this environment. Until then the candidate is inert: the " +
|
|
108
|
+
"route still serves on its keyed candidates, and this one is skipped at dispatch.",
|
|
109
|
+
docsUrl: `${DOCS_BASE}#config_auth`,
|
|
110
|
+
// Mechanical only in the sense of naming the variable — the value is a
|
|
111
|
+
// secret and never appears in a suggested patch.
|
|
112
|
+
suggestedChange: (failure) => failure.provider === undefined
|
|
113
|
+
? null
|
|
114
|
+
: {
|
|
115
|
+
op: "add",
|
|
116
|
+
path: `/env/${envVarNameFor(failure.provider)}`,
|
|
117
|
+
value: "<your API key>",
|
|
118
|
+
because: `Provider "${failure.provider}" has no usable credentials here. Set ` +
|
|
119
|
+
`${envVarNameFor(failure.provider)} in the environment — never in the route file.`,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
budget_exhausted: {
|
|
123
|
+
class: "budget_exhausted",
|
|
124
|
+
what: "The envelope this work was running under hit its cap, so spending stopped mid-run. " +
|
|
125
|
+
"This is the budget machinery working, not a bug.",
|
|
126
|
+
fixHint: "Decide whether the cap was wrong or the run was. Check the envelope's spend against what " +
|
|
127
|
+
"you expected before raising it — a runaway loop and an under-sized cap look identical " +
|
|
128
|
+
"from here, and only one of them is fixed by more money.",
|
|
129
|
+
docsUrl: `${DOCS_BASE}#budget_exhausted`,
|
|
130
|
+
},
|
|
131
|
+
invariant_violation: {
|
|
132
|
+
class: "invariant_violation",
|
|
133
|
+
what: "Something tried to serve a model your route never declared. The run was stopped rather " +
|
|
134
|
+
"than allowed to bill you for a model you did not choose.",
|
|
135
|
+
fixHint: "This is a bug in ModelRig, not in your configuration — the candidate set is meant to be " +
|
|
136
|
+
"an upper bound that nothing can escape. Please report it with the route name and the " +
|
|
137
|
+
"model that appeared.",
|
|
138
|
+
docsUrl: `${DOCS_BASE}#invariant_violation`,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
/** Provider → the env var that keys it. Mirrors config.ts's PROVIDER_KEY_ENV;
|
|
142
|
+
* kept as a local lookup so this table has no import cycle with config. */
|
|
143
|
+
function envVarNameFor(provider) {
|
|
144
|
+
const known = {
|
|
145
|
+
gemini: "GEMINI_API_KEY",
|
|
146
|
+
openai: "OPENAI_API_KEY",
|
|
147
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
148
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
149
|
+
grok: "XAI_API_KEY",
|
|
150
|
+
deepinfra: "MODELRIG_DEEPINFRA_API_KEY",
|
|
151
|
+
fireworks: "MODELRIG_FIREWORKS_API_KEY",
|
|
152
|
+
};
|
|
153
|
+
return known[provider] ?? `${provider.toUpperCase()}_API_KEY`;
|
|
154
|
+
}
|
|
155
|
+
function teach(failure) {
|
|
156
|
+
const entry = exports.FAILURE_TEACHING[failure.class];
|
|
157
|
+
return {
|
|
158
|
+
class: failure.class,
|
|
159
|
+
message: failure.message,
|
|
160
|
+
what: entry.what,
|
|
161
|
+
fixHint: failure.fixHint ?? entry.fixHint,
|
|
162
|
+
docsUrl: entry.docsUrl,
|
|
163
|
+
suggestedChange: entry.suggestedChange?.(failure) ?? null,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** Terminal-friendly rendering — used by the CLI. Kept here so the CLI and
|
|
167
|
+
* the server cannot drift into two different-looking errors. */
|
|
168
|
+
function renderFailureForTerminal(failure) {
|
|
169
|
+
const t = teach(failure);
|
|
170
|
+
const lines = [`[${t.class}] ${t.message}`, "", t.what, "", `Fix: ${t.fixHint}`, `Docs: ${t.docsUrl}`];
|
|
171
|
+
if (t.suggestedChange !== null) {
|
|
172
|
+
const change = t.suggestedChange;
|
|
173
|
+
lines.push("", `Suggested change: ${change.op} ${change.path}` +
|
|
174
|
+
(change.value === undefined ? "" : ` = ${JSON.stringify(change.value)}`), ` ${change.because}`);
|
|
175
|
+
}
|
|
176
|
+
return lines.join("\n");
|
|
177
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed failure taxonomy — spec §2, §4.
|
|
3
|
+
* Adapters MUST map every provider error into a RigFailure; raw provider
|
|
4
|
+
* errors never cross the adapter boundary. Retry budgets are per-class and
|
|
5
|
+
* non-fungible (see run/retry.ts).
|
|
6
|
+
*/
|
|
7
|
+
export type FailureClass = "content_invalid" | "capacity_shed" | "network" | "refusal" | "cache_invalid" | "timeout" | "config_auth" | "budget_exhausted" | "invariant_violation";
|
|
8
|
+
export interface RigFailure {
|
|
9
|
+
readonly class: FailureClass;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
/** Provider/model that produced the failure, when applicable. */
|
|
12
|
+
readonly provider?: string;
|
|
13
|
+
readonly model?: string;
|
|
14
|
+
/** Raw underlying error for diagnostics; never used for control flow. */
|
|
15
|
+
readonly cause?: unknown;
|
|
16
|
+
/** Machine-readable remediation (errors-that-teach lands fully in Phase 5). */
|
|
17
|
+
readonly fixHint?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const TERMINAL_CLASSES: ReadonlySet<FailureClass>;
|
|
20
|
+
/** Error subclass for the rare paths that must throw (config errors, terminal
|
|
21
|
+
* failures surfacing to the caller). The failure travels intact on `.failure`. */
|
|
22
|
+
export declare class RigFailureError extends Error {
|
|
23
|
+
readonly failure: RigFailure;
|
|
24
|
+
constructor(failure: RigFailure);
|
|
25
|
+
}
|
|
26
|
+
export declare function isRigFailure(value: unknown): value is RigFailure;
|
|
27
|
+
//# sourceMappingURL=failures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failures.d.ts","sourceRoot":"","sources":["../../src/types/failures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,YAAY,GACpB,iBAAiB,GACjB,eAAe,GACf,SAAS,GACT,SAAS,GACT,eAAe,GACf,SAAS,GACT,aAAa,GACb,kBAAkB,GAClB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,YAAY,CAGrD,CAAC;AAcH;kFACkF;AAClF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;gBAEjB,OAAO,EAAE,UAAU;CAKhC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAIhE"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Typed failure taxonomy — spec §2, §4.
|
|
4
|
+
* Adapters MUST map every provider error into a RigFailure; raw provider
|
|
5
|
+
* errors never cross the adapter boundary. Retry budgets are per-class and
|
|
6
|
+
* non-fungible (see run/retry.ts).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.RigFailureError = exports.TERMINAL_CLASSES = void 0;
|
|
10
|
+
exports.isRigFailure = isRigFailure;
|
|
11
|
+
exports.TERMINAL_CLASSES = new Set([
|
|
12
|
+
"budget_exhausted",
|
|
13
|
+
"invariant_violation",
|
|
14
|
+
]);
|
|
15
|
+
const FAILURE_CLASSES = new Set([
|
|
16
|
+
"content_invalid",
|
|
17
|
+
"capacity_shed",
|
|
18
|
+
"network",
|
|
19
|
+
"refusal",
|
|
20
|
+
"cache_invalid",
|
|
21
|
+
"timeout",
|
|
22
|
+
"config_auth",
|
|
23
|
+
"budget_exhausted",
|
|
24
|
+
"invariant_violation",
|
|
25
|
+
]);
|
|
26
|
+
/** Error subclass for the rare paths that must throw (config errors, terminal
|
|
27
|
+
* failures surfacing to the caller). The failure travels intact on `.failure`. */
|
|
28
|
+
class RigFailureError extends Error {
|
|
29
|
+
constructor(failure) {
|
|
30
|
+
super(`[${failure.class}] ${failure.message}`);
|
|
31
|
+
this.name = "RigFailureError";
|
|
32
|
+
this.failure = failure;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.RigFailureError = RigFailureError;
|
|
36
|
+
function isRigFailure(value) {
|
|
37
|
+
if (typeof value !== "object" || value === null)
|
|
38
|
+
return false;
|
|
39
|
+
const v = value;
|
|
40
|
+
return typeof v.class === "string" && FAILURE_CLASSES.has(v.class) && typeof v.message === "string";
|
|
41
|
+
}
|