instar 1.3.526 → 1.3.528

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 (50) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +135 -2
  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 +27 -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/PostUpdateMigrator.d.ts.map +1 -1
  12. package/dist/core/PostUpdateMigrator.js +43 -1
  13. package/dist/core/PostUpdateMigrator.js.map +1 -1
  14. package/dist/core/PreferencesReplicatedStore.d.ts +212 -0
  15. package/dist/core/PreferencesReplicatedStore.d.ts.map +1 -0
  16. package/dist/core/PreferencesReplicatedStore.js +330 -0
  17. package/dist/core/PreferencesReplicatedStore.js.map +1 -0
  18. package/dist/core/PreferencesSync.d.ts +8 -0
  19. package/dist/core/PreferencesSync.d.ts.map +1 -1
  20. package/dist/core/PreferencesSync.js +8 -0
  21. package/dist/core/PreferencesSync.js.map +1 -1
  22. package/dist/core/RelationshipManager.d.ts +61 -1
  23. package/dist/core/RelationshipManager.d.ts.map +1 -1
  24. package/dist/core/RelationshipManager.js +86 -2
  25. package/dist/core/RelationshipManager.js.map +1 -1
  26. package/dist/core/RelationshipsReplicatedStore.d.ts +298 -0
  27. package/dist/core/RelationshipsReplicatedStore.d.ts.map +1 -0
  28. package/dist/core/RelationshipsReplicatedStore.js +660 -0
  29. package/dist/core/RelationshipsReplicatedStore.js.map +1 -0
  30. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  31. package/dist/core/devGatedFeatures.js +10 -0
  32. package/dist/core/devGatedFeatures.js.map +1 -1
  33. package/dist/scaffold/templates.d.ts.map +1 -1
  34. package/dist/scaffold/templates.js +3 -1
  35. package/dist/scaffold/templates.js.map +1 -1
  36. package/dist/server/AgentServer.d.ts +4 -0
  37. package/dist/server/AgentServer.d.ts.map +1 -1
  38. package/dist/server/AgentServer.js +1 -0
  39. package/dist/server/AgentServer.js.map +1 -1
  40. package/dist/server/routes.d.ts +7 -0
  41. package/dist/server/routes.d.ts.map +1 -1
  42. package/dist/server/routes.js +19 -0
  43. package/dist/server/routes.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/data/builtin-manifest.json +64 -64
  46. package/src/scaffold/templates.ts +3 -1
  47. package/upgrades/1.3.527.md +31 -0
  48. package/upgrades/1.3.528.md +34 -0
  49. package/upgrades/side-effects/ws21-preferences-replicated-store.md +91 -0
  50. package/upgrades/side-effects/ws23-relationships.md +150 -0
@@ -0,0 +1,34 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The **relationship registry is now the SECOND concrete consumer of the HLC replicated-store foundation and the FIRST PII kind** — `relationship-record` rides the foundation primitives (envelope / union-reader / conflict-store / rollback-unmerge / bounds) so a person the agent knows on one machine is known on the others. Per `docs/specs/ws23-relationships-userregistry-security.md` (§5 merge semantics, §4.2 tombstones, §4.3 right-to-erasure, §7 tests).
9
+
10
+ - **The `relationship-record` replicated kind** (`src/core/RelationshipsReplicatedStore.ts`) — a STRICT typed schema that is a **discriminated union on `op`** (a value schema AND a delete-tombstone schema coexist under one kind, REQ-D6) and **type-clamps every known field on receive** (`firstInteraction`/`lastInteraction` are ISO-8601-only, `interactionCount`/`significance` are finite numbers, free text is length-clamped — so a foreign, attacker-controlled record can't smuggle markup through a render slot that bypasses `sanitize()`, REQ-M3 gap #4/#8). The **disclosure-minimized projection** (REQ-M4) emits ONLY the enumerated resolution + merge-relevant fields — never the raw on-disk blob and never the local UUID `id`. The cross-machine `recordKey` is the **channel-set identity surface** (REQ-D17), derived deterministically from a person's sorted channel-uids, so the same human reaches the same record across machines even though each mints its own UUID. The per-entry cap is **raised to 64KB** for this PII kind (REQ-M3 gap #10) so a fat relationship replicates instead of wedging the stream; a record still over-cap after projection is a NAMED rejection, never a silent truncate. HIGH impact tier (append-both-and-flag, never a silent clobber of two divergent people).
11
+ - **DUAL REGISTRY** — `relationship-record` is registered in BOTH `JOURNAL_KINDS` (`CoherenceJournal.ts` — the static serve/apply/advert half, 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 CI ratchet asserts the coupling.
12
+ - **Emit-on-mutation funnel + tombstones** — `RelationshipManager` routes every persistence mutation through its single `save()` funnel and every deletion through `delete()`/`mergeRelationships()`; an injected (dark-by-default) replication emitter emits a `put` on save and a channel-keyed `op:'delete'` **tombstone** on delete (so an erased person stays erased even on a machine that was offline at delete time, REQ-D4/§4.3). `mergeRelationships` emits a coherent put(survivor)+delete(merged) pair whose recordKeys are DISTINCT — no dangling tombstone, no replication loop.
13
+ - **Read-only neutralized union** — the peer-read surface ("what do my OTHER machines know about this person") resolves THROUGH the bypass-proof `ReplicatedStoreReader` and renders each foreign record inside a `<replicated-untrusted-data origin="…">` envelope (quoted data, never an instruction). It is DISTINCT from the local-authoritative `resolveByChannel`/`getContextForPerson` — identity RESOLUTION of an inbound principal stays local-only (REQ-M14).
14
+ - **Config + advert + awareness + migration** — `multiMachine.stateSync.relationships { enabled:false, dryRun:true }` added to ConfigDefaults (classified in `DARK_GATE_EXCLUSIONS`; the dark-gate line-map recomputed by hand; `applyDefaults` backfills existing agents); the `stateSyncReceive` advert self-reports `relationships` from the registry; the "One Memory" CLAUDE.md section gains a WS2.3 PII line (with the at-rest honesty note) in both `generateClaudeMd` and an idempotent `migrateClaudeMd` splicer (framework shadow markers already cover the section).
15
+ - **Slice** — this PR builds `relationship-record` ONLY. `user-registry` + `topic-operator` (the spec's other two PII kinds) reduce to "add schema + projection + flag" on this proven machinery and are a tracked follow-up (CMT-1416).
16
+
17
+ 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).
18
+
19
+ ## What to Tell Your User
20
+
21
+ None while dark — internal substrate. The user-visible capability — a person I know on one machine is known on your others — becomes real only when an operator turns on cross-machine relationship replication, and it carries an **honest at-rest trade**: while on, every machine in the pool (including any cloud VM the operator rents but doesn't physically control) keeps a plaintext copy of everyone the agent knows under that machine's filesystem permissions, NOT the encrypted vault (the connection between machines IS encrypted, so nobody reads it in transit). That honesty ships in the CLAUDE.md "One Memory" section so the agent surfaces it the moment a user asks "is my contact data shared / encrypted on the other machine?".
22
+
23
+ ## Summary of New Capabilities
24
+
25
+ None user-facing while dark. New internal module `RelationshipsReplicatedStore.ts`; `RelationshipManager` gains injected (dark) replication-emit + union-read seams. No new routes (the foundation `/state/conflicts` · `/state/resolve-conflict` · `/state/quarantine` surface is reused).
26
+
27
+ ## Evidence
28
+
29
+ - `tests/unit/RelationshipsReplicatedStore.test.ts` (35) — dual-registry coupling; recordKey identity derivation (no split identity, no stranger-collision, not the UUID); disclosure-minimization (no local id, no extra field); `fat-record-replicates` + `fat-record-does-not-wedge-stream`; `foreign-record-type-clamped` (ISO-8601 / finite-number clamps reject smuggled markup; unknown field dropped; freetext clamped); `tombstone-coexists-with-value` (the op:'delete' schema branch); the HIGH-impact append-both union merge; foreign-record render safety. Green.
30
+ - `tests/unit/relationship-replication-emit.test.ts` (6) — emit-on-every-mutation; DARK no-op; delete emits a channel-keyed tombstone; `mergeRelationships` put+delete coherence (distinct recordKeys); a throwing emitter never breaks the local write. Green.
31
+ - `tests/unit/relationship-union-read.test.ts` (5) — through the REAL `ReplicatedStoreReader`: union-reader-cannot-be-bypassed (disabled = no-op); append-both; `erasure-reaches-offline-peer` (tombstone wins over a stale value); post-unmerge zero-dangling. Green.
32
+ - `tests/unit/ws23-relationships-wiring.test.ts` (9) — dual-registry + server.ts registration/union-reader/peer-read-seam wiring + ConfigDefaults dark default + the awareness section. Green.
33
+ - `tests/integration/ws23-relationships-routes.test.ts` (3) — the relationship routes still work with the replication funnel attached; a route-driven DELETE fires the channel-keyed tombstone through the funnel. Green.
34
+ - `tests/e2e/ws23-relationships-alive.test.ts` (3, Phase 1) — the WS2.3 path is ALIVE on the real AgentServer boot path with the REAL relationship-record schema registered: an enabled relationship-record conflict is open + readable + resolvable over HTTP (200), disabled returns 503, Bearer auth required. Green.
@@ -0,0 +1,91 @@
1
+ # Side-Effects Review — WS2.1 preferences replicated store
2
+
3
+ **Version / slug:** `ws21-preferences-replicated-store`
4
+ **Author:** `echo`
5
+ **Second-pass reviewer:** `not required`
6
+ **Spec:** `docs/specs/multi-machine-replicated-store-foundation.md` (converged + approved)
7
+
8
+ ## Summary of the change
9
+
10
+ Wire the auto-learned preference store (`PreferencesManager`, `.instar/preferences.json`)
11
+ onto the HLC replicated-store foundation as the FIRST concrete `pref-record` replicated
12
+ kind. A preference learned on one machine is honored on the others — read through the
13
+ no-clobber union with operator-resolved conflicts. New module
14
+ `src/core/PreferencesReplicatedStore.ts` holds the store schema, impact tier (high),
15
+ per-kind bounds, the emit-envelope builder, and the load-bearing union-aware read
16
+ (`mergeUnionToPreferences` / `buildUnionSessionContext`). `pref-record` is registered in
17
+ BOTH `JOURNAL_KINDS` (CoherenceJournal — static half) AND `ReplicatedKindRegistry`
18
+ (dynamic half). The `/preferences/session-context` route consults a wired
19
+ `ReplicatedStoreReader` ONLY when `multiMachine.stateSync.preferences.enabled`. Ships
20
+ DARK (`enabled:false`, `dryRun:true`).
21
+
22
+ ## Decision-point inventory
23
+
24
+ 1. **Impact-tier resolution (spec §15.1):** preferences are HIGH-impact at the
25
+ replication layer (append-both-and-flag), but the consumer read injects BOTH
26
+ variants as advisory hints on an open conflict — never blocking. Decision recorded
27
+ in the build prompt + the spec; the conservative side (never silently clobber).
28
+ 2. **Coordination with the legacy PreferencesSync (CMT-1416):** the foundation path
29
+ SUPERSEDES the seamlessness path; both ship dark → zero runtime duplication. The
30
+ route gives the foundation path precedence when its flag is on.
31
+
32
+ ## 1. Over-block
33
+
34
+ **What legitimate inputs does this change reject?** The pref-record store schema
35
+ rejects a record with an empty `learning` or a path-shaped `provenance` (the §4 jail).
36
+ Both are malformed-data rejections at the RECEIVE door (a replicated record from a
37
+ peer), never a user-initiated action — a local `recordPreference` is unaffected. With
38
+ the flag OFF (the default), nothing is emitted, validated, or read through the union:
39
+ strict no-op, byte-identical single-machine behavior. The route's foundation branch is
40
+ gated on `stateSync.preferences.enabled` so a dark agent keeps its exact legacy read.
41
+
42
+ ## 2. Under-block
43
+
44
+ **What does this still miss?** The `loadOriginRecords` seam reads only the OWN
45
+ preference store today — peer `pref-record` replicas land via the journal apply path in
46
+ a later rollout stage. So on a single-machine / pre-apply agent the union is a no-op
47
+ (just the local record). This is the honest dark-ship boundary, not a gap: the union /
48
+ conflict / both-variants logic is fully exercised by unit tests against synthetic
49
+ multi-origin inputs. The `dryRun` mode (log intended merges without mutating) is the
50
+ config ladder's middle rung; the emit/apply transport wiring is gated by the same flag.
51
+
52
+ ## 3. Level-of-abstraction fit
53
+
54
+ **Right layer?** Yes. The new kind rides the existing foundation primitives
55
+ (ReplicatedRecordEnvelope / UnionReader / ConflictStore / RollbackUnmerge /
56
+ ReplicationBudget / StoreSnapshot) — no foundation change beyond registering the kind in
57
+ the dual registry. The bypass-proof read goes through `ReplicatedStoreReader` (the
58
+ single funnel), so no caller can read the raw store around the no-clobber rule. The
59
+ consumer-specific concerns (the dedupeKey↔recordKey mapping, the local-only
60
+ `violationPattern` strip, the both-variants reconciliation) live in one module.
61
+
62
+ ## 4. Reversibility
63
+
64
+ Fully reversible. The whole feature is dark behind `multiMachine.stateSync.preferences`
65
+ (default `enabled:false`). Disabling it is a strict no-op (the route keeps its legacy
66
+ path; no emission, no union read). A peer's contribution can be rolled back via the
67
+ existing RollbackUnmerge (`/state/quarantine`), which the foundation already wires. No
68
+ destructive operation is introduced; the conflict ledger + dropped-origins set are the
69
+ foundation's existing durable state under `.instar/state/state-sync/`.
70
+
71
+ ## 5. Migration / fleet-rollout surface
72
+
73
+ - **Config:** `multiMachine.stateSync.preferences { enabled:false, dryRun:true }` added
74
+ to `ConfigDefaults` — `applyDefaults` add-missing backfills existing agents on update
75
+ (Migration Parity; no bespoke migrateConfig block needed). Classified in
76
+ `DARK_GATE_EXCLUSIONS` (optional-integration, staged rollout) so the literal
77
+ `enabled:false` is accounted for by the dark-gate lint.
78
+ - **CLAUDE.md awareness:** the existing "One Memory (replicated stores)" section gains a
79
+ WS2.1 preferences-consumer line in BOTH `generateClaudeMd` (new agents) and
80
+ `migrateClaudeMd` (existing agents, via an idempotent content-sniff splicer guarded by
81
+ the unique "Preferences are the FIRST live store" marker).
82
+ - **Dual-registry CI ratchet:** the `ReplicatedRecordEnvelope.test.ts` coupling test now
83
+ asserts `pref-record` is coupled in both registries — a future edit removing it from
84
+ `JOURNAL_KINDS` would RED.
85
+
86
+ ## Verdict
87
+
88
+ Safe to ship dark. Zero behavior change for any agent until an operator flips
89
+ `multiMachine.stateSync.preferences.enabled`. The advisory both-variants read is the
90
+ load-bearing correctness property and is proven by a §12 wiring test (an open conflict
91
+ never suppresses a usable hint).
@@ -0,0 +1,150 @@
1
+ # Side-Effects Review — WS2.3 relationships (first PII replicated store)
2
+
3
+ **Version / slug:** `ws23-relationships`
4
+ **Author:** `echo`
5
+ **Second-pass reviewer:** `not required (dark-ship, observe-only until flag flip)`
6
+ **Spec:** `docs/specs/ws23-relationships-userregistry-security.md` (converged + approved, CMT-1413)
7
+
8
+ ## Summary of the change
9
+
10
+ Wire the relationship registry (`RelationshipManager`, `relationships/<id>.json`) onto
11
+ the HLC replicated-store foundation as the SECOND concrete consumer and the FIRST PII
12
+ kind, `relationship-record`. New module `src/core/RelationshipsReplicatedStore.ts` holds
13
+ the strict typed schema (discriminated union on `op` for value + tombstone, with
14
+ per-field type-clamps), the disclosure-minimized projection, the channel-set identity
15
+ recordKey derivation, the HIGH impact tier, per-kind bounds, the 64KB per-entry cap,
16
+ the tombstone builder, the union-aware read, and the foreign-record render-safety
17
+ helper. `relationship-record` is registered in BOTH `JOURNAL_KINDS` (CoherenceJournal —
18
+ static half) AND `ReplicatedKindRegistry` (dynamic half). `RelationshipManager` gains
19
+ two injected, dark-by-default seams: a replication emitter (a `put` on the save funnel,
20
+ a channel-keyed `op:'delete'` tombstone on delete/merge) and a union-read seam (the
21
+ read-only neutralized peer-context surface). Ships DARK
22
+ (`multiMachine.stateSync.relationships { enabled:false, dryRun:true }`).
23
+
24
+ ## Decision-point inventory
25
+
26
+ 1. **recordKey = channel-set identity surface, NOT the local UUID (REQ-D17).** The on-disk
27
+ `id` is per-machine; the same human has different UUIDs on different machines, so the
28
+ replicated key is a deterministic `sha256(sorted(channel-uids))`. This is the
29
+ single most load-bearing identity decision; it is documented in the module and proven
30
+ by the recordKey tests (same person → same key regardless of order; disjoint people →
31
+ different keys; channel-less → null, not replicable).
32
+ 2. **64KB per-entry cap (REQ-M3 gap #10).** A fat relationship exceeds the default 8KB
33
+ applier cap; raised to 64KB so the highest-PII records replicate, with a NAMED
34
+ over-cap rejection (`RelationshipRecordTooLargeError`) rather than silent truncate or
35
+ suspect-wedge.
36
+ 3. **mergeRelationships put+delete coherence (the sharp edge).** A local merge emits a
37
+ survivor `put` (now carrying the consolidated channel set) AND a tombstone keyed on
38
+ the merged record's OLD standalone channel set. The two recordKeys are DISTINCT (the
39
+ survivor subsumes the merged's channels), so the tombstone can never suppress the
40
+ survivor — no dangling tombstone, no replication loop. Proven by the merge test.
41
+ 4. **Slice (CMT-1416).** Only `relationship-record` ships here; user-registry +
42
+ topic-operator are a tracked follow-up on the proven machinery.
43
+
44
+ ## 1. Over-block
45
+
46
+ **What legitimate inputs does this change reject?** The receive-side schema rejects a
47
+ foreign record with a non-ISO-8601 `firstInteraction`/`lastInteraction`, a non-number
48
+ `interactionCount`/`significance`, a missing/empty `name`, a bad channel, or > MAX_CHANNELS
49
+ channels — all malformed-data rejections at the RECEIVE door (a peer's replicated
50
+ record), never a user-initiated local action. A local `findOrCreate`/`updateNotes`/… is
51
+ unaffected (it goes through the normal save path; the emit is best-effort AFTER the
52
+ durable local write and swallows its own failures). With the flag OFF (default), nothing
53
+ is emitted, validated, or read through the union: strict no-op — NO PII ever crosses a
54
+ machine boundary.
55
+
56
+ ## 2. Under-block
57
+
58
+ **What does this still miss?** The `loadOriginRecords` seam reads only the OWN
59
+ relationship store today — peer `relationship-record` replicas land via the journal
60
+ apply path in a later rollout stage (the same staging as WS2.1's shipped reference). So
61
+ on a single-machine / pre-apply agent the union is a no-op (just the local record); the
62
+ union / conflict / append-both / tombstone-wins logic is fully exercised by unit tests
63
+ against synthetic multi-origin inputs and through the real `ReplicatedStoreReader`. The
64
+ spec's deeper PII obligations beyond this slice (operator-mandate-gated destructive
65
+ local delete on receive, the deferred-erasure queue, the merge-skew gate, AAD binding)
66
+ are tracked in the converged spec and ride later increments; this PR establishes the
67
+ schema/projection/recordKey/tombstone/cap machinery they build on. The at-rest
68
+ plaintext-rest exposure delta is the operator-accepted residual (REQ-A2) stated honestly
69
+ in the CLAUDE.md awareness section.
70
+
71
+ ## 3. Level-of-abstraction fit
72
+
73
+ **Right layer?** Yes. The new kind rides the existing foundation primitives
74
+ (ReplicatedRecordEnvelope / UnionReader / ConflictStore / RollbackUnmerge /
75
+ ReplicationBudget) — no foundation change beyond registering the kind in the dual
76
+ registry. The bypass-proof read goes through `ReplicatedStoreReader` (the single
77
+ funnel). The PII-specific concerns (the channel-set recordKey, the type-clamp schema,
78
+ the disclosure-minimized projection, the tombstone, the untrusted-data render envelope)
79
+ live in one module. The emit/read seams on `RelationshipManager` are injected so the
80
+ manager stays pure-testable and dark by default.
81
+
82
+ ## 4. Reversibility
83
+
84
+ Fully reversible. The whole feature is dark behind
85
+ `multiMachine.stateSync.relationships` (default `enabled:false`). Disabling it is a
86
+ strict no-op (no emission, no union read, no peer view). A peer's contribution rolls
87
+ back via the existing RollbackUnmerge (`/state/quarantine`). No destructive operation is
88
+ introduced in THIS slice: the emit funnel only adds a best-effort `put`/tombstone
89
+ AFTER the durable local write; it never blocks or alters a local mutation, and a
90
+ throwing emitter cannot break a local write (proven). The destructive-local-delete-on-
91
+ receive (mandate-gated) is explicitly OUT of this slice.
92
+
93
+ ## 5. Migration / fleet-rollout surface
94
+
95
+ - **Config:** `multiMachine.stateSync.relationships { enabled:false, dryRun:true }` added
96
+ to `ConfigDefaults` — `applyDefaults` add-missing backfills existing agents (Migration
97
+ Parity; no bespoke migrateConfig block needed). Classified in `DARK_GATE_EXCLUSIONS`
98
+ (optional-integration); the hand-authored dark-gate line-map was recomputed via the
99
+ attributor (regeneration forbidden) and the new `relationships.enabled` path + the +14
100
+ shift below it hand-edited.
101
+ - **CLAUDE.md awareness:** the existing "One Memory (replicated stores)" section gains a
102
+ WS2.3 relationships-consumer line (WITH the at-rest PII honesty note) in BOTH
103
+ `generateClaudeMd` (new agents) and `migrateClaudeMd` (existing agents, via an
104
+ idempotent content-sniff splicer guarded by the unique "Relationships are the FIRST PII
105
+ store" marker). The framework shadow markers already cover the section (both `**` and
106
+ `### ` variants), so Codex/Gemini parity is inherited.
107
+ - **Dual-registry CI ratchet:** the `ReplicatedRecordEnvelope.test.ts` coupling test +
108
+ the new wiring test assert `relationship-record` is coupled in both registries — a
109
+ future edit removing it from `JOURNAL_KINDS` would RED.
110
+
111
+ ## Adversarial review (5 lenses — PII-grade)
112
+
113
+ Total findings: **3 real, 3 folded** (0 deferred).
114
+
115
+ 1. **Injection / type-clamp completeness** — FINDING (folded): the `firstInteraction`/
116
+ `lastInteraction` fields render UNSANITIZED in `getContextForPerson`, so a foreign
117
+ record could smuggle markup through them. Folded: the schema validates them as
118
+ ISO-8601-only (rejecting any string with `<`/`>`/`"` or a non-date), and the foreign
119
+ render path additionally escapes every field inside the untrusted-data envelope —
120
+ two independent defenses. Proven by `injection-neutralized-firstInteraction` +
121
+ `schema-type-clamp`.
122
+ 2. **Disclosure minimization** — FINDING (folded): the local UUID `id` must never leave
123
+ the machine. Folded: it is absent from `RELATIONSHIP_STORE_KNOWN_FIELDS` and the
124
+ projection builder never emits it; the `disclosure-minimization` test asserts every
125
+ emitted field is in the allowlist and `id` is absent.
126
+ 3. **Tombstone / erasure correctness** — FINDING (folded): `mergeRelationships` could
127
+ emit a tombstone that suppresses the survivor (the merged channels are now part of
128
+ the survivor). Folded: the tombstone keys on the merged's OLD standalone channel set,
129
+ whose recordKey is DISTINCT from the survivor's consolidated recordKey — proven by
130
+ the merge test (distinct keys) and the union test (`erasure-reaches-offline-peer`,
131
+ tombstone-wins-over-stale-value). No dangling tombstone, no loop.
132
+ 4. **recordKey identity derivation** — no real finding: same channel set → same key
133
+ (order-independent); disjoint sets → different keys; the same person with different
134
+ UUIDs → same key. Collision requires the EXACT same full channel set, which is the
135
+ manager's own definition of "the same person". Proven by the recordKey tests.
136
+ 5. **Flag-coherence PII-leak prevention** — no real finding: emission is gated on
137
+ `isStoreEmissionEnabled` + the per-peer `shouldEmitToPeer` advertise check (foundation
138
+ machinery, REQ-M5); a dark agent never emits, and a peer that does not advertise
139
+ `relationships` is never a forward target. The advert self-reports from registry
140
+ presence + the enabled flag. (Live serve/pull transport is the later rollout stage,
141
+ gated by the same flag; THIS slice keeps PII strictly local until the operator flips
142
+ the flag.)
143
+
144
+ ## Verdict
145
+
146
+ Safe to ship dark. Zero behavior change — and zero PII egress — for any agent until an
147
+ operator flips `multiMachine.stateSync.relationships.enabled`. The channel-set recordKey,
148
+ the type-clamped discriminated-union schema, the disclosure-minimized projection, the
149
+ channel-keyed tombstone, and the untrusted-data render envelope are the load-bearing PII
150
+ correctness properties and are each proven by a named test.