instar 1.3.781 → 1.3.783
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/core/IntelligenceRouter.d.ts +9 -14
- package/dist/core/IntelligenceRouter.d.ts.map +1 -1
- package/dist/core/IntelligenceRouter.js +117 -56
- package/dist/core/IntelligenceRouter.js.map +1 -1
- package/dist/core/MeteredSpendGate.d.ts +119 -0
- package/dist/core/MeteredSpendGate.d.ts.map +1 -0
- package/dist/core/MeteredSpendGate.js +228 -0
- package/dist/core/MeteredSpendGate.js.map +1 -0
- package/dist/core/MeteredSpendLedger.d.ts +154 -0
- package/dist/core/MeteredSpendLedger.d.ts.map +1 -0
- package/dist/core/MeteredSpendLedger.js +434 -0
- package/dist/core/MeteredSpendLedger.js.map +1 -0
- package/dist/core/PinAttemptStore.d.ts +35 -0
- package/dist/core/PinAttemptStore.d.ts.map +1 -0
- package/dist/core/PinAttemptStore.js +85 -0
- package/dist/core/PinAttemptStore.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts +7 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +24 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/RenderedPlanStore.d.ts +61 -0
- package/dist/core/RenderedPlanStore.d.ts.map +1 -0
- package/dist/core/RenderedPlanStore.js +92 -0
- package/dist/core/RenderedPlanStore.js.map +1 -0
- package/dist/core/RoutingSpendCapsStore.d.ts +113 -0
- package/dist/core/RoutingSpendCapsStore.d.ts.map +1 -0
- package/dist/core/RoutingSpendCapsStore.js +241 -0
- package/dist/core/RoutingSpendCapsStore.js.map +1 -0
- package/dist/core/SpendAlertResolver.d.ts +77 -0
- package/dist/core/SpendAlertResolver.d.ts.map +1 -0
- package/dist/core/SpendAlertResolver.js +141 -0
- package/dist/core/SpendAlertResolver.js.map +1 -0
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
- package/dist/core/WriteDomainRegistry.js +17 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +9 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/routingSpendView.d.ts +10 -2
- package/dist/core/routingSpendView.d.ts.map +1 -1
- package/dist/core/routingSpendView.js +18 -10
- package/dist/core/routingSpendView.js.map +1 -1
- package/dist/core/types.d.ts +24 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/server/AgentServer.d.ts +9 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +177 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +10 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +196 -4
- package/dist/server/routes.js.map +1 -1
- package/dist/testing/selfActionRegistry.d.ts.map +1 -1
- package/dist/testing/selfActionRegistry.js +78 -0
- package/dist/testing/selfActionRegistry.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/upgrades/1.3.782.md +60 -0
- package/upgrades/1.3.783.md +66 -0
- package/upgrades/nature-routing-a2.2-enforcing.eli16.md +50 -0
- package/upgrades/side-effects/nature-routing-a2.2-enforcing.md +142 -0
- package/upgrades/side-effects/routing-spend-increment-b.md +44 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
- **`MeteredSpendLedger`** (`src/core/MeteredSpendLedger.ts`): the authoritative, append-only,
|
|
9
|
+
booking-priced money ledger — fsync'd row-append FIRST, atomic totals-cache rewrite second,
|
|
10
|
+
boot-time refold (the fold is canon), high-water re-fold on external append, per-key async
|
|
11
|
+
mutex, reserve/settle/expire idempotent terminal state machine, expiry-aware late settle,
|
|
12
|
+
ATOMIC check-and-reserve (the cap comparison happens inside the booking critical section).
|
|
13
|
+
- **`MeteredSpendGate`** (`src/core/MeteredSpendGate.ts`): the O(1) fail-closed admission gate —
|
|
14
|
+
refuses on not-live / frozen / no-cap-slice / lease-liveness-unconfirmed / unbounded-reservation /
|
|
15
|
+
unknown-price / implausible-price / stale-price policy / invalid-cap / cap-exceeded (strict `>`),
|
|
16
|
+
canonical-manifest-only pricing (an observed price is structurally not gate-eligible), code-defined
|
|
17
|
+
per-provider plausibility floors, conservative-max stale booking, and the per-door billed-token
|
|
18
|
+
mapping (`billedOutputTokens`) that errs HIGH (the Gemini thinking-token trap).
|
|
19
|
+
- **`RoutingSpendCapsStore`** (`src/core/RoutingSpendCapsStore.ts`): the PIN-only money-authority
|
|
20
|
+
store (`state/routing-spend-caps.json`) — versioned (optimistic concurrency), schema-validated
|
|
21
|
+
independently of the plan machinery, before+after audited, freeze set-TRUE-only (Bearer),
|
|
22
|
+
cap-lowering bumps the lease epoch.
|
|
23
|
+
- **`RenderedPlanStore`** (`src/core/RenderedPlanStore.ts`): the canonical rendered-plan machinery —
|
|
24
|
+
single-use nonce, TTL, version pins, commit-derives-solely-from-the-render (smuggled fields
|
|
25
|
+
structurally cannot land).
|
|
26
|
+
- **`PinAttemptStore`** (`src/core/PinAttemptStore.ts`): durable per-IP PIN lockout shared by ALL
|
|
27
|
+
PIN routes — a restart no longer resets brute-force lockout (S2-1).
|
|
28
|
+
- **`SpendAlertResolver`** (`src/core/SpendAlertResolver.ts`): the minimal alert-topic resolution
|
|
29
|
+
ladder (configured id → persisted record → fenced serving-lease-holder-only create-once) with
|
|
30
|
+
lifeline fallback + edge-latch-on-confirmed-delivery; stale-price alerts ride Increment B
|
|
31
|
+
(staleness changes admission behavior).
|
|
32
|
+
- **Routes:** `POST /routing-spend/plan` (Bearer render), `POST /routing-spend/caps/adjust` /
|
|
33
|
+
`go-live` / `unfreeze` (PIN plan commits), `POST /routing-spend/freeze` (Bearer, set-true-only),
|
|
34
|
+
`GET /routing-spend/caps/log` (Bearer audit read). The caps VIEW now composes real committed
|
|
35
|
+
totals + go-live state when the money layer is enabled.
|
|
36
|
+
- **Registry:** `routingSpend.money.enabled` documented in `DARK_GATE_EXCLUSIONS` (action-bearing).
|
|
37
|
+
|
|
38
|
+
## What to Tell Your User
|
|
39
|
+
|
|
40
|
+
Nothing yet — this ships dark and spends nothing. When your operator later enables the money
|
|
41
|
+
layer and PIN-arms a paid door, your Spend tab gains real committed-spend figures and PIN-gated
|
|
42
|
+
cap controls; every paid door stays off until that explicit arming. No paid door can route in
|
|
43
|
+
this release regardless (the metered dispatch seam is not wired — FD-11's release gate).
|
|
44
|
+
|
|
45
|
+
## Summary of New Capabilities
|
|
46
|
+
|
|
47
|
+
- (⚗️ Experimental, dark) The money authority for metered LLM doors: booking ledger + fail-closed
|
|
48
|
+
cap gate + PIN-gated caps/arming with rendered-plan approval + instant Bearer freeze + durable
|
|
49
|
+
PIN lockout + stale-price alert foundation. Increment C (full alert channel), the Layer-1c
|
|
50
|
+
provider-report capture/reconciliation, and the operator's amortized-subscription display land
|
|
51
|
+
in the follow-on PRs of this increment train (tracked: CMT-1929).
|
|
52
|
+
|
|
53
|
+
## Evidence
|
|
54
|
+
|
|
55
|
+
- `tests/unit/metered-spend-ledger.test.ts` — 12 cases incl. both torn-write directions,
|
|
56
|
+
two-concurrent-reserves, expiry-aware late settle, fail-closed unwritable-path refusal.
|
|
57
|
+
- `tests/unit/metered-spend-gate.test.ts` — the full fail-closed matrix (one case per refusal
|
|
58
|
+
reason) + the concurrent cap race (caught a real check-outside-mutex race during the build;
|
|
59
|
+
fixed by moving the comparison inside the booking critical section) + the billed-token mapping.
|
|
60
|
+
- `tests/unit/routing-spend-money-stores.test.ts` — S-F2 structural regression (money state is
|
|
61
|
+
outside PATCH /config), C4-4 validator, S2-3 smuggle, nonce replay, version drift, durable
|
|
62
|
+
lockout across restarts, alert-ladder + lifeline fallback + edge latch.
|
|
63
|
+
- `tests/integration/routing-spend-routes.test.ts` — the full HTTP PIN plan flow, dark-503s,
|
|
64
|
+
freeze/unfreeze asymmetry, smuggle-refusal over HTTP, audit log.
|
|
65
|
+
- `tests/e2e/routing-spend-lifecycle.test.ts` — feature-alive on the REAL AgentServer boot path
|
|
66
|
+
(200 with the explicit enable; 503 on a dev agent WITHOUT it — FD-16 pinned both ways).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# ELI16 — Nature-Axis Routing, Increment A2.2 (enforcing selection)
|
|
2
|
+
|
|
3
|
+
## The one-sentence version
|
|
4
|
+
|
|
5
|
+
We took the nature-routing resolver that has been silently watching ("here's the model+door
|
|
6
|
+
I *would* pick") and wired it so that, when an operator deliberately flips it on, it *actually*
|
|
7
|
+
picks — the resolved model and access door become the real selection, and its fallback list
|
|
8
|
+
drives the real failover.
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
Increment A2.1 shipped a resolver: given an internal check, it works out the *kind* of thinking
|
|
13
|
+
the check does (a quick sort, a careful judgment, open-ended writing), walks an ordered list of
|
|
14
|
+
`(door, model)` candidates, and returns the winner plus a fallback tail. But it was a spectator —
|
|
15
|
+
it computed the plan, logged it, and then still did exactly today's routing. That was the honest,
|
|
16
|
+
observe-first step. A2.2 is the step where the plan becomes the decision. Nothing about the
|
|
17
|
+
resolver's *choice* changes; what changes is that the choice is now applied.
|
|
18
|
+
|
|
19
|
+
## What already exists (and stays exactly as-is)
|
|
20
|
+
|
|
21
|
+
- **The resolver** (`resolveRoute`) — the pure, side-effect-free fold with four honest outcomes:
|
|
22
|
+
a resolved route; "fall through to today's routing" (for an unmapped component); "no route —
|
|
23
|
+
use your own backup heuristic" (for a low-stakes sorter when every door is down); or **throw a
|
|
24
|
+
distinct fail-closed error** (for a safety gate when every door is down — a gate must fail shut).
|
|
25
|
+
- **The safety clamps** — the deny-by-default allowlist that pins any Claude-door bounded/judgment
|
|
26
|
+
call to the single sanctioned Sonnet reserve id (so the measured-banned Opus-via-Claude-CLI route
|
|
27
|
+
can never open), the FD4 chain validators, and A1's always-on degrade clamp. None of these change.
|
|
28
|
+
- **The failure-swap loop** — the existing machinery that, on a runtime failure, tries the next
|
|
29
|
+
door with per-target timeouts, a total budget, a rate-limit backoff, and honest degrade notes.
|
|
30
|
+
|
|
31
|
+
## What this adds
|
|
32
|
+
|
|
33
|
+
When the feature is enabled AND the operator has deliberately turned off dryRun, the resolved plan
|
|
34
|
+
**replaces** today's selection: the primary `(door, model)` becomes the door and model the call runs
|
|
35
|
+
on, and the resolved fallback tail feeds the *existing* failure-swap loop verbatim — each fallback
|
|
36
|
+
position carrying its own concrete model. The four outcomes are honored on this real path: a fail-
|
|
37
|
+
closed gate now actually throws (its caller blocks/denies, never falls open); a low-stakes empty set
|
|
38
|
+
raises the ordinary "use your heuristic" error the caller already catches (never today's category
|
|
39
|
+
routing, so the harness door can't sneak back in); an unmapped component falls through to today's
|
|
40
|
+
routing unchanged. The old one-time "enforcing not yet wired" warning is retired — it is now real.
|
|
41
|
+
|
|
42
|
+
## The safety promise
|
|
43
|
+
|
|
44
|
+
When the feature is **off or unset** (the fleet default), the router is **bit-for-bit like today** —
|
|
45
|
+
same door, same model, same options object passed through untouched. The named byte-identical test
|
|
46
|
+
still guards this. In **dryRun** (the dev-agent default) it is still a spectator: it observes and logs,
|
|
47
|
+
changes nothing. Only a deliberate operator flip of `dryRun:false` — after reviewing the dryRun plan —
|
|
48
|
+
ever activates the re-routing. No defaults changed; the fleet stays dark; the metered paid doors stay
|
|
49
|
+
skipped (that is Increment B, still deferred and PIN-gated). This change makes the switch *real*, but
|
|
50
|
+
leaves it *off*.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Side-Effects Review — Nature-Axis Routing, Increment A2.2 (enforcing selection)
|
|
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). **Tier-1** change: the enforcing
|
|
5
|
+
wiring for FD9's Increment A2, riding the same dev-gated dark / dryRun-defaulted ladder A2.1 shipped on.
|
|
6
|
+
**Parent standards:** "Structure > Willpower", "No Silent Degradation to Brittle Fallback", benchmark-
|
|
7
|
+
cited routing (INSTAR-Bench v3, rules R1/R2), the Maturation Path (dev-gated dark; the operator's
|
|
8
|
+
deliberate `dryRun:false` flip is the only activation), Migration Parity (no new config, no default flip).
|
|
9
|
+
|
|
10
|
+
**Date:** 2026-07-05
|
|
11
|
+
**Author:** Echo (build hand, topic 29723)
|
|
12
|
+
**Second-pass reviewer:** required (touches routing-of-safety-gates) — see §Second-pass.
|
|
13
|
+
|
|
14
|
+
**Files:** src/core/IntelligenceRouter.ts, tests/unit/nature-routing-resolver.test.ts,
|
|
15
|
+
upgrades/nature-routing-a2.2-enforcing.eli16.md, upgrades/side-effects/nature-routing-a2.2-enforcing.md,
|
|
16
|
+
upgrades/next/nature-routing-a2.2-enforcing.md.
|
|
17
|
+
|
|
18
|
+
## Summary of the change
|
|
19
|
+
|
|
20
|
+
`IntelligenceRouter.evaluate()`'s nature block previously OBSERVED (logged the resolved plan) and then
|
|
21
|
+
fell through to today's selection even with `dryRun:false`, guarded by a one-time "enforcing not yet
|
|
22
|
+
wired" warning. This change makes `dryRun:false` ACTUALLY enforce (spec §Resolver steps 8-9): on outcome
|
|
23
|
+
`route` the resolved primary `(door, model)` replaces `resolveFramework()`'s door + the caller's tier,
|
|
24
|
+
and the resolved `swapTail` feeds the EXISTING failure-swap loop (each tail position carrying its own
|
|
25
|
+
concrete model). On `no-route` it raises the ordinary non-gating heuristic error (never legacy routing);
|
|
26
|
+
on a critical-gate empty set it re-throws `RouterFailClosedError`; on `fall-through` (unmapped) it uses
|
|
27
|
+
today's routing untouched. The obsolete warning method + field are removed.
|
|
28
|
+
|
|
29
|
+
## Decision-point inventory
|
|
30
|
+
|
|
31
|
+
- `IntelligenceRouter.evaluate() — nature block` — modify — was observe-only; now, when
|
|
32
|
+
`enabled && !dryRun`, applies the resolved plan.
|
|
33
|
+
- `IntelligenceRouter.evaluate() — framework/model selection` — modify — `framework`/`evalOptions`
|
|
34
|
+
now derive from the enforced primary when a plan is enforced, else today's category routing.
|
|
35
|
+
- `IntelligenceRouter.evaluate() — failure-swap loop` — pass-through/extend — same loop, now driven by
|
|
36
|
+
either the resolved `swapTail` (per-position concrete model) or today's `cfg.failureSwap` frameworks.
|
|
37
|
+
- `warnNatureEnforceNotWired()` + `warnedNatureEnforceNotWired` — remove — the no-op is now real wiring.
|
|
38
|
+
- `resolveRoute` / `clampToReserveOnCleanDoor` / `mergeNatureRoutingChains` / the FD4/FD4.2 validators —
|
|
39
|
+
pass-through — UNCHANGED (A2.2 consumes them; it does not weaken them).
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 1. Over-block
|
|
44
|
+
|
|
45
|
+
The only "block" surface is the critical-gate fail-closed throw. On the real path a mapped FD6 critical
|
|
46
|
+
gate whose chain has NO available door throws `RouterFailClosedError` → the caller blocks/denies. This
|
|
47
|
+
can only fire when EVERY door in the gate's chain is unreachable (metered doors are skipped in Increment
|
|
48
|
+
A, so realistically all CLI doors down). That is the correct fail-closed direction for a safety gate; it
|
|
49
|
+
is not an over-block of legitimate traffic — a reachable door always routes. In dryRun the same throw is
|
|
50
|
+
swallowed (observe-only), so the enforcing throw only ever happens on the operator's deliberate flip.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 2. Under-block
|
|
55
|
+
|
|
56
|
+
`no-route` (low-stakes empty set) deliberately does NOT block — it raises the ordinary non-gating error
|
|
57
|
+
the caller catches into its heuristic. This is spec-mandated (§573-581): a low-stakes sorter degrades to
|
|
58
|
+
its own heuristic, exactly as it does today when its provider is down. It is NOT a safety gate, so not
|
|
59
|
+
blocking is correct. An UNEXPECTED (non-`RouterFailClosedError`) resolver error also does not block — it
|
|
60
|
+
is recorded and falls through to today's routing (fail-safe), because the pure fold only throws the typed
|
|
61
|
+
error by design; a different throw is a bug and must not break routing.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 3. Silent failure / silent degradation
|
|
66
|
+
|
|
67
|
+
No silent path. `no-route` calls `onHeuristicFallthrough` before throwing (never-silent tracking). Every
|
|
68
|
+
swap attempt emits an `onDegrade` note; a successful swap emits the served-by note; a fail-closed gate
|
|
69
|
+
throws a distinct typed error the caller cannot mistake for a model failure. The enforcing primary always
|
|
70
|
+
routes through `resolveProvider` (a real provider), never a heuristic pretending to be an answer. The
|
|
71
|
+
retired warning was itself the only "honest no-op" scaffolding; removing it removes a now-false statement.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 4. Byte-identical-when-off (THE safety case)
|
|
76
|
+
|
|
77
|
+
When `sessions.natureRouting` is absent OR `enabled:false` (the fleet default), the nature block is
|
|
78
|
+
skipped entirely: `enforced` stays undefined, `enforcedNoRoute` false, and `evalOptions` remains the SAME
|
|
79
|
+
`options` object reference. Selection is bit-for-bit today's. In dryRun the plan is logged but `enforced`
|
|
80
|
+
stays undefined (only set when `!dryRun`), so selection is still unchanged. Asserted by the named test
|
|
81
|
+
`natureRouting UNSET ⇒ selection unchanged` (same options object) and the FD4.3 banned-chain-when-off
|
|
82
|
+
test, both green. A new test (`dryRun:true still OBSERVES only`) proves enforcing changes nothing in
|
|
83
|
+
dryRun even with a reachable alternate door.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 5. Model-selection correctness (the concrete-id path)
|
|
88
|
+
|
|
89
|
+
The resolved primary's CONCRETE model id (e.g. `gpt-5.4-mini`, `claude-sonnet-4-6`) is placed on
|
|
90
|
+
`options.model` and rides verbatim to the provider — the CLI adapters (`resolveCliFlag` /
|
|
91
|
+
`resolveCliModelFlag`) return a concrete id as-is and only map the three tier tokens, so a concrete id is
|
|
92
|
+
honored. The claude-code reserve is already reserve-clamped by the resolver, so the caller's `capable`
|
|
93
|
+
(→ Opus, the banned door) is replaced by the pinned Sonnet reserve id — asserted by the enforcing test
|
|
94
|
+
`a JUDGE gate landing on claude-code enforces the CONCRETE reserve id`. Metered doors remain skipped
|
|
95
|
+
(Increment A) — asserted by `a metered primary position is SKIPPED`.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 6. Blast radius
|
|
100
|
+
|
|
101
|
+
- **Scope of the real path:** only when `enabled && dryRun:false` — i.e. an operator's deliberate flip
|
|
102
|
+
after the dryRun soak. Dev agents default to dryRun; the fleet is dark. No default is changed.
|
|
103
|
+
- **A1 / A2.1 untouched:** `clampClaudeCliSwapModel` (A1 tier clamp), `clampToReserveOnCleanDoor`,
|
|
104
|
+
`resolveRoute`, the FD4/FD4.2 validators are consumed as-is, not modified. All their tests stay green.
|
|
105
|
+
- **The failure-swap loop:** reused verbatim, gated on `gating||deferrable` exactly as today; nature
|
|
106
|
+
positions ride the same per-target timeout / total-budget / backoff / degrade machinery. A legacy
|
|
107
|
+
swap (no nature plan) is byte-identical (base is `evalOptions`, which === `options` off the nature
|
|
108
|
+
path; no reference-equality assertion in the swap tests regresses).
|
|
109
|
+
- **`cfg` nullability:** the `if (!cfg) return default` short-circuit now lives in the non-enforced
|
|
110
|
+
branch; downstream `cfg?.fallback` / `cfg?.failureSwap` use optional chaining so an enforced plan with
|
|
111
|
+
a null `cfg` (the common fleet shape) is safe. The enforced primary door is always reachable (the
|
|
112
|
+
resolver only emits reachable CLI doors), so the `!primary` degrade path is not reached under
|
|
113
|
+
enforcement.
|
|
114
|
+
|
|
115
|
+
## Explicitly DEFERRED (tracked remainder — NOT dropped)
|
|
116
|
+
|
|
117
|
+
- **FD6 critical-gate drift NOTICE + baseline** — depends on the durable `state/nature-routing-baseline.json`
|
|
118
|
+
store + N=3 debounce + aggregation, which the spec classifies as an "orthogonal surface AROUND the fold"
|
|
119
|
+
and whose PIN-approved baseline MUTATION is explicitly operator-only / out of this scope. The
|
|
120
|
+
`onNatureRoutePlan` hook already records the resolved plan for a future drift tracker to consume.
|
|
121
|
+
- **R6 doc-tree refuse-to-author branch (FD5c)** — DORMANT in Increment A: no component in the shipped
|
|
122
|
+
nature map carries `claudeBanned`, and R6 chains are authored off-Claude, so a claudeBanned empty set
|
|
123
|
+
already resolves to `no-route` → the caller's heuristic (which for `CartographerSweepEngine` IS
|
|
124
|
+
refuse-rather-than-Claude). A dedicated refuse-to-author router branch is a separate concern.
|
|
125
|
+
- **Increment B** — metered live routing + FD12 money/PIN go-live. Untouched; `metered.goLive` stays
|
|
126
|
+
false and unreferenced by this change.
|
|
127
|
+
- **The durable audit log + `GET /intelligence/routing` enforced-diff surface** — the read surfaces are a
|
|
128
|
+
tracked follow-up; enforcement does not require them.
|
|
129
|
+
|
|
130
|
+
## Second-pass review (Phase 5 — required: routes safety GATES)
|
|
131
|
+
|
|
132
|
+
This change makes safety-gate routing REAL, so it triggers the high-risk second pass. Focus:
|
|
133
|
+
(a) can the banned Opus-via-CLI route open under enforcement? — No: the resolved position is already
|
|
134
|
+
reserve-clamped, and the enforced model is the concrete Sonnet reserve id, asserted by the JUDGE-reserve
|
|
135
|
+
enforcing test. (b) can a critical gate fail OPEN under enforcement? — No: the empty-set critical gate
|
|
136
|
+
re-throws `RouterFailClosedError` on the real path (asserted `rejects.toThrow(RouterFailClosedError)`,
|
|
137
|
+
default provider NOT called), and `no-route` is reserved for non-critical low-stakes components only.
|
|
138
|
+
(c) is "off" still truly inert? — Yes, the byte-identical-when-off tests remain green and a new
|
|
139
|
+
dryRun-observes-only enforcing test proves the flip is the sole activation. **Reviewer focus verdict:
|
|
140
|
+
the enforcing path preserves every safety invariant the resolver already guaranteed; the only new risk
|
|
141
|
+
is the model-application boundary (concrete id on `options.model`), which is covered by the concrete-id
|
|
142
|
+
enforcing tests and the pre-existing adapter tier-or-id resolution.**
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Side-Effects Review — Routing Control Room Increment B (money layer)
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/routing-control-room-spend-alerts.md` (review-convergence r7, approved 2026-07-07)
|
|
4
|
+
**Worktree:** `echo/money-increment-b` off `JKHeadley/main` @ `4811c74bb` (v1.3.782) — remotes verified (`JKHeadley` = canonical, push target), version recorded per Phase 2.
|
|
5
|
+
**Scope of this PR:** Increment B money core — `MeteredSpendLedger` (booking-priced, append-only, fold-is-canon), the O(1) fail-closed money gate, the PIN-only caps/go-live store (`state/routing-spend-caps.json`, versioned), rendered-plan machinery (nonce + TTL + version-pin), durable PIN-attempt store, Bearer freeze (set-true-only), caps-view wiring to real committed totals, and the stale-price/observed-drift alert emission on the minimal topic-resolver foundation (lifeline-fallback rails). Layer 1c provider_cost_report/reconciliation and the full Increment C channel abstraction land as the NEXT PRs in this sequence (tracked: CMT-1929).
|
|
6
|
+
|
|
7
|
+
## Phase 1 — Principle check (signal-vs-authority)
|
|
8
|
+
|
|
9
|
+
**Does this change involve a decision point?** YES — the money gate blocks metered LLM calls at cap.
|
|
10
|
+
|
|
11
|
+
**Compliance:** The gate holds blocking authority with DETERMINISTIC ARITHMETIC over booked dollars (`committed + estCost > cap`, strict `>`), never brittle pattern-matching over meaning — the same class as the fork-bomb spawn cap and the test-runner semaphore, where deterministic authority is the CORRECT shape (docs/signal-vs-authority.md draws the line at brittle/semantic checks holding authority; arithmetic over money is neither brittle nor semantic). Authority is additionally BOUNDED: a cap refusal is a swap-tail ADVANCE (the chain falls to a free door), never a chain kill; `RouterFailClosedError` remains reserved for whole-chain exhaustion. Everything reporting-side (provider reports, drift, subsidies, credits) is SIGNAL-ONLY by structural exclusion — the gate's read set cannot reach those stores (unit-tested).
|
|
12
|
+
|
|
13
|
+
## Phase 2 — Plan
|
|
14
|
+
|
|
15
|
+
- **Decision points touched:** metered-call admission (new gate, fail-closed on every uncertainty); PIN plan-commit (S2-3 rendered-plan, single-use nonce, version drift refusal); Bearer freeze (set-true-only — halting money is cheap, releasing is the operator's).
|
|
16
|
+
- **Existing detectors/authorities interacted with:** `checkMandatePin` (gains a durable attempt write-through, semantics unchanged); `PATCHABLE_CONFIG_KEYS` (regression-tested to NEVER reach the caps store or any gate-consumed price value — S-F2); `RoutingPriceAuthority` (gate consumes canonical-validated points ONLY, with code-defined per-provider plausibility floors); serving-lease staleness window (self-fence read); `DARK_GATE_EXCLUSIONS` (money authority documented as the action-bearing exclusion — dark everywhere until an explicit operator enable, FD-16).
|
|
17
|
+
- **Rollback path:** every surface is additive + dark. `routingSpend.money.enabled` absent ⇒ routes 503, gate never constructed, ledger never written. Disabling reverts to Increment A behavior byte-for-byte. The caps store and ledger files are inert data at rest; no migration touches existing state.
|
|
18
|
+
|
|
19
|
+
## Phase 4 — Side-effects review
|
|
20
|
+
|
|
21
|
+
1. **Over-block:** The gate fails closed on unknown price / stale lease / unbounded reservation — this can refuse legitimate metered calls when the manifest is stale or the pool is partitioned. Accepted BY DESIGN (money safety over availability); the refusal is a swap-tail advance to free doors, so the JOB still completes — only the paid door is withheld. Stale-price default is `book-conservative-max` (spend continues, over-booked), so manifest staleness alone does not halt paid routing.
|
|
22
|
+
2. **Under-block:** A crash between fsync'd row-append and totals update leaves totals STALE-LOW by at most one booking; the next gate read runs the row-count/high-water check and re-folds — bounded, tested both torn directions. Reserve sizing uses `max_tokens` worst-case; a provider that bills MORE than `max_tokens` output would under-reserve — mitigated by settle-books-actual (absolute row) and the billed-token per-door mapping erring HIGH. **Build-time finding worth recording:** the first gate implementation compared committed-vs-cap OUTSIDE the ledger's booking mutex; the two-concurrent-reserves test caught two $8 admits passing against a $12 cap. Fixed by moving the comparison INSIDE the per-key critical section (`admitOnlyUnderCaps` on `reserve()`), which is now the pinned shape — the check-and-reserve is atomic by construction.
|
|
23
|
+
3. **Level-of-abstraction fit:** The ledger/gate live in `src/core/` beside `DriftSpendLedger` (whose write-discipline they adopt); the caps store is a dedicated PIN-only store OUTSIDE config, exactly where the spec pins it (S-F2). The gate is a library consumed at the (future) metered dispatch seam — it does NOT modify `IntelligenceRouter` selection (the seam is declared, not wired; metered doors still skip — FD-11).
|
|
24
|
+
4. **Signal vs authority:** See Phase 1 — compliant; deterministic authority at the right layer, reporting side structurally excluded from the gate.
|
|
25
|
+
5. **Interactions:** Freeze vs go-live: freeze halts NEW admissions only (in-flight settles land — A-Min11). The reserve-expiry sweep takes the per-key lock; the (future) provider-reconciliation sweep never does — named distinctly to keep them un-conflated. Plan commits refuse on store-version drift, so a concurrent PIN action or background change re-renders instead of silently composing. The alert resolver is serving-lease-holder-only for creation; everyone else falls back to the lifeline — no duplicate-topic race with the attention queue (which is never used for spend alerts — Amendment 2).
|
|
26
|
+
6. **External surfaces:** New Bearer routes (plan render, freeze, caps log) and PIN routes (adjust, go-live, price promotion) — all 503 while dark. One new Telegram surface: the "💰 Routing & Spend Alerts" topic, created at most once by the serving-lease holder as a bounded create-once system topic; stale-price/drift alerts are edge-triggered + deduped. No third-party egress beyond Telegram.
|
|
27
|
+
7. **Multi-machine posture:** Money truth is SINGLE-WRITER by design in B (the whole cap on one PIN-designated metered-lease machine; FD-20); the gate self-fences (`lease-liveness-unconfirmed`) on a staleness window pinned shorter than the mesh-death threshold, so a partitioned old holder is $0-fenced before any reclaim can be offered — dual money authority structurally impossible. Caps view: committed figures are holder-known; non-holder reads proxy to the holder (wired when the pool read lands; in B-single-machine the local read IS the holder read, stated in the response). Topic-creation authority follows the SERVING lease (exists in every topology). The ledger/caps files are machine-local BY DESIGN on the designated holder; Increment D replicates slices, not files.
|
|
28
|
+
8. **Rollback cost:** Config-flag revert (dark) with zero data migration; ledger/caps files remain as inert audit history. A wrong booking can never be silently edited (append-only) — corrective action is a new row + the audit trail, never a rewrite.
|
|
29
|
+
|
|
30
|
+
## Phase 5 — Second-pass review
|
|
31
|
+
|
|
32
|
+
Required (money gate = "gate" class). Independent audit performed via a CROSS-MODEL reviewer — the gemini CLI (this build ran as a fork that cannot spawn subagents, so the skill's reviewer-subagent step was satisfied with a cross-model independent audit instead, which is stronger independence than a same-model subagent; codex is not installed on this machine and pi's non-interactive mode wedged on file attachments). Review scope: this artifact + `MeteredSpendGate.ts` in full, with the ledger/caps-store/plan-store contracts summarized; questions posed: fail-closed completeness, reporting-side reachability into the gate, Bearer-level money authority, signal-vs-authority compliance.
|
|
33
|
+
|
|
34
|
+
**Reviewer verdict:** `VERDICT: Concur with the review` (gemini, 2026-07-08)
|
|
35
|
+
|
|
36
|
+
## Self-action convergence (unbounded-self-action — closure: guard)
|
|
37
|
+
|
|
38
|
+
This change adds two self-triggered cadences, both registered as convergence models in `src/testing/selfActionRegistry.ts` and proven to settle by `tests/unit/self-action-convergence.test.ts` (the ratchet — enforcement: ratchet):
|
|
39
|
+
- **Reserve-expiry sweep** (`metered-reserve-expiry-sweep`): the idempotent terminal state machine IS the brake — an expired reserve can never re-expire and the sweep creates no reserves, so the steady-state emission bound is the finite stale pool (per-target 1, ever).
|
|
40
|
+
- **Stale-price alert** (`spend-stale-price-alert`): the 24h edge latch is the brake — a declared **Eternal Sentinel** with a 24h rate floor (stale pricing changes money ADMISSION behavior, so the alarm must re-arm daily while the condition persists; never a flood).
|
|
41
|
+
|
|
42
|
+
## No-deferrals accounting
|
|
43
|
+
|
|
44
|
+
Work sequenced to later PRs in this increment train (Layer 1c capture/reconciliation, Increment C channel abstraction + full emitter set, amortized-subscription display, scheduled web-research price checks) is tracked under the durable commitment CMT-1929 <!-- tracked: CMT-1929 --> and enumerated in `.instar/plans/money-increment-b-brief.md`; nothing in THIS PR's claimed scope is partial.
|