# Upgrade Guide — vNEXT

<!-- assembled-by: assemble-next-md -->
<!-- bump: patch -->

## What Changed

The **user registry + the topic-operator binding are now the SIXTH + SEVENTH concrete consumers of the HLC replicated-store foundation and the SECOND + THIRD PII kinds** (after WS2.3 relationships) — `user-record` and `topic-operator-record` ride the foundation primitives (envelope / union-reader / conflict-store / rollback-unmerge / bounds) so a registered user / a verified operator known on one machine is known on the others. They REUSE the WS2.3 PII machinery rather than reinventing it. **With these landed, the WS2 memory family is COMPLETE** (7 kinds: preferences, relationships, learnings, knowledge, evolution-actions, user-registry, topic-operator; playbook deferred). Per `docs/specs/multi-machine-replicated-store-foundation.md`.

- **The `user-record` replicated kind** (`src/core/UserRegistryReplicatedStore.ts`) — a STRICT typed schema that is a **discriminated union on `op`** and **type-clamps every known field on receive** (`createdAt` is ISO-8601-only, `telegramUserId` is a finite number, `channels[]`/`permissions[]`/free text are length-clamped, a path-shaped channel `type` is jailed out — so a foreign, attacker-controlled record can't smuggle markup through a render slot). The **disclosure-minimized projection** strips the local `userId` (the cross-machine-unstable id). The cross-machine `recordKey` is the **channel set** — `sha256(sorted "type:identifier" pairs)`, the same identity model relationships uses — so the SAME user on two machines collapses to ONE record. HIGH impact at the **replication** layer (append-both-and-flag — auto-merging two divergent profiles could fuse two distinct humans) but **advisory** at the **read** layer: a replicated user record is a HINT about what the agent's OTHER machines know, NEVER the authoritative answer to "who is this inbound sender?" — identity RESOLUTION of an inbound principal stays LOCAL-ONLY (the local channel index always wins). A removed user emits a channel-keyed tombstone (the resurrection guard).
- **The `topic-operator-record` replicated kind** (`src/core/TopicOperatorReplicatedStore.ts`) — the same hardened machinery, with the load-bearing safety invariant baked in. The cross-machine `recordKey` is `sha256(topicId + ":" + verified-uid)` — keyed on the topic + the AUTHENTICATED uid, NEVER a content-name (Know-Your-Principal). The disclosure-minimized projection is exactly `{platform, uid, names, boundAt}`. **THE LOAD-BEARING SAFETY INVARIANT**: a replicated topic-operator record is UNTRUSTED peer data — it can NEVER become this machine's authoritative answer to "who is my verified operator?". The local auth-derived binding (`TopicOperatorStore.setOperator` from an authenticated sender) is ALWAYS authoritative; the replicated record is advisory context only, rendered as quoted untrusted data that EXPLICITLY says so, and there is NO apply path back into `TopicOperatorStore` by construction. This is the mechanical arm of the constitution's Know-Your-Principal standard.
- **DUAL REGISTRY** — both kinds are registered in BOTH `JOURNAL_KINDS` (`CoherenceJournal.ts` — the static serve/apply/advert half, each with a `DEFAULT_RETENTION` entry that is never `rotateKeep:0` for compliance) AND `ReplicatedKindRegistry` (the dynamic half). A kind in only one silently replicates nothing; the coupling test asserts both.
- **Emit-on-write funnels + tombstones** — `UserManager` gains an injected (dark-by-default) `UserReplicationEmitter`: `persistUsers()` re-emits a `put` per surviving user and `removeUser()` emits an `op:'delete'` channel-keyed tombstone (else a peer re-replicates a locally-removed user forever — resurrection). `TopicOperatorStore` gains a `TopicOperatorReplicationEmitter`: `setOperator()` emits a `put` on a REAL bind (the idempotent re-bind is not re-emitted). Both are best-effort: a throwing emitter never breaks the local write.
- **Read-only neutralized union** — each merged read resolves THROUGH the bypass-proof `ReplicatedStoreReader` and renders each foreign record inside a `<replicated-untrusted-data origin="…">` envelope. A replicated record never clobbers a divergent local one — and for topic-operator, it never reaches the local principal authority.
- **Config + advert + awareness + migration** — `multiMachine.stateSync.userRegistry` + `.topicOperator` `{ enabled:false, dryRun:true }` added to ConfigDefaults (both classified in `DARK_GATE_EXCLUSIONS`; the dark-gate line-map recomputed by hand); the `stateSyncReceive` advert self-reports both from the registry; the "One Memory" CLAUDE.md section gains two WS2.6 lines in both `generateClaudeMd` and an idempotent `migrateClaudeMd` splicer — so the awareness, ESPECIALLY the topic-operator untrusted-replicated invariant, reaches already-deployed agents before any operator enables the replication.

Pure MECHANISM, dark by default. A single-machine / flag-off agent is a strict no-op (no PII ever crosses a machine boundary while dark).

## What to Tell Your User

None while dark — internal substrate. The user-visible capability — the people I have registered as users, and the record of which verified operator a topic was bound to, follow you across your machines (collapsing the same user by their channel set instead of duplicating, never by an internal id) — becomes real only when an operator turns on cross-machine replication for those stores. Two safety promises hold even then: I never let a record from another machine decide who is messaging me right now (that stays a local decision), and I never let a record from another machine decide who my verified operator is (only an authenticated message on this machine binds that). That awareness ships in the One Memory section of my project notes so I can honestly answer whether your registered users follow you across machines, and explain that a peer machine's view of your operator is advisory context only. With this, my whole memory family — preferences, relationships, learnings, knowledge base, action items, registered users, and operator bindings — can follow you across machines once you turn it on.

## Summary of New Capabilities

None user-facing while dark. Two new internal modules: `UserRegistryReplicatedStore.ts` and `TopicOperatorReplicatedStore.ts`. `UserManager` + `TopicOperatorStore` each gain an injected (dark) replication emit seam. No new routes (the foundation /state/conflicts, /state/resolve-conflict, /state/quarantine surface is reused).

Migration parity: `PostUpdateMigrator` wires both `multiMachine.stateSync.userRegistry` / `.topicOperator` config defaults and the dark-gate map entries into already-deployed agents on update, and idempotently splices the two WS2.6 One-Memory awareness lines (including the topic-operator untrusted-replicated invariant) into a deployed agent's CLAUDE.md — so existing agents receive the capability and its safety prose, not just new installs.

## Evidence

- `tests/unit/UserRegistryReplicatedStore.test.ts` — dual-registry coupling; recordKey-identity-collapses-cross-machine (channel-set fingerprint, NEVER the local userId; order-independent; case-drift absorbed; collision-resistant; channel-less is null); disclosure-min-strips-local-id; fat-record-replicates + 64KB-named-error; type-clamp (ISO-8601 / finite-number / jailed-channel reject smuggled markup); op:delete-tombstone-erasure; advisory append-both union merge; foreign render safety; own-origin materialization keys on the channel set. Green.
- `tests/unit/TopicOperatorReplicatedStore.test.ts` — dual-registry coupling; recordKey-identity-collapses-cross-machine (topic+uid, NEVER a content-name; a different uid is a different record; field-straddle guard); disclosure-min (exactly {platform, uid, names, boundAt}); 64KB-named-error; type-clamp; **untrusted-replicated-operator-never-authoritative** (the foreign render always says NOT the verified operator + the module exposes NO apply/set/establish export); op:delete-tombstone-erasure; advisory append-both. Green.
- `tests/unit/ws26-user-topicop-wiring.test.ts` — dual-registry + server.ts registration/union-reader wiring + both manager emit seams + ConfigDefaults dark defaults + dev-gate exclusions + the awareness section + the migrator splice + §12 union-reader-cannot-be-bypassed + **THE BLOCKER cross-store proof** (a peer-originated topic-operator record never changes the local getOperator() authority) + dark-ship strict no-op (no emitter = byte-identical manager behavior). Green.
- `tests/unit/PostUpdateMigrator-ws26OneMemory.test.ts` — the chained migrator splices BOTH WS2.6 One Memory bullets (incl. the untrusted-replicated-operator invariant prose) into a deployed WS2.5 agent's CLAUDE.md, before the When-to-use bullet, idempotently. Green.
- `tests/integration/ws26-user-topicop-emit.test.ts` — the emit-on-mutation contract with a real AgentServer (upsertUser fires the put funnel keyed on the channel set; removeUser fires the tombstone; setOperator fires the put on a real bind and skips the idempotent re-bind; getOperator stays the local bind; the shared /state/conflicts substrate is alive). Green.
- `tests/e2e/ws26-user-topicop-alive.test.ts` — the Phase-1 "feature is alive" E2E: enabled = a user-record conflict (two divergent profiles for the same channel set) is open + readable + resolvable over HTTP (200); both kinds registered (dual-registry); disabled = 503; routes require Bearer auth. Green.
