instar 1.3.548 → 1.3.550

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 (46) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +122 -5
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +29 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/CoherenceJournal.d.ts +1 -1
  8. package/dist/core/CoherenceJournal.d.ts.map +1 -1
  9. package/dist/core/CoherenceJournal.js +23 -2
  10. package/dist/core/CoherenceJournal.js.map +1 -1
  11. package/dist/core/CredentialRebalancerSnapshot.d.ts +17 -0
  12. package/dist/core/CredentialRebalancerSnapshot.d.ts.map +1 -1
  13. package/dist/core/CredentialRebalancerSnapshot.js +24 -0
  14. package/dist/core/CredentialRebalancerSnapshot.js.map +1 -1
  15. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  16. package/dist/core/PostUpdateMigrator.js +27 -1
  17. package/dist/core/PostUpdateMigrator.js.map +1 -1
  18. package/dist/core/TopicOperatorReplicatedStore.d.ts +250 -0
  19. package/dist/core/TopicOperatorReplicatedStore.d.ts.map +1 -0
  20. package/dist/core/TopicOperatorReplicatedStore.js +413 -0
  21. package/dist/core/TopicOperatorReplicatedStore.js.map +1 -0
  22. package/dist/core/UserRegistryReplicatedStore.d.ts +287 -0
  23. package/dist/core/UserRegistryReplicatedStore.d.ts.map +1 -0
  24. package/dist/core/UserRegistryReplicatedStore.js +527 -0
  25. package/dist/core/UserRegistryReplicatedStore.js.map +1 -0
  26. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  27. package/dist/core/devGatedFeatures.js +10 -0
  28. package/dist/core/devGatedFeatures.js.map +1 -1
  29. package/dist/scaffold/templates.d.ts.map +1 -1
  30. package/dist/scaffold/templates.js +3 -1
  31. package/dist/scaffold/templates.js.map +1 -1
  32. package/dist/users/TopicOperatorStore.d.ts +24 -0
  33. package/dist/users/TopicOperatorStore.d.ts.map +1 -1
  34. package/dist/users/TopicOperatorStore.js +28 -0
  35. package/dist/users/TopicOperatorStore.js.map +1 -1
  36. package/dist/users/UserManager.d.ts +27 -0
  37. package/dist/users/UserManager.d.ts.map +1 -1
  38. package/dist/users/UserManager.js +44 -0
  39. package/dist/users/UserManager.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/data/builtin-manifest.json +19 -19
  42. package/src/scaffold/templates.ts +3 -1
  43. package/upgrades/1.3.549.md +36 -0
  44. package/upgrades/1.3.550.md +23 -0
  45. package/upgrades/side-effects/multi-machine-replicated-store-ws26-user-topicop.md +123 -0
  46. package/upgrades/side-effects/ws52-busyness.md +38 -0
@@ -0,0 +1,23 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Makes the autonomous balancer's dry-run dogfooding produce meaningful drain decisions: the drain objective now targets the actually-busiest slot (real session-count signal) instead of an arbitrary one.
9
+
10
+ - **`computeBusynessBySlot`** (src/core/CredentialRebalancerSnapshot.ts) — a pure helper: per-slot busyness = count of RUNNING claude-code sessions on each slot. A session's slot is its account's current slot (`ledger.slotOf`); an untagged session counts toward the default slot; non-running and explicit-non-claude sessions don't count.
11
+ - **Wired into the balancer** — the server's `listSlots` provider now feeds this real busyness from the live session list, so the §2.4 drain objective deals an expiring weekly window to the slot where the work actually is. Read-only; dry-run on a dev agent (zero writes), dark on the fleet.
12
+
13
+ ## What to Tell Your User
14
+
15
+ A small sharpening of the (still off-by-default, dry-run) account-balancer so what you watch is actually meaningful: when it considers draining an account whose weekly allowance is about to expire, it now sends that work to whichever of your config slots is genuinely busiest right now, instead of picking one arbitrarily. Nothing moves — it's still dry-run, still observation only — but the decisions you'd watch at the rebalancer status endpoint now reflect real activity, which is exactly the signal that builds confidence before you decide whether to turn on real moves.
16
+
17
+ ## Summary of New Capabilities
18
+
19
+ No new runtime capability — a read-only refinement to the Increment-B balancer's dry-run decisions. New internal helper `computeBusynessBySlot` feeds real per-slot busyness (count of running claude-code sessions) into the balancer's drain-target selection. No route, no config flag, no credential write path; dry-run on dev, dark on fleet.
20
+
21
+ ## Evidence
22
+
23
+ - `tests/unit/credential-rebalancer-snapshot.test.ts` (+5 = 15) — counts running claude-code sessions per slot via the account→slot map; untagged session → default slot; non-running excluded; explicit-non-claude excluded but legacy (undefined framework) counted; a tagged session whose account has no current slot is dropped. tsc + full lint clean.
@@ -0,0 +1,123 @@
1
+ # Side-Effects Review — WS2.6 user-registry + topic-operator replicated stores (the last two memory-family PII kinds, dark)
2
+
3
+ **Version / slug:** `multi-machine-replicated-store-ws26-user-topicop`
4
+ **Date:** `2026-06-13`
5
+ **Author:** `Instar Agent (echo)`
6
+ **Second-pass reviewer:** `5-lens adversarial review (untrusted-replicated-operator / recordKey-identity / disclosure-min-PII-leak / tombstone-erasure / dark-ship-inertness) — verdict + named tests appended below`
7
+ **Parent principle:** Cross-Machine Coherence — One Agent, Robust Under Degraded Conditions
8
+
9
+ ## Summary of the change
10
+
11
+ WS2.6 adds the final two PII kinds to the WS2 memory family on the proven HLC
12
+ replicated-store foundation: `user-record` (the multi-user registry the `UserManager`
13
+ resolves an inbound message to) and `topic-operator-record` (which VERIFIED operator a
14
+ topic was bound to). Both are thin instantiations of the shipped WS2.3 relationships PII
15
+ template: discriminated-union-on-`op` schema (value + tombstone), strict type-clamp on
16
+ receive, disclosure-minimized projection, content/identity-surface `recordKey`, 64KB
17
+ per-entry cap with a named over-cap error, op:delete tombstone for offline-peer erasure,
18
+ HIGH-impact-at-replication / advisory-at-read. With these landed, the WS2 memory family
19
+ is COMPLETE (7 kinds: preferences, relationships, learnings, knowledge, evolution-actions,
20
+ user-registry, topic-operator; playbook deferred CMT-1416).
21
+
22
+ Both ship **DARK** behind `multiMachine.stateSync.userRegistry` and
23
+ `.topicOperator` (`enabled:false`, `dryRun:true` — the graduated dark→dryRun→live ladder).
24
+ Flag-off / single-machine = strict no-op; NO PII ever crosses a machine boundary while dark.
25
+
26
+ ## Decision-point inventory (the decided forks, baked in verbatim)
27
+
28
+ - **user-record recordKey** = `sha256(sorted channel-set "type:identifier" pairs)` — a user IS
29
+ their channel identifiers (same model as relationships, mirroring `UserManager.channelIndex`).
30
+ NEVER the local `userId` (the cross-machine-unstable id).
31
+ - **topic-operator recordKey** = `sha256(topicId + ":" + verified-uid)`. NEVER a content-name
32
+ (Know-Your-Principal: the binding is auth-sender-derived).
33
+ - **Impact tier**: BOTH HIGH@replication (append-both-and-flag on concurrent divergence) /
34
+ advisory@read (a replicated PII record is a hint, never the authoritative answer).
35
+ - **disclosure-min**: user-record strips the local `userId`; topic-operator projects exactly
36
+ `{platform, uid, names, boundAt}` (no internal `boundFrom`).
37
+ - **THE LOAD-BEARING INVARIANT (topic-operator)**: a replicated topic-operator record is
38
+ UNTRUSTED peer data — NEVER this machine's authoritative answer to "who is my verified
39
+ operator?". Only the LOCAL authenticated `TopicOperatorStore.setOperator` binds the principal.
40
+ The store exposes NO apply/set/establish path back into `TopicOperatorStore` by construction.
41
+
42
+ ## 1. Over-block
43
+ None. Both kinds ADD reach (replicated PII as advisory context) and never block a message, a
44
+ route, or a session. Flag-off / single-machine = strict no-op (named E2E + unit tests).
45
+
46
+ ## 2. Under-block (the real risk surface)
47
+ The inverse risk: replicated PII is materialized into a union read. Mitigations: (a) strict
48
+ type-clamp on receive (ISO-8601 dates, finite numbers, jailed slugs) makes markup
49
+ un-smuggleable through a render slot; (b) every foreign record renders inside a
50
+ `<replicated-untrusted-data origin="…">` envelope; (c) the topic-operator foreign render
51
+ EXPLICITLY states the record is NOT the verified operator and cannot establish/override one;
52
+ (d) identity RESOLUTION of an inbound principal stays LOCAL-ONLY for user-record (the local
53
+ channel index always wins); (e) the topic-operator store has no apply path, so a replicated
54
+ record can never reach `getOperator()` authority. Accepted residual: a compromised
55
+ same-operator peer could inject advisory context text, but it cannot escalate to principal
56
+ authority and the blast radius is one operator's own pool — recorded as a known bound.
57
+
58
+ ## 3. Level-of-abstraction fit
59
+ The two `*ReplicatedStore.ts` modules are PURE logic (no fs, no Date, no network) — schema +
60
+ projection + recordKey + merge + render, unit-testable in isolation. The wiring sits in
61
+ server.ts (kind registration + union reader) and the manager seams (`UserManager.persistUsers`/
62
+ `removeUser`, `TopicOperatorStore.setOperator`) exactly where the WS2.2–2.5 siblings sit. No
63
+ new transport, no new auth, no new tick.
64
+
65
+ ## 4. Signal vs authority compliance
66
+ Fully signal-only. Neither kind gates or rewrites anything. The user-registry merged read is a
67
+ HINT (inbound resolution stays local-authoritative); the topic-operator merged read is advisory
68
+ context (the principal stays local-authoritative). The mesh path is the existing read/observe
69
+ replicated-store substrate — adds reach, never authority; the receiver type-clamps + envelopes.
70
+
71
+ ## 5. Interactions
72
+ - Rides the existing `ReplicatedKindRegistry` + dual-registry (`JOURNAL_KINDS`) + union reader +
73
+ conflict/rollback substrate (no new machinery).
74
+ - Two new `enabled:false` ConfigDefaults paths shift the dark-gate EXPECTED line-map by +29;
75
+ hand-edited (regeneration forbidden) + two new attributed entries added.
76
+ - Coexists with the WS2.1–2.5 siblings (same `multiMachine.stateSync.*` block, independent flags).
77
+ - The maintainer's WS5.2 credential-repointing track was NOT touched (separate lane).
78
+
79
+ ## 6. External surfaces
80
+ No new HTTP routes. The kinds register onto the shared registry and ride the existing
81
+ `/state/*` conflict/quarantine routes (already alive). Emission/serve/pull stay dark behind the
82
+ two new flags. The journal-backed emitter is wired at the manager seams but only attached when
83
+ the flag is on (a later rollout stage, mirroring the siblings).
84
+
85
+ ## 7. Multi-machine posture (Cross-Machine Coherence) + Phase C
86
+ - **Per-machine emission; N-machine pool = N independent emitters.** Identity keyed on the
87
+ channel-set (user) / topic+uid (operator), NOT a per-machine id — so the SAME user/operator
88
+ across N machines is ONE record, never N duplicates.
89
+ - **The topic-operator untrusted-replicated invariant scales:** on N machines only the LOCAL
90
+ authenticated binding is authoritative for the principal; replicated records from N-1 peers
91
+ are advisory. A re-bind to a NEW operator (different uid) is a DIFFERENT record (different
92
+ key), so a replica can never overwrite the local binding.
93
+ - **No LAN/broadcast assumption.** Rides the existing per-peer replicated-store substrate.
94
+ - **Offline-peer erasure:** a removed user / unbound operator propagates an op:delete tombstone
95
+ keyed on the SAME identity surface, so an erased record stays erased on a peer that was offline
96
+ at delete time.
97
+
98
+ ## Migration parity
99
+ - ConfigDefaults: two new dark blocks (no migrateConfig needed — an absent per-store key resolves
100
+ to dark, the safe default; identical to the WS2.2–2.5 siblings).
101
+ - CLAUDE.md template (`templates.ts`) + a chained migrator else-if in `PostUpdateMigrator` splice
102
+ the two new One Memory bullets — ESPECIALLY the topic-operator UNTRUSTED-REPLICATED-OPERATOR
103
+ invariant — into already-deployed agents before any operator enables the replication. Idempotent
104
+ (guarded by the unique 'User registry is the SECOND PII store' marker), with a focused test.
105
+ - devGatedFeatures: two new DARK_GATE_EXCLUSIONS entries (optional-integration).
106
+ - Docs: two new `under-the-hood.md` class blocks (≥2 mentions each — docs-coverage passes).
107
+
108
+ ## 5-lens adversarial verdict (folded as named tests)
109
+ 1. **Untrusted-replicated-operator (THE blocker)** — `untrusted-replicated-operator-never-authoritative`:
110
+ the foreign render ALWAYS says NOT the verified operator; the module exposes NO apply/set/establish
111
+ export; the wiring test proves `getOperator()` authority is UNCHANGED by a replicated record. PASS.
112
+ 2. **recordKey identity** — `recordKey-identity-collapses-cross-machine`: same channel-set/topic+uid →
113
+ one key regardless of local id; a different uid is a different record. PASS.
114
+ 3. **disclosure-min / PII leak** — `disclosure-min-strips-local-id` (userId never on wire),
115
+ topic-operator projection exactly `{platform,uid,names,boundAt}`, `64KB-named-error`, `type-clamp`
116
+ (markup injection blocked, path-shaped slug jailed). PASS.
117
+ 4. **tombstone erasure** — `op:delete-tombstone-erasure`: tombstone keyed on the SAME identity surface;
118
+ schema op:delete branch accepts it. PASS.
119
+ 5. **dark-ship inertness** — `dark-ship-strict-noop`: no emitter ⇒ manager behaves byte-identically;
120
+ E2E DISABLED → 503. PASS.
121
+
122
+ No residual blockers. Known accepted bound: a compromised same-operator peer can inject advisory
123
+ context (cannot escalate to authority; one-operator blast radius).
@@ -0,0 +1,38 @@
1
+ # Side-Effects Review — WS5.2 balancer busyness signal (drain-target refinement)
2
+
3
+ **Version / slug:** `ws52-busyness`
4
+ **Date:** 2026-06-13
5
+ **Author:** echo
6
+ **Second-pass reviewer:** not required (a pure read-only data-signal helper + thin wiring; no gate/sentinel/lifecycle/write surface; the balancer it feeds is already dry-run-gated + B3a-reviewed)
7
+
8
+ ## Summary of the change
9
+
10
+ Resolves the tracked busyness follow-up for the autonomous balancer (Increment B). Adds a pure `computeBusynessBySlot(sessions, slotOf, defaultSlot)` to `CredentialRebalancerSnapshot.ts` — per-slot busyness = count of RUNNING claude-code sessions on each slot (a session's slot is its account's current slot via `ledger.slotOf`; an untagged session counts toward the default slot; non-running / explicit-non-claude sessions don't count). Wires it into the server's `listSlots` provider so the balancer's drain objective targets the actually-busiest slot (real signal) instead of uniform-0. Read-only; affects only the §2.4 drain TARGET selection, and only in dry-run on a dev agent (zero credential writes).
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `CredentialRebalancerPolicy` drain-target selection (objective 2) — now fed a real busyness signal instead of uniform 0. No new decision point; it sharpens an existing one (which busy slot a drain deals to). No authority change; the balancer remains dry-run-gated.
15
+
16
+ ---
17
+
18
+ ## 1. Over-block / ## 2. Under-block
19
+ No block/allow surface. The signal only orders drain TARGET candidates; a wrong count would at worst pick a sub-optimal busy slot to drain to (still a valid eligible slot), and only in dry-run.
20
+
21
+ ## 3. Level-of-abstraction fit
22
+ Correct: the busyness computation is a pure mapper in the snapshot module (alongside mapSlot/mapAccount), kept out of server.ts so a counting bug is unit-testable. The server wiring is a one-line provider change passing `state.listSessions()` + the ledger's `slotOf`.
23
+
24
+ ## 4. Signal vs authority compliance
25
+ - [x] No — a read-only data-signal helper; no block/allow surface, no authority. (Ref: docs/signal-vs-authority.md.)
26
+
27
+ ## 5. Interactions
28
+ - Feeds only the policy's drain `busyness` field (already consumed; previously always 0). No shadowing/race — pure, computed fresh each pass from the live session list.
29
+ - The untagged-session-→-default-slot attribution is a deliberate choice (the default interactive session runs on the default account); documented.
30
+
31
+ ## 6. External surfaces
32
+ - None directly. The balancer's `GET /credentials/rebalancer` status (and its dry-run audit) will now reflect drain decisions that target the busiest slot — observably better dogfooding signal, still zero writes (dry-run on dev; dark on fleet).
33
+
34
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
35
+ - **Machine-local BY DESIGN.** Busyness is computed from THIS machine's live session list + THIS machine's ledger; per-machine. No cross-machine input.
36
+
37
+ ## 8. Rollback cost
38
+ Trivial. Revert → the balancer falls back to uniform busyness (drain picks the first eligible busy slot). No state, no migration, no credential touch.