instar 1.3.771 → 1.3.772

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.
@@ -0,0 +1,89 @@
1
+ # Side-Effects Review — Nature-Axis Routing, Increment A2.1 (dark/dryRun mechanism)
2
+
3
+ **Spec:** docs/specs/nature-axis-routing.md (status: **converged — pending operator approval**;
4
+ `review-convergence` tag, NOT `approved:true` — the operator's step). This ships as a **Tier-1**
5
+ change: the smallest independently-landable **dark** unit of FD9's Increment A2. **Parent standards:**
6
+ "Structure > Willpower", "No Silent Degradation to Brittle Fallback", benchmark-cited routing
7
+ (INSTAR-Bench v3, rules R1/R2/R8), the Maturation Path (dev-gated dark), Migration Parity.
8
+
9
+ **Files:** src/data/llmBenchCoverage.ts, src/core/IntelligenceRouter.ts, src/core/types.ts,
10
+ src/commands/server.ts, src/core/PostUpdateMigrator.ts, tests/unit/nature-routing-resolver.test.ts,
11
+ tests/unit/migrate-nature-routing-dark.test.ts, upgrades/nature-routing-a2.eli16.md,
12
+ upgrades/side-effects/nature-routing-a2.md, upgrades/next/nature-routing-a2.md.
13
+
14
+ ## What changed
15
+
16
+ 1. **src/data/llmBenchCoverage.ts — the DATA half.** New pure, read-only constants: `RoutingDoor`
17
+ (CLI + metered-API door taxonomy, FD1), `CLI_ROUTING_DOORS` / `METERED_ROUTING_DOORS`,
18
+ `ChainPosition` + `NATURE_ROUTING_DEFAULT_CHAINS` (the four v3 CLI-only chains, FD2 — metered
19
+ positions are present but always skipped in Increment A), `ROUTING_LABEL_TO_MODEL_ID` (FD-LABEL/
20
+ FD4.1 — benchmark-label → concrete model id), `CLAUDE_CODE_RESERVE_MODEL_ID` (the single
21
+ sanctioned `claude-sonnet-4-6` reserve, FD4 place 1), and `NATURE_ROUTING_CRITICAL_GATES` (FD6).
22
+ Importing this changes NO behavior — it is data actuated only when the feature is enabled.
23
+ 2. **src/core/IntelligenceRouter.ts — the resolver.** New pure exports: `resolveNatureAndChain`
24
+ (FD3 tighten rule `E,B ≥ D ≥ A`), `resolvePositionModelId` (FD4.1), `clampToReserveOnCleanDoor`
25
+ (FD4 place-3 allowlist clamp), `resolveRoute` (the stateless fold with the four outcomes),
26
+ `mergeNatureRoutingChains`, and the `RouterFailClosedError` typed error. Two new
27
+ `IntelligenceRouterOptions` fields (`resolveNatureRouting`, `onNatureRoutePlan`) and a new,
28
+ tightly-scoped block in `evaluate()` that OBSERVES when the feature is enabled.
29
+ 3. **src/core/types.ts** — the `attribution.nature` opt-in tightening field (FD3) and the
30
+ `sessions.natureRouting` config schema (type-only, NOT in ConfigDefaults, per the
31
+ absent-equals-unchanged rule that `componentFrameworks`/`dynamicMcp` follow).
32
+ 4. **src/commands/server.ts** — the construction wiring: `resolveNatureRouting` reads config LIVE
33
+ per call and resolves `enabled` through `resolveDevAgentGate` (live-in-dryRun on a dev agent,
34
+ dark on the fleet); `onNatureRoutePlan` is an env-gated observe-only breadcrumb.
35
+ 5. **src/core/PostUpdateMigrator.ts** — `migrateConfigNatureRoutingDark` seeds `sessions.natureRouting`
36
+ dark on existing agents (`schemaVersion:3`, `dryRun:true`, `metered.goLive:false`; `enabled`
37
+ OMITTED for the dev-gate — the #1001 enable-path-integrity pattern), existence-checked + idempotent.
38
+
39
+ ## Explicitly DEFERRED (tracked A2.2 remainder — NOT dropped)
40
+
41
+ Read this as intentional scope, not omission. This increment ships the pure mechanism + dark/dryRun
42
+ observation. The following are the ordered remainder, each a separate landable change:
43
+
44
+ - **Enforcing SELECTION** (dryRun:false actually re-routes) — A2.1 wires OBSERVATION only; flipping
45
+ `dryRun:false` logs a one-time "enforcing not yet wired" warning and stays byte-identical (an honest
46
+ no-op, never a silent dead switch and never a mis-route).
47
+ - **FD4 places 1 & 2** — the build-time lint (`scripts/lint-nature-chains.mjs`) and the resolve-time
48
+ live-config validator. **Place 3 (the runtime allowlist clamp) DOES ship here** and is the actual
49
+ runtime guarantee: it clamps every resolved claude-code FAST/SORT/JUDGE position to the sanctioned
50
+ reserve id at selection time — so even a hand-edited chain cannot open the banned Opus-via-CLI route.
51
+ Places 1-2 are defense-in-depth for the build + config-edit surfaces.
52
+ - **FD5b injection-exposure map** + R-rule lints (FD5c) — inert in A2.1 anyway: the only injection-
53
+ restricted door (`groq-api`) and the R8 Flash-Lite pin are METERED doors, always skipped in Increment
54
+ A; no CLI door in any chain is injection-restricted.
55
+ - **The durable audit** (`logs/nature-routing.jsonl`) + `GET /intelligence/routing` dryRun plan/diff/
56
+ `?trace` read surface, the **FD6 aggregated critical-gate drift notice** + baseline, the **FD8
57
+ Fable→Opus** migration, and the **CLAUDE.md** capability blurb.
58
+ - **Increment B** (metered-door live routing + FD12 money/PIN go-live) — DEFERRED + PIN-gated, an
59
+ operator step, never autonomous. No metered door routes and no spend ledger is touched here.
60
+
61
+ ## Blast radius
62
+
63
+ - **Byte-identical when off — THE safety case.** When `sessions.natureRouting` is absent OR
64
+ `enabled:false` (the fleet default), the new block in `evaluate()` is skipped entirely and selection
65
+ is bit-for-bit today's. Asserted by name: `natureRouting UNSET ⇒ selection unchanged, onNatureRoutePlan
66
+ NEVER called` (the same options object is passed through untouched).
67
+ - **A1 is untouched.** `clampClaudeCliSwapModel` (A1's always-on degrade/swap clamp, returning the
68
+ `balanced` TIER token) is NOT modified — the new `clampToReserveOnCleanDoor` is a SEPARATE, nature-
69
+ routing-scoped, concrete-id clamp used only inside `resolveRoute`. Touching A1's fn would have changed
70
+ its shipped byte-identical behavior; a test asserts A1 still returns `{model:'balanced'}`.
71
+ - **dryRun cannot mis-route.** In dryRun the resolver only computes + logs; a resolver throw
72
+ (critical-gate fail-closed) is swallowed and recorded, never surfaced to the call path.
73
+ - **Hot path.** The observation block runs on every internal LLM call ONLY when the feature is enabled
74
+ (dev agents). It is a stateless fold over static maps + O(1) door-reachability reads (cached by the
75
+ provider cache); the breadcrumb log is env-gated so a dev agent's hot path stays quiet.
76
+ - **Config.** The seed is dark, existence-checked, idempotent, `enabled` omitted — it can never
77
+ force-dark a dev agent nor clobber an operator's config.
78
+
79
+ ## Second-pass review (Phase 5 — required: touches a "gate" / routing-of-safety-gates)
80
+
81
+ The change routes safety GATES, so it triggers the high-risk second pass. Independent audit focus:
82
+ (a) can the banned Opus-via-CLI route open? — No: the place-3 allowlist clamp is deny-by-default and
83
+ fires on every resolved claude-code bounded/gating position, including a hand-edited chain. (b) can a
84
+ critical gate fail OPEN? — No: the empty-set branch throws a distinct `RouterFailClosedError` for a
85
+ critical gate (never `no-route`, never legacy routing); a unit test asserts both a nature-B gate
86
+ (`MessagingToneGate`) and the nature-A R2-critical gate (`MessageSentinel`) throw. (c) is "off" truly
87
+ inert? — Yes, the byte-identical-when-off test proves it. **Reviewer verdict: Concur with the review.**
88
+ The one residual is that enforcing SELECTION is deferred — but since it is not wired, it cannot
89
+ mis-route; the runtime clamp already makes the future enforcing path safe on its own.