modelrig 0.3.0 → 0.4.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/README.md +66 -14
  3. package/dist/adapters/deepseek.d.ts.map +1 -1
  4. package/dist/adapters/deepseek.js +8 -1
  5. package/dist/adapters/gemini.d.ts +6 -0
  6. package/dist/adapters/gemini.d.ts.map +1 -1
  7. package/dist/adapters/gemini.js +14 -4
  8. package/dist/adapters/grok.d.ts +7 -0
  9. package/dist/adapters/grok.d.ts.map +1 -1
  10. package/dist/adapters/grok.js +23 -1
  11. package/dist/adapters/openai-compat.d.ts +7 -0
  12. package/dist/adapters/openai-compat.d.ts.map +1 -1
  13. package/dist/adapters/openai-compat.js +24 -1
  14. package/dist/adapters/openai.d.ts +7 -0
  15. package/dist/adapters/openai.d.ts.map +1 -1
  16. package/dist/adapters/openai.js +23 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +5 -4
  19. package/dist/config.d.ts +7 -1
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/index.d.ts +6 -6
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +28 -25
  24. package/dist/init/init-cli.d.ts.map +1 -1
  25. package/dist/init/init-cli.js +2 -1
  26. package/dist/init/observe-cli.d.ts.map +1 -1
  27. package/dist/init/observe-cli.js +2 -1
  28. package/dist/routes/sampling-config.d.ts.map +1 -1
  29. package/dist/routes/sampling-config.js +11 -0
  30. package/dist/run/dispatch.d.ts +14 -2
  31. package/dist/run/dispatch.d.ts.map +1 -1
  32. package/dist/run/dispatch.js +31 -2
  33. package/dist/run/ladder-key.d.ts +40 -0
  34. package/dist/run/ladder-key.d.ts.map +1 -0
  35. package/dist/run/ladder-key.js +39 -0
  36. package/dist/run/raw-support.d.ts +123 -0
  37. package/dist/run/raw-support.d.ts.map +1 -0
  38. package/dist/run/raw-support.js +150 -0
  39. package/dist/run/raw.d.ts +41 -11
  40. package/dist/run/raw.d.ts.map +1 -1
  41. package/dist/run/raw.js +67 -23
  42. package/dist/run/rawonly.d.ts +60 -0
  43. package/dist/run/rawonly.d.ts.map +1 -0
  44. package/dist/run/rawonly.js +120 -0
  45. package/dist/run/record-attempt.d.ts +14 -1
  46. package/dist/run/record-attempt.d.ts.map +1 -1
  47. package/dist/run/record-attempt.js +43 -2
  48. package/dist/run/resolve.d.ts +4 -0
  49. package/dist/run/resolve.d.ts.map +1 -1
  50. package/dist/run/resolve.js +9 -2
  51. package/dist/run/routing-docs.d.ts +73 -0
  52. package/dist/run/routing-docs.d.ts.map +1 -0
  53. package/dist/run/routing-docs.js +222 -0
  54. package/dist/run/run.d.ts.map +1 -1
  55. package/dist/run/run.js +24 -22
  56. package/dist/telemetry/exporter.d.ts.map +1 -1
  57. package/dist/telemetry/exporter.js +6 -0
  58. package/dist/telemetry/ingest-protocol.d.ts +7 -0
  59. package/dist/telemetry/ingest-protocol.d.ts.map +1 -1
  60. package/dist/telemetry/load-error-report.d.ts.map +1 -1
  61. package/dist/telemetry/load-error-report.js +4 -1
  62. package/dist/telemetry/otel-map.d.ts.map +1 -1
  63. package/dist/telemetry/otel-map.js +8 -0
  64. package/dist/telemetry/store-migrations.d.ts +15 -0
  65. package/dist/telemetry/store-migrations.d.ts.map +1 -0
  66. package/dist/telemetry/store-migrations.js +119 -0
  67. package/dist/telemetry/store.d.ts.map +1 -1
  68. package/dist/telemetry/store.js +26 -93
  69. package/dist/types/failures-teaching.js +1 -1
  70. package/dist/types/provider.d.ts +13 -0
  71. package/dist/types/provider.d.ts.map +1 -1
  72. package/dist/types/route.d.ts +6 -1
  73. package/dist/types/route.d.ts.map +1 -1
  74. package/dist/types/telemetry.d.ts +31 -0
  75. package/dist/types/telemetry.d.ts.map +1 -1
  76. package/dist/validate-cli.d.ts.map +1 -1
  77. package/dist/validate-cli.js +17 -4
  78. package/package.json +1 -1
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * runRaw-only construction — CE-8 (ruling R1,
4
+ * `docs/ModelRig/customer-efficiency/ce8-runraw-only-rig-spec-plan.md`).
5
+ *
6
+ * A Lane-B customer who only calls `rig.runRaw`/`rig.runRawStream` has no routes.
7
+ * With `routesDir: null`, `createRig` SKIPS route loading, the `rig.yaml`
8
+ * manifest, and the serveable-candidate gate — so a stray route file (or a route
9
+ * whose provider key is absent in this deployment) can no longer down a rig that
10
+ * never serves it. The raw lane is unchanged; every route-dependent surface
11
+ * (`run`/`runStream`/`runBundle`/`replay`/`bakeoff`/`verifySwap`) fails with a
12
+ * TERMINAL `invariant_violation` that teaches the fix — Promise surfaces reject,
13
+ * the two streaming surfaces hand back a handle whose `result`/`events` fail
14
+ * (never a synchronous throw, matching the real `runStream` contract).
15
+ *
16
+ * This module holds the small pieces of that opt-out (the empty store, the
17
+ * teaching failure, the throwing route methods) so `index.ts` stays under the
18
+ * 700-LOC cap — `createRig` remains the ONE construction path (a separate
19
+ * `createRawRig()` factory was considered and REJECTED, spec §2 R1).
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.RAW_ONLY_FIX_HINT = void 0;
23
+ exports.rawOnlyRouteFailure = rawOnlyRouteFailure;
24
+ exports.requireRoutesDir = requireRoutesDir;
25
+ exports.emptyRouteStore = emptyRouteStore;
26
+ exports.loadRoutesOrReport = loadRoutesOrReport;
27
+ exports.rawOnlyRouteMethods = rawOnlyRouteMethods;
28
+ const loader_1 = require("../routes/loader");
29
+ const load_error_report_1 = require("../telemetry/load-error-report");
30
+ const failures_1 = require("../types/failures");
31
+ /** The teaching remediation for a route-lane call on a runRaw-only rig (spec §2
32
+ * R1). One constant so the failure and the docs stay in lock-step. */
33
+ exports.RAW_ONLY_FIX_HINT = "this rig was constructed runRaw-only (routesDir: null); give createRig a routesDir to serve routes";
34
+ /** Build the TERMINAL failure a route-dependent surface throws when the rig was
35
+ * constructed runRaw-only. Reuses `invariant_violation` (already TERMINAL + in
36
+ * the teaching table) — no new FailureClass (spec §3 rationale). */
37
+ function rawOnlyRouteFailure(surface) {
38
+ return new failures_1.RigFailureError({
39
+ class: "invariant_violation",
40
+ message: `${surface} needs routes, but this rig was constructed runRaw-only (routesDir: null)`,
41
+ fixHint: exports.RAW_ONLY_FIX_HINT,
42
+ });
43
+ }
44
+ /** A `RunStreamHandle` that fails the runRaw-only way: `result` rejects and the
45
+ * `events` iterator throws — matching the real `runStream` contract, which NEVER
46
+ * throws synchronously (a stream surface always hands back a handle, errors ride
47
+ * `result`/`events`). A synchronous throw here would break the caller's
48
+ * `const h = rig.runStream(...); await h.result` shape. */
49
+ function rawOnlyStreamHandle(surface) {
50
+ const err = rawOnlyRouteFailure(surface);
51
+ const result = Promise.reject(err);
52
+ // Keep the rejection handled so an unconsumed handle never becomes an
53
+ // unhandledRejection; the events iterator surfaces the same error.
54
+ result.catch(() => undefined);
55
+ const events = {
56
+ // eslint-disable-next-line @typescript-eslint/require-await
57
+ async *[Symbol.asyncIterator]() {
58
+ throw err;
59
+ },
60
+ };
61
+ return { events, result };
62
+ }
63
+ /** Narrow a config's `routesDir` to a string for a route-REQUIRED context (the
64
+ * route-oriented CLI subcommands: bakeoff/watch/swap/init/observe/validate),
65
+ * throwing the teaching failure if the rig was built runRaw-only.
66
+ * `loadConfigFromEnv` always yields a string (R2), so this is unreachable for
67
+ * env-built configs — it guards a hand-built `routesDir: null` reaching a
68
+ * route-only command, and satisfies the `string | null` type at those seams. */
69
+ function requireRoutesDir(config, surface) {
70
+ if (config.routesDir === null)
71
+ throw rawOnlyRouteFailure(surface);
72
+ return config.routesDir;
73
+ }
74
+ /** The empty route store for a runRaw-only rig: `list()` is `[]` (so
75
+ * serveability, capture-detection, and the exported route mirror are all no-ops)
76
+ * and `get()` teaches, though no public surface reaches it — every route method
77
+ * is overridden by {@link rawOnlyRouteMethods}. */
78
+ function emptyRouteStore() {
79
+ return {
80
+ get(route) {
81
+ throw rawOnlyRouteFailure(`get("${route}")`);
82
+ },
83
+ list() {
84
+ return Object.freeze([]);
85
+ },
86
+ };
87
+ }
88
+ /** Load routes from a real (string) `routesDir`, mirroring a malformed-bundle
89
+ * diagnosis to the console before re-throwing — the byte-for-byte behaviour the
90
+ * string path had inline in `createRig` before CE-8 extracted it here. */
91
+ function loadRoutesOrReport(config, routesDir) {
92
+ try {
93
+ return (0, loader_1.loadRoutesWithExtra)(routesDir, config.extraRoutesDir);
94
+ }
95
+ catch (err) {
96
+ // A malformed bundle is teachable, not just fatal: mirror the diagnosis to
97
+ // the console before re-throwing (the rig still cannot serve).
98
+ if (err instanceof loader_1.RouteConfigError)
99
+ (0, load_error_report_1.reportLoadError)(config, err);
100
+ throw err;
101
+ }
102
+ }
103
+ /** The throwing overrides for a runRaw-only rig (spec §2 R1). Each names the
104
+ * surface + the route/task the caller asked for, so the terminal error points at
105
+ * exactly the call that needs a `routesDir`. Promise-returning surfaces REJECT
106
+ * (matching the routed lane, where errors surface as rejections); the two
107
+ * streaming surfaces return a handle synchronously, so they throw synchronously
108
+ * — the caller never reaches an event to await. */
109
+ function rawOnlyRouteMethods() {
110
+ return {
111
+ run: (route) => Promise.reject(rawOnlyRouteFailure(`run("${route}")`)),
112
+ runStream: (route) => rawOnlyStreamHandle(`runStream("${route}")`),
113
+ runBundle: (bundle) => Promise.reject(rawOnlyRouteFailure(`runBundle("${bundle.route}")`)),
114
+ runBundleStream: (bundle) => rawOnlyStreamHandle(`runBundleStream("${bundle.route}")`),
115
+ replay: (route) => Promise.reject(rawOnlyRouteFailure(`replay("${route}")`)),
116
+ bakeoff: (route) => Promise.reject(rawOnlyRouteFailure(`bakeoff("${route}")`)),
117
+ bakeoffFromEvalCases: (task) => Promise.reject(rawOnlyRouteFailure(`bakeoffFromEvalCases("${task}")`)),
118
+ verifySwap: (route) => Promise.reject(rawOnlyRouteFailure(`verifySwap("${route}")`)),
119
+ };
120
+ }
@@ -13,11 +13,17 @@ import type { AttemptTelemetry } from "./dispatch";
13
13
  import type { ActiveRunContext } from "../artifacts/run-context";
14
14
  import type { RetryLedger } from "./retry";
15
15
  import type { RouteBundle, RunOptions, RunTags } from "../types/route";
16
- import type { TelemetryStore } from "../types/telemetry";
16
+ import type { TelemetryRung, TelemetryStore } from "../types/telemetry";
17
+ import type { LadderKeyMinter } from "./ladder-key";
17
18
  export interface RecordAttemptDeps {
18
19
  readonly telemetry: TelemetryStore;
19
20
  readonly route: RouteBundle;
20
21
  readonly ledger: RetryLedger;
22
+ /** Per-call ladder-key minter (spec §2.1 R0) — every row this recorder writes
23
+ * is stamped with the call's id, its 1-based attempt_seq, and the current
24
+ * candidate index. Shared by reference with the run loop, which advances the
25
+ * candidate index as the iterator moves. */
26
+ readonly ladder: LadderKeyMinter;
21
27
  readonly effectiveTags: RunTags;
22
28
  readonly servedVariant: string | null;
23
29
  /** Active run context (E2 chunk A) or null when the artifact namespace is off. */
@@ -33,6 +39,13 @@ export interface RecordAttemptDeps {
33
39
  * uncached), exactly matching the request builder's suppression. */
34
40
  readonly droppedCacheHandle: ReadonlySet<string>;
35
41
  }
42
+ /**
43
+ * Classify a recorded row's ladder rung (R1 review, F1). `primaryDispatched`
44
+ * carries the candidate indices already given a primary dispatch on this call —
45
+ * mutated here so the next same-index non-repair dispatch reads `retry`.
46
+ * Exported for unit tests; the run loop reaches it only through the recorder.
47
+ */
48
+ export declare function rungOf(attempt: AttemptTelemetry, preDispatch: boolean, candidateIndex: number | null, primaryDispatched: Set<number>): TelemetryRung;
36
49
  /** Build the run loop's `recordAttempt(attempt) => inferenceId` over this run's
37
50
  * state. Records one inference row and, with an active run context, mirrors it
38
51
  * as a ground-truth step (fail-open). */
@@ -1 +1 @@
1
- {"version":3,"file":"record-attempt.d.ts","sourceRoot":"","sources":["../../src/run/record-attempt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIvE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;;qFAEiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACrC;;;wEAGoE;IACpE,QAAQ,CAAC,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAClD;AAED;;yCAEyC;AACzC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,MAAM,CA6ElG"}
1
+ {"version":3,"file":"record-attempt.d.ts","sourceRoot":"","sources":["../../src/run/record-attempt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAgB,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B;;;gDAG4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;;qFAEiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACrC;;;wEAGoE;IACpE,QAAQ,CAAC,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAClD;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,OAAO,EAAE,gBAAgB,EACzB,WAAW,EAAE,OAAO,EACpB,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,GAC7B,aAAa,CAUf;AAED;;yCAEyC;AACzC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,MAAM,CA+FlG"}
@@ -11,17 +11,45 @@
11
11
  * in-flight E4 / capture-write work (plan coordination note).
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.rungOf = rungOf;
14
15
  exports.createRecordAttempt = createRecordAttempt;
15
16
  const node_crypto_1 = require("node:crypto");
16
17
  const failures_1 = require("../types/failures");
17
18
  const cache_stamp_1 = require("./cache-stamp");
18
19
  const cache_handle_1 = require("./cache-handle");
20
+ /**
21
+ * Classify a recorded row's ladder rung (R1 review, F1). `primaryDispatched`
22
+ * carries the candidate indices already given a primary dispatch on this call —
23
+ * mutated here so the next same-index non-repair dispatch reads `retry`.
24
+ * Exported for unit tests; the run loop reaches it only through the recorder.
25
+ */
26
+ function rungOf(attempt, preDispatch, candidateIndex, primaryDispatched) {
27
+ if (attempt.rung !== undefined)
28
+ return attempt.rung; // explicit (tool-call path)
29
+ if (attempt.repairedBy !== undefined)
30
+ return "repair";
31
+ if (preDispatch)
32
+ return "predispatch_fail";
33
+ // A real primary/retry candidate dispatch. First time this index is dispatched
34
+ // ⇒ primary; any later dispatch of the same index ⇒ retry.
35
+ if (candidateIndex === null)
36
+ return "primary";
37
+ if (primaryDispatched.has(candidateIndex))
38
+ return "retry";
39
+ primaryDispatched.add(candidateIndex);
40
+ return "primary";
41
+ }
19
42
  /** Build the run loop's `recordAttempt(attempt) => inferenceId` over this run's
20
43
  * state. Records one inference row and, with an active run context, mirrors it
21
44
  * as a ground-truth step (fail-open). */
22
45
  function createRecordAttempt(deps) {
23
46
  const { telemetry, route, ledger, effectiveTags, servedVariant, runCtx, autoStepKey } = deps;
24
- const { cache, droppedCacheHandle } = deps;
47
+ const { cache, droppedCacheHandle, ladder } = deps;
48
+ // The candidate indices for which a real primary dispatch has already been
49
+ // recorded on THIS call — so the SECOND non-repair dispatch of a candidate
50
+ // stamps `retry` rather than a second `primary` (R1 review, F1). Held in the
51
+ // per-call closure (the recorder is built once per invocation).
52
+ const primaryDispatched = new Set();
25
53
  return function recordAttempt(attempt) {
26
54
  // Request-path cache substrate (WS-C): derived from the SAME pure gate the
27
55
  // dispatcher stamps the request with, so `cache_key`/`prefix_fingerprint` on
@@ -53,6 +81,16 @@ function createRecordAttempt(deps) {
53
81
  const dropped = droppedCacheHandle.has(`${attempt.candidate.provider} ${attempt.candidate.model}`);
54
82
  const customer = isPrimary && !preDispatch ? (0, cache_handle_1.customerCacheHandle)(cache, attempt.candidate, dropped) : null;
55
83
  const cacheKey = customer?.key ?? cacheStamp?.cacheKey ?? null;
84
+ // Ladder key (spec §2.1 R0): one stamp per row — shared call_id, monotonic
85
+ // attempt_seq, and the current candidate_index (the candidate being repaired
86
+ // for a repair row, since the iterator has not advanced during its turn).
87
+ const ladderKey = ladder.stamp();
88
+ // Rung tag (R1 review, F1): the row's place on the ladder, so the rollup and
89
+ // ladder view never guess. Precedence: an explicit hint from the caller
90
+ // (only the tool-call path sets one) wins; then a repair row, then a
91
+ // pre-dispatch terminal failure, then primary-vs-retry by whether this
92
+ // candidate index was already dispatched on this call.
93
+ const rung = rungOf(attempt, preDispatch, ladderKey.candidateIndex, primaryDispatched);
56
94
  const row = {
57
95
  id: (0, node_crypto_1.randomUUID)(),
58
96
  ts: new Date().toISOString(),
@@ -75,11 +113,14 @@ function createRecordAttempt(deps) {
75
113
  ttfbMs: attempt.ttfbMs ?? null,
76
114
  failureClass: attempt.failure?.class ?? null,
77
115
  attemptsJson: JSON.stringify(ledger.snapshot()),
78
- tagsJson: JSON.stringify(effectiveTags),
116
+ tagsJson: JSON.stringify({ ...effectiveTags, rung }),
79
117
  fingerprintCell: route.fingerprintCell,
80
118
  servedVariant,
81
119
  repairedBy: attempt.repairedBy ?? null,
82
120
  pricingMissing: attempt.pricingMissing ?? false,
121
+ callId: ladderKey.callId,
122
+ attemptSeq: ladderKey.attemptSeq,
123
+ candidateIndex: ladderKey.candidateIndex,
83
124
  };
84
125
  telemetry.record(row);
85
126
  // Mirror this inference into a ground-truth step attempt (fail-open — the
@@ -12,6 +12,10 @@ export type CapabilityLookup = (provider: ProviderId, model: string) => Readonly
12
12
  export interface CandidateIterator {
13
13
  /** Next eligible candidate, or null when the set is exhausted. */
14
14
  next(): CandidateRef | null;
15
+ /** Position in the EFFECTIVE eligible list of the candidate the most recent
16
+ * `next()` returned (0 = first eligible; ladder key, spec §2.1). `-1` before
17
+ * the first `next()`; unchanged by a `next()` that returns null. */
18
+ readonly index: number;
15
19
  }
16
20
  /**
17
21
  * Request-time candidate filters (G-2) — optional, applied by resolveCandidates
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/run/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAElF,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,CAC7B,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,MAAM,KACV,WAAW,CAAC,cAAc,CAAC,CAAC;AAEjC,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,IAAI,IAAI,YAAY,GAAG,IAAI,CAAC;CAC7B;AA6CD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CACzC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;;oFAEgF;IAChF,QAAQ,CAAC,uBAAuB,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACzF,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;CACjD;AAED;kFACkF;AAClF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtF,QAAQ,CAAC,WAAW,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,gBAAgB,EAC9B,cAAc,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EACtE,eAAe,UAAQ,EACvB,MAAM,CAAC,EAAE,eAAe,EACxB,YAAY,CAAC,EAAE,mBAAmB,GACjC,iBAAiB,CAkFnB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,GAAG,WAAW,CAM5F;AAED;mFACmF;AACnF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,GAAG,SAAS,CAUxF;AAED;;gDAEgD;AAChD,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAMtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAElF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC9D,YAAY,GAAG,IAAI,CAMrB"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/run/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAElF,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,CAC7B,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,MAAM,KACV,WAAW,CAAC,cAAc,CAAC,CAAC;AAEjC,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,IAAI,IAAI,YAAY,GAAG,IAAI,CAAC;IAC5B;;wEAEoE;IACpE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AA6CD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CACzC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC;;oFAEgF;IAChF,QAAQ,CAAC,uBAAuB,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACzF,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;CACjD;AAED;kFACkF;AAClF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtF,QAAQ,CAAC,WAAW,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,gBAAgB,EAC9B,cAAc,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,EACtE,eAAe,UAAQ,EACvB,MAAM,CAAC,EAAE,eAAe,EACxB,YAAY,CAAC,EAAE,mBAAmB,GACjC,iBAAiB,CAwFnB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,GAAG,WAAW,CAM5F;AAED;mFACmF;AACnF,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,GAAG,SAAS,CAUxF;AAED;;gDAEgD;AAChD,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAMtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAElF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC9D,YAAY,GAAG,IAAI,CAMrB"}
@@ -124,10 +124,17 @@ function resolveCandidates(route, capabilities, costUsdPerMTok, searchAvailable
124
124
  // minted. The cast below is the single sanctioned exception to the
125
125
  // "no `as CandidateRef`" grep (scoped to run/resolve.ts in CI).
126
126
  const refs = ordered.map((c) => Object.freeze({ provider: c.provider, model: c.model }));
127
- let index = 0;
127
+ let cursor = 0;
128
+ let lastIndex = -1;
128
129
  return {
129
130
  next() {
130
- return index < refs.length ? refs[index++] : null;
131
+ if (cursor >= refs.length)
132
+ return null;
133
+ lastIndex = cursor;
134
+ return refs[cursor++];
135
+ },
136
+ get index() {
137
+ return lastIndex;
131
138
  },
132
139
  };
133
140
  }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Routing & reliability — the code-anchored boundary and the failure-class
3
+ * section of https://modelrig.dev/routing-reliability.html.
4
+ *
5
+ * ONE table feeds the published page AND the tripwire test, the same way
6
+ * `RAW_KNOB_SUPPORT` (raw-support.ts) feeds the raw-lane matrix and its runtime
7
+ * guard. The docs builder (scripts/build-docs-site.ts) injects
8
+ * `renderRoutingBoundary()` at `<!-- ROUTING_BOUNDARY -->` and
9
+ * `renderFailureClasses()` at `<!-- FAILURE_CLASSES -->`, and inlines the
10
+ * boundary into llms.txt. Nothing here is exported from the package root.
11
+ *
12
+ * Spec: docs/ModelRig/routing-reliability-overview/spec.md §5–§6.
13
+ */
14
+ import type { FailureClass } from "../types/failures";
15
+ export type BoundaryStatus = "not_built" | "deferred" | "shadow" | "by_design";
16
+ export interface BoundaryTripwire {
17
+ /** Path relative to packages/modelrig (the test reads it as text). */
18
+ readonly file: string;
19
+ /** Entry is STALE if this matches — the feature appeared. */
20
+ readonly mustNotMatch?: RegExp;
21
+ /** Entry is STALE if this stops matching — the no-op marker vanished. */
22
+ readonly mustMatch?: RegExp;
23
+ }
24
+ export interface BoundaryEntry {
25
+ /** Stable id (B1…) — becomes the first table column and a citable handle. */
26
+ readonly id: string;
27
+ readonly status: BoundaryStatus;
28
+ /** <= 8 words. */
29
+ readonly capability: string;
30
+ /** ONE sentence, present tense: what is true today. */
31
+ readonly statement: string;
32
+ /** Repo doc path (or issue) a reader can cite. */
33
+ readonly tracking: string;
34
+ readonly tripwire?: BoundaryTripwire | {
35
+ readonly noImportersOf: string;
36
+ };
37
+ }
38
+ /** Rendered group headings, in order. */
39
+ export declare const BOUNDARY_STATUS_LABEL: Readonly<Record<BoundaryStatus, string>>;
40
+ /**
41
+ * The ten boundary entries, transcribed verbatim from spec §5.3. Statuses,
42
+ * statements, and tripwires are BINDING content — do not add, drop, or reword
43
+ * an entry without an architect addendum to the spec. Shipping a capability
44
+ * listed here breaks `routing-docs.test.ts` until the entry is flipped or
45
+ * removed and the page updated.
46
+ */
47
+ export declare const ROUTING_BOUNDARY: readonly BoundaryEntry[];
48
+ /**
49
+ * Markdown (renderer subset only): one `###` per non-empty status group, a pipe
50
+ * table per group with columns id | capability | what is true today | tracking.
51
+ * No nested lists, no HTML — the docs-site renderer supports neither.
52
+ *
53
+ * `published` (the site's page slugs) is passed by the docs builder so a tracking
54
+ * cell naming a published page renders as a link; a bare call (the shape/tripwire
55
+ * tests, which assert the plain table) leaves every cell as plain text.
56
+ */
57
+ export declare function renderRoutingBoundary(published?: ReadonlySet<string>): string;
58
+ /** The failure classes in taxonomy order (types/failures.ts `FailureClass`
59
+ * union). FAILURE_TEACHING is keyed in that same order and its completeness is
60
+ * pinned by failures-teaching.test.ts, so its key order is the canonical walk.
61
+ * A test in routing-docs.test.ts asserts this equals the union order in
62
+ * failures.ts, so a taxonomy reorder cannot drift silently. */
63
+ export declare function failureClassOrder(): readonly FailureClass[];
64
+ /**
65
+ * Markdown: `### <class>` per FailureClass in taxonomy order, then the teaching
66
+ * `what`, the retry line, the backoff schedule (when any), and the `fixHint`.
67
+ * Heading text is the bare class name so the docs-site id equals the class
68
+ * (failures-teaching.ts docsUrl anchors depend on it — spec §6).
69
+ */
70
+ export declare function renderFailureClasses(): string;
71
+ /** Exposed for the anchor-completeness test: the heading id each class gets. */
72
+ export declare function failureClassAnchor(cls: FailureClass): string;
73
+ //# sourceMappingURL=routing-docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-docs.d.ts","sourceRoot":"","sources":["../../src/run/routing-docs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,kBAAkB;IAClB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG;QAAE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AAED,yCAAyC;AACzC,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAK1E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EA0FpD,CAAC;AAwBF;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAoB7E;AAED;;;;+DAI+D;AAC/D,wBAAgB,iBAAiB,IAAI,SAAS,YAAY,EAAE,CAE3D;AA0BD;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAW7C;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAE5D"}
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ /**
3
+ * Routing & reliability — the code-anchored boundary and the failure-class
4
+ * section of https://modelrig.dev/routing-reliability.html.
5
+ *
6
+ * ONE table feeds the published page AND the tripwire test, the same way
7
+ * `RAW_KNOB_SUPPORT` (raw-support.ts) feeds the raw-lane matrix and its runtime
8
+ * guard. The docs builder (scripts/build-docs-site.ts) injects
9
+ * `renderRoutingBoundary()` at `<!-- ROUTING_BOUNDARY -->` and
10
+ * `renderFailureClasses()` at `<!-- FAILURE_CLASSES -->`, and inlines the
11
+ * boundary into llms.txt. Nothing here is exported from the package root.
12
+ *
13
+ * Spec: docs/ModelRig/routing-reliability-overview/spec.md §5–§6.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.ROUTING_BOUNDARY = exports.BOUNDARY_STATUS_LABEL = void 0;
17
+ exports.renderRoutingBoundary = renderRoutingBoundary;
18
+ exports.failureClassOrder = failureClassOrder;
19
+ exports.renderFailureClasses = renderFailureClasses;
20
+ exports.failureClassAnchor = failureClassAnchor;
21
+ const failures_1 = require("../types/failures");
22
+ const failures_teaching_1 = require("../types/failures-teaching");
23
+ /** Rendered group headings, in order. */
24
+ exports.BOUNDARY_STATUS_LABEL = {
25
+ not_built: "Not built",
26
+ deferred: "Deferred",
27
+ shadow: "Built, not actuating",
28
+ by_design: "By design — will not do",
29
+ };
30
+ /**
31
+ * The ten boundary entries, transcribed verbatim from spec §5.3. Statuses,
32
+ * statements, and tripwires are BINDING content — do not add, drop, or reword
33
+ * an entry without an architect addendum to the spec. Shipping a capability
34
+ * listed here breaks `routing-docs.test.ts` until the entry is flipped or
35
+ * removed and the page updated.
36
+ */
37
+ exports.ROUTING_BOUNDARY = [
38
+ {
39
+ id: "B1",
40
+ status: "not_built",
41
+ capability: "Quality / value-accuracy gate in the ladder",
42
+ statement: "Fall-through is triggered by structure and infrastructure only; no builder-defined predicate or judge can reject a schema-valid output at run time — value accuracy exists offline, in bake-offs.",
43
+ tracking: "customer request on file (2026-08-21); not scheduled",
44
+ tripwire: { file: "src/types/route.ts", mustNotMatch: /qualityGate|"judged"/ },
45
+ },
46
+ {
47
+ id: "B2",
48
+ status: "not_built",
49
+ capability: "Structure on the raw lane",
50
+ statement: '`runRaw` never validates or repairs; `responseFormat: "json_object"` shapes MIME, not schema, so `meta.validated` stays false (ruled schema-free in CE-2 F4b; re-requested #223 R2).',
51
+ tracking: "ruled schema-free on the raw lane; re-request on file",
52
+ tripwire: { file: "src/run/raw.ts", mustNotMatch: /readonly outputSchema\??:/ },
53
+ },
54
+ {
55
+ id: "B3",
56
+ status: "deferred",
57
+ capability: "`prefer: latency` / observed-health ordering",
58
+ statement: "`latency` is accepted and does nothing — declared order stands; observed p50/p90/p99 are aggregated but unused by routing.",
59
+ tracking: "deferred to the observed-health work (latency signal first)",
60
+ tripwire: { file: "src/run/resolve.ts", mustMatch: /declared order stands/ },
61
+ },
62
+ {
63
+ id: "B4",
64
+ status: "shadow",
65
+ capability: "Cache-aware (warmth) candidate ordering",
66
+ statement: "The cache-adjusted ranking is computed and logged beside the chosen order but never changes it.",
67
+ tracking: "cache-aware routing — shadow mode; see caching-lifecycle.md",
68
+ tripwire: { file: "src/run/resolve.ts", mustMatch: /returns the UNCHANGED list-cost order/ },
69
+ },
70
+ {
71
+ id: "B5",
72
+ status: "shadow",
73
+ capability: "Cache-miss streak alert",
74
+ statement: "A miss-streak detector exists; nothing consumes it yet, so no alert fires.",
75
+ tracking: "cache-aware routing — monitor wiring pending",
76
+ tripwire: { noImportersOf: "telemetry/cache-miss-streak" },
77
+ },
78
+ {
79
+ id: "B6",
80
+ status: "not_built",
81
+ capability: "Run-level idempotency / dedupe key",
82
+ statement: "Two identical `rig.run` calls dispatch twice; the only idempotency is the exporter's watermark.",
83
+ // Spec §5.3 tracking reads "— (record here)": B6 has no external tracking
84
+ // doc; this generated boundary IS its record, so the rendered cell is "—".
85
+ tracking: "—",
86
+ tripwire: { file: "src/types/route.ts", mustNotMatch: /idempotencyKey/ },
87
+ },
88
+ {
89
+ id: "B7",
90
+ status: "by_design",
91
+ capability: "Automatic model swap",
92
+ statement: "Nothing changes what serves a route without a bake-off and an explicit human approval (`modelrig swap execute <id> --approve`).",
93
+ tracking: "optimization-loop.md — human-approved swaps only",
94
+ // No file tripwire: the P6 never-silent-swap invariant is covered by its
95
+ // own dev-rule tests (spec §5.3).
96
+ },
97
+ {
98
+ id: "B8",
99
+ status: "by_design",
100
+ capability: "Provider cache resource lifecycle",
101
+ statement: "ModelRig passes your cache handle through and prices the hits; it never creates, refreshes, or deletes the resource — your heartbeat does.",
102
+ tracking: "caching-lifecycle.md — who owns what",
103
+ },
104
+ {
105
+ id: "B9",
106
+ status: "by_design",
107
+ capability: "Tool execution",
108
+ statement: "ModelRig normalizes the tool-call protocol and returns `meta.toolCalls`; the caller runs the tool.",
109
+ tracking: "route-bundles.md — tools are normalized, never executed",
110
+ },
111
+ {
112
+ id: "B10",
113
+ status: "by_design",
114
+ capability: "Raw-lane flex → standard degrade",
115
+ statement: "A flex capacity rejection on `runRaw` surfaces as `capacity_shed` for your own retry ladder; ModelRig does not degrade the tier for you.",
116
+ tracking: "route-bundles.md — raw-lane serviceTier (customer-side degrade)",
117
+ },
118
+ ];
119
+ const STATUS_ORDER = ["not_built", "deferred", "shadow", "by_design"];
120
+ /** Escape a table cell so a literal `|` in content cannot break the pipe table.
121
+ * (None of the current statements contain one; this keeps a future edit safe.) */
122
+ function cell(text) {
123
+ return text.replace(/\|/g, "\\|");
124
+ }
125
+ /**
126
+ * Turn a `<slug>.md` token in a tracking cell into a markdown link to that
127
+ * published page (S8) — but ONLY when the slug is one the site actually
128
+ * publishes, so a stray `.md` reference never becomes a dead link. The URL is
129
+ * absolute (`modelrig.dev/<slug>.html`) so the same rendered text works on the
130
+ * page (the docs renderer leaves an already-`.html` href alone) AND inline in
131
+ * llms.txt, which is plain text and never runs the `.md`→`.html` rewrite.
132
+ */
133
+ function linkifyTracking(text, published) {
134
+ return text.replace(/\b([a-z0-9][a-z0-9-]*)\.md\b/gi, (whole, slug) => published.has(slug) ? `[${slug}](https://modelrig.dev/${slug}.html)` : whole);
135
+ }
136
+ /**
137
+ * Markdown (renderer subset only): one `###` per non-empty status group, a pipe
138
+ * table per group with columns id | capability | what is true today | tracking.
139
+ * No nested lists, no HTML — the docs-site renderer supports neither.
140
+ *
141
+ * `published` (the site's page slugs) is passed by the docs builder so a tracking
142
+ * cell naming a published page renders as a link; a bare call (the shape/tripwire
143
+ * tests, which assert the plain table) leaves every cell as plain text.
144
+ */
145
+ function renderRoutingBoundary(published) {
146
+ const blocks = [];
147
+ for (const status of STATUS_ORDER) {
148
+ const entries = exports.ROUTING_BOUNDARY.filter((e) => e.status === status);
149
+ if (entries.length === 0)
150
+ continue;
151
+ const rows = entries.map((e) => {
152
+ const tracking = published ? linkifyTracking(e.tracking, published) : e.tracking;
153
+ return `| ${e.id} | ${cell(e.capability)} | ${cell(e.statement)} | ${cell(tracking)} |`;
154
+ });
155
+ blocks.push([
156
+ `### ${exports.BOUNDARY_STATUS_LABEL[status]}`,
157
+ "",
158
+ "| id | capability | what is true today | tracking |",
159
+ "|---|---|---|---|",
160
+ ...rows,
161
+ ].join("\n"));
162
+ }
163
+ return blocks.join("\n\n");
164
+ }
165
+ /** The failure classes in taxonomy order (types/failures.ts `FailureClass`
166
+ * union). FAILURE_TEACHING is keyed in that same order and its completeness is
167
+ * pinned by failures-teaching.test.ts, so its key order is the canonical walk.
168
+ * A test in routing-docs.test.ts asserts this equals the union order in
169
+ * failures.ts, so a taxonomy reorder cannot drift silently. */
170
+ function failureClassOrder() {
171
+ return Object.keys(failures_teaching_1.FAILURE_TEACHING);
172
+ }
173
+ /**
174
+ * Backoff schedule per class, in reader-facing words. The numbers mirror the
175
+ * (non-exported) constants in run/retry.ts — `CAPACITY_BACKOFF_MS` and the
176
+ * `NETWORK_BACKOFF_*` pair — and a test in routing-docs.test.ts reads retry.ts
177
+ * to pin them, so this string cannot drift from the engine (spec §6).
178
+ */
179
+ function backoffLine(cls) {
180
+ if (cls === "capacity_shed")
181
+ return "Backoff between retries: 30 s -> 60 s -> 120 s -> 240 s.";
182
+ if (cls === "network")
183
+ return "Backoff between retries: 1 s, doubling each time, capped at 30 s.";
184
+ // A class that is never retried — terminal, or `config_auth` (straight to the
185
+ // next candidate) — has no backoff to describe. Every OTHER class is retryable
186
+ // with a zero delay, so say so: §2 promises "with backoff", and leaving the
187
+ // remaining classes silent read as "unspecified" rather than "none" (S7).
188
+ if (failures_1.TERMINAL_CLASSES.has(cls) || cls === "config_auth")
189
+ return null;
190
+ return "No backoff between retries (immediate retry).";
191
+ }
192
+ /** The retry disposition sentence for one class (spec §6). */
193
+ function retryLine(cls) {
194
+ if (failures_1.TERMINAL_CLASSES.has(cls))
195
+ return "Terminal — never retried; aborts the run.";
196
+ if (cls === "config_auth")
197
+ return "Never retried — the run loop advances to the next candidate.";
198
+ return "Retryable — default budget 0 (declare every budget you want under `policy.retries`).";
199
+ }
200
+ /**
201
+ * Markdown: `### <class>` per FailureClass in taxonomy order, then the teaching
202
+ * `what`, the retry line, the backoff schedule (when any), and the `fixHint`.
203
+ * Heading text is the bare class name so the docs-site id equals the class
204
+ * (failures-teaching.ts docsUrl anchors depend on it — spec §6).
205
+ */
206
+ function renderFailureClasses() {
207
+ const blocks = [];
208
+ for (const cls of failureClassOrder()) {
209
+ const teaching = failures_teaching_1.FAILURE_TEACHING[cls];
210
+ const parts = [`### ${cls}`, "", teaching.what, "", retryLine(cls)];
211
+ const backoff = backoffLine(cls);
212
+ if (backoff !== null)
213
+ parts.push("", backoff);
214
+ parts.push("", `Fix: ${teaching.fixHint}`);
215
+ blocks.push(parts.join("\n"));
216
+ }
217
+ return blocks.join("\n\n");
218
+ }
219
+ /** Exposed for the anchor-completeness test: the heading id each class gets. */
220
+ function failureClassAnchor(cls) {
221
+ return cls;
222
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run/run.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAMrD,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,mBAAmB,CAAC;AAGvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,KAAK,EAAsC,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAQlD,OAAO,KAAK,EAAE,cAAc,EAAoC,MAAM,aAAa,CAAC;AASpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACxD,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC;;gDAE4C;IAC5C,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACvC;2EACuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACxC;;0BAEsB;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACxC,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;mFAG+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,eAAe,CAAC;IACzC;;;;;uDAKmD;IACnD,QAAQ,CAAC,QAAQ,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACjD;AAsED,wBAAsB,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAoiBhG"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run/run.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAMrD,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,mBAAmB,CAAC;AAGvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,KAAK,EAAsC,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAQlD,OAAO,KAAK,EAAE,cAAc,EAAoC,MAAM,aAAa,CAAC;AAUpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAKlD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACxD,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC;;gDAE4C;IAC5C,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACvC;2EACuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IACxC;;0BAEsB;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACxC,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;mFAG+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,eAAe,CAAC;IACzC;;;;;uDAKmD;IACnD,QAAQ,CAAC,QAAQ,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACjD;AAqDD,wBAAsB,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAmjBhG"}