@ibgib/core-gib 0.1.57 → 0.1.58

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 (132) hide show
  1. package/dist/keystone/keystone-config-builder.d.mts +12 -1
  2. package/dist/keystone/keystone-config-builder.d.mts.map +1 -1
  3. package/dist/keystone/keystone-config-builder.mjs +58 -4
  4. package/dist/keystone/keystone-config-builder.mjs.map +1 -1
  5. package/dist/keystone/keystone-constants.d.mts +40 -5
  6. package/dist/keystone/keystone-constants.d.mts.map +1 -1
  7. package/dist/keystone/keystone-constants.mjs +39 -5
  8. package/dist/keystone/keystone-constants.mjs.map +1 -1
  9. package/dist/keystone/keystone-helpers.d.mts +11 -1
  10. package/dist/keystone/keystone-helpers.d.mts.map +1 -1
  11. package/dist/keystone/keystone-helpers.mjs +37 -1
  12. package/dist/keystone/keystone-helpers.mjs.map +1 -1
  13. package/dist/keystone/keystone-policy-types.d.mts +23 -0
  14. package/dist/keystone/keystone-policy-types.d.mts.map +1 -0
  15. package/dist/keystone/keystone-policy-types.mjs +2 -0
  16. package/dist/keystone/keystone-policy-types.mjs.map +1 -0
  17. package/dist/sync/graft-info/graft-info-helpers.respec.mjs +8 -8
  18. package/dist/sync/graft-info/graft-info-helpers.respec.mjs.map +1 -1
  19. package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs +22 -22
  20. package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs.map +1 -1
  21. package/dist/sync/sync-conflict-basic-divergence.respec.mjs +3 -3
  22. package/dist/sync/sync-conflict-basic-divergence.respec.mjs.map +1 -1
  23. package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs +6 -6
  24. package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs.map +1 -1
  25. package/dist/sync/sync-conflict-text-merge.respec.mjs +26 -26
  26. package/dist/sync/sync-conflict-text-merge.respec.mjs.map +1 -1
  27. package/dist/sync/sync-helpers.d.mts +19 -0
  28. package/dist/sync/sync-helpers.d.mts.map +1 -1
  29. package/dist/sync/sync-helpers.mjs +51 -1
  30. package/dist/sync/sync-helpers.mjs.map +1 -1
  31. package/dist/sync/sync-innerspace-constants.respec.mjs +2 -2
  32. package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
  33. package/dist/sync/sync-innerspace-deep-updates.respec.mjs +2 -2
  34. package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
  35. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +4 -4
  36. package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
  37. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +2 -2
  38. package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
  39. package/dist/sync/sync-innerspace-partial-update.respec.mjs +3 -3
  40. package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
  41. package/dist/sync/sync-innerspace.respec.mjs +4 -4
  42. package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
  43. package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.d.mts +5 -0
  44. package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.d.mts.map +1 -1
  45. package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mjs +18 -0
  46. package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mjs.map +1 -1
  47. package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.d.mts +5 -0
  48. package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.d.mts.map +1 -1
  49. package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mjs +21 -3
  50. package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mjs.map +1 -1
  51. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +12 -0
  52. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
  53. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +34 -0
  54. package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
  55. package/dist/sync/sync-peer/sync-peer-types.d.mts +69 -1
  56. package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
  57. package/dist/sync/sync-peer/sync-peer-v1.d.mts +30 -0
  58. package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
  59. package/dist/sync/sync-peer/sync-peer-v1.mjs +88 -1
  60. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  61. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts +30 -0
  62. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts.map +1 -0
  63. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mjs +2 -0
  64. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mjs.map +1 -0
  65. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts +66 -0
  66. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts.map +1 -0
  67. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs +280 -0
  68. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs.map +1 -0
  69. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts +85 -0
  70. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts.map +1 -0
  71. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs +332 -0
  72. package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs.map +1 -0
  73. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts +29 -0
  74. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts.map +1 -0
  75. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mjs +2 -0
  76. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mjs.map +1 -0
  77. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts +42 -0
  78. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts.map +1 -0
  79. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs +282 -0
  80. package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs.map +1 -0
  81. package/dist/sync/sync-saga-coordinator.d.mts +35 -1
  82. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  83. package/dist/sync/sync-saga-coordinator.mjs +62 -1
  84. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  85. package/dist/sync/sync-withid.connect.respec.d.mts +12 -0
  86. package/dist/sync/sync-withid.connect.respec.d.mts.map +1 -0
  87. package/dist/sync/sync-withid.connect.respec.mjs +205 -0
  88. package/dist/sync/sync-withid.connect.respec.mjs.map +1 -0
  89. package/dist/sync/sync-withid.establish.respec.d.mts +19 -0
  90. package/dist/sync/sync-withid.establish.respec.d.mts.map +1 -0
  91. package/dist/sync/sync-withid.establish.respec.mjs +322 -0
  92. package/dist/sync/sync-withid.establish.respec.mjs.map +1 -0
  93. package/package.json +4 -4
  94. package/src/keystone/keystone-config-builder.mts +73 -4
  95. package/src/keystone/keystone-constants.mts +42 -6
  96. package/src/keystone/keystone-helpers.mts +44 -2
  97. package/src/keystone/keystone-policy-types.mts +25 -0
  98. package/src/keystone/keystone-policy.schema.json +51 -0
  99. package/src/keystone/keystone-service-v1.mts +3 -3
  100. package/src/sync/docs/architecture.md +20 -0
  101. package/src/sync/docs/security.md +207 -3
  102. package/src/sync/graft-info/graft-info-helpers.respec.mts +7 -7
  103. package/src/sync/sync-conflict-adv-multitimelines.respec.mts +21 -21
  104. package/src/sync/sync-conflict-basic-divergence.respec.mts +2 -2
  105. package/src/sync/sync-conflict-basic-multitimelines.respec.mts +5 -5
  106. package/src/sync/sync-conflict-text-merge.respec.mts +25 -25
  107. package/src/sync/sync-helpers.mts +51 -1
  108. package/src/sync/sync-innerspace-constants.respec.mts +1 -1
  109. package/src/sync/sync-innerspace-deep-updates.respec.mts +1 -1
  110. package/src/sync/sync-innerspace-dest-ahead.respec.mts +3 -3
  111. package/src/sync/sync-innerspace-multiple-timelines.respec.mts +1 -1
  112. package/src/sync/sync-innerspace-partial-update.respec.mts +2 -2
  113. package/src/sync/sync-innerspace.respec.mts +3 -3
  114. package/src/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mts +20 -0
  115. package/src/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mts +23 -3
  116. package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +38 -1
  117. package/src/sync/sync-peer/sync-peer-types.mts +70 -1
  118. package/src/sync/sync-peer/sync-peer-v1.mts +94 -1
  119. package/src/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mts +36 -0
  120. package/src/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mts +337 -0
  121. package/src/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mts +388 -0
  122. package/src/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mts +35 -0
  123. package/src/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mts +321 -0
  124. package/src/sync/sync-saga-coordinator.mts +84 -0
  125. package/src/sync/sync-withid.connect.respec.mts +243 -0
  126. package/src/sync/sync-withid.establish.respec.mts +361 -0
  127. package/src/sync/unused-identity-backup.mts.md +1 -1
  128. package/dist/sync/sync-innerspace-dest-ahead-withid.respec.d.mts +0 -2
  129. package/dist/sync/sync-innerspace-dest-ahead-withid.respec.d.mts.map +0 -1
  130. package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs +0 -310
  131. package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs.map +0 -1
  132. package/src/sync/sync-innerspace-dest-ahead-withid.respec.mts +0 -364
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "KeystonePolicyConfigTemplate",
4
+ "type": "object",
5
+ "properties": {
6
+ "behaviorProfiles": {
7
+ "type": "object",
8
+ "additionalProperties": {
9
+ "type": "object",
10
+ "properties": {
11
+ "size": { "type": "integer", "minimum": 1 },
12
+ "replenish": { "type": "string", "enum": ["top-up", "replace-all", "consume", "delete-all"] },
13
+ "selectSequentially": { "type": "integer", "minimum": 0 },
14
+ "selectRandomly": { "type": "integer", "minimum": 0 },
15
+ "targetBindingChars": { "type": "integer", "minimum": 0 }
16
+ },
17
+ "required": ["size", "replenish", "selectSequentially", "selectRandomly", "targetBindingChars"]
18
+ }
19
+ },
20
+ "pools": {
21
+ "type": "object",
22
+ "additionalProperties": {
23
+ "type": "object",
24
+ "properties": {
25
+ "id": { "type": "string" },
26
+ "allowedVerbs": {
27
+ "type": "array",
28
+ "items": { "type": "string" }
29
+ },
30
+ "behaviorProfile": { "type": "string" },
31
+ "behaviorInline": {
32
+ "type": "object",
33
+ "properties": {
34
+ "size": { "type": "integer", "minimum": 1 },
35
+ "replenish": { "type": "string", "enum": ["top-up", "replace-all", "consume", "delete-all"] },
36
+ "selectSequentially": { "type": "integer", "minimum": 0 },
37
+ "selectRandomly": { "type": "integer", "minimum": 0 },
38
+ "targetBindingChars": { "type": "integer", "minimum": 0 }
39
+ },
40
+ "required": ["size", "replenish", "selectSequentially", "selectRandomly", "targetBindingChars"]
41
+ },
42
+ "algo": { "type": "string", "enum": ["SHA-256", "SHA-512"] },
43
+ "rounds": { "type": "integer", "minimum": 1 },
44
+ "type": { "type": "string", "enum": ["hash-reveal-v1"] }
45
+ },
46
+ "required": ["id", "allowedVerbs", "algo", "rounds"]
47
+ }
48
+ }
49
+ },
50
+ "required": ["pools"]
51
+ }
@@ -256,10 +256,10 @@ export class KeystoneService_V1 {
256
256
 
257
257
  /**
258
258
  * Retrieves the latest keystone frame for the given address.
259
- *
259
+ *
260
260
  * Uses `metaspace.getLatestAddr` to find the actual tip instead of trusting
261
261
  * an incoming payload's `past` rel8n.
262
- *
262
+ *
263
263
  * @see `ibgib-get-pattern` SKILL.md
264
264
  */
265
265
  async getLatestKeystone({
@@ -274,7 +274,7 @@ export class KeystoneService_V1 {
274
274
  const lc = `${this.lc}[${this.getLatestKeystone.name}]`;
275
275
  try {
276
276
  if (logalot) { console.log(`${lc} starting...`); }
277
-
277
+
278
278
  const latestAddr = await metaspace.getLatestAddr({ tjpAddr: addr, space });
279
279
  if (!latestAddr) {
280
280
  throw new Error(`could not find latest addr for ${addr}`);
@@ -68,6 +68,17 @@ The protocol supports two types of data:
68
68
 
69
69
  See [Security](./security.md) documentation.
70
70
 
71
+ ### 4.1 Ephemeral Session Domain-Binding
72
+ To prevent an ephemeral session keystone `S` from acting as a "blank check" that could authorize operations on arbitrary timelines, the protocol implements strict domain-binding:
73
+ * **Genesis Binding**: The first frame of the session keystone ($S^{Stjp}$) must explicitly contain a `targetAddrs` array inside its `frameDetails` mapping to the exact addresses of the ibgibs being synchronized.
74
+ * **Verification Boundary**: During the initial turn-based synchronization (e.g., `handleInitFrame`), the validating peer (Receiver) must extract this `targetAddrs` array from $S^{Stjp}$'s genesis `frameDetails` and verify it contains the synchronizing domain's target address, rejecting the handshake on any mismatch.
75
+
76
+ ## 5. Sync Peer Lifetime
77
+
78
+ A `SyncPeer` is **single-saga scoped**. One peer instance services exactly one `sync(...)` call from creation through saga completion. The coordinator's `newTestPeer()` factory pattern (or equivalent in production) must create a fresh peer per saga.
79
+
80
+ **Rationale**: The session keystone `S` is derived from `KDF(senderSecret, sagaId)`. Since `sagaId` is unique per saga, `S` is inherently saga-bound. A peer that outlived its saga would hold stale identity state, violating the security model.
81
+
71
82
  > [!WARNING]
72
83
  > ## 5. Sync Storage & Persistence Rules (Critical Security)
73
84
  >
@@ -88,3 +99,12 @@ See [Security](./security.md) documentation.
88
99
  > * **First step:** Intrinsically validate ibgibs (hashes, basic structure).
89
100
  > * **Second step:** Perform AuthN/AuthZ checks (using the session keystone).
90
101
  > * **Final step:** Once verified, saga "control" ibgibs (which only contain "soft" links to domain ibgibs) can be safely persisted to durable spaces.
102
+
103
+ ## 6. Transport & Framing (Naive WebSocket Implementation)
104
+
105
+ > [!NOTE]
106
+ > ### TODO: Robust WebSocket Framing & TCP Reassembly
107
+ > The current custom WebSocket integration on the node server (`ws-helper.mts`) relies on a naive framing implementation. It assumes each socket `data` event emitted by Node's TCP stream contains exactly one complete WebSocket frame.
108
+ >
109
+ > * **TCP Fragmentation Risk**: When payload sizes grow (e.g. sending larger cryptographic proofs or domain datasets), TCP will split the frame across multiple packets or buffers. The naive decoder will decode a partial frame, causing parse errors (e.g., `Unterminated string in JSON`).
110
+ > * **Future Action**: Replace the custom, naive WebSocket framing code with a robust stateful accumulator that aggregates incoming TCP chunks until the entire frame length (indicated in the frame header) is present in memory, or transition to a fully tested, production-grade library (such as the NPM `ws` package).
@@ -56,11 +56,17 @@ The session keystone has **two dedicated pools**, each with a matching `poolId`
56
56
 
57
57
  Note: The `senderIdentity` Domain Keystone also uses the `"sync"` verb in its claim when it signs/authorizes the session keystone genesis during `establishSessionIdentity`.
58
58
 
59
+ ### ✅ Agreed: Sync Peer Lifetime — One Peer per Saga
60
+
61
+ A `SyncPeer` instance is **scoped to exactly one sync saga**. A new peer must be created for each call to `senderCoordinator.sync(...)`. Reusing a peer across multiple sagas is not supported and would compromise the identity security model, since the session keystone `S` is saga-specific (derived from `KDF(senderSecret, sagaId)`).
62
+
63
+ The `sagaId` is passed to the peer via `initializeOpts` so that `establishSessionIdentity` can derive the correct `sessionSecret` before any other phase begins.
64
+
59
65
  ### ✅ Agreed: `establishSessionIdentity` — Pre-Connect Phase
60
66
 
61
67
  A new `establishSessionIdentity` method on the sync peer base class is the **mandatory first step** before `connect`. It encapsulates:
62
68
 
63
- 1. **Generate session keystone**: `sessionIdentity` genesis (S^Stjp) is created locally from `KDF(senderSecret, sagaId)`. The genesis includes metadata about `senderIdentity` TJP and the target domain.
69
+ 1. **Generate session keystone**: `sessionIdentity` genesis (S^Stjp) is created locally from `KDF(senderSecret, sagaId)`. The genesis contains the exact target domain addresses (`targetAddrs` in `frameDetails`) to cryptographically bind the session to those specific target domain timelines, preventing it from being used as a "blank check" for other domains.
64
70
  2. **Sign `senderIdentity`**: The Sender signs their own `senderIdentity` keystone with a `sync` claim targeting `S^Stjp`. The result (`newSenderIdentity`) is the evolved sender frame that proves delegation. The name `newSenderIdentity` is **only** used within `establishSessionIdentity` and its helper — after this method returns, it becomes the active `senderIdentity`.
65
71
  3. **Post to domain provider**: Both `newSenderIdentity` and `sessionIdentity` genesis are transmitted to the Receiver via a pre-connect API call (cf. `putEvolveKeystone` in `dev-tools.mts`).
66
72
  4. **Receiver validates**: The Receiver independently loads the **latest known tip** of `I^Itjp` from its own registry (never trusting `newSenderIdentity.rel8ns.past`). It validates the evolution and, if authorized, stores both keystones in the domain.
@@ -75,9 +81,10 @@ A new `establishSessionIdentity` method on the sync peer base class is the **man
75
81
 
76
82
  ### 🔲 Pending: `authenticateContext` Placement
77
83
 
78
- The receiver-side validation that runs on each incoming context has two steps:
84
+ The receiver-side validation that runs on each incoming context has three steps:
79
85
  1. **Transition validity**: Replay the session keystone evolution — verify `data.n` is sequential, challenge solutions are valid, pool not exhausted, etc.
80
86
  2. **Target binding**: Verify that the incoming `signedSessionIdentity`'s proof actually targets *this* context's exact address (`proof.claim.target === contextAddr`).
87
+ 3. **Domain target binding**: Verify that the first frame of the session keystone `S` ($S^{Stjp}$) contains `targetAddrs` (array) in its `frameDetails`, and that this array contains the exact addresses of the domain ibgibs being synchronized. This must be checked at least in the first sync turn (e.g., `handleInitFrame`) to authenticate that the session is not a "blank check".
81
88
 
82
89
  **Open**: Where does this code live?
83
90
  * **On the sync peer** (e.g., as `peer.authenticateContext(...)`): the peer already knows its local durable space, which is needed to load the latest `senderIdentity` tip. This makes the tip-lookup natural.
@@ -173,4 +180,201 @@ Context ibgib now has a clean, consistent pair:
173
180
 
174
181
  **Current position**: This is likely a **higher-layer business rule** rather than a sync protocol concern. The sync protocol's job is to ensure the session is cryptographically authorized (via keystone proofs). What is stored under an authorized session is the domain owner's responsibility. Enforcement of content policies (e.g., "only ibgibs of type X are allowed") belongs in the server's `authorizeContext` hook, not in the core sync coordinator.
175
182
 
176
- **Tracked in**: `space-gib.sync-walkthrough.md`
183
+ **Tracked in**: `space-gib.sync-walkthrough.md`
184
+
185
+ ---
186
+
187
+ ## Implementation Plan
188
+
189
+ Each phase below is a discrete, verifiable step. We proceed **one checkbox at a time**, **reviewing/discussing** code/results before moving to the next. Each phase has two targets: **innerspace** (automated unit test) and **space-gib** (manual/integrated e2e).
190
+
191
+ The innerspace tests live in `libs/core-gib/src/sync/`. The space-gib integrated tests are done via the `dev-tools.mts` UI in the browser. Separating identity phases into their own `*.respec.mts` file keeps logs clean and complexity isolated.
192
+
193
+ _Note: respec-gib `*.respec.mts` files execute the **entire** file if any `respecfullyDear`/`ifWeMight` blocks are found. Within that file, any `ifWe` block (without the `Might`) will **still** execute and affect logging. `ifWeMight` **only** affects reporting pass/fail once the file is selected to execute. This is why we isolate individual files as needed._
194
+
195
+ ---
196
+
197
+ ### Phase 1 — `establishSessionIdentity` (Pre-Connect)
198
+
199
+ **Goal**: Get `I^Itjp` onto the domain provider, generate `S^Stjp` locally, evolve `I → I1` with a `sync` claim targeting `S^Stjp`, and post both `I1` and `S` to the provider. Verify both keystones are in the appropriate durable spaces at the right times.
200
+
201
+ `senderCoordinator.sync(...)` **will** be called; we are not mocking this out. We expect it may throw at first, and we are examining side-effects (keystone presence in durable spaces) rather than end-to-end correctness. As phases succeed and sync no longer throws, test assertions will be adjusted accordingly.
202
+
203
+ #### Phase 1A — Innerspace unit test (`sync-withid.establish.respec.mts`)
204
+
205
+ - [x] Create `sync-withid.establish.respec.mts` with scaffold:
206
+ - `Metaspace_Innerspace` + two `InnerSpace_V1` (source/dest) + `SyncSagaCoordinator` pair + `newTestPeer()` factory
207
+ - A `senderSecret` constant (test-only plaintext)
208
+ - A `KeystoneService_V1` instance (new'd inline, no injection needed)
209
+ - [x] Add `respecfully` block: `"Phase 1: establishSessionIdentity"`
210
+ - [x] `ifWeMight` — `"creates sessionIdentity genesis (S) locally"`: assert `S^Stjp` was generated and exists in `sourceSpace`
211
+ - [x] `ifWeMight` — `"evolves senderIdentity (I → I1) with sync claim"`: assert `I1` frame has a proof whose `claim.verb === 'sync'` and `claim.target === S^Stjp`
212
+ - [x] `ifWeMight` — `"posts I1 and S to destSpace (receiver)"`: assert both addrs are retrievable from `destSpace`
213
+
214
+ #### Phase 1B — Space-Gib integrated (manual) (`dev-tools.mts`)
215
+
216
+ Add a new horizontal flexbox row dedicated to the "establish" test flow. Existing debug button rows stay as-is; the new row activates and disables the existing rows (page refresh required to reset). The new row contains:
217
+
218
+ - [x] Button: **"new identity"** — creates a fresh `senderIdentity` (I) keystone locally
219
+ - [x] Button: **"confirm sender identity"** — asserts I exists in the sender's local space
220
+ - [x] Button: **"confirm receiver identity"** — asserts I (or its absence) in the receiver's domain registry
221
+ - [x] Button: **"sync - establish"** — calls `sync(...)` (which internally runs `establishSessionIdentity`)
222
+ - [x] Button: **"confirm sender identity evolved"** — asserts I1 (evolved frame) is in sender's space
223
+ - [x] Button: **"confirm receiver identity evolved"** — asserts I1 is in receiver's domain registry
224
+ - [x] Button: **"confirm sender session identity"** — asserts S genesis exists in sender's space
225
+ - [x] Button: **"confirm receiver session identity"** — asserts S genesis exists in receiver's domain registry
226
+ - [x] Clean up buttons / consolidate confirms once the flow is stable
227
+
228
+ ##### 1) WebSocket Peer Sender (`SyncPeerWebSocketSender_V1`)
229
+ - [x] Define options and data types (`wsUrl` for sync, `httpEvolveUrl` for pre-connect establish routing).
230
+ - [x] Implement `postEstablishToReceiver` to execute Phase 1 HTTP POST/PUT of evolved $I_1$ and session genesis $S$ to the registry.
231
+ - [x] Implement `connectImpl` to:
232
+ - [x] Consistently load the handshake solution from local storage/memory.
233
+ - [x] Perform the RFC 6455 upgrade request with `sAddr` and upfront `solution` parameters.
234
+ - [x] Listen and respond to multi-turn JSON RPC handshake frames (`auth-challenge-init` -> `auth-init` -> `auth-challenge` -> `auth-proof` -> `auth-ok`).
235
+ - [x] Implement `sendContextRequest` to push context and transactional payloads down the active WebSocket pipe.
236
+
237
+ ##### 2) WebSocket Peer Receiver (`SyncPeerWebSocketReceiver_V1`)
238
+ - [x] Review dev-tools.mts and especially sync-upgrade.handler.mts, moving what code we can from the handler into this peer receiver.
239
+ - [x] Define server-side receiver options matching core registry namespaces.
240
+ - [x] Implement robust event/message routers to handle stateful context payloads during the established synchronization session.
241
+ - [x] Design standard static/pure functions to manage Keystone verification and registration during the establish phase without requiring an active saga connection instance.
242
+
243
+ ##### 3) Serve-Gib Establish Handler (`KeystoneEvolveHandler`)
244
+ - [x] Should delegate to sync peer receiver static methods or pure functions in sync-websocket-peer-helpers.mts (or whatever the exact filename turns out to be for the sync peer's websocket helper functions)
245
+ - [x] Cleanly handle the pre-connect registry POST.
246
+ - [x] Validate signature proof of identity evolution ($I \to I_1$) and store both $I_1$ and $S$ into the domain's server-side durable space registry.
247
+ - [x] Ensure it remains entirely stateless, coordinating purely through persistent database/filesystem states.
248
+
249
+ ##### 4) Serve-Gib Connect/Sync Handler (`SyncChannelHandler`)
250
+ - [x] Refactor and rename the WebSocket handler (moving beyond "upgrade" to reflect its status as the Sync Peer Connect & Transport Channel wrapper).
251
+ - [x] Implement upfront picket-fence validation on upgrade (`validateUpfrontConnect`) by retrieving $S$ from durable space and checking the solution parameter.
252
+ - [x] Manage the stateful WebSocket challenge handshake generically.
253
+ - [x] Instantiate `SyncPeerWebSocketReceiver_V1` and wire it to the active socket stream upon successful authentication.
254
+
255
+ ##### 5) Downstream App Registration (`space-gib`)
256
+ - [x] Update `server.mts` (or equivalent serve-gib orchestrator) to mount the generalized `KeystoneEvolveHandler` (Establish) and `SyncChannelHandler` (Connect/Sync) with simple, clean `regex` + `verb` routing declarations.
257
+ - [x] Wire the Dev Tools perform-sync button to instantiate the unified `SyncSagaCoordinator` and `SyncPeerWebSocketSender_V1` to execute the full sequence.
258
+
259
+ ---
260
+
261
+ ### Phase 2 — `peer.connect()` (Transport Handshake)
262
+
263
+ **Goal**: Open the transport channel (if applicable to the peer implementation) and sign/deplete S's `connect` pool as proof-of-possession.
264
+
265
+ We still call `senderCoordinator.sync(...)` — the phase focus is on what `peer.connect()` does internally, not on a separate call.
266
+
267
+ #### Phase 2A — Innerspace unit test (`sync-withid.connect.respec.mts`)
268
+
269
+ - [x] Create `sync-withid.connect.respec.mts` (separate file to isolate connect-phase logging)
270
+ - [x] Add `respecfully` block: `"Phase 2: connect"`
271
+ - [x] `ifWeMight` — `"connect completes without error"`: call sync and assert no exception at/before the connect phase
272
+ - [x] `ifWeMight` — `"connect pool is fully depleted after connect"`: assert `S.data.pools['connect']` is exhausted (all challenges consumed), since after connect only the `sync` pool remains active
273
+ - [x] 🔲 **Open question**: Does innerspace issue demanded challenge IDs for the `connect` pool, or is this behavior strictly peer-specific (WebSocket only)? Confirm during implementation — if innerspace skips challenge issuance, the depletion check may not apply and we document the asymmetry.
274
+
275
+ #### Phase 2B — Space-Gib integrated (manual)
276
+
277
+ - [x] Confirm WebSocket connection is established — check the browser's Network tab (WS frames) rather than adding any server-side detection endpoint (avoids introducing insecure surface)
278
+ - [x] Confirm server issues and accepts challenge from S's `connect` pool — verify via server logs or debugger
279
+ - [x] Confirm `connect` pool is exhausted in S after handshake (inspect S's latest frame in debugger)
280
+ - [x] Confirm session is considered active server-side after handshake
281
+
282
+ ---
283
+
284
+ ### Phase 3 — Basic Single-Timeline Sync with Identity
285
+
286
+ **Goal**: With identity fully established and transport connected, sync a single ibgib `X` from source to dest. Verify S evolves correctly per turn (`sync` pool), and X's full dependency graph arrives on dest. After commit, both sender and receiver durable spaces must contain: the same evolved I, the full S dependency graph, and the full X dependency graph.
287
+
288
+ #### Phase 3A — Innerspace unit test (`sync-withid.fullsync.respec.mts`)
289
+
290
+ - [ ] Create `sync-withid.fullsync.respec.mts`
291
+ - [ ] Create `r1_alpha_v0_source` via `TestTransformer`
292
+ - [ ] Call `senderCoordinator.sync({ domainIbGibs: [alpha], senderIdentity, senderSecret, ... })`
293
+ - [ ] `ifWeMight` — `"alpha dep graph matches on source and dest"`: use `getDependencyGraph` + `graphsAreEquivalent`
294
+ - [ ] `ifWeMight` — `"sessionIdentity evolved the expected number of times"`: assert `S.data.n` equals the known deterministic turn count
295
+ - [ ] 🔲 **TODO**: Manually inspect the full session keystone graph after a first run and count the exact number of outgoing turns (Init + Delta(s) + Commit), then hard-code that number into this assertion
296
+ - [ ] `ifWeMight` — `"sender durable space has I (evolved frame)"`: assert I1 addr is in `sourceSpace`
297
+ - [ ] `ifWeMight` — `"receiver durable space has I (evolved frame)"`: assert I1 addr is in `destSpace`
298
+ - [ ] `ifWeMight` — `"sender durable space has full S dep graph"`: use `getDependencyGraph` on S in `sourceSpace`
299
+ - [ ] `ifWeMight` — `"receiver durable space has full S dep graph"`: use `getDependencyGraph` on S in `destSpace`
300
+
301
+ #### Phase 3B — Space-Gib integrated (manual)
302
+
303
+ - [ ] Trigger a sync of a known test ibgib via `dev-tools.mts` (add "sync - fullsync" button to a new row)
304
+ - [ ] Confirm S evolves on each outgoing turn (inspect frame `data.n` in debugger)
305
+ - [ ] Confirm X's full dependency graph is present on the client after commit
306
+ - [ ] Confirm X's full dependency graph is present on the server after commit
307
+ - [ ] Confirm S's full dependency graph is present on the client after commit
308
+ - [ ] Confirm S's full dependency graph is present on the server after commit
309
+ - [ ] Confirm I's evolved frame (I1) is present on the client after commit
310
+ - [ ] Confirm I's evolved frame (I1) is present on the server after commit
311
+
312
+
313
+ ---
314
+
315
+ ### Phase 1 — `establishSessionIdentity` (Pre-Connect)
316
+
317
+ **Goal**: Get `I^Itjp` onto the domain provider, generate `S^Stjp` locally, evolve `I → I1` with a `sync` claim targeting `S^Stjp`, and post both `I1` and `S` to the provider. Verify the provider accepted and stored them.
318
+
319
+ The sync proper will not be called. The test only cares that the pre-connect identity handoff succeeded.
320
+
321
+ #### Phase 1A — Innerspace unit test
322
+
323
+ - [ ] Create `sync-with-identity-basic.respec.mts` with scaffold:
324
+ - `Metaspace_Innerspace` + two `InnerSpace_V1` (source/dest) + `SyncSagaCoordinator` pair + `newTestPeer()` factory
325
+ - A `senderSecret` constant (test-only plaintext)
326
+ - A `KeystoneService_V1` instance (new'd inline)
327
+ - [ ] Add `respecfully` block: `"Phase 1: establishSessionIdentity"`
328
+ - [ ] `ifWeMight` — `"creates sessionIdentity genesis (S) locally"`: assert `S^Stjp` was generated and exists in `sourceSpace`
329
+ - [ ] `ifWeMight` — `"evolves senderIdentity (I → I1) with sync claim"`: assert `I1` frame has a proof whose `claim.verb === 'sync'` and `claim.target === S^Stjp`
330
+ - [ ] `ifWeMight` — `"posts I1 and S to destSpace (receiver)"`: assert both addrs are retrievable from `destSpace`
331
+
332
+ #### Phase 1B — Space-Gib integrated (manual)
333
+
334
+ - [x] Confirm `dev-tools.mts` "Create Session Keystone (S)" button produces a valid genesis keystone (inspect in browser debugger)
335
+ - [x] Confirm "Evolve Domain Keystone (I → I1)" button produces `I1` with correct `sync` claim targeting `S^Stjp`
336
+ - [x] Confirm "Post Evolution" (`putEvolveKeystone`) is accepted by the server (HTTP 200 / success response)
337
+ - [x] Confirm server stores both `I1` and `S` — verify via GET or server-side debugger
338
+
339
+ ---
340
+
341
+ ### Phase 2 — `peer.connect()` (Transport H andshake)
342
+
343
+ **Goal**: Open the transport channel, if applicable, and sign session identity's `connect` pool.
344
+
345
+ #### Phase 2A — Innerspace unit test
346
+
347
+ - [x] Extend `sync-with-identity-basic.respec.mts` with a `respecfully` block: `"Phase 2: connect"` (Note: implemented in separate file `sync-withid.connect.respec.mts`)
348
+ - [x] `ifWeMight` — `"connect completes without error"`: call `peer.connect()` and assert no exception
349
+ - [x] `ifWeMight` — `"connect pool is depleted by exactly one challenge"`: assert `S.data.pools['connect'].used` increased by the expected count
350
+
351
+ #### Phase 2B — Space-Gib integrated (manual)
352
+
353
+ - [x] Confirm "Connect (WS H andshake)" button in `dev-tools.mts` opens WebSocket successfully
354
+ - [x] Confirm server issues challenge from S's `connect` pool and accepts the solution
355
+ - [x] Confirm session is considered active server-side after handshake
356
+
357
+ ---
358
+
359
+ ### Phase 3 — Basic Single-Timeline Sync with Identity
360
+
361
+ **Goal**: With identity fully established and transport connected, sync a single ibgib `X` from source to dest. Verify S evolves correctly per turn (`sync` pool), and X's full dependency graph arrives on dest and that after sync, both sender and receiver durable spaces contain the same identity I, full session keystone S dependency graph, and full X dependency graph.
362
+
363
+ #### Phase 3A — Innerspace unit test
364
+
365
+ - [ ] Extend `sync-with-identity-basic.respec.mts` or create `sync-with-identity-singletimeline.respec.mts`
366
+ - [ ] Create `r1_alpha_v0_source` via `TestTransformer`
367
+ - [ ] Call `senderCoordinator.sync({ domainIbGibs: [alpha], senderIdentity, senderSecret, ... })`
368
+ - [ ] `ifWeMight` — `"alpha dep graph matches on source and dest"`: use `getDependencyGraph` + `graphsAreEquivalent`
369
+ - [ ] `ifWeMight` — `"sessionIdentity evolved once per sync turn"`: assert `S.data.n` equals number of outgoing turns
370
+
371
+ #### Phase 3B — Space-Gib integrated (manual)
372
+
373
+ - [ ] Trigger a sync of a known test ibgib via `dev-tools.mts` or equivalent UI
374
+ - [ ] Confirm S evolves on each outgoing turn (inspect frame `data.n` in debugger)
375
+ - [ ] Confirm X's full dependency graph is present on the client after commit
376
+ - [ ] Confirm X's full dependency graph is present on the server after commit
377
+ - [ ] Confirm S's full dependency graph is present on the client after commit
378
+ - [ ] Confirm S's full dependency graph is present on the server after commit
379
+ - [ ] Confirm I's full dependency graph is present on the client after commit
380
+ - [ ] Confirm I's full dependency graph is present on the server after commit
@@ -8,7 +8,7 @@ await respecfully(sir, 'Graft Info Helpers', async () => {
8
8
 
9
9
  await respecfully(sir, 'mergeTextLCS', async () => {
10
10
 
11
- await ifWeMight(sir, 'mergeTextLCS should interleave unique lines (A then B)', async () => {
11
+ await ifWe(sir, 'mergeTextLCS should interleave unique lines (A then B)', async () => {
12
12
  const textA = "Line 1\nLine A\nLine 3";
13
13
  const textB = "Line 1\nLine B\nLine 3";
14
14
  const res = await mergeTextByLongestCommonSubsequence({ textA, textB });
@@ -16,7 +16,7 @@ await respecfully(sir, 'Graft Info Helpers', async () => {
16
16
  iReckon(sir, res).asTo('result').isGonnaBe(expected);
17
17
  });
18
18
 
19
- await ifWeMight(sir, 'mergeTextLCS should handle insertions', async () => {
19
+ await ifWe(sir, 'mergeTextLCS should handle insertions', async () => {
20
20
  const textA = "Line 1\nLine 3";
21
21
  const textB = "Line 1\nLine 2\nLine 3";
22
22
  const res = await mergeTextByLongestCommonSubsequence({ textA, textB });
@@ -24,7 +24,7 @@ await respecfully(sir, 'Graft Info Helpers', async () => {
24
24
  iReckon(sir, res).asTo('result').isGonnaBe(expected);
25
25
  });
26
26
 
27
- await ifWeMight(sir, 'mergeTextLCS should handle appends', async () => {
27
+ await ifWe(sir, 'mergeTextLCS should handle appends', async () => {
28
28
  const textA = "Line 1";
29
29
  const textB = "Line 1\nLine 2";
30
30
  const res = await mergeTextByLongestCommonSubsequence({ textA, textB });
@@ -32,7 +32,7 @@ await respecfully(sir, 'Graft Info Helpers', async () => {
32
32
  iReckon(sir, res).asTo('result').isGonnaBe(expected);
33
33
  });
34
34
 
35
- await ifWeMight(sir, 'mergeTextLCS should handle simultaneous list additions', async () => {
35
+ await ifWe(sir, 'mergeTextLCS should handle simultaneous list additions', async () => {
36
36
  const textA = "- Item 1\n- Item 2\n- Item A";
37
37
  const textB = "- Item 1\n- Item 2\n- Item B";
38
38
  const res = await mergeTextByLongestCommonSubsequence({ textA, textB });
@@ -42,7 +42,7 @@ await respecfully(sir, 'Graft Info Helpers', async () => {
42
42
  iReckon(sir, res).asTo('result').isGonnaBe(expected);
43
43
  });
44
44
 
45
- await ifWeMight(sir, 'mergeTextLCS should handle distinct modifications in code block', async () => {
45
+ await ifWe(sir, 'mergeTextLCS should handle distinct modifications in code block', async () => {
46
46
  const textA = `function foo() {
47
47
  console.log("start");
48
48
  doA();
@@ -64,7 +64,7 @@ await respecfully(sir, 'Graft Info Helpers', async () => {
64
64
  iReckon(sir, res).asTo('result').isGonnaBe(expected);
65
65
  });
66
66
 
67
- await ifWeMight(sir, 'mergeTextLCS should return same string if identical', async () => {
67
+ await ifWe(sir, 'mergeTextLCS should return same string if identical', async () => {
68
68
  const textA = "Hello";
69
69
  const res = await mergeTextByLongestCommonSubsequence({ textA, textB: textA });
70
70
  iReckon(sir, res).asTo('result').isGonnaBe(textA);
@@ -75,7 +75,7 @@ await respecfully(sir, 'Graft Info Helpers', async () => {
75
75
  await respecfully(sir, 'graftTimelines', async () => {
76
76
  // We need a more complex setup to test this properly (mock space, etc.)
77
77
  // For now, this placeholder ensures the test file runs.
78
- await ifWeMight(sir, 'graftTimelines placeholders', async () => {
78
+ await ifWe(sir, 'graftTimelines placeholders', async () => {
79
79
  iReckon(sir, true).isGonnaBeTrue();
80
80
  });
81
81
  });
@@ -127,7 +127,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
127
127
  // #endregion r1 setup
128
128
 
129
129
  await respecfully(sir, `r1 verify pre`, async () => {
130
- await ifWeMight(sir, 'dest should NOT have alpha', async () => {
130
+ await ifWe(sir, 'dest should NOT have alpha', async () => {
131
131
  const resGet = await getFromSpace({
132
132
  space: destSpace,
133
133
  addr: alpha_tjpAddr,
@@ -150,7 +150,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
150
150
  if (logalot) { console.log(`${lc} r1_syncSaga Complete.`); }
151
151
 
152
152
  await respecfully(sir, `r1 verify post`, async () => {
153
- await ifWeMight(sir, 'dest should have alpha', async () => {
153
+ await ifWe(sir, 'dest should have alpha', async () => {
154
154
  // alpha's full dep graph should exist on dest
155
155
  const [alpha_dest] = await getIbGibsFromCache_fallbackToSpaces({
156
156
  addrs: [r1_alpha_v0_source.addr],
@@ -230,7 +230,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
230
230
  // Verify Receiver has correct KV (Pre-Sync Check)
231
231
  // This ensures the conflict precondition exists.
232
232
  await respecfully(sir, `r2 verify pre`, async () => {
233
- await ifWeMight(sir, 'dest has alpha v1 common', async () => {
233
+ await ifWe(sir, 'dest has alpha v1 common', async () => {
234
234
  try {
235
235
  const destKV = await receiverCoordinator.getKnowledgeMap({
236
236
  space: destSpace,
@@ -308,7 +308,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
308
308
  if (!alpha_dest_tipAddr) {
309
309
  throw new Error(`dest Space missing timeline tip for ${alpha_tjpAddr} (E: 30b018c6349917aa28c9f538fa567826)`);
310
310
  }
311
- await ifWeMight(sir, 'tip addrs', async () => {
311
+ await ifWe(sir, 'tip addrs', async () => {
312
312
  iReckon(sir, alpha_source_tipAddr).asTo('source/dest have same tip addrs').isGonnaBe(alpha_dest_tipAddr);
313
313
  })
314
314
 
@@ -334,7 +334,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
334
334
  // #endregion DEBUG SANITY CHECK
335
335
 
336
336
 
337
- await ifWeMight(sir, 'r2 basics of alpha merge', async () => {
337
+ await ifWe(sir, 'r2 basics of alpha merge', async () => {
338
338
  iReckon(sir, alpha_source_tipAddr)
339
339
  .asTo(`Source Tip (${alpha_source_tipAddr}) should NOT be r2_alpha_v3_source_rel8dBeta`)
340
340
  .not.isGonnaBe(r2_alpha_v3_source_rel8dBeta.addr);
@@ -357,7 +357,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
357
357
  iReckon(sir, gotten_alpha_source_tipIbGib.data![r2_dest_mut8Info.key!]).asTo(`New Tip has dest field ${r2_dest_mut8Info.key!}`).isGonnaBe(r2_dest_mut8Info.value!);
358
358
  });
359
359
 
360
- await ifWeMight(sir, 'r2 alpha and deps synced', async () => {
360
+ await ifWe(sir, 'r2 alpha and deps synced', async () => {
361
361
  // alpha's full dep graph should exist on dest, even though its
362
362
  // timeline was grafted
363
363
  const [alpha_dest] = await getIbGibsFromCache_fallbackToSpaces({
@@ -390,7 +390,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
390
390
  }
391
391
  });
392
392
 
393
- await ifWeMight(sir, 'r2 beta and deps synced', async () => {
393
+ await ifWe(sir, 'r2 beta and deps synced', async () => {
394
394
  // beta's full dep graph should exist on dest
395
395
  const [beta_dest] = await getIbGibsFromCache_fallbackToSpaces({
396
396
  addrs: [r2_beta_v0_source.addr],
@@ -510,7 +510,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
510
510
  // #endregion r3 dest edits
511
511
 
512
512
  await respecfully(sir, `r3 verify pre`, async () => {
513
- await ifWeMight(sir, 'dest has both alpha and beta tips (pre-conflict)', async () => {
513
+ await ifWe(sir, 'dest has both alpha and beta tips (pre-conflict)', async () => {
514
514
  try {
515
515
  const destKV = await receiverCoordinator.getKnowledgeMap({
516
516
  space: destSpace,
@@ -585,13 +585,13 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
585
585
  });
586
586
  const r3_beta_dest_tipAddr = beta_destKV_afterR3[beta_tjpAddr];
587
587
  if (!r3_beta_dest_tipAddr) {
588
- await ifWeMight(sir, 'r3_beta_dest_tipAddr is falsy?', async () => {
588
+ await ifWe(sir, 'r3_beta_dest_tipAddr is falsy?', async () => {
589
589
  iReckon(sir, true).asTo('fail').isGonnaBeFalse();
590
590
  });
591
591
  return; /* <<<< returns early */
592
592
  }
593
593
 
594
- await ifWeMight(sir, 'r3 tip addrs match (both timelines)', async () => {
594
+ await ifWe(sir, 'r3 tip addrs match (both timelines)', async () => {
595
595
  iReckon(sir, r3_alpha_source_tipAddr).asTo('alpha source/dest have same tip addrs').isGonnaBe(r3_alpha_dest_tipAddr);
596
596
  iReckon(sir, r3_beta_source_tipAddr).asTo('beta source/dest have same tip addrs').isGonnaBe(r3_beta_dest_tipAddr);
597
597
  });
@@ -610,7 +610,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
610
610
  const resGet_r3_beta_tip = await getFromSpace({ space: sourceSpace, addr: r3_beta_source_tipAddr });
611
611
  const r3_beta_tipIbGib = resGet_r3_beta_tip.ibGibs![0] as IbGib_V1<TestData>;
612
612
 
613
- await ifWeMight(sir, 'r3 alpha merge has all four fields (commonField, fieldA, fieldB, fieldC, fieldD)', async () => {
613
+ await ifWe(sir, 'r3 alpha merge has all four fields (commonField, fieldA, fieldB, fieldC, fieldD)', async () => {
614
614
  // Should have new graft point with different addr than before
615
615
  iReckon(sir, r3_alpha_source_tipAddr)
616
616
  .asTo(`Alpha R3 tip should NOT be r3_alpha_v4_source_mut8fieldC`)
@@ -631,7 +631,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
631
631
  .isGonnaBe(r3_alpha_v4_dest_mut8Info.value!);
632
632
  });
633
633
 
634
- await ifWeMight(sir, 'r3 beta merge has both fields (betaFieldA, betaFieldB)', async () => {
634
+ await ifWe(sir, 'r3 beta merge has both fields (betaFieldA, betaFieldB)', async () => {
635
635
  // Should have new graft point with different addr than before
636
636
  iReckon(sir, r3_beta_source_tipAddr)
637
637
  .asTo(`Beta R3 tip should NOT be r3_beta_v1_source`)
@@ -652,7 +652,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
652
652
  .isGonnaBe(r3_dest_beta_mut8Info.value!);
653
653
  });
654
654
 
655
- await ifWeMight(sir, 'r3 alpha and deps synced', async () => {
655
+ await ifWe(sir, 'r3 alpha and deps synced', async () => {
656
656
  const [alpha_dest] = await getIbGibsFromCache_fallbackToSpaces({
657
657
  // addrs: [r1_alpha_v0_source.addr],
658
658
  addrs: [r3_alpha_source_tipAddr],
@@ -684,7 +684,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
684
684
  }
685
685
  });
686
686
 
687
- await ifWeMight(sir, 'r3 beta and deps synced', async () => {
687
+ await ifWe(sir, 'r3 beta and deps synced', async () => {
688
688
  const [beta_dest] = await getIbGibsFromCache_fallbackToSpaces({
689
689
  // addrs: [r2_beta_v0_source.addr],
690
690
  addrs: [r3_beta_dest_tipAddr],
@@ -836,7 +836,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
836
836
  // #endregion r4 dest edits
837
837
 
838
838
  await respecfully(sir, `r4 verify pre`, async () => {
839
- await ifWeMight(sir, 'dest has alpha and beta post-R3 tips', async () => {
839
+ await ifWe(sir, 'dest has alpha and beta post-R3 tips', async () => {
840
840
  try {
841
841
  const destKV = await receiverCoordinator.getKnowledgeMap({
842
842
  space: destSpace,
@@ -913,7 +913,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
913
913
  });
914
914
  const r4_beta_dest_tipAddr = r4_beta_destKV[beta_tjpAddr];
915
915
  if (!r4_beta_dest_tipAddr) {
916
- await ifWeMight(sir, 'r4_beta_dest_tipAddr is falsy?', async () => {
916
+ await ifWe(sir, 'r4_beta_dest_tipAddr is falsy?', async () => {
917
917
  iReckon(sir, true).asTo('fail').isGonnaBeFalse();
918
918
  });
919
919
  return; /* <<<< returns early */
@@ -933,7 +933,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
933
933
  });
934
934
  const r4_gamma_dest_tipAddr = r4_gamma_destKV[gamma_tjpAddr];
935
935
 
936
- await ifWeMight(sir, 'r4 tip addrs match (alpha, beta, gamma)', async () => {
936
+ await ifWe(sir, 'r4 tip addrs match (alpha, beta, gamma)', async () => {
937
937
  iReckon(sir, r4_alpha_source_tipAddr).asTo('alpha source/dest have same tip addrs').isGonnaBe(r4_alpha_dest_tipAddr);
938
938
  iReckon(sir, r4_beta_source_tipAddr).asTo('beta source/dest have same tip addrs').isGonnaBe(r4_beta_dest_tipAddr);
939
939
  iReckon(sir, r4_gamma_source_tipAddr).asTo('gamma source/dest have same tip addrs').isGonnaBe(r4_gamma_dest_tipAddr);
@@ -958,7 +958,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
958
958
  const resGet_r4_gamma_tip = await getFromSpace({ space: sourceSpace, addr: r4_gamma_source_tipAddr });
959
959
  const r4_gamma_tipIbGib = resGet_r4_gamma_tip.ibGibs![0] as IbGib_V1<TestData>;
960
960
 
961
- await ifWeMight(sir, 'r4 alpha has complete chain of edits and gamma relation', async () => {
961
+ await ifWe(sir, 'r4 alpha has complete chain of edits and gamma relation', async () => {
962
962
  // Should have new graft point with different addr than either pre-graft version
963
963
  iReckon(sir, r4_alpha_source_tipAddr)
964
964
  .asTo(`Alpha R4 tip should NOT be r4_alpha_v7_source`)
@@ -994,7 +994,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
994
994
  .isGonnaBeTrue();
995
995
  });
996
996
 
997
- await ifWeMight(sir, 'r4 beta has all edits', async () => {
997
+ await ifWe(sir, 'r4 beta has all edits', async () => {
998
998
  // Beta should have new graft from R4
999
999
  iReckon(sir, r4_beta_source_tipAddr)
1000
1000
  .asTo(`Beta R4 tip should NOT be r4_beta_v2_source (source version pre-graft)`)
@@ -1015,7 +1015,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
1015
1015
  .isGonnaBe(r4_dest_beta_mut8Info.value!);
1016
1016
  });
1017
1017
 
1018
- await ifWeMight(sir, 'r4 gamma synced with deps', async () => {
1018
+ await ifWe(sir, 'r4 gamma synced with deps', async () => {
1019
1019
  const [gamma_dest] = await getIbGibsFromCache_fallbackToSpaces({
1020
1020
  addrs: [r4_gamma_v0_source.addr],
1021
1021
  space: destSpace,
@@ -1046,7 +1046,7 @@ await respecfully(sir, `Multi-round/timeline permutations`, async () => {
1046
1046
  }
1047
1047
  });
1048
1048
 
1049
- await ifWeMight(sir, 'r4 all timelines dep graphs synced', async () => {
1049
+ await ifWe(sir, 'r4 all timelines dep graphs synced', async () => {
1050
1050
  // Verify alpha dep graphs
1051
1051
  const [alpha_dest] = await getIbGibsFromCache_fallbackToSpaces({
1052
1052
  addrs: [r4_alpha_source_tipAddr],
@@ -230,7 +230,7 @@ await respecfully(sir, `Two different fields`, async () => {
230
230
 
231
231
  // Verify Receiver has correct KV (Pre-Sync Check)
232
232
  // This ensures the conflict precondition exists.
233
- await ifWeMight(sir, 'verify receiver KV pre-sync', async () => {
233
+ await ifWe(sir, 'verify receiver KV pre-sync', async () => {
234
234
  try {
235
235
  const destKV = await receiverCoordinator.getKnowledgeMap({
236
236
  space: destSpace,
@@ -304,7 +304,7 @@ await respecfully(sir, `Two different fields`, async () => {
304
304
 
305
305
  const resSourceTip = await getFromSpace({ space: sourceSpace, addr: getIbGibAddr({ ibGib: v2a }) });
306
306
 
307
- await ifWeMight(sir, `verify merge`, async () => {
307
+ await ifWe(sir, `verify merge`, async () => {
308
308
  // Retrieve updated KV from Source (or check what happened to v2a)
309
309
  // Ideally, we just check the source space for the timeline tip
310
310
  // The timeline tip for testRoot/v0 should now be NEW.