instar 1.3.569 → 1.3.571

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 (36) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +127 -53
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/CoherenceJournal.d.ts +46 -0
  5. package/dist/core/CoherenceJournal.d.ts.map +1 -1
  6. package/dist/core/CoherenceJournal.js +94 -2
  7. package/dist/core/CoherenceJournal.js.map +1 -1
  8. package/dist/core/JournalSyncApplier.d.ts +22 -0
  9. package/dist/core/JournalSyncApplier.d.ts.map +1 -1
  10. package/dist/core/JournalSyncApplier.js +39 -2
  11. package/dist/core/JournalSyncApplier.js.map +1 -1
  12. package/dist/core/MachinePoolRegistry.d.ts.map +1 -1
  13. package/dist/core/MachinePoolRegistry.js +26 -4
  14. package/dist/core/MachinePoolRegistry.js.map +1 -1
  15. package/dist/core/PeerPresencePuller.d.ts +39 -0
  16. package/dist/core/PeerPresencePuller.d.ts.map +1 -1
  17. package/dist/core/PeerPresencePuller.js +46 -1
  18. package/dist/core/PeerPresencePuller.js.map +1 -1
  19. package/dist/core/ReplicatedPeerStreamReader.d.ts +97 -0
  20. package/dist/core/ReplicatedPeerStreamReader.d.ts.map +1 -0
  21. package/dist/core/ReplicatedPeerStreamReader.js +274 -0
  22. package/dist/core/ReplicatedPeerStreamReader.js.map +1 -0
  23. package/dist/core/ReplicatedRecordEmitter.d.ts +102 -0
  24. package/dist/core/ReplicatedRecordEmitter.d.ts.map +1 -0
  25. package/dist/core/ReplicatedRecordEmitter.js +122 -0
  26. package/dist/core/ReplicatedRecordEmitter.js.map +1 -0
  27. package/dist/core/ws2SendWiring.d.ts +52 -0
  28. package/dist/core/ws2SendWiring.d.ts.map +1 -0
  29. package/dist/core/ws2SendWiring.js +71 -0
  30. package/dist/core/ws2SendWiring.js.map +1 -0
  31. package/package.json +1 -1
  32. package/src/data/builtin-manifest.json +2 -2
  33. package/upgrades/1.3.570.md +69 -0
  34. package/upgrades/1.3.571.md +64 -0
  35. package/upgrades/side-effects/statesync-peer-advert-propagation-fix.md +125 -0
  36. package/upgrades/side-effects/ws2-send-side-emission.md +40 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws2SendWiring.js","sourceRoot":"","sources":["../../src/core/ws2SendWiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;+CAE+C;AAC/C,MAAM,CAAC,MAAM,qBAAqB,GAA0B,MAAM,CAAC,MAAM,CAAC;IACxE,WAAW;CACZ,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAA0B,MAAM,CAAC,MAAM,CAAC;IAC1E,eAAe;IACf,WAAW;IACX,kBAAkB;IAClB,cAAc;IACd,eAAe;IACf,aAAa;CACd,CAAC,CAAC;AAKH,wDAAwD;AACxD,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,qBAAqB,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IAC1D,IAAI,uBAAuB,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9D,OAAO,cAAc,CAAC;AACxB,CAAC;AAWD;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,gBAAuC;IACxE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,gBAAgB,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,MAAM,KAAK,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aACrC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YAC9C,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;AACzE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.569",
3
+ "version": "1.3.571",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-15T06:43:57.537Z",
5
- "instarVersion": "1.3.569",
4
+ "generatedAt": "2026-06-15T09:28:36.949Z",
5
+ "instarVersion": "1.3.571",
6
6
  "entryCount": 201,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,69 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ When an agent runs on more than one machine, each machine advertises which kinds of
9
+ records it can durably RECEIVE (its `stateSyncReceive` capability, carried inside
10
+ `seamlessnessFlags` on the machine-pool heartbeat). Before a machine replicates a record
11
+ to a peer, the flag-coherence gate checks that the peer advertises it can receive that
12
+ kind — otherwise it withholds, because the journal applier silently drops unknown kinds.
13
+
14
+ That peer advert was being DROPPED on the receive side, so every machine saw its peers as
15
+ having zero receive capability and the gate blocked replication in both directions.
16
+ Root-caused live on a Laptop↔Mac-Mini pair (2026-06-14): each machine's `GET /pool` showed
17
+ itself with its full receive-key set but every peer with 0.
18
+
19
+ The fix is three receive-side changes, plus a regression guard:
20
+
21
+ - **`src/core/PeerPresencePuller.ts`** — extracted the peer `session-status` narrowing into a
22
+ single exported pure helper, `narrowSessionStatusToPeerCapacity`, that now passes
23
+ `seamlessnessFlags` through (it had been dropped, exactly like commitmentsAdvert in #930,
24
+ quotaState in A2/#804, and preferencesAdvert in WS2.1 — this is the fourth instance of the
25
+ same "narrowing return forgets a field" class). Added the field to the `PeerCapacity`
26
+ interface and the `recordHeartbeat` dependency signature, and forwarded it in `pullOnce`.
27
+ Added an exported `SESSION_STATUS_ADVERT_FIELDS` registry that drives the regression test.
28
+ - **`src/commands/server.ts`** — `fetchPeerCapacity` now delegates to that shared helper
29
+ (keeping only the closure-bound journal-advert unwrap), so production and the round-trip
30
+ integration test run ONE mapping instead of a hand-copied mirror.
31
+ - **`src/core/MachinePoolRegistry.ts`** — `recordHeartbeat` now carries a peer's
32
+ `seamlessnessFlags` forward when a beat omits it (the same pattern guardPosture already
33
+ uses), so the 30-second sparse liveness heartbeat can no longer wipe an HTTP-pulled advert.
34
+ A genuine withdrawal (a present object with a flag flipped) still propagates.
35
+
36
+ The sender side already emitted the advert (via `getCapacity(self)`), so no sender change was
37
+ needed. The whole path ships behind the already-merged WS2 stateSync substrate; a
38
+ single-machine install is a strict no-op.
39
+
40
+ ## What to Tell Your User
41
+
42
+ If you run your agent on more than one computer — say a laptop and a desktop — it is supposed
43
+ to share what it learns between them. That sharing was silently blocked: each machine could
44
+ not see that the other was ready to receive, so neither ever sent. This release fixes that.
45
+ Each machine now correctly sees its peers' readiness, and the pool view shows real peer
46
+ capability instead of a blank. If you only run on one machine, nothing changes for you. After
47
+ updating on both machines, you can confirm it works by saving a learning on one machine and
48
+ reading it on the other.
49
+
50
+ ## Summary of New Capabilities
51
+
52
+ No new user-facing capability or configuration — this is a correctness fix to an existing,
53
+ default-off multi-machine feature. It removes a silent block so cross-machine memory
54
+ replication can actually take effect once the feature is enabled. The pool view now shows each
55
+ peer's real receive-capability set instead of zero.
56
+
57
+ ## Evidence
58
+
59
+ - Unit: `tests/unit/peer-presence-puller.test.ts` — the shared helper preserves every advert
60
+ field in `SESSION_STATUS_ADVERT_FIELDS` (a forgotten field fails loudly); presence-guard,
61
+ not truthiness, so an all-disabled object survives; the puller forwards `seamlessnessFlags`.
62
+ - Unit: `tests/unit/MachinePoolRegistry.test.ts` — a sparse liveness beat does not wipe a
63
+ pulled `seamlessnessFlags`; a genuine withdrawal still propagates; the carry-forward is
64
+ scoped to `seamlessnessFlags` (quotaState still clears) and is per-peer (correct for N≥1).
65
+ - Integration: `tests/integration/peer-presence-roundtrip.test.ts` — over a real signed
66
+ `/mesh/rpc` round-trip using the SHARED production mapping, a peer's advertised
67
+ `stateSyncReceive` lands in the puller's pool registry and SURVIVES a subsequent sparse beat.
68
+ - Spec + convergence: `docs/specs/STATESYNC-PEER-ADVERT-PROPAGATION-FIX-SPEC.md`
69
+ (review-convergence, approved; codex-cli gpt-5.5 + gemini-cli gemini-2.5-pro external passes).
@@ -0,0 +1,64 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ The send half of multi-machine memory replication is now wired. The WS2 substrate
9
+ shipped the replicated-kind registry, the receive/apply machinery, and the
10
+ capability advert, but the journal-backed emitter that the per-store managers' write
11
+ hooks call was never constructed — so memory records were never written to the
12
+ coherence-journal own-streams, and a peer had nothing to pull (root-caused live on a
13
+ Laptop↔Mac-Mini pair).
14
+
15
+ This change adds the generic, store-agnostic `ReplicatedRecordEmitter` (HLC tick +
16
+ last-writer-witness + the store's record builder + journal append, behind the
17
+ per-store dark gate), closes the three matching gaps so a record crosses end-to-end —
18
+ the journal can now append/validate a `*-record` kind, the applier accepts a peer's
19
+ `*-record` instead of suspect-flagging the stream, and the union read materializes
20
+ own + peer journal streams (so a received record is readable) — and replaces the
21
+ snapshot-serve `loadOwnEntries` stub with a real own-stream loader. The first kind,
22
+ `learnings`, is wired end-to-end; the remaining memory stores follow on the same
23
+ machinery (tracked in the send-wiring manifest). A wiring-integrity ratchet now fails
24
+ CI if a future replicated kind is added receive-only with no send path.
25
+
26
+ Everything is gated behind the per-store multi-machine memory-sync switch (off by
27
+ default; the development-agent gate flips it live on a dev agent only). Records ride
28
+ the existing journal-sync tail and pull — no new HTTP route or mesh verb.
29
+
30
+ ## What to Tell Your User
31
+
32
+ - **Cross-machine memory (lessons) now actually crosses**: "If you run me on more than
33
+ one machine, a lesson I learn on one is now known on the others — one shared learning
34
+ registry instead of one per machine. A copy from another machine is always treated as
35
+ a hint, never as the boss: it never silently overwrites what this machine already
36
+ believes, and if two machines disagree I surface both and flag it for you. It stays
37
+ off until you ask me to turn on multi-machine memory sync."
38
+
39
+ ## Summary of New Capabilities
40
+
41
+ | Capability | How to Use |
42
+ |-----------|-----------|
43
+ | Cross-machine replication of learned lessons (learnings) | Automatic once multi-machine memory sync is enabled for the store (off by default) |
44
+ | A received peer memory is readable through the no-clobber union read | Automatic (read path) |
45
+ | Snapshot serve returns real own-stream entries | Automatic (bootstrap path) |
46
+ | Wiring-integrity ratchet — no new kind can ship receive-only | CI test (automatic) |
47
+
48
+ ## Evidence
49
+
50
+ The live gap: after the receive-advert fix (#1167) deployed to both machines, a
51
+ learning written on the Laptop never appeared on the Mac-Mini after 5+ minutes; the
52
+ Laptop's coherence-journal meta listed only the 5 lifecycle kinds — none of the 7 WS2
53
+ record kinds — proving no record was ever emitted to an own-stream.
54
+
55
+ Verified by a two-instance in-process E2E
56
+ (tests/e2e/ws2-learnings-cross-instance.test.ts): a learning written on instance A,
57
+ shipped over the real journal serve/apply path, is read back on instance B through the
58
+ bypass-proof union reader as a foreign-origin record; a markApplied edit replicates and
59
+ the latest wins; the same lesson learned on both machines collapses to one record key
60
+ across origins. Before this change B's union read returned null (the reproduced bug);
61
+ after, it returns A's record. Plus integration (serve→apply→read, forged-batch
62
+ rejection, snapshot serve returns entries) and unit coverage (emitter dark-gate /
63
+ witness-order / throw-isolation, journal record append + op-key dedup + 80 KB cap, peer
64
+ stream materialization). 32 new tests, all green.
@@ -0,0 +1,125 @@
1
+ # Side-Effects Review — Fix: peer stateSync receive-advert dropped → cross-machine memory replication never crosses
2
+
3
+ **Version / slug:** `statesync-peer-advert-propagation-fix`
4
+ **Date:** `2026-06-15`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `echo (independent reviewer subagent — Phase 5; touches a coherence gate's input)`
7
+ **Spec:** `docs/specs/STATESYNC-PEER-ADVERT-PROPAGATION-FIX-SPEC.md` (review-convergence + approved; codex/gemini external passes)
8
+ **Parent principle:** Cross-Machine Coherence — One Agent, Robust Under Degraded Conditions
9
+
10
+ ## Summary of the change
11
+
12
+ A peer's `seamlessnessFlags` advert (the carrier of `stateSyncReceive` — which replicated
13
+ kinds a machine can durably receive) was emitted by the sender but DROPPED on the receive
14
+ side, so the flag-coherence gate read every peer as "cannot receive" and blocked
15
+ cross-machine replication in both directions (root-caused live Laptop↔Mini, 2026-06-14).
16
+ Three receive-side files: `src/core/PeerPresencePuller.ts` (new exported pure helper
17
+ `narrowSessionStatusToPeerCapacity` + `PeerCapacity` field + `recordHeartbeat` dep +
18
+ `pullOnce` forward + `SESSION_STATUS_ADVERT_FIELDS` registry), `src/commands/server.ts`
19
+ (`fetchPeerCapacity` delegates to the shared helper), `src/core/MachinePoolRegistry.ts`
20
+ (`recordHeartbeat` carries `seamlessnessFlags` forward on a beat that omits it). No new
21
+ decision point — this FEEDS the existing `checkPoolFlagCoherence` / `shouldEmitToPeer` gate
22
+ with the data it was missing.
23
+
24
+ ## Decision-point inventory
25
+
26
+ - `checkPoolFlagCoherence` / `shouldEmitToPeer` (`src/core/ReplicatedRecordEnvelope.ts`) — **pass-through (feeds, does not modify)** — the gate already exists and already reads `peer.stateSyncReceive`; this change makes that field actually arrive. No gate logic changed.
27
+ - `MachinePoolRegistry.recordHeartbeat` carry-forward — **modify (data retention)** — carries the last pulled `seamlessnessFlags` across a beat that omits it; not a block/allow decision, a freshness-of-observation rule mirroring the existing `guardPosture` carry-forward.
28
+ - `narrowSessionStatusToPeerCapacity` — **add (pure data-narrowing helper, no decision logic)** — extracted from the inline `fetchPeerCapacity` so production + tests share one mapping.
29
+
30
+ ---
31
+
32
+ ## 1. Over-block
33
+
34
+ **What legitimate inputs does this change reject that it shouldn't?**
35
+
36
+ No block/allow surface is added. The change REMOVES an unintended over-block: before it, a
37
+ peer that was genuinely receive-capable was treated as not, so legitimate replication was
38
+ withheld. The carry-forward never fabricates capability where none was pulled (the "no prior
39
+ pull → sparse beat does not fabricate" unit test pins this), so it cannot over-advertise.
40
+
41
+ ## 2. Under-block
42
+
43
+ **What failure modes does this still miss?**
44
+
45
+ `quotaState` and `inboundQueue` ride the same light-beat clobber and are NOT carry-forwarded
46
+ here — but both are fail-open (absent = not-blocked / depth-unknown), so a transient wipe is
47
+ benign (bounded to ~one 30s beat, self-correcting on the next rich pull). Scoping the
48
+ carry-forward to the fail-CLOSED `seamlessnessFlags` is deliberate; the fail-open siblings are
49
+ tracked (`<!-- tracked: CMT-statesync-lightbeat-carryforward -->`). A genuinely offline peer
50
+ still ages out of `online` via `routerReceivedAtMs`, so a stale carried advert is never acted
51
+ on as a live one.
52
+
53
+ ## 3. Level-of-abstraction fit
54
+
55
+ **Is this at the right layer?**
56
+
57
+ Yes. The narrowing belongs in the receive mapping (`PeerPresencePuller`), the carry-forward
58
+ belongs in the registry that owns the per-machine observation (`MachinePoolRegistry`, beside
59
+ the identical `guardPosture` carry-forward), and the gate that consumes the advert
60
+ (`ReplicatedRecordEnvelope`) is untouched. Extracting the inline narrowing into a shared pure
61
+ helper raises a hard-to-test closure into a unit-testable function — the correct layer for the
62
+ regression guard the recurring bug class demands.
63
+
64
+ ## 4. Signal vs authority compliance
65
+
66
+ **Does this hold blocking authority with brittle logic, or feed a smart gate?**
67
+
68
+ It FEEDS. The change adds no blocking authority. The flag-coherence gate
69
+ (`checkPoolFlagCoherence` / `shouldEmitToPeer`) is the existing authority; this change only
70
+ delivers the peer-advert signal it was already designed to read. Per
71
+ `docs/signal-vs-authority.md`, this is a signal-delivery fix, not a new gate. The helper is a
72
+ pure data map; the carry-forward is a freshness rule, not a decision.
73
+
74
+ ## 5. Interactions
75
+
76
+ **Does it shadow another check, get shadowed, double-fire, race?**
77
+
78
+ Two writers race on each peer's registry entry every 30s: the rich `PeerPresencePuller.pullOnce`
79
+ and the sparse `refreshPool` liveness echo. Before the fix the sparse beat clobbered the rich
80
+ advert; the carry-forward resolves that race deterministically (omission → keep prior). It does
81
+ NOT shadow `guardPosture` (separate field, separate carry-forward) or `quotaState` (intentionally
82
+ still clears). The shared helper means `server.ts:fetchPeerCapacity` and the round-trip test can
83
+ never drift apart (the gap that let this bug hide from the existing test).
84
+
85
+ ## 6. External surfaces
86
+
87
+ **Does it change anything visible to other agents/users/systems?**
88
+
89
+ `GET /pool` now shows each peer's real `seamlessnessFlags.stateSyncReceive` instead of an empty
90
+ 0 — a more-correct read, no schema change (the field was already in the `MachineCapacity` type
91
+ and the assemble output; it was simply always undefined for peers). No new route, no new MeshRpc
92
+ verb. The peer advert is fixed-size booleans from an Ed25519-authenticated registered peer,
93
+ stored as-is exactly like its `quotaState`/`guardPosture` siblings — no new untrusted surface.
94
+ Old peers that never advertise are treated as non-participants (the conservative side), unchanged.
95
+
96
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
97
+
98
+ This change IS the cross-machine path. Posture: **replicated-capability advert** — the peer's
99
+ receive capability crosses via the existing signed `session-status` mesh pull and is recorded
100
+ per-peer in `MachinePoolRegistry`. Correct for N ≥ 1 peers: `checkPoolFlagCoherence` iterates all
101
+ online peers with no pairwise/"exactly 2" assumption; each peer is an independent registry Map
102
+ entry (pinned by the per-peer carry-forward unit test). No LAN assumption, no broadcast. No
103
+ user-facing notice, no durable-state-on-transfer, no generated URL involved.
104
+
105
+ ## 8. Rollback cost
106
+
107
+ Low. Pure runtime code fix in three files, no config/schema/migration. Back-out is a plain
108
+ revert of the commit — the prior behavior (peers read as non-receiving, replication withheld)
109
+ is the safe-but-broken state this fixes, so reverting cannot cause data loss, only re-disable
110
+ the (default-off) replication path. No data migration, no agent-state repair.
111
+
112
+ ---
113
+
114
+ ## Second-pass review
115
+
116
+ **Concur with the review.** Independent reviewer audited the artifact against the actual
117
+ `git diff src/ tests/` and verified: (1) no new gate/block logic — `ReplicatedRecordEnvelope.ts`
118
+ untouched, the change only feeds the existing flag-coherence gate; (2) the carry-forward in
119
+ `MachinePoolRegistry.recordHeartbeat` is correctly scoped to `seamlessnessFlags` only and does
120
+ not alter `quotaState`/`guardPosture`/`inboundQueue` handling (`quotaState` still clears on a
121
+ sparse beat); (3) the shared helper uses `!== undefined` presence guards so an all-disabled
122
+ `{ stateSyncReceive: {} }` survives, and preserves all six advert fields with the same shape as
123
+ the old inline mapping (none dropped or added); (4) `server.ts` preserves the `journalAdvert`
124
+ unwrap (computed locally with `machineId` context, then passed to the helper and re-emitted
125
+ unconditionally, identical to the old inline return). No missed side effect identified.
@@ -0,0 +1,40 @@
1
+ # Side-Effects Review — WS2 send-side emission (wire the journal-backed replicated-record emitter)
2
+
3
+ **Spec:** docs/specs/WS2-SEND-SIDE-EMISSION-SPEC.md (converged + approved — operator pre-approval, Justin topic 13481, the multi-machine memory-replication headline). **Parent:** Cross-Machine Coherence — One Agent, Robust Under Degraded Conditions.
4
+ **Ships DARK** behind `multiMachine.stateSync.<store>.enabled` (default false; the dev-agent gate flips it live on a dev agent only). Single-machine installs, and any agent with the stores off, are a strict no-op.
5
+ **Files:** src/core/ReplicatedRecordEmitter.ts (new), src/core/ReplicatedPeerStreamReader.ts (new), src/core/ws2SendWiring.ts (new), src/core/CoherenceJournal.ts, src/core/JournalSyncApplier.ts, src/commands/server.ts.
6
+
7
+ ## What changed
8
+
9
+ 1. **ReplicatedRecordEmitter.ts (new):** the generic, store-agnostic, journal-backed emitter the per-store managers' existing `emitPut`/`emitDelete` hooks call. One `emit(store, recordKey, build)` path: dark gate → degenerate-key guard → `observed` witness → HLC tick → store builder → `journal.emitReplicatedRecord`. Never throws into the manager (a builder/journal fault is a counted no-op). Single-origin by construction (stamps `origin = this machine`).
10
+ 2. **ReplicatedPeerStreamReader.ts (new):** materializes the union's per-origin records from the OWN journal stream + every peer replica stream (`peers/<M>.<kind>.jsonl`, quarantine/meta excluded), validated through `validateReplicatedEnvelope` + the store schema, folded to the latest per (origin, recordKey) by HLC. Supplies three seams: `loadOriginRecords`/`listRecordKeys` (the union reader), `loadWitness` (the emitter's `observed` source), and `loadOwnEntries` (the snapshot-serve source — replaces the `() => ({})` stub).
11
+ 3. **ws2SendWiring.ts (new):** the send-wiring manifest (WIRED vs PENDING stores) + the `auditWs2SendWiring` ratchet — every registered replicated kind must be consciously classified, so a future kind cannot be added receive-only with a silent no-op SEND half.
12
+ 4. **CoherenceJournal.ts:** optional `setReplicatedKindRegistry`; new public `emitReplicatedRecord(kind, data)` (validates a registered `*-record` kind through the generic envelope validator, op-key = `recordKey:hlcKey`); the `validate()` switch gains ONE branch delegating a registered replicated kind to `validateReplicatedEnvelope`; the per-entry byte cap is per-kind (80 KB for `*-record` kinds, the 8 KB lifecycle cap unchanged). Absent registry ⇒ byte-identical prior behavior.
13
+ 5. **JournalSyncApplier.ts:** optional `setReplicatedKindRegistry` (+ config option); `validateData()` delegates a registered replicated kind to `validateReplicatedEnvelope`; the per-entry size cap is per-kind (matching the writer). Without the registry a peer's `*-record` would `invalid`-flag the stream — the receive-only gap; with it, the record applies on the existing tail transport.
14
+ 6. **server.ts:** constructs (when the coherence journal is live) the peer-stream reader + a persisted HLC clock + the generic emitter; injects the now-populated registry into BOTH the journal writer and the applier; replaces the `loadOwnEntries` stub with the reader; switches the `learnings` union reader to read own + peer journal streams; attaches the learnings emitter adapter to `EvolutionManager.setLearningReplicationEmitter`.
15
+
16
+ ## Blast radius
17
+
18
+ - **Config-gated, not wiring-gated.** With `multiMachine.stateSync.learnings.enabled` false (the fleet default), the emitter is a strict no-op (the dark gate returns before any tick/append), so no `*-record` stream is ever written and the union read is byte-identical to today. The seams are always constructed (so the feature turns on without a restart-to-rewire) but do nothing until the store is enabled.
19
+ - **No new HTTP route, no new MeshRpc verb.** Records ride the EXISTING `journal-sync` tail (`buildServeBatch` serve + `apply` receive) and the EXISTING receiver-driven `PeerPresencePuller.driveJournalDelta` (which already pulls every advertised kind). The `state-snapshot` serve verb (already wired) now returns real entries via the real `loadOwnEntries`.
20
+ - **Single-origin + first-hop binding unchanged.** The emitter stamps `origin = this machine`; the applier's first-hop binding (`entry.machine === sender`) still rejects a forged cross-origin record. A peer's record lands only in its own `peers/<M>.<kind>.jsonl` namespace; the union read keeps origins separate and never writes a foreign record back into the local manager store (read-only union, no origin laundering).
21
+ - **No-clobber read.** A received record is read through the existing `ReplicatedStoreReader` + `UnionReader` (append-both-and-flag for high-impact); a replicated record never clobbers a divergent local one. The conflict ledger + dropped-origin exclusion are untouched.
22
+
23
+ ## Risk + mitigation
24
+
25
+ - **Risk:** a replication emit fault breaks a local memory write. **Mitigation:** the emitter catches every builder/journal throw (counted in stats), and the managers' hooks were already try-wrapped — the durable local write is persisted before the emit hook runs. Proven by the "catches a builder/journal throw" unit tests.
26
+ - **Risk:** a wrong `observed` witness marks a genuinely-concurrent pair as sequential (a silent clobber). **Mitigation:** the witness is the MAX HLC over records PROVABLY on disk, read BEFORE the tick — it can only ever under-witness (a not-yet-pulled peer version is absent ⇒ the pair flags concurrent, the err-toward-flag safe direction). Proven by the witness-order unit test.
27
+ - **Risk:** a fat-but-legal learning is dropped as oversize. **Mitigation:** the per-entry byte cap is raised to 80 KB for `*-record` kinds on BOTH the writer and the applier (the store builders already cap `data` at 64 KB), so a record the writer emits is never rejected on receive. Proven by the 20 KB-description journal test.
28
+ - **Risk:** a future kind is added receive-only again (the exact original gap). **Mitigation:** the wiring-integrity ratchet (`auditWs2SendWiring`) fails CI if any registered store is neither send-wired nor explicitly send-pending.
29
+
30
+ ## Migration parity
31
+
32
+ - No agent-installed files change. The feature is server-internal and activates on the next restart of an agent whose `multiMachine.stateSync.<store>` is enabled (the dev-agent gate decides for a dev agent). No `migrateConfig` / `migrateClaudeMd` / `migrateHooks` change is required for the dark slice; CLAUDE.md awareness lands when a kind is flipped on for the fleet (a later rollout step).
33
+
34
+ ## Dark-gate line-map
35
+
36
+ - UNCHANGED. This PR adds no new `enabled:` line to `ConfigDefaults.ts` — the per-store stateSync flags already exist there (omitted `enabled`, resolved by the dev-agent gate, shipped in the WS2.x substrate PRs). The emitter reads the SAME resolved `_stateSyncStoresResolved` map. `node scripts/lint-dev-agent-dark-gate.js` stays clean.
37
+
38
+ ## Rollback
39
+
40
+ - Revert the PR (or set `multiMachine.stateSync.learnings.enabled: false`). The emitter goes dark, no `*-record` streams are written, the union read returns to own-only/no-op, and any already-replicated peer replica files age out under the journal's per-kind retention. No durable migration to unwind.