@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.
- package/dist/keystone/keystone-config-builder.d.mts +12 -1
- package/dist/keystone/keystone-config-builder.d.mts.map +1 -1
- package/dist/keystone/keystone-config-builder.mjs +58 -4
- package/dist/keystone/keystone-config-builder.mjs.map +1 -1
- package/dist/keystone/keystone-constants.d.mts +40 -5
- package/dist/keystone/keystone-constants.d.mts.map +1 -1
- package/dist/keystone/keystone-constants.mjs +39 -5
- package/dist/keystone/keystone-constants.mjs.map +1 -1
- package/dist/keystone/keystone-helpers.d.mts +11 -1
- package/dist/keystone/keystone-helpers.d.mts.map +1 -1
- package/dist/keystone/keystone-helpers.mjs +37 -1
- package/dist/keystone/keystone-helpers.mjs.map +1 -1
- package/dist/keystone/keystone-policy-types.d.mts +23 -0
- package/dist/keystone/keystone-policy-types.d.mts.map +1 -0
- package/dist/keystone/keystone-policy-types.mjs +2 -0
- package/dist/keystone/keystone-policy-types.mjs.map +1 -0
- package/dist/sync/graft-info/graft-info-helpers.respec.mjs +8 -8
- package/dist/sync/graft-info/graft-info-helpers.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs +22 -22
- package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-basic-divergence.respec.mjs +3 -3
- package/dist/sync/sync-conflict-basic-divergence.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs +6 -6
- package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-text-merge.respec.mjs +26 -26
- package/dist/sync/sync-conflict-text-merge.respec.mjs.map +1 -1
- package/dist/sync/sync-helpers.d.mts +19 -0
- package/dist/sync/sync-helpers.d.mts.map +1 -1
- package/dist/sync/sync-helpers.mjs +51 -1
- package/dist/sync/sync-helpers.mjs.map +1 -1
- package/dist/sync/sync-innerspace-constants.respec.mjs +2 -2
- package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs +2 -2
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +4 -4
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +2 -2
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-partial-update.respec.mjs +3 -3
- package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace.respec.mjs +4 -4
- package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.d.mts +5 -0
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mjs +18 -0
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.d.mts +5 -0
- package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mjs +21 -3
- package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +12 -0
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs +34 -0
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts +69 -1
- package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +30 -0
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +88 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts +30 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mjs +2 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mjs.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts +66 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs +280 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mjs.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts +85 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs +332 -0
- package/dist/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mjs.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts +29 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mjs +2 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mjs.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts +42 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.d.mts.map +1 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs +282 -0
- package/dist/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mjs.map +1 -0
- package/dist/sync/sync-saga-coordinator.d.mts +35 -1
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +62 -1
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-withid.connect.respec.d.mts +12 -0
- package/dist/sync/sync-withid.connect.respec.d.mts.map +1 -0
- package/dist/sync/sync-withid.connect.respec.mjs +205 -0
- package/dist/sync/sync-withid.connect.respec.mjs.map +1 -0
- package/dist/sync/sync-withid.establish.respec.d.mts +19 -0
- package/dist/sync/sync-withid.establish.respec.d.mts.map +1 -0
- package/dist/sync/sync-withid.establish.respec.mjs +322 -0
- package/dist/sync/sync-withid.establish.respec.mjs.map +1 -0
- package/package.json +4 -4
- package/src/keystone/keystone-config-builder.mts +73 -4
- package/src/keystone/keystone-constants.mts +42 -6
- package/src/keystone/keystone-helpers.mts +44 -2
- package/src/keystone/keystone-policy-types.mts +25 -0
- package/src/keystone/keystone-policy.schema.json +51 -0
- package/src/keystone/keystone-service-v1.mts +3 -3
- package/src/sync/docs/architecture.md +20 -0
- package/src/sync/docs/security.md +207 -3
- package/src/sync/graft-info/graft-info-helpers.respec.mts +7 -7
- package/src/sync/sync-conflict-adv-multitimelines.respec.mts +21 -21
- package/src/sync/sync-conflict-basic-divergence.respec.mts +2 -2
- package/src/sync/sync-conflict-basic-multitimelines.respec.mts +5 -5
- package/src/sync/sync-conflict-text-merge.respec.mts +25 -25
- package/src/sync/sync-helpers.mts +51 -1
- package/src/sync/sync-innerspace-constants.respec.mts +1 -1
- package/src/sync/sync-innerspace-deep-updates.respec.mts +1 -1
- package/src/sync/sync-innerspace-dest-ahead.respec.mts +3 -3
- package/src/sync/sync-innerspace-multiple-timelines.respec.mts +1 -1
- package/src/sync/sync-innerspace-partial-update.respec.mts +2 -2
- package/src/sync/sync-innerspace.respec.mts +3 -3
- package/src/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mts +20 -0
- package/src/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mts +23 -3
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +38 -1
- package/src/sync/sync-peer/sync-peer-types.mts +70 -1
- package/src/sync/sync-peer/sync-peer-v1.mts +94 -1
- package/src/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-types.mts +36 -0
- package/src/sync/sync-peer/sync-peer-websocket-receiver/sync-peer-websocket-receiver-v1.mts +337 -0
- package/src/sync/sync-peer/sync-peer-websocket-receiver/sync-websocket-peer-helpers.mts +388 -0
- package/src/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-types.mts +35 -0
- package/src/sync/sync-peer/sync-peer-websocket-sender/sync-peer-websocket-sender-v1.mts +321 -0
- package/src/sync/sync-saga-coordinator.mts +84 -0
- package/src/sync/sync-withid.connect.respec.mts +243 -0
- package/src/sync/sync-withid.establish.respec.mts +361 -0
- package/src/sync/unused-identity-backup.mts.md +1 -1
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.d.mts +0 -2
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.d.mts.map +0 -1
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs +0 -310
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs.map +0 -1
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|