instar 1.3.543 → 1.3.545

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.
Files changed (39) hide show
  1. package/dist/commands/server.js +3 -3
  2. package/dist/commands/server.js.map +1 -1
  3. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  4. package/dist/config/ConfigDefaults.js +9 -5
  5. package/dist/config/ConfigDefaults.js.map +1 -1
  6. package/dist/core/CredentialRebalancer.d.ts +116 -0
  7. package/dist/core/CredentialRebalancer.d.ts.map +1 -0
  8. package/dist/core/CredentialRebalancer.js +159 -0
  9. package/dist/core/CredentialRebalancer.js.map +1 -0
  10. package/dist/core/CredentialRebalancerPolicy.d.ts +137 -0
  11. package/dist/core/CredentialRebalancerPolicy.d.ts.map +1 -0
  12. package/dist/core/CredentialRebalancerPolicy.js +284 -0
  13. package/dist/core/CredentialRebalancerPolicy.js.map +1 -0
  14. package/dist/core/CredentialRebalancerSnapshot.d.ts +66 -0
  15. package/dist/core/CredentialRebalancerSnapshot.d.ts.map +1 -0
  16. package/dist/core/CredentialRebalancerSnapshot.js +111 -0
  17. package/dist/core/CredentialRebalancerSnapshot.js.map +1 -0
  18. package/dist/core/PostUpdateMigrator.d.ts +12 -0
  19. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  20. package/dist/core/PostUpdateMigrator.js +49 -1
  21. package/dist/core/PostUpdateMigrator.js.map +1 -1
  22. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  23. package/dist/core/devGatedFeatures.js +10 -5
  24. package/dist/core/devGatedFeatures.js.map +1 -1
  25. package/dist/scaffold/templates.js +1 -1
  26. package/dist/server/CapabilityIndex.js +1 -1
  27. package/dist/server/CapabilityIndex.js.map +1 -1
  28. package/dist/server/routes.js +2 -2
  29. package/dist/server/routes.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/data/builtin-manifest.json +63 -63
  32. package/src/scaffold/templates.ts +1 -1
  33. package/upgrades/1.3.544.md +27 -0
  34. package/upgrades/1.3.545.md +68 -0
  35. package/upgrades/side-effects/ws52-incb-b1-rebalancer-policy.md +44 -0
  36. package/upgrades/side-effects/ws52-incb-b2-default-eviction.md +42 -0
  37. package/upgrades/side-effects/ws52-incb-b3a-rebalancer-orchestrator.md +44 -0
  38. package/upgrades/side-effects/ws52-incb-b3b-snapshot-mappers.md +37 -0
  39. package/upgrades/side-effects/ws52-regate-dev-live.md +42 -0
@@ -0,0 +1,68 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Begins Increment B (the autonomous use-it-or-lose-it drainer) with its decision core, deliberately split out so the entire policy is unit-testable before any autonomous write exists.
9
+
10
+ - **`CredentialRebalancerPolicy.decidePass()`** (src/core/CredentialRebalancerPolicy.ts) — a PURE function computing the zero-or-more credential swaps for one balancer pass from a read-only snapshot (per-account quota + reset proximity, per-slot tenancy/verify/activity, cooldown state, resolved config):
11
+ - **Objective 1 — wall avoidance:** a tenant over the high-water mark (default 85%, either window) gets the highest-headroom eligible account; a tenant over the critical mark (default 95%) triggers a wall-OVERRIDE that bypasses the cooldowns and the 1-swap/pass cap — itself bounded by the fresh-data gate (never re-fires on the same sensor snapshot), `maxForcedSwapsPerPass`, and a per-window override budget whose exhaustion surfaces a degraded + attention terminal state instead of looping. The rescue target must be verified-recent (the recency gate).
12
+ - **Objective 2 — use-it-or-lose-it drain:** an account whose WEEKLY window resets within the horizon with ≥30% unused headroom is dealt to the busiest eligible slot (weekly only — 5h windows regenerate); a per-slot "drain in progress" hold stops the 3-way drain rotation from fragmenting the window.
13
+ - **Eligibility + hysteresis:** needs-reauth/disabled/quarantined/unverified never participate; stale-quota accounts are SOURCE-only; per-pair + per-tenant cooldowns (the tenant cooldown defeats the 3-way rotation attack); a min-improvement floor with urgency clamped at 4h-to-reset; 1 swap per pass for non-forced objectives.
14
+ - **Unwired + dark.** Nothing actuates on the decision yet. Dead-default eviction, the correlated-oracle-outage floor, quarantine-exit, the P19 breaker, and the scheduled identity audit are the next Increment-B step <!-- tracked: 20905 -->; wiring the decision through the live executor (under the existing `subscriptionPool.credentialRepointing` dark/dry-run gate), the live `GET /credentials/rebalancer` status, and the routes follow <!-- tracked: 20905 -->.
15
+
16
+ Adds the highest-priority objective to the balancer decision core: keep `~/.claude` serving a healthy account so manual `claude` invocations stay predictable (§2.4 objective-0).
17
+
18
+ - **Dead/quarantined-default eviction** — when the default slot's tenant goes needs-reauth/disabled OR the default slot is quarantined, `decidePass()` now deals a healthy, identity-verified-recent tenant into `~/.claude` (a `default-eviction` decision), parking the dead/quarantined credential in the vacated slot with an attention note. This runs before wall-avoidance and drain — a frozen default freezes the operator's manual `claude`.
19
+ - **Correlated-oracle-outage floor** — when NO slot is currently oracle-verifiable (an `api.anthropic.com` storm quarantines every probe at once), the policy does NOT empty or churn the default: it preserves the last-known-good assignment, surfaces a degraded + attention entry, and performs no eviction until the oracle returns. Honest bound: the floor's guarantee is "preserve last-known-good + flag", explicitly NOT "manual claude is certified live".
20
+ - **Bounded fallback** — verifiable slots exist but none is an eligible healthy tenant → surface, do not act. Two new inputs (`desiredDefaultAccountId`, per-slot `lastKnownGoodAccountId`) + the `default-eviction` objective. Still pure, still unwired; consecutive-forced-eviction P19-capping is the next step <!-- tracked: 20905 -->.
21
+
22
+ Adds the stateful orchestrator around the Increment-B decision core — the loop that will eventually run the autonomous balancer, built and tested before it is wired to anything.
23
+
24
+ - **`CredentialRebalancer.tick()`** (src/core/CredentialRebalancer.ts) — builds the read-only pass snapshot from injected providers, calls `decidePass()`, and actuates each accepted swap through the injected executor wrapper, under the feature's dark/dry-run gate:
25
+ - **Dark = strict no-op:** when the feature is disabled the pass returns immediately having called the providers and the executor ZERO times.
26
+ - **Dry-run actuates the decision but not the write:** the executor enforces dry-run (no credential moves); the pass advances cooldown state so a dry-run soak shows realistic anti-churn cadence.
27
+ - **The executor is the only write path:** a swap rejection is caught and treated as a failure (no crash).
28
+ - **P19 breaker:** N consecutive LIVE failed swaps opens it; a success resets it; it self-heals by re-probing on later passes. A dry-run never trips it.
29
+ - **Cross-pass hysteresis:** cooldown timestamps recorded by the tenant-account pair (matching the policy's cooldown check), the per-window forced-override budget incremented only on a successful wall-override and time-reset.
30
+ - **Unwired + dark.** The server `setInterval` pass, the live `GET /credentials/rebalancer` status, and a tick-serialization (reentrancy) guard are the next step <!-- tracked: 20905 -->.
31
+
32
+ Adds the pure translation layer between the live system state and the balancer's decision core, kept out of server.ts so a units/sign bug can't silently mis-steer the balancer.
33
+
34
+ - **`CredentialRebalancerSnapshot`** (src/core/CredentialRebalancerSnapshot.ts) — pure mappers:
35
+ - `mapAccount`/`mapAccounts`: a SubscriptionPool account → the policy's `AccountState` (5h/weekly utilization %, weekly-reset hours, status). A missing quota reading maps to an epoch `measuredAt` so the account is treated as STALE (source-only) — never dealt in on a reading we don't have. `rate-limited` stays eligible (`ok`) so wall-avoidance can rescue its slot; only `needs-reauth`/`disabled` are ineligible.
36
+ - `mapSlot`/`mapSlots`: a CredentialLocationLedger assignment → the policy's `SlotState` (default-slot flag, quarantine, last-verified time; a quarantined-empty slot normalizes to a null tenant).
37
+ - `resolveRebalancerConfig`: clamp the configured knobs into the resolved config and derive the cooldowns from the poll interval (per-pair 1×, per-tenant 2×) + the stale-quota window (N poll periods).
38
+ - Unwired + dark; the server construction + the timer pass + the live status route are the next step <!-- tracked: 20905 -->.
39
+
40
+ ## What to Tell Your User
41
+
42
+ Nothing changes — this is off-by-default internal logic with no effect yet. It's the "brain" of the eventual automatic account-balancer: given how much quota each of your accounts has left and how close each is to its weekly reset, it works out whether to shuffle one account's login to a different slot to (a) rescue a session about to hit a wall, or (b) burn down a weekly allowance that would otherwise expire unused. Right now it only *decides* — nothing acts on those decisions, so no credential moves. It's built first and on its own precisely so every rule can be tested in isolation before anything is ever wired to actually move a login, and the whole balancer stays switched off until you decide otherwise.
43
+
44
+ Still nothing changes — off-by-default internal logic. This teaches the (not-yet-active) account-balancer to protect the one login that must always work: your default Claude account. If that account ever needs a re-login or gets quarantined, the balancer's plan is to slide a known-good account into its place so your plain claude command keeps working, and flag the broken one for you. And if the check it relies on (Anthropic's identity endpoint) is down for everything at once, it deliberately does nothing rather than risk making the default worse — preserving the last-known-good and telling you honestly that it can't currently certify it's live. It only decides; nothing acts yet.
45
+
46
+ Still nothing changes — off-by-default internal logic with no effect yet. This is the loop that will eventually run the automatic account-balancer: every few minutes it would look at your quota picture, decide whether a login should move, and carry it out. It's built and tested now, but deliberately not connected to anything that runs on a timer, so it never fires. When it is connected, it stays switched off until you turn it on, and even then a first dry-run mode lets it show what it would do without moving a single credential. There's also a built-in circuit-breaker: if a move ever fails repeatedly, it stops trying and flags it rather than thrashing.
47
+
48
+ Still nothing changes — off-by-default internal plumbing. This is the translator that turns your real account quota and login layout into the form the (not-yet-active) balancer's brain understands. It's careful about safety even here: if it doesn't actually have a fresh quota reading for an account, it treats that account as "don't move anything onto it" rather than guessing; and a rate-limited account stays eligible to be rescued rather than being written off. It only translates data — nothing acts.
49
+
50
+ ## Summary of New Capabilities
51
+
52
+ No new runtime capability — this is the unwired decision core for the autonomous balancer (Increment B), shipped dark. New internal module `CredentialRebalancerPolicy` (pure `decidePass()`): computes the per-pass wall-avoidance / drain / default-preference swap decisions with bounded wall-override, eligibility, and hysteresis. Not wired into any runtime path; no route, no config flag, no credential write path.
53
+
54
+ No new runtime capability — extends the unwired Increment-B decision core. `decidePass()` gains §2.4 objective-0: dead/quarantined-default eviction (deal a healthy verified tenant into `~/.claude`) and the correlated-oracle-outage floor (preserve last-known-good, never empty the default during an outage). Not wired into any runtime path; no route, no config flag, no credential write path.
55
+
56
+ No new runtime capability — the unwired autonomous-write orchestrator for the Increment-B balancer, shipped dark. New internal module `CredentialRebalancer` (`tick()` + `status()`): wraps the pure decision core in a pass loop, actuates via the gated executor under the dark/dry-run gate, carries cross-pass cooldown state + a P19 breaker. Not wired into any runtime path; no route, no config flag, no credential write path of its own.
57
+
58
+ No new runtime capability — pure translation helpers for the Increment-B balancer, shipped dark. New internal module `CredentialRebalancerSnapshot` (mapAccount/mapSlot/resolveRebalancerConfig): the unwired providers the balancer orchestrator will consume. Not wired into any runtime path; no route, no config flag, no credential write path.
59
+
60
+ ## Evidence
61
+
62
+ - `tests/unit/credential-rebalancer-policy.test.ts` (18) — eligibility (needs-reauth tenant excluded; quarantined slot not a target); wall avoidance (rescue with highest-headroom; no rescue when only target also walls; held behind per-pair cooldown; triggers on the weekly window too); wall-override (bypasses cooldown; fresh-data gate no-re-fire; per-window budget exhaustion → degraded + attention; ≤ maxForcedSwapsPerPass when multiple critical; recency gate rejects a stale-verify target); stale-quota source-only; drain (weekly-only, headroom floor, drain-in-progress hold excluded, busiest-slot destination); zero-actuation reason; 1-swap-per-pass. tsc + full lint clean.
63
+
64
+ - `tests/unit/credential-rebalancer-policy.test.ts` (+6, 24 total) — deals a healthy verified tenant in when the default tenant is needs-reauth; rescues a quarantined default too; correlated-outage floor preserves last-known-good and does NOT evict; dead default with no healthy tenant surfaces without acting; healthy default is a no-op (normal objectives run); inert when no default account is configured. tsc + full lint clean.
65
+
66
+ - `tests/unit/credential-rebalancer.test.ts` (7) — dark = strict no-op (zero executor calls); actuates one swap for a walling scenario; dry-run drives the executor (no-op write) and advances cooldowns; the per-pair cooldown holds a re-swap on the next pass; the P19 breaker opens after 3 consecutive live failures and resets on a success; a dry-run ok never trips the breaker; the status surface reports enabled + breaker + last pass. Independent second-pass review CONCUR. tsc + full lint clean.
67
+
68
+ - `tests/unit/credential-rebalancer-snapshot.test.ts` (10) — status eligibility (rate-limited stays ok; needs-reauth/disabled ineligible); quota mapping incl. weekly-reset hours + missing-reading → stale; slot mapping (default flag, quarantined-empty → null tenant, override fields); config defaults + clamping + cooldown derivation. tsc + full lint clean.
@@ -0,0 +1,44 @@
1
+ # Side-Effects Review — WS5.2 Increment B / B1: the balancer decision core (dark/unwired)
2
+
3
+ **Version / slug:** `ws52-incb-b1-rebalancer-policy`
4
+ **Date:** 2026-06-13
5
+ **Author:** echo
6
+ **Second-pass reviewer:** not required at B1 (pure decision logic, unwired, zero IO, no autonomous-write surface — the second-pass review attaches to B3, where the decision is actuated through the live executor under the dark gate)
7
+
8
+ ## Summary of the change
9
+
10
+ Adds `src/core/CredentialRebalancerPolicy.ts` — the §2.4 "stock-trader loop" decision core as a PURE function `decidePass(input) → { decisions, degraded, attention, noActuationReason }`, plus exhaustive unit tests. Given a read-only snapshot (per-account quota + reset proximity, per-slot tenancy/verify/activity, cooldown state, resolved+clamped config) it computes the zero-or-more swap decisions for one pass: objective-1 wall avoidance + the bounded wall-override, objective-2 use-it-or-lose-it drain, eligibility, and the hysteresis floors. It performs NO IO and holds NO authority — it DECIDES; the actuator (step B3) routes an accepted decision through the Step-5 executor under the dark/dry-run gate. This is the first of Increment B's steps (B2 = dead-default eviction + correlated-outage floor + quarantine-exit + P19 breaker + scheduled identity audit; B3 = wiring/actuation/routes; B4 = integration/e2e/livetest), sequenced so the entire policy is testable before any autonomous write exists.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `decidePass()` — add — the per-pass swap policy. It is a DECISION producer, not an authority: it returns proposed swaps + surfaced terminal states. Nothing actuates on its output yet (unwired). When B3 wires it, actuation is gated by `subscriptionPool.credentialRepointing.enabled` + `dryRun` and verified by the oracle (the Tier-0 justification, §2.4: deterministic policy over enumerable numeric thresholds, every decision audited, reversible swap verified by the oracle).
15
+
16
+ ---
17
+
18
+ ## 1. Over-block
19
+ No block/allow surface. The nearest analog — refusing to emit a swap — is the conservative direction (a withheld swap is a no-op; the spec's "a pass with no actuation performs zero keychain/CLI operations" invariant). Concretely conservative: stale-quota accounts are SOURCE-only (never dealt in, lest stale headroom mask a wall); ineligible (needs-reauth/disabled/quarantined/unverified) tenants never participate; a critical slot whose override is blocked WAITS rather than being non-forced-rescued.
20
+
21
+ ## 2. Under-block
22
+ The honest residual the spec names (§2.4 round-4): the wall-path recency gate NARROWS but does not CLOSE the "target passed its last audit then died after it" window — a just-died-but-recently-verified target can still be dealt into a walling slot, bounded to "victim slot quarantined + one re-auth, surfaced". B1 implements the recency gate (a target must be verified within the audit cadence and non-divergent); the residual is accepted per the spec's oracle-split rationale, and the post-commit verify (B3, via the executor) is the backstop.
23
+
24
+ ## 3. Level-of-abstraction fit
25
+ Correct layer, and the deliberate point of B1: the policy is split OUT of the actuation so every threshold/cap/cooldown is unit-testable without a keychain (the same fake-deps discipline as the executor). It consumes already-resolved+clamped config (the resolver lives in B3) so the pure core never reads raw config or clamps.
26
+
27
+ ## 4. Signal vs authority compliance
28
+ - [x] No — this change has no block/allow surface and, at B1, no authority at all (its output is unwired).
29
+
30
+ The policy is a deterministic evaluator over numeric thresholds; the authority to actuate is B3's and is gated dark + dry-run-first + oracle-verified. (Ref: docs/signal-vs-authority.md; §2.4 Tier-0 supervision justification.)
31
+
32
+ ## 5. Interactions
33
+ - **Hysteresis interactions (designed, tested):** 1-swap-per-pass for non-forced objectives; the wall-override may emit up to `maxForcedSwapsPerPass` and bypasses cooldowns but is itself bounded by the fresh-data gate (no re-fire on the same sensor snapshot) and `maxForcedOverridesPerWindow` (exhaustion → surfaced terminal state, never a loop). Per-pair + per-tenant cooldowns both key on the ACCOUNT pair/tenant (consistent basis; the per-tenant cooldown is what defeats the 3-way rotation attack pairwise cooldowns miss). Drain carries a per-slot "drain in progress" hold so the 3-way drain rotation can't fragment the very window it means to use.
34
+ - **Priority interaction:** wall (forced) short-circuits the pass; otherwise non-forced wall → drain → default-preference, one swap max. A critical slot is never downgraded to a non-forced rescue in the same pass.
35
+ - No shadowing/race — the module is pure and unwired; nothing else calls it.
36
+
37
+ ## 6. External surfaces
38
+ None today (unwired). The module changes no API, no config, no agent-facing surface. When B3 wires it the only external effect is the (dark-gated, dry-run-first, audited, oracle-verified, reversible) credential swap the executor already owns.
39
+
40
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
41
+ - **Machine-local BY DESIGN.** The balancer permutes THIS machine's credential slots based on THIS machine's quota/activity snapshot. Each machine runs its own pass over its own keychain; there is no cross-machine input or coordination (a credential has exactly one home, on one machine, §0.d). No replication, no proxied read, no generated URL.
42
+
43
+ ## 8. Rollback cost
44
+ Trivial. Revert the commit — the module is unwired, so no runtime behavior changes. No state, no migration, no credential touch (tests are pure). Any swap the wired balancer (B3) ever performs is the reversible, oracle-verified, dry-run-gated round-trip the executor already owns.
@@ -0,0 +1,42 @@
1
+ # Side-Effects Review — WS5.2 Increment B / B2: dead-default eviction + correlated-outage floor (dark/unwired)
2
+
3
+ **Version / slug:** `ws52-incb-b2-default-eviction`
4
+ **Date:** 2026-06-13
5
+ **Author:** echo
6
+ **Second-pass reviewer:** not required at B2 (pure decision logic extending B1's unwired policy; no IO, no autonomous-write surface — the second-pass review attaches to B3 where the decision is actuated)
7
+
8
+ ## Summary of the change
9
+
10
+ Extends `CredentialRebalancerPolicy.decidePass()` with §2.4's objective-0: keep `~/.claude` serving a healthy verified tenant. When the DEFAULT slot's tenant is needs-reauth/disabled OR the default slot is quarantined, the policy deals a healthy verified tenant into `~/.claude` (a `default-eviction` decision, highest priority — a frozen default freezes the operator's manual `claude`). Two bounded fallbacks: when verifiable slots exist but none is an eligible healthy tenant it SURFACES and does not act; when NO slot is oracle-verifiable (the correlated-outage signature) it applies the floor — preserve the default's last-known-good assignment, surface a degraded + attention entry, and perform NO eviction until the oracle returns. Adds two input fields (`desiredDefaultAccountId`, per-slot `lastKnownGoodAccountId`) and the `default-eviction` objective. Still pure, still unwired.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `decidePass()` objective-0 branch — add — decides whether to rescue a dead/quarantined DEFAULT slot. A decision producer, not an authority (unwired). The eviction target is identity-verified-recent in the pure policy (B3's actuation re-verifies live before the move); the correlated-outage floor is the conservative direction (never empty/churn the default during an oracle storm).
15
+
16
+ ---
17
+
18
+ ## 1. Over-block
19
+ No block/allow surface. The conservative directions are deliberate: the correlated-outage floor refuses to act (preserve last-known-good) rather than risk dealing a dead tenant into the one slot that must stay alive; a dead default with no healthy tenant surfaces rather than forcing a bad move.
20
+
21
+ ## 2. Under-block
22
+ Honest residual (§2.4 round-4): the floor's guarantee is "preserve the last KNOWN-GOOD assignment + surface", NOT "manual claude is certified live" — a correlated outage is observationally identical to "every grant died at once", and the oracle (the only liveness signal) is down by construction. The policy surfaces this explicitly ("NOT certified live") rather than over-claiming continuity. P19-capping of consecutive forced default evictions is B3 (stateful breaker), tracked there.
23
+
24
+ ## 3. Level-of-abstraction fit
25
+ Correct layer — a pure extension of the B1 decision core. Objective-0 runs FIRST (before wall/drain) because a frozen default is the highest-impact freeze; the branch returns immediately when it acts or applies the floor, so it never races the other objectives.
26
+
27
+ ## 4. Signal vs authority compliance
28
+ - [x] No — no block/allow surface; unwired, no authority (output not actuated yet). Deterministic policy over the same enumerable thresholds. (Ref: docs/signal-vs-authority.md.)
29
+
30
+ ## 5. Interactions
31
+ - **Priority interaction:** objective-0 short-circuits the pass before objective-1/2/3. Correct — the default slot's liveness beats wall/drain optimization. It only fires when a default account is configured AND the default slot is genuinely dead/quarantined, so a healthy default is a pure no-op that falls through to the normal objectives.
32
+ - **Eligibility interaction:** the dead default tenant is (by design) excluded from `participatingSlots`; objective-0 reads the raw `slots` for the default specifically — the one place the quarantine-exclusion rule is intentionally overridden (only for `~/.claude`).
33
+ - No shadowing/race — pure, unwired.
34
+
35
+ ## 6. External surfaces
36
+ None today (unwired). No API/config/agent-facing change. When B3 wires it, the only effect is the dark-gated, dry-run-first, oracle-verified, reversible swap the executor owns — plus the surfaced attention/degraded entries.
37
+
38
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
39
+ - **Machine-local BY DESIGN.** The default slot, its tenant, and the oracle answers are all per-machine; each machine keeps its OWN `~/.claude` alive from its own snapshot. No cross-machine input or coordination.
40
+
41
+ ## 8. Rollback cost
42
+ Trivial. Revert the commit — the module is unwired, so no runtime behavior changes. No state, no migration, no credential touch (tests are pure).
@@ -0,0 +1,44 @@
1
+ # Side-Effects Review — WS5.2 Increment B / B3a: the balancer orchestrator (dark/unwired)
2
+
3
+ **Version / slug:** `ws52-incb-b3a-rebalancer-orchestrator`
4
+ **Date:** 2026-06-13
5
+ **Author:** echo
6
+ **Second-pass reviewer:** independent reviewer subagent — CONCUR (this is the autonomous-write actuation orchestrator → Phase 5 required)
7
+
8
+ ## Summary of the change
9
+
10
+ Adds `src/core/CredentialRebalancer.ts` — the stateful orchestrator that wraps the pure `decidePass()` core in a pass loop: on `tick()` it builds the read-only snapshot from injected providers, asks the policy for the zero-or-more swaps, and ACTUATES each through the injected `swap` dep (a wrapper over the gated CredentialSwapExecutor) — but only under the feature's dark/dry-run gate. It carries the hysteresis state the pure policy cannot (cooldown timestamps across passes) and the §2.4 P19 breaker (N consecutive LIVE failed swaps opens it; a success resets it; it self-heals by re-probing). Unwired: the server `setInterval` pass + the live `GET /credentials/rebalancer` status is step B3b.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `CredentialRebalancer.tick()` — add — the autonomous balancer pass. This is the actuation layer (the decision was made in B1/B2). It actuates ONLY via `deps.swap()` (the gated, oracle-verified, staged executor) and ONLY when `isEnabled()` is true; even then the executor's own `dryRun` enforces no-write. The supervision is Tier-0 (§2.4): deterministic policy, oracle-verified reversible swaps, every pass audited.
15
+
16
+ ---
17
+
18
+ ## 1. Over-block
19
+ No block/allow surface. The conservative direction is dark = strict no-op (zero provider/executor calls when disabled) and the breaker (stop actuating after repeated failures).
20
+
21
+ ## 2. Under-block
22
+ The breaker self-heals by re-probing rather than dead-latching — a deliberate choice (a transient keychain failure shouldn't permanently freeze the balancer); a persistent failure keeps it open every pass. The reviewer confirmed there is no path where an open breaker is never re-checked.
23
+
24
+ ## 3. Level-of-abstraction fit
25
+ Correct: the orchestrator holds ONLY the cross-pass state (cooldowns, breaker, forced-override window) and delegates the decision to the pure policy and the write to the executor. It re-implements neither. The cooldown key (tenant-account pair) matches exactly what `decidePass`'s `cooldownOk` reads.
26
+
27
+ ## 4. Signal vs authority compliance
28
+ - [x] Yes — but the logic is a deterministic policy evaluator (the §2.4 Tier-0 justification), and its authority to actuate is gated dark + dry-run-first + oracle-verified + reversible.
29
+
30
+ The orchestrator holds actuation authority, but it is the conservatively-bounded kind the spec sanctions: a deterministic policy over numeric thresholds, every swap reversible and oracle-verified by the executor, every pass audited, shipped dark + dry-run-first. (Ref: docs/signal-vs-authority.md; §2.4.)
31
+
32
+ ## 5. Interactions
33
+ - **Executor interaction:** the orchestrator's ONLY write path is `deps.swap()`; a swap throw is caught and treated as `ok:false` (no crash), incrementing the breaker only on a LIVE failure. The executor enforces enabled/dryRun internally, so the orchestrator's gate + the executor's gate are belt-and-suspenders.
34
+ - **Cooldown/breaker state:** carried in-memory across passes; recorded by the tenant-account pair the decision exchanged (not the slot seat); NOT advanced on a failed swap (so the next pass retries). The forced-override window increments only on a successful wall-override and time-resets.
35
+ - **Reentrancy (latent, scoped to B3b):** `tick()` has no in-flight guard; overlapping ticks could double-read state. B3b (the `setInterval` wiring) MUST serialize ticks (skip-if-running) <!-- tracked: 20905 -->. Harmless at B3a (unwired; the unit tests call tick() sequentially).
36
+
37
+ ## 6. External surfaces
38
+ None today (unwired). No API/config/agent-facing change. When B3b wires it, the only effect is the dark-gated, dry-run-first, oracle-verified, reversible swap the executor owns, plus the `GET /credentials/rebalancer` status read.
39
+
40
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
41
+ - **Machine-local BY DESIGN.** Each machine runs its own pass over its own keychain/quota snapshot; the in-memory cooldown/breaker state is per-machine-per-process. No cross-machine input or coordination (a credential has one home, on one machine).
42
+
43
+ ## 8. Rollback cost
44
+ Trivial. Revert the commit — the module is unwired, so no runtime behavior changes. No state, no migration, no credential touch (tests use fakes). Any swap the wired balancer ever performs is the reversible, oracle-verified, dry-run-gated round-trip the executor owns.
@@ -0,0 +1,37 @@
1
+ # Side-Effects Review — WS5.2 Increment B / B3b-snapshot: balancer snapshot mappers (dark/unwired)
2
+
3
+ **Version / slug:** `ws52-incb-b3b-snapshot-mappers`
4
+ **Date:** 2026-06-13
5
+ **Author:** echo
6
+ **Second-pass reviewer:** not required (pure stateless mappers, unwired, no IO, no decision/authority surface)
7
+
8
+ ## Summary of the change
9
+
10
+ Adds `src/core/CredentialRebalancerSnapshot.ts` — pure functions translating the live system's state into the balancer's read-only pass snapshot: `mapAccount(s)`/`mapAccounts` (SubscriptionPool account → policy `AccountState`), `mapSlot(s)`/`mapSlots` (CredentialLocationLedger assignment → policy `SlotState`), `mapAccountStatus`, and `resolveRebalancerConfig` (clamp the configured knobs + derive cooldowns from the poll interval). These ARE the `listSlots`/`listAccounts`/`resolveConfig` providers the B3a orchestrator consumes; kept pure + out of server.ts so the units/sign translation (where a silent bug would mis-steer the balancer) is unit-testable. Still unwired — the server.ts construction + setInterval + live route is the next step.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - No decision point. These are stateless data mappers. The one judgment encoded — `rate-limited` maps to eligible (`ok`) — is a correctness requirement, not a gate: a walled account must stay eligible so wall-avoidance can rescue its slot (excluding it would strand the slot that needs help). Only `needs-reauth`/`disabled` (dead credentials) map to ineligible.
15
+
16
+ ---
17
+
18
+ ## 1. Over-block / ## 2. Under-block
19
+ No block/allow surface — not applicable. Conservative defaults: a missing quota reading maps to `measuredAt: 0` (epoch) → always stale → the account is SOURCE-only (never dealt in on a reading we don't actually have); a quarantined-empty slot (ledger `accountId: ''`) normalizes to a null tenant (can't be moved).
20
+
21
+ ## 3. Level-of-abstraction fit
22
+ Correct: the mapping is the boundary between the live stores (SubscriptionPool, CredentialLocationLedger, config) and the pure policy. Putting it in its own pure module (not inline in server.ts) is the deliberate testability choice — the same reason the policy + orchestrator are pure.
23
+
24
+ ## 4. Signal vs authority compliance
25
+ - [x] No — no block/allow surface; stateless mappers, no authority. (Ref: docs/signal-vs-authority.md.)
26
+
27
+ ## 5. Interactions
28
+ - Consumed only by the B3a orchestrator's injected providers (and its tests). The cooldown derivation (per-pair = 1× poll interval, per-tenant = 2×) and stale-quota window (N poll periods) are computed here so the orchestrator/policy stay config-agnostic. No shadowing/race — pure.
29
+
30
+ ## 6. External surfaces
31
+ None today (unwired). No API/config/agent-facing change.
32
+
33
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
34
+ - **Machine-local BY DESIGN.** The mappers read THIS machine's pool accounts + ledger; the resulting snapshot is per-machine. No cross-machine input.
35
+
36
+ ## 8. Rollback cost
37
+ Trivial. Revert the commit — unwired pure functions, no runtime behavior, no state, no credential touch.
@@ -0,0 +1,42 @@
1
+ # Side-Effects Review — WS5.2 re-gate: credential re-pointing live-on-dev (dry-run), dark fleet
2
+
3
+ **Version / slug:** `ws52-regate-dev-live`
4
+ **Date:** 2026-06-13
5
+ **Author:** echo
6
+ **Second-pass reviewer:** independent reviewer subagent — CONCUR (changes a credential-WRITE feature's gate posture → Phase 5 required)
7
+
8
+ ## Summary of the change
9
+
10
+ Per the operator directive (2026-06-13, topic 20905: "NONE of this should be dark for development agents"), live credential re-pointing is re-gated from `DARK_GATE_EXCLUSIONS` (off+dry-run for everyone) to the **developmentAgent gate**: `enabled` is OMITTED in `ConfigDefaults` so `resolveDevAgentGate` resolves it LIVE on a dev agent + DARK on the fleet. The destructive credential WRITE stays gated by the SEPARATE `dryRun:true` default (the dry-run canary): on a dev agent the `/credentials/*` levers return real data and the balancer runs its full decision loop, but the `CredentialSwapExecutor` returns outcome `dry-run` with ZERO writes before any keychain step. Real writes still require a deliberate `dryRun:false` (gated behind the §5 livetest). Touches: devGatedFeatures.ts (entry moved to DEV_GATED_FEATURES, removed from DARK_GATE_EXCLUSIONS), ConfigDefaults.ts (enabled omitted, dryRun:true kept), the 6 runtime `enabled` read sites (server.ts ×3, routes.ts ×2, CapabilityIndex.ts ×1 → resolveDevAgentGate), PostUpdateMigrator.ts (a strip migration + a CLAUDE.md re-word), templates.ts (awareness wording), the spec §2.8 amendment, and the affected tests.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `subscriptionPool.credentialRepointing.enabled` gate resolution — MODIFY — from "explicit false for everyone (DARK_GATE_EXCLUSIONS)" to "developmentAgent-gated (live-on-dev, dark-fleet)". The destructive authority (the WRITE) is unchanged: still gated by the separate `dryRun` flag, which the executor enforces. The change moves the feature from dark to alive-in-dry-run on dev — it does NOT grant any new write authority.
15
+
16
+ ---
17
+
18
+ ## 1. Over-block
19
+ No block/allow message surface. The conservative invariant is preserved structurally: the fleet stays dark (`resolveDevAgentGate(undefined, {developmentAgent:false}) === false`), and even on a dev agent the dry-run canary blocks every credential write.
20
+
21
+ ## 2. Under-block
22
+ The honest exposure this change ADDS on a dev agent: the `/credentials/*` levers now return real ledger data + run the decision loop (previously 503). This is the intended dogfooding. It does NOT add write exposure — `dryRun:true` keeps writes off until a deliberate `dryRun:false`. The reviewer confirmed there is no path where a dev agent writes a credential without that explicit flip, and no path where the fleet goes live.
23
+
24
+ ## 3. Level-of-abstraction fit
25
+ Correct: this uses the established dev-agent dry-run-canary gate pattern (topicProfiles / threadline.singleNegotiator — write-capable features that ship live-on-dev in dry-run). The R2 spec decision that put it in DARK_GATE_EXCLUSIONS conflated `enabled` with `dryRun`; the amendment (§2.8) corrects that: `enabled` controls aliveness, `dryRun` controls writes, and they are independent flags.
26
+
27
+ ## 4. Signal vs authority compliance
28
+ - [x] No new authority — the destructive write authority is UNCHANGED (still `dryRun`-gated + oracle-verified + reversible). Only the aliveness gate moved. (Ref: docs/signal-vs-authority.md; §2.8 amendment.)
29
+
30
+ ## 5. Interactions
31
+ - **Two-flag independence:** `enabled` (dev-gate-resolved) and `dryRun` (default true) are read at separate sites; the executor's `dryRun` read is unchanged (`!== false`). The reviewer verified all 6 `enabled` sites route through `resolveDevAgentGate` and the lone `dryRun` site is untouched.
32
+ - **Migration:** `migrateConfigCredentialRepointingDevGate` strips ONLY a default-shaped `enabled:false` (preserves an explicit operator `true`); idempotent; mutates only that one key. The CLAUDE.md re-word replaces the stale "Ships DARK" sentence in agents that already have the section.
33
+ - **Env-token gate (Step 8) still ANDs in:** on a dev agent with an env-token fleet, the location gate still refuses (the §2.10 gate is unchanged) — re-gating `enabled` does not bypass it.
34
+
35
+ ## 6. External surfaces
36
+ - On a **dev agent**, the `/credentials/*` routes now return 200 (were 503) and the balancer status surface is live — observable, but performing zero writes (dry-run). On the **fleet**, byte-for-byte unchanged (still 503/dark). The CLAUDE.md awareness text is updated (new + existing agents) so the agent describes the feature accurately (live-on-dev dry-run, not "ships dark").
37
+
38
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
39
+ - **Machine-local BY DESIGN.** The gate resolves per-machine from that machine's `developmentAgent` flag; each dev machine runs its own dry-run loop over its own keychain. No cross-machine coordination. A dev agent on multiple machines runs the dry-run canary independently on each.
40
+
41
+ ## 8. Rollback cost
42
+ Low + ordered. Revert the commit → the feature returns to DARK_GATE_EXCLUSIONS dark-for-everyone (the migration's strip is idempotent and an agent with `enabled` already stripped simply resolves via the gate; re-adding `enabled:false` to ConfigDefaults darks it again). No credential was ever written (dry-run), so there is no credential state to repair. The ordered-rollback discipline of §2.8 (restore-enrollment before dark) is unaffected — it applies only once real writes have happened, which dry-run never does.