instar 1.3.778 → 1.3.780

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.
@@ -1003,3 +1003,44 @@ export const NATURE_ROUTING_CRITICAL_GATES: ReadonlySet<string> = new Set([
1003
1003
  'ProjectDriftChecker',
1004
1004
  'MessageSentinel', // nature A, R2-critical
1005
1005
  ]);
1006
+
1007
+ /* ────────────────────────────────────────────────────────────────────────────
1008
+ * S4 FD4.2 / FD5c — the R-rule STRUCTURAL-EXCLUSION component sets (R6, R8).
1009
+ * Spec: docs/specs/nature-axis-routing.md FD5(c) §296-314.
1010
+ *
1011
+ * The R-rule lints split into two kinds. R3/R4/R5/R7 are POSITION bans over the
1012
+ * authored chains (a banned door/model must never appear in a given chain) —
1013
+ * they live as pure predicates in IntelligenceRouter.ts + the build-lint. R6/R8
1014
+ * are COMPONENT-scoped structural pins — they guard the per-component maps
1015
+ * (nature, injection-exposure) that live config can NEVER override, so they are
1016
+ * BUILD-LINT-only (a source edit is the only way to break them, and the lint
1017
+ * catches it). These two sets are the data those two lints assert over.
1018
+ * ──────────────────────────────────────────────────────────────────────────── */
1019
+
1020
+ /**
1021
+ * R6 (spec §298-307, absolute) — doc-tree / cartographer components carry
1022
+ * `claudeBanned`: NO chain position may route them to any `claude-code` door.
1023
+ * Nature routing must NOT re-open the Claude route `CartographerSweepEngine.
1024
+ * probeRouting()` already forbids (their authoring uses off-Claude doors only:
1025
+ * codex → groq). This set is the structural pin: the FD4.2 R6 lint FAILS the
1026
+ * build if any member is ever given a nature/chain whose positions include a
1027
+ * `claude-code` door — so a future edit can't silently re-open the Claude route.
1028
+ */
1029
+ export const NATURE_ROUTING_CLAUDE_BANNED_COMPONENTS: ReadonlySet<string> = new Set([
1030
+ 'CartographerSweep', // authors doc-tree summaries over untrusted CODE — Claude-banned (R6)
1031
+ ]);
1032
+
1033
+ /**
1034
+ * R8 (spec §308-310) — the input-classifier-nature components. They are marked
1035
+ * `injectionExposed` (so the FD5b injection gate skips any non-injection-safe
1036
+ * door for them) AND pinned OFF the `gemini-api/flash-lite` position (Flash-Lite's
1037
+ * one reproduced trap-fall is exactly the input-classifier slot). The FD4.2 R8
1038
+ * lint asserts (a) each member is `exposed: true` in LLM_ROUTING_INJECTION_EXPOSURE,
1039
+ * and (b) the `flash-lite` position is behind the metered gate (structurally
1040
+ * unreachable in Increment A) — so no input-classifier can land on Flash-Lite.
1041
+ */
1042
+ export const NATURE_ROUTING_INPUT_CLASSIFIER_COMPONENTS: ReadonlySet<string> = new Set([
1043
+ 'InputClassifier',
1044
+ 'MessageSentinel',
1045
+ 'TaskClassifier',
1046
+ ]);
@@ -0,0 +1,23 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ Added a READ-ONLY "routing map" surface (Surface 3 of the routing control-room; FD11 readable canary, docs/specs/nature-axis-routing.md).
9
+
10
+ - New pure composer `src/core/natureRoutingMap.ts` (`buildNatureRoutingMap` / `traceComponent`) that reads the shipped static routing maps (`NATURE_ROUTING_DEFAULT_CHAINS`, `LLM_ROUTING_NATURE`, `ROUTING_LABEL_TO_MODEL_ID`, `NATURE_ROUTING_CRITICAL_GATES`, `LLM_UNTRUSTED_INPUT`, door taxonomy) and composes them into a display structure. Zero writes, mutates no config, changes no routing behavior.
11
+ - New Bearer-auth sibling route `GET /intelligence/routing/chains` — for every `knownComponents()` entry: its nature/chain, the ordered door+model fallback list (each label mapped to its concrete model id), and per-position/per-component flags (door class, injection-safe, money-gated, metered-skipped-in-Increment-A, critical-gate, untrusted-input). Optional `?trace=<component>` drills into one job-kind. Same 503-when-no-IntelligenceRouter shape as the legacy `GET /intelligence/routing`, which is left untouched.
12
+ - New "Routing Map" dashboard tab that renders the map as two tables (the four lanes + a by-job-kind breakdown). Read-only, no controls beyond Refresh.
13
+ - Tests: `tests/unit/nature-routing-map.test.ts` (composition + purity) and `tests/integration/intelligence-routing-chains-route.test.ts` (alive 200-not-503, `?trace`, 503-when-no-router, legacy route unchanged).
14
+
15
+ Read-only and reversible: no behavior change, no money, no PIN. The write/adjust/go-live controls (Surfaces 1/2, money/PIN-gated) are out of scope.
16
+
17
+ ## What to Tell Your User
18
+
19
+ - **See where your AI work is routed**: "There's a new Routing Map page in your dashboard. It shows, for every background AI job I run, which model provider it uses and its full ordered backup list if the first one is unavailable — plus a note on any that are paid or reserved. It's read-only: it just shows the map, it doesn't change anything."
20
+
21
+ ## Summary of New Capabilities
22
+
23
+ A new read-only observability surface for the internal LLM routing map — one API route (`GET /intelligence/routing/chains`) and one dashboard tab ("Routing Map"). It lets an operator see, for every internal AI job-kind, which door + model it uses and its full ordered fallback list, without changing any routing behavior.
@@ -0,0 +1,28 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Builds FD4.2 of the (still-dark) nature-axis router (spec: docs/specs/nature-axis-routing.md §296-314): the R-rule structural-exclusion guards R3–R8 that keep a bench-condemned model placement out of the router's authored chains and per-component maps.
9
+
10
+ - **R3/R4/R5/R7 position bans** (src/core/IntelligenceRouter.ts + scripts/lint-nature-chains.mjs): a pure predicate (validateChainPositionRRule) and matching build-lint reject a chain that places qwen-tier in a strict-format position (R3), the gemini-cli door in a JUDGE safety-gate position (R4), gpt-oss-20b / llama-4-scout in a JUDGE position (R5), or a DeepSeek door/model in a JUDGE position (R7). Wired into both the config-load merge and the resolve-time chain check alongside the existing FD4 harness-door ban, so a violating operator override is rejected → built-in default.
11
+ - **R6/R8 component-map pins** (src/data/llmBenchCoverage.ts + the build lint): doc-tree/cartographer components (NATURE_ROUTING_CLAUDE_BANNED_COMPONENTS) may never route to any claude-code door (R6); the input-classifier components (NATURE_ROUTING_INPUT_CLASSIFIER_COMPONENTS) must stay injection-exposed and pinned off the Flash-Lite door (R8, using the just-merged FD5b injection-exposure map). These guard per-component maps a config override can never touch, so they are build-lint-only.
12
+ - A new 18-test unit file covers each of R3–R8 (a compliant chain passes, a crafted violation fails with the right reason), the lint↔validator drift guard, fail-closed behavior, and config-load rejection.
13
+
14
+ NO runtime selection change: the shipped chains already obey every R-rule, so the rejection branches are never taken; the metered doors the rules touch (Flash-Lite, Groq) are already skipped in this increment. Dev-gated / dark: the whole nature block runs only when sessions.natureRouting is enabled; unset/off is byte-identical to before (asserted). This is the guard-lints increment only — NOT the metered-door Increment B, and NOT the go-live flip.
15
+
16
+ ## What to Tell Your User
17
+
18
+ This is internal plumbing for how I choose which model runs my own background checks — nothing to turn on, and nothing about our conversations changes. In plain terms: a benchmark found a handful of "never use this model for that job" rules — some cheaper models get fooled by hidden instructions when they act as a safety judge, or mangle the strict output a quick check needs. I turned those rules into build-time guards, so a future change to my settings can never quietly re-introduce one of those bad pairings. The routing feature is still off by default, so today this is invisible — it just seals those unsafe choices shut in advance.
19
+
20
+ ## Summary of New Capabilities
21
+
22
+ - **R-rule exclusion guards**: build-enforced rules that keep bench-condemned model-for-job pairings out of my internal routing — a fool-able judge model off a safety-gate, an over-thinking model off a strict-format check, a doc-tree writer off the Claude door, and my message classifiers off the cheapest easily-tricked door.
23
+ - **Config-safe by construction**: an operator routing override that would re-introduce a banned pairing is rejected and falls back to the safe default, so the exclusions survive any future edit.
24
+
25
+ ## Evidence
26
+
27
+ - tests/unit/nature-routing-rrule-lints.test.ts (18 tests) — green: the real authored chains + maps pass all R-rules via both the build lint and the TS validator; a crafted violation of each of R3–R8 fails with the correct reason; the lint↔validator drift guard for R3/R4/R5/R7; R6/R8 fail-closed on empty sets; config-load rejection of a violating override; and the combined validator flagging both FD4 and R-rule violations on one chain.
28
+ - npm run lint clean (the extended nature-chains lint passes on the real chains); npx tsc --noEmit clean; the pre-existing byte-identical-when-off block, A1's clamp assertion, the FD5b injection-exposure ratchet, and the FD4 harness-door ratchet all untouched and green.
@@ -0,0 +1,41 @@
1
+ # Routing Map surface — Plain-English Overview
2
+
3
+ > The one-line version: a new read-only dashboard page (and the API behind it) that shows, for every internal AI job the agent runs, which "door" and model it uses and its full ordered fallback list — a map you can read, that changes nothing.
4
+
5
+ ## The problem in one breath
6
+
7
+ Instar routes its background AI work (sentinels, gates, summarizers) across several "doors" — Claude Code, Codex, Gemini, and some paid API doors — each with an ordered fallback list. That routing map already exists in code as static data, but there was no way to actually SEE it: an operator diagnosing an unexpected route had to piece it together from source files and live config. This adds a single readable surface for "which door + model does each job-kind use, and what does it fall back to?"
8
+
9
+ ## What already exists
10
+
11
+ - **The four routing lanes ("chains")** — `FAST`, `SORT`, `JUDGE`, `WRITE`, each an ordered list of `{door, model}` positions with flags (money-gated, injection-safe, metered). Pure static data in `src/data/llmBenchCoverage.ts`.
12
+ - **The job-kind → lane registry** — a map from each internal component (MessagingToneGate, PresenceProxy, …) to its nature + lane.
13
+ - **The label → concrete-model-id registry** — resolves a benchmark label like `gpt-5.5` to the real model id.
14
+ - **The legacy route `GET /intelligence/routing`** — already shows the live "which framework does each component use" view (a different, narrower question). We do NOT touch it.
15
+ - **The `LLM Activity` dashboard tab** — the existing pattern for a read-only observability tab. We mirror it exactly.
16
+
17
+ ## What this adds
18
+
19
+ One new read-only API route and one new read-only dashboard tab. The route (`GET /intelligence/routing/chains`) composes the existing static maps into a single structure: every known job-kind, its lane, and its full ordered door+model fallback list, each position annotated (metered/skipped-now, money-gated, unsafe-for-untrusted-input, critical-gate). The dashboard "Routing Map" tab renders that as two clear tables. Nothing is added to the routing logic — the page only DISPLAYS the maps the router already uses.
20
+
21
+ ## The new pieces
22
+
23
+ - **`buildNatureRoutingMap()`** (a new pure module, `src/core/natureRoutingMap.ts`) — reads the shipped static maps and composes the display structure. It is side-effect-free: it performs zero writes, mutates no config, and changes no routing behavior. It is NOT allowed to select a route, adjust a cap, toggle anything, or touch money/PIN surfaces — it only describes.
24
+ - **`GET /intelligence/routing/chains`** — a Bearer-auth sibling of the existing routing route. Same 503-when-no-router shape. Optional `?trace=<component>` drills into one job-kind. Pure reads.
25
+ - **The "Routing Map" dashboard tab** — a read-only page, no inputs, no buttons that change state, just a Refresh.
26
+
27
+ ## The safeguards
28
+
29
+ **Prevents any behavior change.** The whole feature is a read of static data plus a live read-only annotation (each component's currently-enforced framework). It never writes, never routes, never flips a flag. The legacy route is left byte-for-byte unchanged, verified by test.
30
+
31
+ **Prevents scope creep into money/PIN territory.** This is explicitly Surface 3 (the read-only map) of a larger "control room". The write/adjust/go-live controls (Surfaces 1/2, money- and PIN-gated) are out of scope and untouched.
32
+
33
+ **Prevents a broken map from misleading.** The page states plainly that it shows the shipped map, not live actuation (nature routing is dev-gated / dry-run), and that metered doors are skipped for now — so an operator never mistakes the map for "what ran".
34
+
35
+ ## What ships when
36
+
37
+ All of it in one PR: the pure module, the route, the dashboard tab, and the tests (unit for the composer, integration/alive for the route returning 200 not 503). It is dark/reversible in the sense that it adds a surface only — removing it is a straight revert with no persistent state.
38
+
39
+ ## What you actually need to decide
40
+
41
+ Ship a read-only routing-map page + API that displays the existing routing map without changing any routing behavior — yes/no?
@@ -0,0 +1,63 @@
1
+ # Side-Effects Review — FD4.2: R-rule structural-exclusion lints (R3–R8)
2
+
3
+ **Version / slug:** `nature-axis-routing-fd42-rrule-lints`
4
+ **Date:** `2026-07-05`
5
+ **Author:** `echo (build hand, topic 29723 routing follow-through)`
6
+ **Second-pass reviewer:** `not required (Tier 1 — deterministic static structural exclusions over authored config data + pure candidate-eligibility predicates, dark/dev-gated, byte-identical no-op over the shipped chains; same surface + precedent as the FD4-lints #1388 and FD5b #1393)`
7
+
8
+ ## Summary of the change
9
+
10
+ Builds FD4.2 of `docs/specs/nature-axis-routing.md` (§296-314): the R-rule structural exclusions R3–R8 over the (still-dark) nature router's authored chains and per-component maps. Files touched:
11
+
12
+ - `src/data/llmBenchCoverage.ts` — two new component sets: `NATURE_ROUTING_CLAUDE_BANNED_COMPONENTS` (R6, doc-tree/cartographer) and `NATURE_ROUTING_INPUT_CLASSIFIER_COMPONENTS` (R8, the input-classifier pins).
13
+ - `src/core/IntelligenceRouter.ts` — the pure R-rule POSITION-ban predicate `validateChainPositionRRule` (R3/R4/R5/R7) + `validateNatureRoutingChainRRules` + a combined `validateNatureRoutingChainAll`; the `NatureChainBanRule` union extended with the four R-rule tags; the combined validator wired into both `resolveRoute`'s chain-rejection and `mergeNatureRoutingChains`'s config-load rejection alongside the existing FD4 ban.
14
+ - `scripts/lint-nature-chains.mjs` — the build lint extended with R3–R8: `rruleViolationForPosition` (mirrors the TS predicate), source-parsing helpers (`extractStringSet`/`extractNatureMap`/`extractExposureMap`), `r6Violations`/`r8Violations`, and `runNatureRuleLints`; `main()` now fails the build on any FD4 OR R-rule violation.
15
+ - Tests — NEW `tests/unit/nature-routing-rrule-lints.test.ts` (18 tests: real chains pass all R-rules via both lint + TS validator; a crafted violation of each of R3–R8 fails with the right reason; the lint↔validator drift guard for R3/R4/R5/R7; R6/R8 fail-closed on empty sets; config-load rejection of a violating override; the combined validator flagging both FD4 and R-rule violations). The pre-existing byte-identical block (`nature-routing-resolver.test.ts:261-296`), A1's clamp assertion, the FD5b ratchet, and the FD4 harness-door ratchet are UNTOUCHED and green.
16
+
17
+ ## Decision-point inventory
18
+
19
+ - `FD4.2 R-rule position bans (R3/R4/R5/R7)` — add — pure candidate-eligibility predicates over authored chains; a violating chain is rejected → built-in default (same mechanism as the FD4 ban). NO-OP over the shipped chains (already clean).
20
+ - `FD4.2 R6/R8 component-map pins` — add — build-lint-only structural assertions over per-component maps that a config override can never touch.
21
+ - `resolveRoute chain-rejection` — modify — now runs FD4 + R-rule violations via `validateNatureRoutingChainAll`. Byte-identical for a clean chain (rejection branch untaken).
22
+ - `mergeNatureRoutingChains config-load` — modify — rejects an operator override that violates FD4 OR an R-rule → built-in default + notice.
23
+ - `sessions.natureRouting enable/dryRun gate` — pass-through — the whole nature block still runs ONLY when enabled.
24
+
25
+ ## 1. Over-block
26
+
27
+ **What legitimate inputs does this reject that it shouldn't?** Over the shipped chains: none. Every default position passes all R-rules (asserted: "the REAL authored chain map + maps pass ALL R-rule lints"), so no route is removed and no chain is rejected. The rules only reject a chain that PLACES a bench-condemned model in an unsafe slot — a placement no legitimate config authors. R3's model match is `qwen`, R5's is `gpt-oss-20b|llama-4-scout`, R7's is `deepseek` — narrow, bench-cited denylist terms that appear in no shipped or plausible-safe chain; R4 bans only the `gemini-cli` DOOR from the JUDGE chain. False-positive surface is effectively nil.
28
+
29
+ ## 2. Under-block
30
+
31
+ **What failure modes does this still miss?** The R-rule set is exactly the spec's R3–R8 — it does not attempt to catch every conceivable bad placement, only the bench-condemned classes. R6 is a structural pin that is vacuously satisfied today (the cartographer component has no nature/chain yet), so it guards against a FUTURE edit rather than fixing a present defect — that is by design (the spec's "makes their exclusion structural so a future edit can't reintroduce them"). R8's flash-lite guarantee is realized by asserting flash-lite stays behind the metered gate (unreachable in Increment A) rather than a per-component resolve-time skip — the per-component walk-skip would be a SELECTION change, which this increment deliberately does not make (out of scope; the metered-skip already delivers the same runtime guarantee in Increment A). The FD7 prompt-anchor semantic-drift lint remains a separate deferred increment.
32
+
33
+ ## 3. Level-of-abstraction fit
34
+
35
+ Correct altitude. The R-rule predicates live beside the FD4 ban predicate in `IntelligenceRouter.ts` and ride the identical shape (`NatureChainViolation`, per-position → per-chain → all-chains) and the identical config-load/resolve wiring. The build lint extends the existing `lint-nature-chains.mjs` (the spec's named home for the compile-time place). The two component sets sit beside `NATURE_ROUTING_CRITICAL_GATES` in the same data module. No new engine, config surface, or route.
36
+
37
+ ## 4. Signal vs authority compliance
38
+
39
+ Compliant. The R-rule maps/predicates are deterministic build-time DATA + pure predicates (signals), enforced by a lint ratchet — not brittle runtime checks with blocking authority. The resolve/config-load rejection DOES exercise authority (it discards a violating chain → safe default), but only ever in the SAFE direction, on a MODEL-ROUTING decision — it never blocks a user message, never reads or credits a principal, never grants anything. `docs/signal-vs-authority.md` satisfied.
40
+
41
+ ## 5. Interactions
42
+
43
+ Interacts with the FD4 ban (orthogonal and composable: `validateNatureRoutingChainAll` = FD4 violations ∪ R-rule violations; a drift-guard/combined test proves both surface independently on one chain). Placed alongside — never shadowing — FD4; the two target different unsafe placements (FD4 = the claude-code harness door; R3/R4/R5/R7 = qwen/gemini-cli/weak-gate/deepseek by chain). The resolver's existing FD5b injection gate and A1 clamp are untouched (their tests stay green). The build lint's new source-parsing reads the same `llmBenchCoverage.ts` the FD4 lint already parses.
44
+
45
+ ## 6. External surfaces
46
+
47
+ No external surface. No new HTTP route, CLI command, MCP tool, Telegram/Slack path, or network egress. The lint is a `npm run lint` step (already wired for FD4). No secrets, tokens, or file paths surface anywhere.
48
+
49
+ ## 6b. Operator-surface quality
50
+
51
+ No operator-facing surface added or changed. The rejection "notice" path is the pre-existing FD4 `onInvalidChain`/`onReject` callback (internal); nothing new is emitted to a user.
52
+
53
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
54
+
55
+ Machine-local by design, and correctly so. The R-rule predicates and maps are static source data compiled into every machine identically; the validators are pure functions over that data with no persisted or replicated state. Each machine resolves the identical verdict independently — there is no cross-machine state to strand, replicate, or coalesce. No lease, ledger, or notice interaction.
56
+
57
+ ## 8. Rollback cost
58
+
59
+ Low and clean. Revert the PR: the two component sets, the R-rule predicates, and the two wiring call sites (reverting `validateNatureRoutingChainAll` back to `validateNatureRoutingChain`) drop out; the lint returns to FD4-only. No migration, no persisted state, no config schema change, no data-format change. Because the R-rules are a no-op over the shipped chains and only run when `natureRouting.enabled`, a rollback is invisible to every fleet agent (feature dark) and to any dev agent in dryRun.
60
+
61
+ ## Conclusion
62
+
63
+ FD4.2 ships the R3–R8 structural exclusions as a build lint (extended `lint-nature-chains.mjs`, wired into `npm run lint`) plus resolve-time/config-load rejection for the position bans (R3/R4/R5/R7) mirroring the FD4 ban, and build-lint pins for the component-scoped rules (R6/R8). It changes no runtime selection (the shipped chains are clean; the metered doors it touches are already skipped in Increment A), is byte-identical when the feature is off, and is dark/dev-gated. R8 consumes the just-merged FD5b injection-exposure map. The FD7 prompt-anchor semantic-drift lint and the metered-door Increment B are the tracked next increments, not built here; the spec is NOT marked approved by this change.
@@ -0,0 +1,117 @@
1
+ # Side-Effects Review — Read-only Routing Map surface (Surface 3)
2
+
3
+ **Version / slug:** `routing-map-surface`
4
+ **Date:** `2026-07-05`
5
+ **Author:** `Echo (instar-dev build hand)`
6
+ **Second-pass reviewer:** `not required (Tier 1, read-only surface)`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds a READ-ONLY "routing map" surface: a Bearer-auth API route `GET /intelligence/routing/chains` and a "Routing Map" dashboard tab. For every known internal job-kind (`knownComponents()`), it shows its nature/lane (`LLM_ROUTING_NATURE`), the ordered `NATURE_ROUTING_DEFAULT_CHAINS` fallback positions resolved to concrete model ids (`ROUTING_LABEL_TO_MODEL_ID`), and per-position/per-component flags (door class, per-position injection-safe, money-gated, metered-skipped-in-Increment-A, critical-gate, per-component untrusted-input, and the FD5b `LLM_ROUTING_INJECTION_EXPOSURE` classification — exposed + user/model/tool channels — surfaced now that it is on the rebased base). Files touched: new `src/core/natureRoutingMap.ts` (pure composer), `src/server/routes.ts` (one new sibling route + import), `dashboard/index.html` (nav button + panel + registry entry + `loadRoutingMap()`), and two new tests. It aligns with the spec's FD11 "readable canary" (`docs/specs/nature-axis-routing.md` §475). It composes existing static maps only — it changes NO routing behavior.
11
+
12
+ ## Decision-point inventory
13
+
14
+ The change adds NO decision point. It is pure display of existing routing decisions.
15
+
16
+ - `resolveRoute` / selection logic — pass-through (NOT touched; the map imports only the static data maps + `resolvePositionModelId`-equivalent label resolution).
17
+ - `GET /intelligence/routing` (legacy category→framework view) — pass-through (untouched; a sibling route was added instead of extending it, to keep it byte-identical).
18
+
19
+ ---
20
+
21
+ ## 1. Over-block
22
+
23
+ No block/allow surface — over-block not applicable. The route only returns data; it never rejects any input except an unknown `?trace=` name (a 404, correct).
24
+
25
+ ---
26
+
27
+ ## 2. Under-block
28
+
29
+ No block/allow surface — under-block not applicable.
30
+
31
+ ---
32
+
33
+ ## 3. Level-of-abstraction fit
34
+
35
+ Correct layer: an observability read surface, mirroring the existing `GET /intelligence/routing` + `LLM Activity` tab pattern. The composition logic lives in a pure `src/core/natureRoutingMap.ts` module (not in the route handler) so it is unit-testable in isolation and reusable; the route is a thin adapter that adds the one live read-only annotation (each component's currently-enforced legacy framework via `IntelligenceRouter.for`). It re-uses the shipped static maps rather than re-deriving routing — no parallel implementation of routing logic.
36
+
37
+ ---
38
+
39
+ ## 4. Signal vs authority compliance
40
+
41
+ **Required reference:** docs/signal-vs-authority.md
42
+
43
+ - [x] No — this change has no block/allow surface.
44
+
45
+ It is a pure read. It holds zero authority: it cannot route, gate, block, or mutate anything. It only reads and formats existing data.
46
+
47
+ ---
48
+
49
+ ## 5. Interactions
50
+
51
+ - **Shadowing:** none. The new route path (`/intelligence/routing/chains`) is distinct from the legacy `/intelligence/routing`; Express matches the exact path, no overlap. The 503-when-no-router guard mirrors the legacy route.
52
+ - **Double-fire:** none — read-only, idempotent, no events emitted.
53
+ - **Races:** none — reads shared IMMUTABLE static config maps (`NATURE_ROUTING_DEFAULT_CHAINS` etc.) and returns; writes nothing, holds no state.
54
+ - **Feedback loops:** none — it does not feed any system; nothing consumes its output except a human reading the dashboard.
55
+
56
+ Verified: an integration test asserts the legacy `GET /intelligence/routing` still returns its own shape (no `chains` key) and is not broken by the sibling.
57
+
58
+ ---
59
+
60
+ ## 6. External surfaces
61
+
62
+ - **Other agents / install base:** none — a new read route + a new dashboard tab, both additive.
63
+ - **External systems:** none — no Telegram/Slack/GitHub/Cloudflare calls; zero egress.
64
+ - **Persistent state:** none — the composer and route write nothing to disk, config, ledgers, or memory. Purity is asserted by test (repeated calls deep-equal; the shared static input map is unchanged after a build).
65
+ - **Operator surface (Mobile-Complete):** the new dashboard tab IS the operator surface, and it is fully phone-reachable via the standard dashboard (PIN-gated) — no API-only action. There is no operator ACTION here (nothing to do but read/refresh), so Mobile-Complete is trivially satisfied.
66
+ - **Timing/runtime:** none.
67
+
68
+ ---
69
+
70
+ ## 6b. Operator-surface quality (Operator-Surface Quality standard)
71
+
72
+ This change touches an operator surface (`dashboard/index.html`), so the operator-surface quality review is required.
73
+
74
+ 1. **Leads with the primary action?** Yes. The tab's purpose is to READ the routing map; on arrival it shows the four lanes and the by-job-kind table immediately (the loader fires on tab activation). There is no primary "action" to lead with beyond the content itself; a single Refresh button sits top-right, matching the sibling `LLM Activity` tab.
75
+ 2. **Zero raw internals as primary content?** The subject matter here IS technical identifiers (door names and model ids) — that is the whole point of a routing map, exactly as the `LLM Activity` tab shows provider + model names. They are presented in labeled table columns and plain-language flag chips ("metered · skipped now", "money-gated", "unsafe for untrusted input", "no-Claude"), NOT as raw JSON blobs, UUIDs, or hashes. A plain-English intro paragraph frames the page in non-engineer language. There are no inputs asking the operator to paste raw text (verified: the surface has no `<input>`/`<textarea>`; the raw-input lint passes).
76
+ 3. **Destructive actions de-emphasized?** No destructive actions exist on this surface (read-only) — nothing to revoke/delete/stop.
77
+ 4. **Plain language + phone width?** The lane cards use an auto-fit responsive grid (`minmax(240px,1fr)`) that stacks at phone width; the by-job-kind table lives in an `overflow-x:auto` container (matching the `LLM Activity` table pattern) so it scrolls inside its own box rather than breaking the page. Labels read plainly ("Job-kind", "Lane", "Enforced now", "Critical gate", "Untrusted input", "Fallback order").
78
+
79
+ ---
80
+
81
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
82
+
83
+ **machine-local BY DESIGN — pure per-machine observability.** The routing map is composed from static code-shipped data (identical on every machine of the same version) plus each machine's own live enforced-framework read. There is no durable state to strand on a topic transfer, no user-facing notice (so no one-voice gating needed), and no generated URL (so nothing to survive a machine boundary). An operator viewing the tab on any machine sees that machine's map; since the static data is version-pinned, the map is the same across machines at the same version (a version-skew would show honestly via the machine's own model ids). No pool-wide merge is warranted — "which doors does THIS machine's build route to" is a per-machine truth.
84
+
85
+ ---
86
+
87
+ ## 8. Rollback cost
88
+
89
+ - **Hot-fix release:** pure additive code change — revert the route + module + dashboard additions and ship as the next patch.
90
+ - **Data migration:** none — no persistent state introduced.
91
+ - **Agent state repair:** none — no existing agents need notification or reset; the surface simply disappears on revert.
92
+ - **User visibility:** none beyond the tab vanishing; no regression to any existing behavior during the rollback window (the legacy route and all routing logic are untouched).
93
+
94
+ ---
95
+
96
+ ## Conclusion
97
+
98
+ This review produced no design changes. The feature is a pure, additive, read-only observability surface that displays the existing routing map without altering any routing behavior, holding any authority, or writing any state. It is clear to ship. Scope was deliberately confined to Surface 3 (the read-only map); the money/PIN-gated write controls (Surfaces 1/2) are explicitly out of scope and untouched.
99
+
100
+ ---
101
+
102
+ ## Second-pass review (if required)
103
+
104
+ Not required — Tier 1, read-only surface, no block/allow authority, no persistent state.
105
+
106
+ ---
107
+
108
+ ## Evidence pointers
109
+
110
+ - `tests/unit/nature-routing-map.test.ts` — composer resolves known components to expected ordered chains + model ids + flags; purity assertion (repeated calls deep-equal, static input unmutated).
111
+ - `tests/integration/intelligence-routing-chains-route.test.ts` — alive test (200 + full map, not 503); `?trace` + 404; 503 when no router; legacy route unchanged; deterministic across calls.
112
+
113
+ ---
114
+
115
+ ## Class-Closure Declaration (display-only mirror)
116
+
117
+ No agent-authored-artifact defect and no self-triggered controller — not applicable. This change adds no loop/monitor/sentinel/reaper/scheduler/recovery path; it is a passive read surface.