instar 1.3.771 → 1.3.773
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/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +36 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/IntelligenceRouter.d.ts +184 -1
- package/dist/core/IntelligenceRouter.d.ts.map +1 -1
- package/dist/core/IntelligenceRouter.js +354 -1
- package/dist/core/IntelligenceRouter.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +14 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +37 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +51 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/data/llmBenchCoverage.d.ts +63 -0
- package/dist/data/llmBenchCoverage.d.ts.map +1 -1
- package/dist/data/llmBenchCoverage.js +92 -0
- package/dist/data/llmBenchCoverage.js.map +1 -1
- package/package.json +3 -2
- package/scripts/lint-nature-chains.mjs +203 -0
- package/src/data/builtin-manifest.json +19 -19
- package/src/data/llmBenchCoverage.ts +145 -0
- package/upgrades/1.3.772.md +63 -0
- package/upgrades/1.3.773.md +27 -0
- package/upgrades/nature-routing-a2.eli16.md +50 -0
- package/upgrades/side-effects/nature-axis-routing-fd4-lints.md +75 -0
- package/upgrades/side-effects/nature-routing-a2.md +89 -0
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
import type { IntelligenceProvider, IntelligenceOptions } from './types.js';
|
|
27
27
|
import type { IntelligenceFramework } from './intelligenceProviderFactory.js';
|
|
28
28
|
import { type ComponentCategory } from './componentCategories.js';
|
|
29
|
-
import { type RoutingNature } from '../data/llmBenchCoverage.js';
|
|
29
|
+
import { type RoutingNature, type TaskNature, type RoutingChain, type RoutingDoor, type ChainPosition, type NatureRoutingChains } from '../data/llmBenchCoverage.js';
|
|
30
30
|
export interface ComponentFrameworksConfig {
|
|
31
31
|
/** Framework for anything not otherwise specified. Defaults to the router's defaultFramework. */
|
|
32
32
|
default?: IntelligenceFramework;
|
|
@@ -158,6 +158,23 @@ export interface IntelligenceRouterOptions {
|
|
|
158
158
|
/** Bound (ms) for a single enqueued deferrable call so a stuck/abandoned one self-terminates. */
|
|
159
159
|
queueAttemptTimeoutMs?: number;
|
|
160
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* S4 A2 — the live, dev-gate-RESOLVED view of `sessions.natureRouting` (read on EVERY
|
|
163
|
+
* call so the kill switch is hot). `enabled` is already the gate-resolved boolean
|
|
164
|
+
* (resolved at the construction boundary, like `ladder`). Absent OR `enabled:false`
|
|
165
|
+
* ⇒ the nature router is inert and routing is BYTE-IDENTICAL to today. When enabled +
|
|
166
|
+
* dryRun (the dev-agent default) the resolver OBSERVES (computes + logs the plan via
|
|
167
|
+
* `onNatureRoutePlan`) and still passes through to today's selection. Enforcing
|
|
168
|
+
* selection (dryRun:false) is a tracked A2.2 remainder.
|
|
169
|
+
*/
|
|
170
|
+
resolveNatureRouting?: () => NatureRoutingRuntime | undefined;
|
|
171
|
+
/**
|
|
172
|
+
* S4 A2 — the dryRun observation sink (FD11 readable canary). Invoked with the resolved
|
|
173
|
+
* plan when nature routing is enabled + dryRun. No-op downstream by default; the router
|
|
174
|
+
* calls it freely. The durable `logs/nature-routing.jsonl` + `GET /intelligence/routing`
|
|
175
|
+
* surfaces that consume this are a tracked A2.2 remainder.
|
|
176
|
+
*/
|
|
177
|
+
onNatureRoutePlan?: (plan: NatureRoutePlan) => void;
|
|
161
178
|
}
|
|
162
179
|
/**
|
|
163
180
|
* resolveSwapCap — per-TARGET swap-attempt cap resolution
|
|
@@ -224,6 +241,146 @@ export declare function routingNatureFor(component: string | undefined): Routing
|
|
|
224
241
|
* map, so it never depends on `sessions.natureRouting` being set.
|
|
225
242
|
*/
|
|
226
243
|
export declare function isBoundedGatingDegrade(component: string | undefined, options?: IntelligenceOptions): boolean;
|
|
244
|
+
/**
|
|
245
|
+
* The critical-gate fail-closed outcome (spec CR10-3/CR8-4): a DISTINCT typed error
|
|
246
|
+
* so a caller can never mistake it for an ordinary model failure. A mapped FD6
|
|
247
|
+
* critical gate whose chain has NO available door throws this; the caller applies its
|
|
248
|
+
* existing gating fail-closed semantics (block/deny). The low-stakes empty-set case is
|
|
249
|
+
* deliberately the ordinary non-gating error instead ('no-route'), typed apart on purpose.
|
|
250
|
+
*/
|
|
251
|
+
export declare class RouterFailClosedError extends Error {
|
|
252
|
+
readonly component: string | undefined;
|
|
253
|
+
readonly resolvedChain: RoutingChain;
|
|
254
|
+
constructor(component: string | undefined, resolvedChain: RoutingChain);
|
|
255
|
+
}
|
|
256
|
+
/** A chain position resolved to a concrete model id, after the FD4 allowlist clamp. */
|
|
257
|
+
export interface ResolvedRoutePosition {
|
|
258
|
+
readonly door: RoutingDoor;
|
|
259
|
+
/** The original benchmark label / tier hint from the chain position. */
|
|
260
|
+
readonly label: string;
|
|
261
|
+
/** The concrete model id the door should be asked for (post-registry, post-clamp). */
|
|
262
|
+
readonly modelId: string;
|
|
263
|
+
/** Whether the FD4 harness-door allowlist clamp rewrote this position to the reserve id. */
|
|
264
|
+
readonly clamped: boolean;
|
|
265
|
+
}
|
|
266
|
+
/** The four load-bearing `resolveRoute` outcomes (verifier r7 — never collapse them). */
|
|
267
|
+
export type RouteResolution = {
|
|
268
|
+
outcome: 'route';
|
|
269
|
+
resolvedNature: TaskNature;
|
|
270
|
+
resolvedChain: RoutingChain;
|
|
271
|
+
primary: ResolvedRoutePosition;
|
|
272
|
+
swapTail: ResolvedRoutePosition[];
|
|
273
|
+
} | {
|
|
274
|
+
outcome: 'fall-through';
|
|
275
|
+
} | {
|
|
276
|
+
outcome: 'no-route';
|
|
277
|
+
};
|
|
278
|
+
/**
|
|
279
|
+
* FD3 — resolve `{ resolvedNature, resolvedChain }` from the component's static map row
|
|
280
|
+
* and an optional caller-declared `attribution.nature`. The component's OWN map row
|
|
281
|
+
* `{nature, chain}` is authoritative by default (preserving a per-component A/FAST vs
|
|
282
|
+
* A/SORT choice a pure nature→chain function could not). A caller-declared nature that
|
|
283
|
+
* TIGHTENS (strictly raises the tier) replaces the chain with the deterministically
|
|
284
|
+
* safe `JUDGE`; anything else is ignored. Returns `undefined` for an unmapped component.
|
|
285
|
+
* Pure — always available, independent of `sessions.natureRouting`.
|
|
286
|
+
*/
|
|
287
|
+
export declare function resolveNatureAndChain(component: string | undefined, declaredNature?: unknown): {
|
|
288
|
+
resolvedNature: TaskNature;
|
|
289
|
+
resolvedChain: RoutingChain;
|
|
290
|
+
} | undefined;
|
|
291
|
+
/**
|
|
292
|
+
* FD-LABEL / FD4.1 — resolve a chain position's `model` label to a concrete model id via
|
|
293
|
+
* `ROUTING_LABEL_TO_MODEL_ID`. A label absent from the registry (a tier hint like `fast`
|
|
294
|
+
* / `capable`) passes through unchanged and is resolved downstream by the existing
|
|
295
|
+
* per-adapter tier map. Pure.
|
|
296
|
+
*/
|
|
297
|
+
export declare function resolvePositionModelId(pos: ChainPosition): string;
|
|
298
|
+
/**
|
|
299
|
+
* FD4 place 3 — the harness-door ALLOWLIST clamp (deny-by-default), the nature-routing
|
|
300
|
+
* RUNTIME safety guarantee. For a bounded/gating (FAST/SORT/JUDGE) chain, the ONLY
|
|
301
|
+
* permitted `claude-code` position is the single sanctioned CONCRETE reserve id
|
|
302
|
+
* (`CLAUDE_CODE_RESERVE_MODEL_ID`); any OTHER claude-code id is clamped down to it. This
|
|
303
|
+
* is an allowlist, NOT a denylist — a future/unrecognized capable Claude id can never
|
|
304
|
+
* slip past. `WRITE` is exempt (its Opus-via-CLI quality lane is legitimate), and every
|
|
305
|
+
* non-claude-code door passes through.
|
|
306
|
+
*
|
|
307
|
+
* DELIBERATELY SEPARATE from A1's `clampClaudeCliSwapModel` (which returns the `balanced`
|
|
308
|
+
* TIER token and fires UNCONDITIONALLY on the always-on degrade/swap path): touching that
|
|
309
|
+
* function would change A1's shipped, byte-identical-when-off behavior. This clamp is
|
|
310
|
+
* nature-routing-scoped and concrete-id-based (FD4.1), applied only inside `resolveRoute`.
|
|
311
|
+
*/
|
|
312
|
+
export declare function clampToReserveOnCleanDoor(pos: ResolvedRoutePosition, resolvedChain: RoutingChain): ResolvedRoutePosition;
|
|
313
|
+
/** The three FD4 static-ban rules a chain position can violate. */
|
|
314
|
+
export type NatureChainBanRule = 'claude-code-non-reserve' | 'claude-code-tier-label' | 'fable-banned';
|
|
315
|
+
/** One authored chain position that violates the FD4 harness-door ban. */
|
|
316
|
+
export interface NatureChainViolation {
|
|
317
|
+
chain: RoutingChain;
|
|
318
|
+
index: number;
|
|
319
|
+
door: RoutingDoor;
|
|
320
|
+
model: string;
|
|
321
|
+
resolvedModelId: string;
|
|
322
|
+
rule: NatureChainBanRule;
|
|
323
|
+
detail: string;
|
|
324
|
+
}
|
|
325
|
+
/** A model label/id names a Fable model — FD8 §393, never emitted by any nature chain. Pure. */
|
|
326
|
+
export declare function isFableModel(s: string): boolean;
|
|
327
|
+
/**
|
|
328
|
+
* Validate ONE chain position against the FD4 static ban (pure). Returns a violation or
|
|
329
|
+
* null. The claude-code allowlist test compares the REGISTRY-RESOLVED concrete id against
|
|
330
|
+
* the single sanctioned reserve id (FD4.1 — a tier label is permitted ONLY because/if it
|
|
331
|
+
* pins through `ROUTING_LABEL_TO_MODEL_ID` to that concrete id; an unpinned label fails).
|
|
332
|
+
*/
|
|
333
|
+
export declare function validateChainPosition(chain: RoutingChain, pos: ChainPosition, index: number): NatureChainViolation | null;
|
|
334
|
+
/** Validate ONE chain's positions against the FD4 ban (pure). */
|
|
335
|
+
export declare function validateNatureRoutingChain(chain: RoutingChain, positions: ReadonlyArray<ChainPosition>): NatureChainViolation[];
|
|
336
|
+
/** Validate ALL four chains against the FD4 ban (pure). Empty array ⇒ the chain map is clean. */
|
|
337
|
+
export declare function validateNatureRoutingChains(chains: NatureRoutingChains): NatureChainViolation[];
|
|
338
|
+
/** True iff every chain passes the FD4 static harness-door ban. Pure. */
|
|
339
|
+
export declare function isNatureRoutingChainsValid(chains: NatureRoutingChains): boolean;
|
|
340
|
+
/**
|
|
341
|
+
* The PURE nature-routing fold (spec §Resolver, steps 1–7). Deps are injected so the
|
|
342
|
+
* function is side-effect-free + trivially testable: `isDoorReachable` reports CLI-door
|
|
343
|
+
* reachability (the class wraps its provider cache). Metered doors are ALWAYS skipped in
|
|
344
|
+
* Increment A. The empty-`available` branch splits by authority class (CR6-2/CR3-1):
|
|
345
|
+
* unmapped → 'fall-through'; low-stakes mapped → 'no-route'; FD6 critical gate → throw
|
|
346
|
+
* RouterFailClosedError. (The R6 doc-tree refuse-to-author branch is a tracked A2.2
|
|
347
|
+
* remainder; until it lands a `claudeBanned` component falls into the low-stakes 'no-route'
|
|
348
|
+
* path — never onto Claude, since resolveRoute never emits a claude-code position for it
|
|
349
|
+
* unless its chain contains one, and R6 chains are authored off-Claude.)
|
|
350
|
+
*/
|
|
351
|
+
export declare function resolveRoute(component: string | undefined, declaredNature: unknown, chains: NatureRoutingChains, deps: {
|
|
352
|
+
isDoorReachable: (door: RoutingDoor) => boolean;
|
|
353
|
+
/** FD4.3 — called when a live chain is rejected for a harness-door-ban violation (→ defaults). */
|
|
354
|
+
onInvalidChain?: (chain: RoutingChain, violations: NatureChainViolation[]) => void;
|
|
355
|
+
}): RouteResolution;
|
|
356
|
+
/**
|
|
357
|
+
* Merge an operator's partial `chains` override (config) over the built-in v3 defaults,
|
|
358
|
+
* per-chain. A chain the operator did not override keeps its default. (The FD4.3
|
|
359
|
+
* resolve-time validation that REJECTS a harness-door-violating override → defaults +
|
|
360
|
+
* notice is a tracked A2.2 remainder; the FD4 place-3 runtime clamp above already keeps
|
|
361
|
+
* the banned route closed on every resolved position regardless.)
|
|
362
|
+
*/
|
|
363
|
+
export declare function mergeNatureRoutingChains(override: Partial<Record<RoutingChain, ReadonlyArray<ChainPosition>>> | undefined, onReject?: (chain: RoutingChain, violations: NatureChainViolation[]) => void): NatureRoutingChains;
|
|
364
|
+
/**
|
|
365
|
+
* The dev-gate-resolved live view of `sessions.natureRouting` the router reads per call.
|
|
366
|
+
* `enabled` is ALREADY gate-resolved at the construction boundary (like `ladder`), so the
|
|
367
|
+
* router just reads the boolean. Absent ⇒ feature off ⇒ byte-identical routing.
|
|
368
|
+
*/
|
|
369
|
+
export interface NatureRoutingRuntime {
|
|
370
|
+
enabled: boolean;
|
|
371
|
+
/** Observe-only (default true on first enable): compute + log the plan, do NOT re-route. */
|
|
372
|
+
dryRun: boolean;
|
|
373
|
+
chains?: Partial<Record<RoutingChain, ReadonlyArray<ChainPosition>>>;
|
|
374
|
+
}
|
|
375
|
+
/** The dryRun observation record handed to `onNatureRoutePlan` (FD11 readable canary). */
|
|
376
|
+
export interface NatureRoutePlan {
|
|
377
|
+
component: string | undefined;
|
|
378
|
+
category: ComponentCategory;
|
|
379
|
+
dryRun: boolean;
|
|
380
|
+
resolution?: RouteResolution;
|
|
381
|
+
/** Set when the resolver would FAIL CLOSED (critical-gate empty set). */
|
|
382
|
+
failClosed?: boolean;
|
|
383
|
+
}
|
|
227
384
|
export declare class IntelligenceRouter implements IntelligenceProvider {
|
|
228
385
|
private readonly opts;
|
|
229
386
|
private readonly cache;
|
|
@@ -261,6 +418,32 @@ export declare class IntelligenceRouter implements IntelligenceProvider {
|
|
|
261
418
|
private providerFor;
|
|
262
419
|
/** Default framework → shared provider; else the cached per-framework provider (null if binary missing). */
|
|
263
420
|
private resolveProvider;
|
|
421
|
+
/** Already warned that nature-routing enforcing mode is not yet wired (warn ONCE). */
|
|
422
|
+
private warnedNatureEnforceNotWired;
|
|
423
|
+
/**
|
|
424
|
+
* S4 A2 — CLI-door reachability probe for the nature router (FD5 availability walk).
|
|
425
|
+
* CLI doors coincide 1:1 with `IntelligenceFramework`. `resolveRoute` skips metered
|
|
426
|
+
* doors before ever calling this, so `door` is always a CLI door. The default framework
|
|
427
|
+
* is always reachable (shared provider); a non-default door is reachable iff its provider
|
|
428
|
+
* builds (binary present). Result is cached by `providerFor`.
|
|
429
|
+
*/
|
|
430
|
+
private isCliDoorReachable;
|
|
431
|
+
/**
|
|
432
|
+
* S4 A2 — enforcing-mode honesty guard. Flipping `dryRun:false` in A2.1 does NOT re-route
|
|
433
|
+
* (the selection integration is a tracked A2.2 remainder); routing stays byte-identical.
|
|
434
|
+
* Warn ONCE so the flip is an honest no-op rather than a silent dead switch.
|
|
435
|
+
*/
|
|
436
|
+
private warnNatureEnforceNotWired;
|
|
437
|
+
/** Chains already warned about as rejected (warn ONCE per chain, never per call). */
|
|
438
|
+
private warnedRejectedChains?;
|
|
439
|
+
/**
|
|
440
|
+
* FD4.3 — a live/hot chain (an operator `PATCH /config` override, or a resolved chain)
|
|
441
|
+
* violated the static harness-door ban and was REJECTED → built-in defaults. Warn ONCE
|
|
442
|
+
* per chain so the rejection is visible (the banned route is never opened; the built-in
|
|
443
|
+
* default is used instead). The full FD6 aggregated critical-gate attention item is a
|
|
444
|
+
* tracked A2.2 remainder; this is the honest, non-silent minimum.
|
|
445
|
+
*/
|
|
446
|
+
private warnNatureChainRejected;
|
|
264
447
|
evaluate(prompt: string, options?: IntelligenceOptions): Promise<string>;
|
|
265
448
|
/**
|
|
266
449
|
* Queue rung (Resilient Degradation Ladder §3b.3) — DEFERRABLE calls only. Enqueue the SAME
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntelligenceRouter.d.ts","sourceRoot":"","sources":["../../src/core/IntelligenceRouter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,
|
|
1
|
+
{"version":3,"file":"IntelligenceRouter.d.ts","sourceRoot":"","sources":["../../src/core/IntelligenceRouter.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* outage's worth of calls onto Claude all at once.
|
|
25
25
|
*/
|
|
26
26
|
import { isComponentCategory, categoryForComponent, } from './componentCategories.js';
|
|
27
|
-
import { LLM_ROUTING_NATURE } from '../data/llmBenchCoverage.js';
|
|
27
|
+
import { LLM_ROUTING_NATURE, NATURE_ROUTING_DEFAULT_CHAINS, ROUTING_LABEL_TO_MODEL_ID, CLAUDE_CODE_RESERVE_MODEL_ID, METERED_ROUTING_DOORS, NATURE_ROUTING_CRITICAL_GATES, } from '../data/llmBenchCoverage.js';
|
|
28
28
|
/**
|
|
29
29
|
* Runtime framework ids for the unknown-key hygiene warning on
|
|
30
30
|
* `swapAttemptTimeoutMsByFramework` (per-target-swap-timeout-spec.md, change
|
|
@@ -174,6 +174,286 @@ export function isBoundedGatingDegrade(component, options) {
|
|
|
174
174
|
return false; // unmapped + not gating ⇒ out of R1's scope, unchanged
|
|
175
175
|
return row.chain !== 'WRITE'; // WRITE is the sanctioned Opus-CLI lane; everything else is bounded/gating
|
|
176
176
|
}
|
|
177
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
178
|
+
* S4 Increment A2 — the nature-axis routing RESOLVER (dark/dryRun mechanism).
|
|
179
|
+
*
|
|
180
|
+
* `resolveRoute` is a PURE, side-effect-free evaluator (spec CR2-5): a stateless
|
|
181
|
+
* fold `component → resolvedNature → ordered eligible positions → { primary, swapTail }
|
|
182
|
+
* | 'fall-through' | 'no-route' | throw`. It owns no retry/backoff/breaker/budget
|
|
183
|
+
* machinery — those stay in the existing IntelligenceRouter primitives. Wired into
|
|
184
|
+
* `evaluate()` ONLY when `sessions.natureRouting.enabled` (dev-gated dark). In dryRun
|
|
185
|
+
* (the dev-agent default) it OBSERVES: computes + logs the plan, then passes through to
|
|
186
|
+
* today's routing (byte-identical selection). Enforcing selection (dryRun:false) is a
|
|
187
|
+
* tracked A2.2 remainder — see the honest guard in `evaluate()`.
|
|
188
|
+
* Spec: docs/specs/nature-axis-routing.md §Resolver / FD3 / FD4 / FD9.
|
|
189
|
+
* ──────────────────────────────────────────────────────────────────────────── */
|
|
190
|
+
/**
|
|
191
|
+
* The critical-gate fail-closed outcome (spec CR10-3/CR8-4): a DISTINCT typed error
|
|
192
|
+
* so a caller can never mistake it for an ordinary model failure. A mapped FD6
|
|
193
|
+
* critical gate whose chain has NO available door throws this; the caller applies its
|
|
194
|
+
* existing gating fail-closed semantics (block/deny). The low-stakes empty-set case is
|
|
195
|
+
* deliberately the ordinary non-gating error instead ('no-route'), typed apart on purpose.
|
|
196
|
+
*/
|
|
197
|
+
export class RouterFailClosedError extends Error {
|
|
198
|
+
component;
|
|
199
|
+
resolvedChain;
|
|
200
|
+
constructor(component, resolvedChain) {
|
|
201
|
+
super(`RouterFailClosedError: critical gate '${component ?? '(none)'}' has no available door on ` +
|
|
202
|
+
`chain ${resolvedChain} — failing closed (never legacy category routing, never the harness door)`);
|
|
203
|
+
this.component = component;
|
|
204
|
+
this.resolvedChain = resolvedChain;
|
|
205
|
+
this.name = 'RouterFailClosedError';
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Tier ordering for the FD3 nature tighten rule `E, B ≥ D ≥ A`. E and B are
|
|
210
|
+
* EQUIVALENT (both JUDGE-tier). A caller-declared nature may only ever RAISE the tier
|
|
211
|
+
* (tighten, the safe direction); a same-tier or lower declared nature is ignored (map
|
|
212
|
+
* wins), and a value outside {A,B,D,E} is ignored.
|
|
213
|
+
*/
|
|
214
|
+
const NATURE_TIER_RANK = { A: 0, D: 1, B: 2, E: 2 };
|
|
215
|
+
function isTaskNature(v) {
|
|
216
|
+
return v === 'A' || v === 'B' || v === 'D' || v === 'E';
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* FD3 — resolve `{ resolvedNature, resolvedChain }` from the component's static map row
|
|
220
|
+
* and an optional caller-declared `attribution.nature`. The component's OWN map row
|
|
221
|
+
* `{nature, chain}` is authoritative by default (preserving a per-component A/FAST vs
|
|
222
|
+
* A/SORT choice a pure nature→chain function could not). A caller-declared nature that
|
|
223
|
+
* TIGHTENS (strictly raises the tier) replaces the chain with the deterministically
|
|
224
|
+
* safe `JUDGE`; anything else is ignored. Returns `undefined` for an unmapped component.
|
|
225
|
+
* Pure — always available, independent of `sessions.natureRouting`.
|
|
226
|
+
*/
|
|
227
|
+
export function resolveNatureAndChain(component, declaredNature) {
|
|
228
|
+
const row = routingNatureFor(component);
|
|
229
|
+
if (!row)
|
|
230
|
+
return undefined;
|
|
231
|
+
const tightened = isTaskNature(declaredNature) && NATURE_TIER_RANK[declaredNature] > NATURE_TIER_RANK[row.nature];
|
|
232
|
+
if (tightened) {
|
|
233
|
+
// A tightened nature is always B or E (you only tighten UP a tier) → JUDGE ladder.
|
|
234
|
+
return { resolvedNature: declaredNature, resolvedChain: 'JUDGE' };
|
|
235
|
+
}
|
|
236
|
+
return { resolvedNature: row.nature, resolvedChain: row.chain };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* FD-LABEL / FD4.1 — resolve a chain position's `model` label to a concrete model id via
|
|
240
|
+
* `ROUTING_LABEL_TO_MODEL_ID`. A label absent from the registry (a tier hint like `fast`
|
|
241
|
+
* / `capable`) passes through unchanged and is resolved downstream by the existing
|
|
242
|
+
* per-adapter tier map. Pure.
|
|
243
|
+
*/
|
|
244
|
+
export function resolvePositionModelId(pos) {
|
|
245
|
+
return ROUTING_LABEL_TO_MODEL_ID[pos.door]?.[pos.model] ?? pos.model;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* FD4 place 3 — the harness-door ALLOWLIST clamp (deny-by-default), the nature-routing
|
|
249
|
+
* RUNTIME safety guarantee. For a bounded/gating (FAST/SORT/JUDGE) chain, the ONLY
|
|
250
|
+
* permitted `claude-code` position is the single sanctioned CONCRETE reserve id
|
|
251
|
+
* (`CLAUDE_CODE_RESERVE_MODEL_ID`); any OTHER claude-code id is clamped down to it. This
|
|
252
|
+
* is an allowlist, NOT a denylist — a future/unrecognized capable Claude id can never
|
|
253
|
+
* slip past. `WRITE` is exempt (its Opus-via-CLI quality lane is legitimate), and every
|
|
254
|
+
* non-claude-code door passes through.
|
|
255
|
+
*
|
|
256
|
+
* DELIBERATELY SEPARATE from A1's `clampClaudeCliSwapModel` (which returns the `balanced`
|
|
257
|
+
* TIER token and fires UNCONDITIONALLY on the always-on degrade/swap path): touching that
|
|
258
|
+
* function would change A1's shipped, byte-identical-when-off behavior. This clamp is
|
|
259
|
+
* nature-routing-scoped and concrete-id-based (FD4.1), applied only inside `resolveRoute`.
|
|
260
|
+
*/
|
|
261
|
+
export function clampToReserveOnCleanDoor(pos, resolvedChain) {
|
|
262
|
+
if (resolvedChain === 'WRITE')
|
|
263
|
+
return pos; // WRITE is the sole Opus-via-CLI-exempt lane
|
|
264
|
+
if (pos.door !== 'claude-code')
|
|
265
|
+
return pos; // the ban applies only to the harness door
|
|
266
|
+
if (pos.modelId === CLAUDE_CODE_RESERVE_MODEL_ID)
|
|
267
|
+
return pos; // allowlisted — the sanctioned reserve
|
|
268
|
+
return { ...pos, modelId: CLAUDE_CODE_RESERVE_MODEL_ID, clamped: true }; // deny-by-default → reserve
|
|
269
|
+
}
|
|
270
|
+
/** Tier LABELS (as opposed to concrete model ids) — for message classification only; not load-bearing. */
|
|
271
|
+
const KNOWN_TIER_LABELS = new Set([
|
|
272
|
+
'fast',
|
|
273
|
+
'balanced',
|
|
274
|
+
'capable',
|
|
275
|
+
'ultra',
|
|
276
|
+
'reasoning',
|
|
277
|
+
]);
|
|
278
|
+
/** A model label/id names a Fable model — FD8 §393, never emitted by any nature chain. Pure. */
|
|
279
|
+
export function isFableModel(s) {
|
|
280
|
+
return /fable/i.test(s);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Validate ONE chain position against the FD4 static ban (pure). Returns a violation or
|
|
284
|
+
* null. The claude-code allowlist test compares the REGISTRY-RESOLVED concrete id against
|
|
285
|
+
* the single sanctioned reserve id (FD4.1 — a tier label is permitted ONLY because/if it
|
|
286
|
+
* pins through `ROUTING_LABEL_TO_MODEL_ID` to that concrete id; an unpinned label fails).
|
|
287
|
+
*/
|
|
288
|
+
export function validateChainPosition(chain, pos, index) {
|
|
289
|
+
const resolvedModelId = ROUTING_LABEL_TO_MODEL_ID[pos.door]?.[pos.model] ?? pos.model;
|
|
290
|
+
// FD8 §393 — no chain (incl. the Opus-CLI-exempt WRITE lane) may resolve to a Fable model.
|
|
291
|
+
if (isFableModel(resolvedModelId) || isFableModel(pos.model)) {
|
|
292
|
+
return {
|
|
293
|
+
chain,
|
|
294
|
+
index,
|
|
295
|
+
door: pos.door,
|
|
296
|
+
model: pos.model,
|
|
297
|
+
resolvedModelId,
|
|
298
|
+
rule: 'fable-banned',
|
|
299
|
+
detail: `chain ${chain}[${index}] (${pos.door}/'${pos.model}') resolves to a Fable model ` +
|
|
300
|
+
`('${resolvedModelId}') — no nature chain may emit Fable (FD8 §393).`,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// The FD4 harness-door ban is scoped to bounded/gating chains; WRITE is the sole
|
|
304
|
+
// Opus-via-CLI-exempt lane (open-ended writing is where Opus-via-CLI is the best route).
|
|
305
|
+
if (chain === 'WRITE')
|
|
306
|
+
return null;
|
|
307
|
+
if (pos.door !== 'claude-code')
|
|
308
|
+
return null; // the ban targets ONLY the harness door
|
|
309
|
+
if (resolvedModelId === CLAUDE_CODE_RESERVE_MODEL_ID)
|
|
310
|
+
return null; // allowlisted — the sanctioned pinned reserve
|
|
311
|
+
const pinnedInRegistry = ROUTING_LABEL_TO_MODEL_ID['claude-code']?.[pos.model] !== undefined;
|
|
312
|
+
if (!pinnedInRegistry && KNOWN_TIER_LABELS.has(pos.model)) {
|
|
313
|
+
return {
|
|
314
|
+
chain,
|
|
315
|
+
index,
|
|
316
|
+
door: pos.door,
|
|
317
|
+
model: pos.model,
|
|
318
|
+
resolvedModelId,
|
|
319
|
+
rule: 'claude-code-tier-label',
|
|
320
|
+
detail: `chain ${chain}[${index}] is claude-code/'${pos.model}' — an UNPINNED tier label. The one ` +
|
|
321
|
+
`permitted claude-code position in a bounded/gating (FAST/SORT/JUDGE) chain must be the PINNED ` +
|
|
322
|
+
`concrete reserve id '${CLAUDE_CODE_RESERVE_MODEL_ID}', not a tier label (FD4 §205: a label could ` +
|
|
323
|
+
`resolve differently under a future CLI alias/tier remap).`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
chain,
|
|
328
|
+
index,
|
|
329
|
+
door: pos.door,
|
|
330
|
+
model: pos.model,
|
|
331
|
+
resolvedModelId,
|
|
332
|
+
rule: 'claude-code-non-reserve',
|
|
333
|
+
detail: `chain ${chain}[${index}] resolves claude-code → '${resolvedModelId}', which is NOT the sanctioned ` +
|
|
334
|
+
`reserve '${CLAUDE_CODE_RESERVE_MODEL_ID}'. Deny-by-default allowlist ban — the only permitted ` +
|
|
335
|
+
`claude-code FAST/SORT/JUDGE position is that single reserve id (FD4 §202-217).`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
/** Validate ONE chain's positions against the FD4 ban (pure). */
|
|
339
|
+
export function validateNatureRoutingChain(chain, positions) {
|
|
340
|
+
const out = [];
|
|
341
|
+
positions.forEach((p, i) => {
|
|
342
|
+
const v = validateChainPosition(chain, p, i);
|
|
343
|
+
if (v)
|
|
344
|
+
out.push(v);
|
|
345
|
+
});
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
/** Validate ALL four chains against the FD4 ban (pure). Empty array ⇒ the chain map is clean. */
|
|
349
|
+
export function validateNatureRoutingChains(chains) {
|
|
350
|
+
const out = [];
|
|
351
|
+
for (const c of ['FAST', 'SORT', 'JUDGE', 'WRITE']) {
|
|
352
|
+
out.push(...validateNatureRoutingChain(c, chains[c] ?? []));
|
|
353
|
+
}
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
/** True iff every chain passes the FD4 static harness-door ban. Pure. */
|
|
357
|
+
export function isNatureRoutingChainsValid(chains) {
|
|
358
|
+
return validateNatureRoutingChains(chains).length === 0;
|
|
359
|
+
}
|
|
360
|
+
/** The base component key (strip a "/segment" operation suffix + a `server:` prefix). */
|
|
361
|
+
function baseComponentKey(component) {
|
|
362
|
+
if (!component)
|
|
363
|
+
return undefined;
|
|
364
|
+
return component.split('/')[0].replace(/^server:/, '').trim();
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* The PURE nature-routing fold (spec §Resolver, steps 1–7). Deps are injected so the
|
|
368
|
+
* function is side-effect-free + trivially testable: `isDoorReachable` reports CLI-door
|
|
369
|
+
* reachability (the class wraps its provider cache). Metered doors are ALWAYS skipped in
|
|
370
|
+
* Increment A. The empty-`available` branch splits by authority class (CR6-2/CR3-1):
|
|
371
|
+
* unmapped → 'fall-through'; low-stakes mapped → 'no-route'; FD6 critical gate → throw
|
|
372
|
+
* RouterFailClosedError. (The R6 doc-tree refuse-to-author branch is a tracked A2.2
|
|
373
|
+
* remainder; until it lands a `claudeBanned` component falls into the low-stakes 'no-route'
|
|
374
|
+
* path — never onto Claude, since resolveRoute never emits a claude-code position for it
|
|
375
|
+
* unless its chain contains one, and R6 chains are authored off-Claude.)
|
|
376
|
+
*/
|
|
377
|
+
export function resolveRoute(component, declaredNature, chains, deps) {
|
|
378
|
+
const nc = resolveNatureAndChain(component, declaredNature);
|
|
379
|
+
if (!nc)
|
|
380
|
+
return { outcome: 'fall-through' }; // unmapped ⇒ legacy category routing (LA5 byte-identical)
|
|
381
|
+
// FD4.3 resolve-time assertion (spec §221-225 / §Resolver step 3): a live chain that
|
|
382
|
+
// violates the static harness-door ban is REJECTED → built-in defaults + notice. Because
|
|
383
|
+
// chains are read live per call and an operator may `PATCH /config` a chain wholesale, this
|
|
384
|
+
// runs on EVERY resolution (the same predicate the config-load merge + the build-lint use) —
|
|
385
|
+
// a runtime chain edit can never open the banned route. The per-position clamp below is a
|
|
386
|
+
// belt-and-suspenders third place; this rejection keeps the whole banned chain out.
|
|
387
|
+
let positions = chains[nc.resolvedChain] ?? NATURE_ROUTING_DEFAULT_CHAINS[nc.resolvedChain];
|
|
388
|
+
const chainViolations = validateNatureRoutingChain(nc.resolvedChain, positions);
|
|
389
|
+
if (chainViolations.length > 0) {
|
|
390
|
+
deps.onInvalidChain?.(nc.resolvedChain, chainViolations);
|
|
391
|
+
positions = NATURE_ROUTING_DEFAULT_CHAINS[nc.resolvedChain];
|
|
392
|
+
}
|
|
393
|
+
const available = [];
|
|
394
|
+
for (const p of positions) {
|
|
395
|
+
// Increment A: metered-API doors are DEFINED but always unavailable (skipped) until Increment B.
|
|
396
|
+
if (METERED_ROUTING_DOORS.has(p.door))
|
|
397
|
+
continue;
|
|
398
|
+
if (!deps.isDoorReachable(p.door))
|
|
399
|
+
continue;
|
|
400
|
+
const resolved = {
|
|
401
|
+
door: p.door,
|
|
402
|
+
label: p.model,
|
|
403
|
+
modelId: resolvePositionModelId(p),
|
|
404
|
+
clamped: false,
|
|
405
|
+
};
|
|
406
|
+
available.push(clampToReserveOnCleanDoor(resolved, nc.resolvedChain));
|
|
407
|
+
}
|
|
408
|
+
if (available.length === 0) {
|
|
409
|
+
// FD6 critical gate with no door ⇒ FAIL CLOSED (distinct typed error). NEVER legacy routing.
|
|
410
|
+
if (NATURE_ROUTING_CRITICAL_GATES.has(baseComponentKey(component) ?? '')) {
|
|
411
|
+
throw new RouterFailClosedError(component, nc.resolvedChain);
|
|
412
|
+
}
|
|
413
|
+
// Low-stakes mapped ⇒ the caller's own non-gating heuristic (the ordinary provider-down contract).
|
|
414
|
+
return { outcome: 'no-route' };
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
outcome: 'route',
|
|
418
|
+
resolvedNature: nc.resolvedNature,
|
|
419
|
+
resolvedChain: nc.resolvedChain,
|
|
420
|
+
primary: available[0],
|
|
421
|
+
swapTail: available.slice(1),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Merge an operator's partial `chains` override (config) over the built-in v3 defaults,
|
|
426
|
+
* per-chain. A chain the operator did not override keeps its default. (The FD4.3
|
|
427
|
+
* resolve-time validation that REJECTS a harness-door-violating override → defaults +
|
|
428
|
+
* notice is a tracked A2.2 remainder; the FD4 place-3 runtime clamp above already keeps
|
|
429
|
+
* the banned route closed on every resolved position regardless.)
|
|
430
|
+
*/
|
|
431
|
+
export function mergeNatureRoutingChains(override, onReject) {
|
|
432
|
+
if (!override)
|
|
433
|
+
return NATURE_ROUTING_DEFAULT_CHAINS;
|
|
434
|
+
// FD4.3 config-load assertion (spec §221-225): an operator override chain that violates the
|
|
435
|
+
// static harness-door ban is REJECTED at LOAD → the built-in default for that chain (never the
|
|
436
|
+
// banned override). Same predicate as the build-lint + the resolve-time assertion. A valid
|
|
437
|
+
// override is passed through verbatim; an un-overridden chain keeps its default (unvalidated —
|
|
438
|
+
// the shipped defaults are lint-verified clean).
|
|
439
|
+
const pick = (c) => {
|
|
440
|
+
const ov = override[c];
|
|
441
|
+
if (!ov)
|
|
442
|
+
return NATURE_ROUTING_DEFAULT_CHAINS[c];
|
|
443
|
+
const violations = validateNatureRoutingChain(c, ov);
|
|
444
|
+
if (violations.length > 0) {
|
|
445
|
+
onReject?.(c, violations);
|
|
446
|
+
return NATURE_ROUTING_DEFAULT_CHAINS[c]; // reject the banned override → built-in default
|
|
447
|
+
}
|
|
448
|
+
return ov;
|
|
449
|
+
};
|
|
450
|
+
return {
|
|
451
|
+
FAST: pick('FAST'),
|
|
452
|
+
SORT: pick('SORT'),
|
|
453
|
+
JUDGE: pick('JUDGE'),
|
|
454
|
+
WRITE: pick('WRITE'),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
177
457
|
export class IntelligenceRouter {
|
|
178
458
|
opts;
|
|
179
459
|
cache = new Map();
|
|
@@ -256,6 +536,51 @@ export class IntelligenceRouter {
|
|
|
256
536
|
resolveProvider(framework) {
|
|
257
537
|
return framework === this.opts.defaultFramework ? this.opts.defaultProvider : this.providerFor(framework);
|
|
258
538
|
}
|
|
539
|
+
/** Already warned that nature-routing enforcing mode is not yet wired (warn ONCE). */
|
|
540
|
+
warnedNatureEnforceNotWired = false;
|
|
541
|
+
/**
|
|
542
|
+
* S4 A2 — CLI-door reachability probe for the nature router (FD5 availability walk).
|
|
543
|
+
* CLI doors coincide 1:1 with `IntelligenceFramework`. `resolveRoute` skips metered
|
|
544
|
+
* doors before ever calling this, so `door` is always a CLI door. The default framework
|
|
545
|
+
* is always reachable (shared provider); a non-default door is reachable iff its provider
|
|
546
|
+
* builds (binary present). Result is cached by `providerFor`.
|
|
547
|
+
*/
|
|
548
|
+
isCliDoorReachable(door) {
|
|
549
|
+
return this.resolveProvider(door) !== null;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* S4 A2 — enforcing-mode honesty guard. Flipping `dryRun:false` in A2.1 does NOT re-route
|
|
553
|
+
* (the selection integration is a tracked A2.2 remainder); routing stays byte-identical.
|
|
554
|
+
* Warn ONCE so the flip is an honest no-op rather than a silent dead switch.
|
|
555
|
+
*/
|
|
556
|
+
warnNatureEnforceNotWired() {
|
|
557
|
+
if (this.warnedNatureEnforceNotWired)
|
|
558
|
+
return;
|
|
559
|
+
this.warnedNatureEnforceNotWired = true;
|
|
560
|
+
console.warn('IntelligenceRouter: sessions.natureRouting.dryRun is false, but nature-routing ENFORCING ' +
|
|
561
|
+
'selection is not wired in this build (Increment A2.1 ships the dark/dryRun observation ' +
|
|
562
|
+
'mechanism; enforcing selection is the tracked A2.2 remainder). Routing stays byte-identical — ' +
|
|
563
|
+
'the resolver plan is logged but not applied.');
|
|
564
|
+
}
|
|
565
|
+
/** Chains already warned about as rejected (warn ONCE per chain, never per call). */
|
|
566
|
+
warnedRejectedChains;
|
|
567
|
+
/**
|
|
568
|
+
* FD4.3 — a live/hot chain (an operator `PATCH /config` override, or a resolved chain)
|
|
569
|
+
* violated the static harness-door ban and was REJECTED → built-in defaults. Warn ONCE
|
|
570
|
+
* per chain so the rejection is visible (the banned route is never opened; the built-in
|
|
571
|
+
* default is used instead). The full FD6 aggregated critical-gate attention item is a
|
|
572
|
+
* tracked A2.2 remainder; this is the honest, non-silent minimum.
|
|
573
|
+
*/
|
|
574
|
+
warnNatureChainRejected(chain, violations) {
|
|
575
|
+
if (!this.warnedRejectedChains)
|
|
576
|
+
this.warnedRejectedChains = new Set();
|
|
577
|
+
if (this.warnedRejectedChains.has(chain))
|
|
578
|
+
return;
|
|
579
|
+
this.warnedRejectedChains.add(chain);
|
|
580
|
+
console.warn(`IntelligenceRouter: nature-routing chain '${chain}' REJECTED — it violates the FD4 harness-door ` +
|
|
581
|
+
`ban and was replaced with the built-in default (the banned route is never opened). ` +
|
|
582
|
+
violations.map((v) => v.detail).join(' | '));
|
|
583
|
+
}
|
|
259
584
|
async evaluate(prompt, options) {
|
|
260
585
|
const component = options?.attribution?.component;
|
|
261
586
|
const explicitCategory = options?.attribution?.category;
|
|
@@ -263,6 +588,34 @@ export class IntelligenceRouter {
|
|
|
263
588
|
? explicitCategory
|
|
264
589
|
: categoryForComponent(component);
|
|
265
590
|
const cfg = this.opts.resolveConfig();
|
|
591
|
+
// ── S4 A2 — nature-axis routing OBSERVATION (dev-gated dark; dryRun-first). ──
|
|
592
|
+
// Byte-identical when unset/off: when the feature is absent OR `enabled:false` this
|
|
593
|
+
// whole block is skipped and NOTHING about selection changes. In dryRun (the dev-agent
|
|
594
|
+
// default) it OBSERVES only — computes + logs the plan via `onNatureRoutePlan` — and
|
|
595
|
+
// still falls through to today's selection below. Enforcing SELECTION (dryRun:false) is
|
|
596
|
+
// a tracked A2.2 remainder; guarded here so a flip is an HONEST no-op, never a silent
|
|
597
|
+
// dead switch and never a mis-route. A resolver error can never break routing here.
|
|
598
|
+
const natureRt = this.opts.resolveNatureRouting?.();
|
|
599
|
+
if (natureRt?.enabled) {
|
|
600
|
+
const dryRun = natureRt.dryRun !== false; // default true on first enable
|
|
601
|
+
try {
|
|
602
|
+
const resolution = resolveRoute(component, options?.attribution?.nature, mergeNatureRoutingChains(natureRt.chains, (c, v) => this.warnNatureChainRejected(c, v)), {
|
|
603
|
+
isDoorReachable: (d) => this.isCliDoorReachable(d),
|
|
604
|
+
onInvalidChain: (c, v) => this.warnNatureChainRejected(c, v),
|
|
605
|
+
});
|
|
606
|
+
this.opts.onNatureRoutePlan?.({ component, category, dryRun, resolution });
|
|
607
|
+
}
|
|
608
|
+
catch (e) {
|
|
609
|
+
if (e instanceof RouterFailClosedError) {
|
|
610
|
+
this.opts.onNatureRoutePlan?.({ component, category, dryRun, failClosed: true });
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
this.opts.onNatureRoutePlan?.({ component, category, dryRun });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (!dryRun)
|
|
617
|
+
this.warnNatureEnforceNotWired();
|
|
618
|
+
}
|
|
266
619
|
// Unconfigured ⇒ exactly today's behavior.
|
|
267
620
|
if (!cfg)
|
|
268
621
|
return this.opts.defaultProvider.evaluate(prompt, options);
|