instar 1.3.273 → 1.3.274

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,47 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The multi-machine session pool no longer places conversations onto a machine
9
+ whose LLM account is rate-limited or quota-blocked. Each machine's 30-second
10
+ capacity heartbeat now self-reports a `quotaState` (provider block in effect,
11
+ or the 5-hour window ≥95% — read strictly from that machine's OWN quota
12
+ tracker), and the placement engine:
13
+
14
+ - drops quota-blocked machines from the candidate pool (new placements,
15
+ soft preferences, AND stickiness — a conversation currently on a machine
16
+ that becomes blocked moves off it on the next message);
17
+ - still honors an explicit hard pin to a blocked machine (the user's command
18
+ wins), flagged `pinned-machine-quota-blocked` so the user can be told why
19
+ it's quiet;
20
+ - falls back to least-loaded-among-blocked when EVERY machine is blocked,
21
+ flagged `all-machines-quota-blocked`.
22
+
23
+ Older machines that don't report the field are treated as not blocked —
24
+ mixed-version pools behave exactly as before. `GET /pool` shows each
25
+ machine's `quotaState`.
26
+
27
+ ## What to Tell Your User
28
+
29
+ Your messages stop landing on a machine whose AI account has hit its limit —
30
+ the pool quietly routes conversations to a machine that can actually answer.
31
+ The only way to stay on a rate-limited machine is to have pinned the
32
+ conversation there yourself, and then I can tell you exactly why it's quiet
33
+ and when the limit resets.
34
+
35
+ ## Summary of New Capabilities
36
+
37
+ - Per-machine `quotaState` in capacity heartbeats + `GET /pool`.
38
+ - Placement avoids quota-blocked machines; blocked owners lose stickiness.
39
+ - Hard pins win with a `pinned-machine-quota-blocked` flag; all-blocked
40
+ pools place least-loaded with `all-machines-quota-blocked`.
41
+
42
+ ## Evidence
43
+
44
+ `tests/unit/PlacementExecutor.test.ts` +8 (including the verbatim incident
45
+ case — a quota-blocked current owner loses stickiness) with all 29
46
+ pre-existing placement tests green; `tests/unit/MachinePoolRegistry.test.ts`
47
+ +1 (passthrough + clears on a quota-free heartbeat). tsc + lint clean.
@@ -0,0 +1,99 @@
1
+ # Side-Effects Review — Quota-aware placement
2
+
3
+ **Version / slug:** `quota-aware-placement`
4
+ **Date:** `2026-06-05`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required` (pure-function gate + additive optional field; absent field = bit-identical behavior; every boundary side test-pinned)
7
+
8
+ ## Summary of the change
9
+
10
+ Capacity heartbeats self-report a per-machine `quotaState` (from THIS
11
+ machine's own QuotaTracker: provider block active OR 5-hour ≥95%), the
12
+ registry passes it through to `MachineCapacity`, and `PlacementExecutor`
13
+ drops quota-blocked machines from the candidate pool — with a
14
+ place-somewhere fallback when ALL are blocked (`all-machines-quota-blocked`)
15
+ and a pin-wins exception (`pinned-machine-quota-blocked`).
16
+
17
+ ## Decision-point inventory
18
+
19
+ 1. Heartbeat quota computation: block active / 5h exhausted / neither /
20
+ tracker absent / tracker throws. All produce defined outputs (blocked /
21
+ blocked / not-blocked / omitted / omitted); the omitted side = not blocked.
22
+ 2. Gate: blocked machine vs not vs absent-field vs `blocked:false`. All four
23
+ pinned.
24
+ 3. All-blocked fallback vs some-unblocked. Both pinned.
25
+ 4. Hard pin to blocked (honored+flagged) vs soft preference (degrades) vs
26
+ sticky owner blocked (loses stickiness — the incident case). All pinned.
27
+ 5. Pin path capability check preserved (never capability-blind). Pinned.
28
+
29
+ ## 1. Over-block
30
+
31
+ A quota-blocked machine still serves: hard pins, the all-blocked fallback,
32
+ and every EXISTING session (placement only affects new/re-placements — the
33
+ gate cannot strand a running conversation). A false `blocked: true`
34
+ self-report (tracker over-reporting) shifts new placements to peers, which is
35
+ exactly the desired degraded behavior; the worst case equals today's behavior
36
+ when all machines report blocked.
37
+
38
+ ## 2. Under-block
39
+
40
+ - A false `blocked: false` (stale tracker file) reproduces today's behavior —
41
+ never worse than the status quo.
42
+ - The signal refreshes every 30s heartbeat; a block that begins mid-window is
43
+ seen at most 30s late.
44
+ - A topic already on a machine that becomes blocked moves only on its NEXT
45
+ message (placement is per-message by design) — no proactive eviction here.
46
+
47
+ ## 3. Level-of-abstraction fit
48
+
49
+ The quota READ lives on the machine that owns the account (heartbeat
50
+ producer); the DECISION lives in the one pure placement function every
51
+ placement flows through; the registry is a dumb passthrough. No new
52
+ cross-machine channel — the signal rides the existing heartbeat.
53
+
54
+ ## 4. Signal vs authority compliance
55
+
56
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
57
+
58
+ The gate redirects NEW placements only; it never kills, blocks a message, or
59
+ overrides the user (pin wins). Failure modes degrade to current behavior
60
+ (absent/unknown = not blocked); the heartbeat computation is try/caught with
61
+ an explicit unknown ≠ blocked rule.
62
+
63
+ ## 5. Interactions
64
+
65
+ - Stickiness intentionally weakened for blocked owners — that IS the incident
66
+ fix; hysteresis still applies among unblocked candidates.
67
+ - The gemini quota-conflation lesson honored: each machine reads only its own
68
+ QuotaTracker — no cross-machine quota file reads.
69
+ - Mixed-version pools safe: older machines simply omit the field.
70
+ - `GET /pool` and the Machines tab now show `quotaState` (additive).
71
+
72
+ ## 6. External surfaces
73
+
74
+ One optional field on `MachineCapacity`/`HeartbeatObservation` (additive,
75
+ visible in `GET /pool`). No config, no routes, no migration (absence =
76
+ default). CLAUDE.md template line + idempotent append migration.
77
+
78
+ ## 7. Rollback cost
79
+
80
+ Trivial — revert the PR; the field disappears from heartbeats and the gate
81
+ from decide(). No state, no schema.
82
+
83
+ ## Conclusion
84
+
85
+ The quota-blind placement hole closed at the pure-function layer with
86
+ strictly-degrading failure modes: every uncertain case behaves exactly like
87
+ today.
88
+
89
+ ## Second-pass review (if required)
90
+
91
+ Not required — see header.
92
+
93
+ ## Evidence pointers
94
+
95
+ - `tests/unit/PlacementExecutor.test.ts` (+8, incl. the verbatim incident
96
+ case: a blocked current owner loses stickiness).
97
+ - `tests/unit/MachinePoolRegistry.test.ts` (+1 passthrough + clears-on-next-heartbeat).
98
+ - 29 pre-existing placement tests green; tsc + lint clean.
99
+ - `docs/specs/quota-aware-placement.md` + `.eli16.md`.