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,436 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Routes-as-code loader — spec §3, plan step 3.
|
|
4
|
+
* Scans a routes directory for *.yaml bundles, validates structurally,
|
|
5
|
+
* resolves schema/prompt paths relative to the bundle file, freezes.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.RouteConfigError = void 0;
|
|
9
|
+
exports.loadRoutes = loadRoutes;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const yaml_1 = require("yaml");
|
|
13
|
+
const failures_1 = require("../types/failures");
|
|
14
|
+
const fingerprint_1 = require("../registry/fingerprint");
|
|
15
|
+
const template_1 = require("./template");
|
|
16
|
+
/**
|
|
17
|
+
* Structured config error thrown for any invalid bundle (never a raw Error).
|
|
18
|
+
* Load-time problems are deliberately NOT RigFailures — the failure taxonomy
|
|
19
|
+
* classifies inference attempts; a malformed bundle means the rig was never
|
|
20
|
+
* runnable for that route.
|
|
21
|
+
*/
|
|
22
|
+
class RouteConfigError extends Error {
|
|
23
|
+
constructor(message, opts = {}) {
|
|
24
|
+
const problems = opts.problems ?? [];
|
|
25
|
+
super(problems.length > 0 ? `${message}: ${problems.join("; ")}` : message);
|
|
26
|
+
this.name = "RouteConfigError";
|
|
27
|
+
this.file = opts.file ?? null;
|
|
28
|
+
this.problems = problems;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.RouteConfigError = RouteConfigError;
|
|
32
|
+
const PROVIDERS = new Set([
|
|
33
|
+
"gemini",
|
|
34
|
+
"openai",
|
|
35
|
+
"deepseek",
|
|
36
|
+
"anthropic",
|
|
37
|
+
"grok",
|
|
38
|
+
]);
|
|
39
|
+
const REQUIREMENTS = new Set([
|
|
40
|
+
"schema_conformant",
|
|
41
|
+
"grounded",
|
|
42
|
+
"zero_retention",
|
|
43
|
+
"trace_visible",
|
|
44
|
+
]);
|
|
45
|
+
const PREFERENCES = new Set(["cost", "latency"]);
|
|
46
|
+
const TIERS = new Set(["standard", "flex", "priority"]);
|
|
47
|
+
const JSON_MODES = new Set(["native", "json_mode"]);
|
|
48
|
+
// config_auth is deliberately absent (F2): non-retryable by contract — a
|
|
49
|
+
// bundle configuring retries for it is a config error, same as terminals.
|
|
50
|
+
const RETRYABLE_CLASSES = new Set([
|
|
51
|
+
"content_invalid",
|
|
52
|
+
"capacity_shed",
|
|
53
|
+
"network",
|
|
54
|
+
"refusal",
|
|
55
|
+
"cache_invalid",
|
|
56
|
+
"timeout",
|
|
57
|
+
].filter((c) => !failures_1.TERMINAL_CLASSES.has(c)));
|
|
58
|
+
function isRecord(v) {
|
|
59
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
60
|
+
}
|
|
61
|
+
function parseCandidates(raw, problems) {
|
|
62
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
63
|
+
problems.push("candidates must be a non-empty list");
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const [i, c] of raw.entries()) {
|
|
68
|
+
if (!isRecord(c) || typeof c.provider !== "string" || typeof c.model !== "string" || c.model === "") {
|
|
69
|
+
problems.push(`candidates[${i}] must be {provider, model}`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!PROVIDERS.has(c.provider)) {
|
|
73
|
+
problems.push(`candidates[${i}].provider "${c.provider}" is not a known provider`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
out.push(Object.freeze({ provider: c.provider, model: c.model }));
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
function parseStringList(raw, field, allowed, problems) {
|
|
81
|
+
if (raw === undefined || raw === null)
|
|
82
|
+
return [];
|
|
83
|
+
if (!Array.isArray(raw)) {
|
|
84
|
+
problems.push(`${field} must be a list`);
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const out = [];
|
|
88
|
+
for (const v of raw) {
|
|
89
|
+
if (typeof v !== "string" || !allowed.has(v)) {
|
|
90
|
+
problems.push(`${field} entry "${String(v)}" is not one of [${[...allowed].join(", ")}]`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
out.push(v);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
function parsePolicy(raw, problems) {
|
|
98
|
+
const fallback = Object.freeze({
|
|
99
|
+
retries: Object.freeze({}),
|
|
100
|
+
timeoutMs: 300000,
|
|
101
|
+
tier: "standard",
|
|
102
|
+
json: "native",
|
|
103
|
+
});
|
|
104
|
+
if (!isRecord(raw)) {
|
|
105
|
+
problems.push("policy must be an object");
|
|
106
|
+
return fallback;
|
|
107
|
+
}
|
|
108
|
+
const retries = {};
|
|
109
|
+
if (raw.retries !== undefined) {
|
|
110
|
+
if (!isRecord(raw.retries)) {
|
|
111
|
+
problems.push("policy.retries must be an object");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
for (const [cls, n] of Object.entries(raw.retries)) {
|
|
115
|
+
if (!RETRYABLE_CLASSES.has(cls)) {
|
|
116
|
+
problems.push(`policy.retries key "${cls}" is not a retryable failure class`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 0) {
|
|
120
|
+
problems.push(`policy.retries.${cls} must be a non-negative integer`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
retries[cls] = n;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const timeoutMs = raw.timeout_ms;
|
|
128
|
+
if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
129
|
+
problems.push("policy.timeout_ms must be a positive integer");
|
|
130
|
+
}
|
|
131
|
+
const tier = raw.tier ?? "standard";
|
|
132
|
+
if (typeof tier !== "string" || !TIERS.has(tier)) {
|
|
133
|
+
problems.push(`policy.tier "${String(tier)}" is not one of [${[...TIERS].join(", ")}]`);
|
|
134
|
+
}
|
|
135
|
+
const json = raw.json ?? "native";
|
|
136
|
+
if (typeof json !== "string" || !JSON_MODES.has(json)) {
|
|
137
|
+
problems.push(`policy.json "${String(json)}" is not one of [native, json_mode]`);
|
|
138
|
+
}
|
|
139
|
+
if (problems.length > 0)
|
|
140
|
+
return fallback;
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
retries: Object.freeze(retries),
|
|
143
|
+
timeoutMs: timeoutMs,
|
|
144
|
+
tier: tier,
|
|
145
|
+
json: json,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/** "provider/model" string or {provider, model} mapping → CandidateSpec. */
|
|
149
|
+
function parseRepairModel(raw, field, problems) {
|
|
150
|
+
let provider;
|
|
151
|
+
let model;
|
|
152
|
+
if (typeof raw === "string") {
|
|
153
|
+
const slash = raw.indexOf("/");
|
|
154
|
+
if (slash <= 0 || slash === raw.length - 1) {
|
|
155
|
+
problems.push(`${field} must be "provider/model" or {provider, model}`);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
provider = raw.slice(0, slash);
|
|
159
|
+
model = raw.slice(slash + 1);
|
|
160
|
+
}
|
|
161
|
+
else if (isRecord(raw)) {
|
|
162
|
+
provider = raw.provider;
|
|
163
|
+
model = raw.model;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
problems.push(`${field} must be "provider/model" or {provider, model}`);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
if (typeof provider !== "string" || !PROVIDERS.has(provider) || typeof model !== "string" || model === "") {
|
|
170
|
+
problems.push(`${field} has an unknown provider or empty model`);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return Object.freeze({ provider: provider, model });
|
|
174
|
+
}
|
|
175
|
+
function parseRepair(raw, field, candidates, problems) {
|
|
176
|
+
if (raw === undefined || raw === null)
|
|
177
|
+
return undefined;
|
|
178
|
+
if (!isRecord(raw)) {
|
|
179
|
+
problems.push(`${field} must be an object {max_repairs, repair_model?}`);
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
const maxRepairs = raw.max_repairs;
|
|
183
|
+
if (typeof maxRepairs !== "number" || !Number.isInteger(maxRepairs) || maxRepairs < 1 || maxRepairs > 2) {
|
|
184
|
+
problems.push(`${field}.max_repairs must be 1 or 2 (retry-with-errors, then optional repair model)`);
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
let repairModel = null;
|
|
188
|
+
if (raw.repair_model !== undefined) {
|
|
189
|
+
repairModel = parseRepairModel(raw.repair_model, `${field}.repair_model`, problems);
|
|
190
|
+
if (repairModel !== null &&
|
|
191
|
+
!candidates.some((c) => c.provider === repairModel?.provider && c.model === repairModel?.model)) {
|
|
192
|
+
// Candidate-set invariant: the repair model must be a declared candidate.
|
|
193
|
+
problems.push(`${field}.repair_model (${repairModel.provider}/${repairModel.model}) is not in the route's declared candidates`);
|
|
194
|
+
repairModel = null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (maxRepairs === 2 && repairModel === null) {
|
|
198
|
+
problems.push(`${field}.max_repairs=2 requires repair_model (attempt 2 is the designated repair model)`);
|
|
199
|
+
}
|
|
200
|
+
return Object.freeze({
|
|
201
|
+
maxRepairs,
|
|
202
|
+
...(repairModel !== null ? { repairModel } : {}),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function parseVariants(raw, routeCandidates, problems) {
|
|
206
|
+
if (raw === undefined || raw === null)
|
|
207
|
+
return [];
|
|
208
|
+
if (!Array.isArray(raw)) {
|
|
209
|
+
problems.push("variants must be a list");
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
const seen = new Set();
|
|
213
|
+
const out = [];
|
|
214
|
+
const inDeclared = (c) => routeCandidates.some((rc) => rc.provider === c.provider && rc.model === c.model);
|
|
215
|
+
for (const [i, v] of raw.entries()) {
|
|
216
|
+
const field = `variants[${i}]`;
|
|
217
|
+
if (!isRecord(v)) {
|
|
218
|
+
problems.push(`${field} must be a mapping`);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (typeof v.name !== "string" || v.name === "") {
|
|
222
|
+
problems.push(`${field}.name must be a non-empty string`);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (v.name === "default") {
|
|
226
|
+
problems.push(`${field}.name "default" is reserved (the route's base config)`);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (seen.has(v.name)) {
|
|
230
|
+
problems.push(`duplicate variant name "${v.name}"`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
seen.add(v.name);
|
|
234
|
+
let candidates;
|
|
235
|
+
if (v.candidates !== undefined) {
|
|
236
|
+
candidates = parseCandidates(v.candidates, problems);
|
|
237
|
+
for (const c of candidates) {
|
|
238
|
+
if (!inDeclared(c)) {
|
|
239
|
+
// Candidate-set invariant: variants narrow, never widen.
|
|
240
|
+
problems.push(`${field} candidate (${c.provider}, ${c.model}) is not in the route's declared candidates`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (v.prompt_override !== undefined && v.prompt_append !== undefined) {
|
|
245
|
+
problems.push(`${field} sets both prompt_override and prompt_append — they are mutually exclusive`);
|
|
246
|
+
}
|
|
247
|
+
for (const key of ["prompt_override", "prompt_append", "scaffold"]) {
|
|
248
|
+
if (v[key] !== undefined && typeof v[key] !== "string") {
|
|
249
|
+
problems.push(`${field}.${key} must be a string`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (v.json !== undefined && (typeof v.json !== "string" || !JSON_MODES.has(v.json))) {
|
|
253
|
+
problems.push(`${field}.json must be one of [native, json_mode]`);
|
|
254
|
+
}
|
|
255
|
+
const repair = parseRepair(v.repair, `${field}.repair`, routeCandidates, problems);
|
|
256
|
+
out.push(Object.freeze({
|
|
257
|
+
name: v.name,
|
|
258
|
+
...(candidates !== undefined ? { candidates: Object.freeze(candidates) } : {}),
|
|
259
|
+
...(typeof v.prompt_override === "string" ? { promptOverride: v.prompt_override } : {}),
|
|
260
|
+
...(typeof v.prompt_append === "string" ? { promptAppend: v.prompt_append } : {}),
|
|
261
|
+
...(typeof v.scaffold === "string" ? { scaffold: v.scaffold } : {}),
|
|
262
|
+
...(typeof v.json === "string" && JSON_MODES.has(v.json)
|
|
263
|
+
? { json: v.json }
|
|
264
|
+
: {}),
|
|
265
|
+
...(repair !== undefined ? { repair } : {}),
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
function readSchema(schemaRaw, bundleDir, problems) {
|
|
271
|
+
if (schemaRaw === null || schemaRaw === undefined)
|
|
272
|
+
return null;
|
|
273
|
+
if (typeof schemaRaw !== "string" || schemaRaw === "") {
|
|
274
|
+
problems.push("schema must be a relative path or null");
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const schemaPath = (0, node_path_1.resolve)(bundleDir, schemaRaw);
|
|
278
|
+
if (!(0, node_fs_1.existsSync)(schemaPath)) {
|
|
279
|
+
problems.push(`schema file not found: ${schemaRaw}`);
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(schemaPath, "utf8"));
|
|
284
|
+
if (!isRecord(parsed)) {
|
|
285
|
+
problems.push(`schema file ${schemaRaw} must contain a JSON object`);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return parsed;
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
problems.push(`schema file ${schemaRaw} is not valid JSON: ${err.message}`);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function loadBundleFile(filePath) {
|
|
296
|
+
const problems = [];
|
|
297
|
+
let doc;
|
|
298
|
+
try {
|
|
299
|
+
doc = (0, yaml_1.parse)((0, node_fs_1.readFileSync)(filePath, "utf8"));
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
throw new RouteConfigError(`invalid YAML in route bundle`, {
|
|
303
|
+
file: filePath,
|
|
304
|
+
problems: [err.message],
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
if (!isRecord(doc)) {
|
|
308
|
+
throw new RouteConfigError("route bundle must be a YAML mapping", { file: filePath });
|
|
309
|
+
}
|
|
310
|
+
const route = doc.route;
|
|
311
|
+
if (typeof route !== "string" || route === "")
|
|
312
|
+
problems.push("route must be a non-empty string");
|
|
313
|
+
const version = doc.version;
|
|
314
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version < 1) {
|
|
315
|
+
problems.push("version must be an integer >= 1");
|
|
316
|
+
}
|
|
317
|
+
const bundleDir = (0, node_path_1.dirname)(filePath);
|
|
318
|
+
const candidates = parseCandidates(doc.candidates, problems);
|
|
319
|
+
const require = parseStringList(doc.require, "require", REQUIREMENTS, problems);
|
|
320
|
+
const prefer = parseStringList(doc.prefer, "prefer", PREFERENCES, problems);
|
|
321
|
+
const outputSchema = readSchema(doc.schema, bundleDir, problems);
|
|
322
|
+
const policy = parsePolicy(doc.policy, problems);
|
|
323
|
+
// Phase 3 (bundle v2, all additive): variants, repair, capture opt-in,
|
|
324
|
+
// grounding-inject query template.
|
|
325
|
+
const variants = parseVariants(doc.variants, candidates, problems);
|
|
326
|
+
const repair = parseRepair(doc.repair, "repair", candidates, problems);
|
|
327
|
+
const capture = doc.capture ?? false;
|
|
328
|
+
if (typeof capture !== "boolean") {
|
|
329
|
+
problems.push("capture must be a boolean");
|
|
330
|
+
}
|
|
331
|
+
let groundingQuery;
|
|
332
|
+
if (doc.grounding !== undefined) {
|
|
333
|
+
if (!isRecord(doc.grounding) || typeof doc.grounding.query !== "string" || doc.grounding.query === "") {
|
|
334
|
+
problems.push("grounding must be an object {query: <template string>}");
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
groundingQuery = doc.grounding.query;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
let systemTemplate = "";
|
|
341
|
+
let variables = [];
|
|
342
|
+
if (!isRecord(doc.prompt) || typeof doc.prompt.system !== "string") {
|
|
343
|
+
problems.push("prompt.system must be a relative path");
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
const promptPath = (0, node_path_1.resolve)(bundleDir, doc.prompt.system);
|
|
347
|
+
if (!(0, node_fs_1.existsSync)(promptPath)) {
|
|
348
|
+
problems.push(`prompt.system file not found: ${doc.prompt.system}`);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
systemTemplate = (0, node_fs_1.readFileSync)(promptPath, "utf8");
|
|
352
|
+
}
|
|
353
|
+
const rawVars = doc.prompt.variables ?? [];
|
|
354
|
+
if (!Array.isArray(rawVars) || rawVars.some((v) => typeof v !== "string")) {
|
|
355
|
+
problems.push("prompt.variables must be a list of strings");
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
variables = rawVars;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (systemTemplate !== "") {
|
|
362
|
+
const declared = new Set(variables);
|
|
363
|
+
for (const ref of (0, template_1.scanTemplateVariables)(systemTemplate)) {
|
|
364
|
+
if (!declared.has(ref)) {
|
|
365
|
+
problems.push(`template references undeclared variable "${ref}" (declare it in prompt.variables)`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (groundingQuery !== undefined) {
|
|
370
|
+
const declared = new Set(variables);
|
|
371
|
+
for (const ref of (0, template_1.scanTemplateVariables)(groundingQuery)) {
|
|
372
|
+
if (!declared.has(ref)) {
|
|
373
|
+
problems.push(`grounding.query references undeclared variable "${ref}"`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (problems.length > 0) {
|
|
378
|
+
throw new RouteConfigError("invalid route bundle", { file: filePath, problems });
|
|
379
|
+
}
|
|
380
|
+
return Object.freeze({
|
|
381
|
+
route: route,
|
|
382
|
+
version: version,
|
|
383
|
+
outputSchema: outputSchema === null ? null : Object.freeze(outputSchema),
|
|
384
|
+
candidates: Object.freeze(candidates),
|
|
385
|
+
require: Object.freeze(require),
|
|
386
|
+
prefer: Object.freeze(prefer),
|
|
387
|
+
systemTemplate,
|
|
388
|
+
variables: Object.freeze(variables),
|
|
389
|
+
policy,
|
|
390
|
+
...(repair !== undefined ? { repair } : {}),
|
|
391
|
+
variants: Object.freeze(variants),
|
|
392
|
+
capture: capture === true,
|
|
393
|
+
...(groundingQuery !== undefined ? { groundingQuery } : {}),
|
|
394
|
+
// Network-effect instrumentation (spec WS6): computed once at load,
|
|
395
|
+
// stamped on every telemetry row for this route.
|
|
396
|
+
fingerprintCell: (0, fingerprint_1.fingerprintSchema)(outputSchema)?.cellKey ?? null,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function loadRoutes(routesDir) {
|
|
400
|
+
if (!(0, node_fs_1.existsSync)(routesDir)) {
|
|
401
|
+
// First-run moment (Phase 5 WS4): this is the most likely error a new
|
|
402
|
+
// user ever sees, so it teaches instead of just reporting.
|
|
403
|
+
throw new RouteConfigError(`routes directory not found: ${routesDir}\n\n` +
|
|
404
|
+
"A route is a task your code calls by name — one small YAML file each, in that\n" +
|
|
405
|
+
"directory. There isn't one here yet. Two ways to get your first:\n\n" +
|
|
406
|
+
" modelrig init . draft routes from the provider calls already in this repo\n" +
|
|
407
|
+
" mkdir -p " +
|
|
408
|
+
routesDir +
|
|
409
|
+
" start empty and write one by hand\n\n" +
|
|
410
|
+
"Point somewhere else with MODELRIG_ROUTES_DIR. Docs: https://modelrig.dev/quickstart.html");
|
|
411
|
+
}
|
|
412
|
+
const files = (0, node_fs_1.readdirSync)(routesDir)
|
|
413
|
+
.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"))
|
|
414
|
+
.sort()
|
|
415
|
+
.map((f) => (0, node_path_1.join)(routesDir, f));
|
|
416
|
+
const byName = new Map();
|
|
417
|
+
for (const file of files) {
|
|
418
|
+
const bundle = loadBundleFile(file);
|
|
419
|
+
if (byName.has(bundle.route)) {
|
|
420
|
+
throw new RouteConfigError(`duplicate route "${bundle.route}"`, { file });
|
|
421
|
+
}
|
|
422
|
+
byName.set(bundle.route, bundle);
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
get(route) {
|
|
426
|
+
const bundle = byName.get(route);
|
|
427
|
+
if (!bundle) {
|
|
428
|
+
throw new RouteConfigError(`unknown route "${route}" — known routes: [${[...byName.keys()].join(", ")}]`);
|
|
429
|
+
}
|
|
430
|
+
return bundle;
|
|
431
|
+
},
|
|
432
|
+
list() {
|
|
433
|
+
return Object.freeze([...byName.values()]);
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rig manifest (modelrig/rig.yaml) — cost-dimensions-design.md §2a.
|
|
3
|
+
* Project-level, optional; absence means "no declared dimensions" and
|
|
4
|
+
* everything continues to work (declaration upgrades, never gates).
|
|
5
|
+
*/
|
|
6
|
+
import type { RunTags } from "../types/route";
|
|
7
|
+
export interface DimensionSpec {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly required: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface RigManifest {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly dimensions: readonly DimensionSpec[];
|
|
15
|
+
/** Conformance Matrix contribution consent — default false (Phase 2: flag
|
|
16
|
+
* + mirror only; no pipeline consumes it yet). */
|
|
17
|
+
readonly contribution: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function loadManifest(rigDir: string): RigManifest | null;
|
|
20
|
+
/** Required dimensions with no (non-empty) value in the run's tags — pure. */
|
|
21
|
+
export declare function missingRequiredDimensions(manifest: RigManifest | null, tags: RunTags): readonly string[];
|
|
22
|
+
export declare function warnMissingRequiredDimensions(manifest: RigManifest | null, tags: RunTags, log?: (message: string) => void): void;
|
|
23
|
+
export declare function resetDimensionWarningsForTest(): void;
|
|
24
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/routes/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C;sDACkD;IAClD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AA+CD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAoC/D;AAED,8EAA8E;AAC9E,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,WAAW,GAAG,IAAI,EAC5B,IAAI,EAAE,OAAO,GACZ,SAAS,MAAM,EAAE,CASnB;AAMD,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,WAAW,GAAG,IAAI,EAC5B,IAAI,EAAE,OAAO,EACb,GAAG,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAmB,GAC5C,IAAI,CASN;AAED,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The rig manifest (modelrig/rig.yaml) — cost-dimensions-design.md §2a.
|
|
4
|
+
* Project-level, optional; absence means "no declared dimensions" and
|
|
5
|
+
* everything continues to work (declaration upgrades, never gates).
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.loadManifest = loadManifest;
|
|
9
|
+
exports.missingRequiredDimensions = missingRequiredDimensions;
|
|
10
|
+
exports.warnMissingRequiredDimensions = warnMissingRequiredDimensions;
|
|
11
|
+
exports.resetDimensionWarningsForTest = resetDimensionWarningsForTest;
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_path_1 = require("node:path");
|
|
14
|
+
const yaml_1 = require("yaml");
|
|
15
|
+
const loader_1 = require("./loader");
|
|
16
|
+
const TAG_SAFE_KEY = /^[A-Za-z0-9_]{1,64}$/;
|
|
17
|
+
function isRecord(v) {
|
|
18
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19
|
+
}
|
|
20
|
+
function parseDimensions(raw, problems) {
|
|
21
|
+
if (raw === undefined || raw === null)
|
|
22
|
+
return [];
|
|
23
|
+
if (!Array.isArray(raw)) {
|
|
24
|
+
problems.push("dimensions must be a list");
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const out = [];
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
for (const [i, d] of raw.entries()) {
|
|
30
|
+
if (!isRecord(d) || typeof d.key !== "string") {
|
|
31
|
+
problems.push(`dimensions[${i}] must be an object with a string key`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!TAG_SAFE_KEY.test(d.key)) {
|
|
35
|
+
problems.push(`dimensions[${i}].key "${d.key}" is not tag-safe (must match [A-Za-z0-9_]{1,64})`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (seen.has(d.key)) {
|
|
39
|
+
problems.push(`duplicate dimension key "${d.key}"`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
seen.add(d.key);
|
|
43
|
+
const label = d.label ?? d.key;
|
|
44
|
+
if (typeof label !== "string" || label === "") {
|
|
45
|
+
problems.push(`dimensions[${i}].label must be a non-empty string`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const required = d.required ?? false;
|
|
49
|
+
if (typeof required !== "boolean") {
|
|
50
|
+
problems.push(`dimensions[${i}].required must be a boolean`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
out.push(Object.freeze({ key: d.key, label, required }));
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function loadManifest(rigDir) {
|
|
58
|
+
const manifestPath = (0, node_path_1.join)(rigDir, "rig.yaml");
|
|
59
|
+
if (!(0, node_fs_1.existsSync)(manifestPath))
|
|
60
|
+
return null; // absence never gates
|
|
61
|
+
let doc;
|
|
62
|
+
try {
|
|
63
|
+
doc = (0, yaml_1.parse)((0, node_fs_1.readFileSync)(manifestPath, "utf8"));
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
throw new loader_1.RouteConfigError("invalid YAML in rig manifest", {
|
|
67
|
+
file: manifestPath,
|
|
68
|
+
problems: [err.message],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (!isRecord(doc)) {
|
|
72
|
+
throw new loader_1.RouteConfigError("rig manifest must be a YAML mapping", { file: manifestPath });
|
|
73
|
+
}
|
|
74
|
+
const problems = [];
|
|
75
|
+
if (typeof doc.name !== "string" || doc.name === "") {
|
|
76
|
+
problems.push("name must be a non-empty string");
|
|
77
|
+
}
|
|
78
|
+
const dimensions = parseDimensions(doc.dimensions, problems);
|
|
79
|
+
const contribution = doc.contribution ?? false;
|
|
80
|
+
if (typeof contribution !== "boolean") {
|
|
81
|
+
problems.push("contribution must be a boolean");
|
|
82
|
+
}
|
|
83
|
+
if (problems.length > 0) {
|
|
84
|
+
throw new loader_1.RouteConfigError("invalid rig manifest", { file: manifestPath, problems });
|
|
85
|
+
}
|
|
86
|
+
return Object.freeze({
|
|
87
|
+
name: doc.name,
|
|
88
|
+
dimensions: Object.freeze(dimensions),
|
|
89
|
+
contribution: contribution,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** Required dimensions with no (non-empty) value in the run's tags — pure. */
|
|
93
|
+
function missingRequiredDimensions(manifest, tags) {
|
|
94
|
+
if (manifest === null)
|
|
95
|
+
return [];
|
|
96
|
+
return manifest.dimensions
|
|
97
|
+
.filter((d) => d.required)
|
|
98
|
+
.filter((d) => {
|
|
99
|
+
const value = tags[d.key];
|
|
100
|
+
return value === undefined || value === "";
|
|
101
|
+
})
|
|
102
|
+
.map((d) => d.key);
|
|
103
|
+
}
|
|
104
|
+
/** Once per process per dimension (spec WS5) — repeated untagged runs stay
|
|
105
|
+
* quiet after the first warning so pipelines aren't spammed. */
|
|
106
|
+
const warnedDimensions = new Set();
|
|
107
|
+
function warnMissingRequiredDimensions(manifest, tags, log = console.warn) {
|
|
108
|
+
for (const key of missingRequiredDimensions(manifest, tags)) {
|
|
109
|
+
if (warnedDimensions.has(key))
|
|
110
|
+
continue;
|
|
111
|
+
warnedDimensions.add(key);
|
|
112
|
+
log(`[modelrig] required dimension "${key}" missing from run tags — costs for these runs ` +
|
|
113
|
+
`will be under-attributed on /costs (declared in rig.yaml)`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function resetDimensionWarningsForTest() {
|
|
117
|
+
warnedDimensions.clear();
|
|
118
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load-time route serveability — Phase 4 resilience (architect-flagged
|
|
3
|
+
* change to the Phase-1 fail-fast guard).
|
|
4
|
+
*
|
|
5
|
+
* A declared candidate whose provider has no registered adapter (no API key
|
|
6
|
+
* in THIS environment) is INERT, not fatal: the gateway must not crash at
|
|
7
|
+
* boot because a bake-off challenger arm lacks a key here. This is the
|
|
8
|
+
* "safe but inert" behaviour the route-v3 commit assumed — true on the
|
|
9
|
+
* InferWealth cutover wrapper (it catches + falls back to legacy), but the
|
|
10
|
+
* standalone modelrig-server builds the rig eagerly at boot with no catch,
|
|
11
|
+
* so a keyless arm downed staging.
|
|
12
|
+
*
|
|
13
|
+
* Rule: warn per inert candidate and keep serving on the keyed ones; a route
|
|
14
|
+
* with ZERO serveable candidates is still a hard error (genuinely dead). The
|
|
15
|
+
* candidate-set UPPER bound is untouched — resolveCandidates still only
|
|
16
|
+
* yields declared candidates, and the run loop skips inert ones at dispatch.
|
|
17
|
+
*/
|
|
18
|
+
import type { RigFailure } from "../types/failures";
|
|
19
|
+
import type { CandidateRef } from "../types/provider";
|
|
20
|
+
import type { CandidateSpec } from "../types/route";
|
|
21
|
+
/** One route's inert candidates, structured so surfaces beyond the log —
|
|
22
|
+
* the server boot banner and console /setup (F1) — can name them and say
|
|
23
|
+
* exactly which env var fixes each. */
|
|
24
|
+
export interface ServeabilityWarning {
|
|
25
|
+
readonly route: string;
|
|
26
|
+
/** Declared candidates with no adapter in this environment. */
|
|
27
|
+
readonly inert: readonly CandidateSpec[];
|
|
28
|
+
/** Distinct providers behind `inert`, in declaration order. */
|
|
29
|
+
readonly missingProviders: readonly string[];
|
|
30
|
+
/** How many candidates CAN serve — the route is alive on these. */
|
|
31
|
+
readonly serveableCount: number;
|
|
32
|
+
/** The log/banner line (what `checkServeability` returns). */
|
|
33
|
+
readonly message: string;
|
|
34
|
+
/** The teaching half: the env vars to set, named. */
|
|
35
|
+
readonly fixHint: string;
|
|
36
|
+
}
|
|
37
|
+
/** Typed failure for an inert (keyless) candidate the run loop skips at
|
|
38
|
+
* dispatch — non-terminal config_auth so the loop advances to the next
|
|
39
|
+
* declared candidate; surfaced as the run's error only if EVERY candidate
|
|
40
|
+
* is inert. */
|
|
41
|
+
export declare function inertCandidateFailure(candidate: CandidateRef, route: string): RigFailure;
|
|
42
|
+
export declare function serveabilityWarnings(bundles: ReadonlyArray<{
|
|
43
|
+
readonly route: string;
|
|
44
|
+
readonly candidates: readonly CandidateSpec[];
|
|
45
|
+
}>, hasAdapter: (provider: string) => boolean): ServeabilityWarning[];
|
|
46
|
+
/** String façade over {@link serveabilityWarnings} — one traversal, one
|
|
47
|
+
* source of wording. Throws identically on a zero-serveable route. */
|
|
48
|
+
export declare function checkServeability(bundles: ReadonlyArray<{
|
|
49
|
+
readonly route: string;
|
|
50
|
+
readonly candidates: readonly CandidateSpec[];
|
|
51
|
+
}>, hasAdapter: (provider: string) => boolean): string[];
|
|
52
|
+
//# sourceMappingURL=serveability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serveability.d.ts","sourceRoot":"","sources":["../../src/routes/serveability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;uCAEuC;AACvC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,+DAA+D;IAC/D,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,mEAAmE;IACnE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAMD;;;eAGe;AACf,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAUxF;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,CAAC;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,CAAC,EACjG,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GACxC,mBAAmB,EAAE,CA4BvB;AAED;sEACsE;AACtE,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,aAAa,CAAC;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,CAAC,EACjG,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GACxC,MAAM,EAAE,CAEV"}
|