@ibgib/core-gib 0.1.43 → 0.1.45

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 (85) hide show
  1. package/dist/keystone/kdf/kdf-constants.d.mts +25 -0
  2. package/dist/keystone/kdf/kdf-constants.d.mts.map +1 -0
  3. package/dist/keystone/kdf/kdf-constants.mjs +28 -0
  4. package/dist/keystone/kdf/kdf-constants.mjs.map +1 -0
  5. package/dist/keystone/kdf/kdf-helpers.d.mts +45 -0
  6. package/dist/keystone/kdf/kdf-helpers.d.mts.map +1 -0
  7. package/dist/keystone/kdf/kdf-helpers.mjs +94 -0
  8. package/dist/keystone/kdf/kdf-helpers.mjs.map +1 -0
  9. package/dist/keystone/kdf/kdf-types.d.mts +49 -0
  10. package/dist/keystone/kdf/kdf-types.d.mts.map +1 -0
  11. package/dist/keystone/kdf/kdf-types.mjs +2 -0
  12. package/dist/keystone/kdf/kdf-types.mjs.map +1 -0
  13. package/dist/keystone/keystone-config-builder.d.mts +65 -12
  14. package/dist/keystone/keystone-config-builder.d.mts.map +1 -1
  15. package/dist/keystone/keystone-config-builder.mjs +138 -46
  16. package/dist/keystone/keystone-config-builder.mjs.map +1 -1
  17. package/dist/keystone/keystone-config-builder.respec.mjs +21 -13
  18. package/dist/keystone/keystone-config-builder.respec.mjs.map +1 -1
  19. package/dist/keystone/keystone-constants.d.mts +15 -0
  20. package/dist/keystone/keystone-constants.d.mts.map +1 -1
  21. package/dist/keystone/keystone-constants.mjs +16 -0
  22. package/dist/keystone/keystone-constants.mjs.map +1 -1
  23. package/dist/keystone/keystone-helpers.d.mts +8 -4
  24. package/dist/keystone/keystone-helpers.d.mts.map +1 -1
  25. package/dist/keystone/keystone-helpers.mjs +76 -6
  26. package/dist/keystone/keystone-helpers.mjs.map +1 -1
  27. package/dist/keystone/keystone-service-v1.d.mts +1 -1
  28. package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
  29. package/dist/keystone/keystone-service-v1.mjs +6 -5
  30. package/dist/keystone/keystone-service-v1.mjs.map +1 -1
  31. package/dist/keystone/keystone-service-v1.respec.mjs +72 -45
  32. package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
  33. package/dist/keystone/keystone-types.d.mts +28 -18
  34. package/dist/keystone/keystone-types.d.mts.map +1 -1
  35. package/dist/keystone/keystone-types.mjs +26 -15
  36. package/dist/keystone/keystone-types.mjs.map +1 -1
  37. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.d.mts.map +1 -1
  38. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs +7 -10
  39. package/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs.map +1 -1
  40. package/dist/sync/sync-constants.d.mts +9 -0
  41. package/dist/sync/sync-constants.d.mts.map +1 -1
  42. package/dist/sync/sync-constants.mjs +10 -0
  43. package/dist/sync/sync-constants.mjs.map +1 -1
  44. package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs +49 -19
  45. package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs.map +1 -1
  46. package/dist/sync/sync-peer/sync-peer-v1.mjs +3 -3
  47. package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
  48. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +0 -38
  49. package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
  50. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +1 -83
  51. package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
  52. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +24 -4
  53. package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
  54. package/dist/sync/sync-saga-coordinator.d.mts +36 -13
  55. package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
  56. package/dist/sync/sync-saga-coordinator.mjs +246 -38
  57. package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
  58. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +1 -7
  59. package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
  60. package/dist/sync/sync-types.d.mts +11 -0
  61. package/dist/sync/sync-types.d.mts.map +1 -1
  62. package/dist/sync/sync-types.mjs.map +1 -1
  63. package/package.json +1 -1
  64. package/src/keystone/README.md +4 -3
  65. package/src/keystone/docs/architecture.md +3 -1
  66. package/src/keystone/kdf/kdf-constants.mts +34 -0
  67. package/src/keystone/kdf/kdf-helpers.mts +105 -0
  68. package/src/keystone/kdf/kdf-types.mts +58 -0
  69. package/src/keystone/keystone-config-builder.mts +170 -47
  70. package/src/keystone/keystone-config-builder.respec.mts +21 -14
  71. package/src/keystone/keystone-constants.mts +21 -2
  72. package/src/keystone/keystone-helpers.mts +100 -14
  73. package/src/keystone/keystone-service-v1.mts +23 -22
  74. package/src/keystone/keystone-service-v1.respec.mts +71 -44
  75. package/src/keystone/keystone-types.mts +37 -23
  76. package/src/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mts +9 -13
  77. package/src/sync/sync-constants.mts +12 -0
  78. package/src/sync/sync-innerspace-dest-ahead-withid.respec.mts +53 -20
  79. package/src/sync/sync-peer/sync-peer-v1.mts +3 -3
  80. package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +3 -107
  81. package/src/sync/sync-saga-context/sync-saga-context-types.mts +25 -4
  82. package/src/sync/sync-saga-coordinator.mts +313 -40
  83. package/src/sync/sync-saga-message/sync-saga-message-types.mts +1 -7
  84. package/src/sync/sync-types.mts +12 -0
  85. package/tmp.md +0 -274
@@ -16,13 +16,16 @@ import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
16
16
  import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
17
17
  import { IbGibSpaceAny } from "../witness/space/space-base-v1.mjs";
18
18
  import { putInSpace, getLatestAddrs, getFromSpace, registerNewIbGib } from "../witness/space/space-helper.mjs";
19
- import { KeystoneIbGib_V1 } from "../keystone/keystone-types.mjs";
19
+ import { KeystoneIbGib_V1, KeystonePoolConfig_HashV1 } from "../keystone/keystone-types.mjs";
20
20
  import { KeystoneService_V1 } from "../keystone/keystone-service-v1.mjs";
21
+ import { deriveKey } from "../keystone/kdf/kdf-helpers.mjs";
22
+ import { KdfStrategy } from "../keystone/kdf/kdf-constants.mjs";
21
23
  import { MetaspaceService } from "../witness/space/metaspace/metaspace-types.mjs";
22
24
  import {
23
25
  SyncStage, SYNC_ATOM, SYNC_MSG_REL8N_NAME, SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN,
24
26
  SyncConflictStrategy,
25
27
  SYNC_CONFLICT_STRATEGY_VALID_VALUES,
28
+ DEFAULT_SESSION_IDENTITY_INITIAL_DELEGATE_SECRET,
26
29
  } from "./sync-constants.mjs";
27
30
  import {
28
31
  appendToTimeline, createTimeline, getHistory, getHistoryAddrs, Rel8nInfo, Rel8nRemovalInfo
@@ -47,8 +50,8 @@ import { SYNC_SAGA_MSG_ATOM } from "./sync-saga-message/sync-saga-message-consta
47
50
  import { SyncSagaInfo } from "./sync-types.mjs";
48
51
  import { splitPerTjpAndOrDna, getTimelinesGroupedByTjp, isIbGib, getIbGibsFromCache_fallbackToSpaces } from "../common/other/ibgib-helper.mjs";
49
52
  import { SyncPeerWitness } from "./sync-peer/sync-peer-types.mjs";
50
- import { SyncSagaContextIbGib_V1, } from "./sync-saga-context/sync-saga-context-types.mjs";
51
- import { createSyncSagaContext, validateContextAndSagaFrame } from "./sync-saga-context/sync-saga-context-helpers.mjs";
53
+ import { SyncSagaContextData_V1, SyncSagaContextIbGib_V1, SyncSagaContextRel8ns_V1, } from "./sync-saga-context/sync-saga-context-types.mjs";
54
+ import { getSyncSagaContextIb, validateContextAndSagaFrame } from "./sync-saga-context/sync-saga-context-helpers.mjs";
52
55
  import { newupSubject, } from "../common/pubsub/subject/subject-helper.mjs";
53
56
  import { SubjectWitness } from "../common/pubsub/subject/subject-types.mjs";
54
57
  import { getSyncSagaMessageFromFrame } from "./sync-saga-message/sync-saga-message-helpers.mjs";
@@ -60,6 +63,9 @@ import { graftTimelines, } from "./graft-info/graft-info-helpers.mjs";
60
63
  import { GRAFT_INFO_REL8N_NAME } from "./graft-info/graft-info-constants.mjs";
61
64
  import { GraftInfoIbGib_V1 } from "./graft-info/graft-info-types.mjs";
62
65
  import { validateIbGibIntrinsically } from "@ibgib/ts-gib/dist/V1/validate-helper.mjs";
66
+ import { KEYSTONE_VERB_MANAGE } from "../keystone/keystone-constants.mjs";
67
+ import { validateKeystoneGraph } from "../keystone/keystone-helpers.mjs";
68
+ import { SYNC_SAGA_CONTEXT_ATOM } from "./sync-saga-context/sync-saga-context-constants.mjs";
63
69
 
64
70
 
65
71
  const logalot = GLOBAL_LOG_A_LOT;
@@ -83,7 +89,7 @@ export class SyncSagaCoordinator {
83
89
  private lc: string = `[${SyncSagaCoordinator.name}]`;
84
90
 
85
91
  constructor(
86
- private keystone: KeystoneService_V1
92
+ private keystoneSvc: KeystoneService_V1
87
93
  ) {
88
94
 
89
95
  }
@@ -94,18 +100,14 @@ export class SyncSagaCoordinator {
94
100
  * @remarks
95
101
  * **Execution Context**: **Sender (Local)**.
96
102
  * This method is the entry point for starting a sync session.
97
- *
98
- * @param opts.peer - The remote peer witness to communicate with.
99
- * @param opts.localSpace - The local space that will be read from and written to.
100
- * @param opts.metaspace - Service for creating temp spaces and managing ibgibs.
101
- * @param opts.domainIbGibs - The root ibgibs defining the scope of the sync.
102
- * @param opts.useSessionIdentity - (Optional) Whether to create an ephemeral session identity. Default: true.
103
103
  */
104
104
  public async sync({
105
105
  peer,
106
106
  domainIbGibs,
107
107
  conflictStrategy = SyncConflictStrategy.abort,
108
108
  useSessionIdentity = true,
109
+ identity,
110
+ identitySecret,
109
111
  metaspace,
110
112
  localSpace,
111
113
  }: {
@@ -121,7 +123,8 @@ export class SyncSagaCoordinator {
121
123
  */
122
124
  domainIbGibs: IbGib_V1[],
123
125
  /**
124
- * The space containing the {@link domainIbGibs} we want to sync.
126
+ * The space containing the {@link domainIbGibs} we want to sync. If
127
+ * sync is successful, any updates to timelines will be stored here.
125
128
  */
126
129
  localSpace: IbGibSpaceAny;
127
130
  /**
@@ -129,11 +132,17 @@ export class SyncSagaCoordinator {
129
132
  */
130
133
  metaspace: MetaspaceService;
131
134
  /**
132
- * The identity authorizing this sync.
135
+ * The primary (i.e. non-session) identity authorizing this sync.
136
+ *
137
+ * If this is truthy, then {@link identitySecret} must also be provided.
133
138
  */
134
139
  identity?: KeystoneIbGib_V1;
135
140
  /**
136
141
  * The secret for the identity (to sign the commit).
142
+ *
143
+ * If provided, this will drive both signing {@link identity} keystone
144
+ * (if provided) AND session keystone (if {@link useSessionIdentity} is
145
+ * true).
137
146
  */
138
147
  identitySecret?: string;
139
148
  /**
@@ -144,8 +153,10 @@ export class SyncSagaCoordinator {
144
153
  */
145
154
  conflictStrategy?: SyncConflictStrategy;
146
155
  /**
147
- * If true, creates an ephemeral session identity for the sync process to
148
- * secure the sync transaction itself.
156
+ * If true, creates an ephemeral session identity for the sync process
157
+ * to secure the sync transaction itself.
158
+ *
159
+ * If this is true, {@link identitySecret} must also be provided.
149
160
  *
150
161
  * @default true
151
162
  */
@@ -196,9 +207,16 @@ export class SyncSagaCoordinator {
196
207
  try {
197
208
 
198
209
  // BOOTSTRAP IDENTITY (Session Keystone)
199
- const sessionIdentity = useSessionIdentity
200
- ? await this.getSessionIdentity({ sagaId, metaspace, localSpace })
201
- : undefined;
210
+ let sessionIdentity: KeystoneIbGib_V1 | undefined = undefined;
211
+
212
+ if (useSessionIdentity) {
213
+ if (!identitySecret) { throw new Error(`useSessionIdentity is true, but identitySecret is falsy. Must provide a secret if you want to use a session identity. (E: 81915860c4dd3ea4dfd81825fa74c126)`); }
214
+ // creates the initial session identity (keystone). the flow
215
+ // (i think) will go: evolve saga frame, then sign keystone,
216
+ // then create/send context.
217
+ sessionIdentity = await this.getSessionIdentity({ sagaId, identitySecret, metaspace, localSpace });
218
+ }
219
+
202
220
  // if (logalot) { console.log(`${lc} sessionIdentity: ${sessionIdentity ? pretty(sessionIdentity) : 'undefined'} (I: abc01872800b3a66b819a05898bba826)`); }
203
221
 
204
222
  // CREATE INITIAL FRAME (Stage.init)
@@ -214,6 +232,7 @@ export class SyncSagaCoordinator {
214
232
  await this.executeSagaLoop({
215
233
  initFrame, initDomainGraph,
216
234
  peer,
235
+ identitySecret,
217
236
  sessionIdentity,
218
237
  updates$,
219
238
  localSpace,
@@ -266,7 +285,13 @@ export class SyncSagaCoordinator {
266
285
  * Local temp space relative to the execution context's POV
267
286
  */
268
287
  myTempSpace: IbGibSpaceAny,
288
+ /**
289
+ * @see {@link sync} `identity` param.
290
+ */
269
291
  identity?: KeystoneIbGib_V1,
292
+ /**
293
+ * @see {@link sync} `identitySecret` param.
294
+ */
270
295
  identitySecret?: string,
271
296
  metaspace: MetaspaceService,
272
297
  }): Promise<SyncSagaContextIbGib_V1 | null> {
@@ -279,7 +304,6 @@ export class SyncSagaCoordinator {
279
304
  mySpace,
280
305
  myTempSpace,
281
306
  identity,
282
- identitySecret,
283
307
  metaspace,
284
308
  });
285
309
 
@@ -303,12 +327,13 @@ export class SyncSagaCoordinator {
303
327
  // create the return context
304
328
  const { frame, payloadIbGibsDomain } = contextResult.nextFrameInfo;
305
329
 
306
- const responseCtx = await createSyncSagaContext({
330
+ const responseCtx = await this.createSyncSagaContext({
307
331
  sagaFrame: frame,
308
332
  localSpace: mySpace,
309
333
  payloadIbGibsDomain,
310
- // todo: we need to thoroughly go through the identity per each step after getting basic merging
311
- sessionKeystones: identity ? [identity] : undefined, // ??
334
+ sessionKeystone: identity,
335
+ sessionSecret: identitySecret,
336
+ metaspace,
312
337
  });
313
338
 
314
339
  const immediateValidationErrors = await validateContextAndSagaFrame({
@@ -325,12 +350,74 @@ export class SyncSagaCoordinator {
325
350
  }
326
351
  }
327
352
 
353
+ private async getSessionSecret({
354
+ sagaId,
355
+ identitySecret,
356
+ }: {
357
+ sagaId: string,
358
+ identitySecret: string,
359
+ }): Promise<string> {
360
+ const lc = `${this.lc}[${this.getSessionSecret.name}]`;
361
+ try {
362
+ if (logalot) { console.log(`${lc} starting... (I: 0de03f8dcd3e32f1fca244e8f2a8a826)`); }
363
+
364
+ // Derive session-specific secret using KDF
365
+ const sessionSecret = await deriveKey({
366
+ masterSecret: identitySecret,
367
+ kdfOpts: {
368
+ strategy: KdfStrategy.recursive_salt_wrap,
369
+ salt: sagaId,
370
+ rounds: 10000,
371
+ algorithm: 'SHA-256'
372
+ }
373
+ });
374
+
375
+ return sessionSecret;
376
+ } catch (error) {
377
+ console.error(`${lc} ${extractErrorMsg(error)}`);
378
+ throw error;
379
+ } finally {
380
+ if (logalot) { console.log(`${lc} complete.`); }
381
+ }
382
+ }
383
+
384
+ private async getInitialWeakDelegateSessionSecret({
385
+ sagaId,
386
+ }: {
387
+ sagaId: string,
388
+ }): Promise<string> {
389
+ const lc = `${this.lc}[${this.getInitialWeakDelegateSessionSecret.name}]`;
390
+ try {
391
+ if (logalot) { console.log(`${lc} starting... (I: 872ab81a78827b9f2822b78459203226)`); }
392
+
393
+ // Create delegate pool bootstrap secret (publicly derivable)
394
+ const initialDelegateSecret = await deriveKey({
395
+ masterSecret: DEFAULT_SESSION_IDENTITY_INITIAL_DELEGATE_SECRET, // Weak, publicly derivable secret
396
+ kdfOpts: {
397
+ strategy: KdfStrategy.recursive_salt_wrap,
398
+ salt: sagaId,
399
+ rounds: 1, // Minimal rounds - this is meant to be weak
400
+ algorithm: 'SHA-256'
401
+ }
402
+ });
403
+
404
+ return initialDelegateSecret;
405
+ } catch (error) {
406
+ console.error(`${lc} ${extractErrorMsg(error)}`);
407
+ throw error;
408
+ } finally {
409
+ if (logalot) { console.log(`${lc} complete.`); }
410
+ }
411
+ }
412
+
328
413
  private async getSessionIdentity({
329
414
  sagaId,
415
+ identitySecret,
330
416
  metaspace,
331
417
  localSpace,
332
418
  }: {
333
419
  sagaId: string,
420
+ identitySecret: string,
334
421
  metaspace: MetaspaceService,
335
422
  localSpace: IbGibSpaceAny,
336
423
  }): Promise<KeystoneIbGib_V1> {
@@ -338,20 +425,59 @@ export class SyncSagaCoordinator {
338
425
  try {
339
426
  if (logalot) { console.log(`${lc} starting... (I: 428392a4ee636b7bd8f7d5d89a87e826)`); }
340
427
 
341
- const config: any = {
342
- id: 'default',
428
+ if (!identitySecret) { throw new Error(`(UNEXPECTED) identitySecret falsy? This is expected to be truthy by this point. (E: 8ce053fe59825a6678713128953b9d26)`); }
429
+
430
+ const sessionSecret = await this.getSessionSecret({ sagaId, identitySecret });
431
+
432
+ const init = await this.getInitialWeakDelegateSessionSecret({ sagaId });
433
+
434
+
435
+ // Create TWO pool configs: Primary (strong) + Delegate (weak bootstrap)
436
+ const primaryPoolConfig: KeystonePoolConfig_HashV1 = {
437
+ allowedVerbs: [KEYSTONE_VERB_MANAGE],
438
+ id: 'primary',
439
+ type: 'hash-reveal-v1',
440
+ salt: sagaId,
441
+ behavior: {
442
+ size: 100, // Large pool for many signatures
443
+ replenish: 'top-up',
444
+ selectSequentially: 2,
445
+ selectRandomly: 2,
446
+ targetBindingChars: 10
447
+ },
448
+ algo: 'SHA-256',
449
+ rounds: 1
450
+ };
451
+
452
+ const delegatePoolConfig: any = {
453
+ id: 'delegate',
343
454
  type: 'hash-reveal-v1',
344
455
  salt: sagaId,
345
- behavior: { size: 10, replenish: 'top-up', selectSequentially: 1, selectRandomly: 1, targetBindingChars: 0 },
346
- algo: 'SHA-256', rounds: 1
456
+ behavior: {
457
+ size: 10, // Small pool - only for initial handshake
458
+ replenish: 'top-up',
459
+ selectSequentially: 1,
460
+ selectRandomly: 1,
461
+ targetBindingChars: 0
462
+ },
463
+ algo: 'SHA-256',
464
+ rounds: 1
347
465
  };
348
- const sessionIdentity = await this.keystone.genesis({
349
- masterSecret: sagaId,
350
- configs: [config],
466
+
467
+ // Generate keystone with DUAL pools. We have to first genesis with
468
+ // one and then add the other, because we have two distinct
469
+ // secrets.
470
+ const sessionIdentity = await this.keystoneSvc.genesis({
471
+ masterSecret: sessionSecret,
472
+ configs: [primaryPoolConfig],
351
473
  metaspace,
352
- space: localSpace // ✅ FIXED: Use durable space, not temp space
474
+ space: localSpace
353
475
  });
354
476
 
477
+ // TODO: Store delegate pool separate challenges derived from bootstrap secret
478
+ // This allows receiver to verify/evolve the delegate pool
479
+ // For now, the keystone genesis with multiple configs handles this
480
+
355
481
  return sessionIdentity;
356
482
  } catch (error) {
357
483
  console.error(`${lc} ${extractErrorMsg(error)}`);
@@ -381,6 +507,7 @@ export class SyncSagaCoordinator {
381
507
  initDomainGraph,
382
508
  peer,
383
509
  sessionIdentity,
510
+ identitySecret,
384
511
  updates$,
385
512
  localSpace,
386
513
  tempSpace,
@@ -396,6 +523,10 @@ export class SyncSagaCoordinator {
396
523
  initDomainGraph: FlatIbGibGraph,
397
524
  peer: SyncPeerWitness,
398
525
  sessionIdentity?: KeystoneIbGib_V1,
526
+ /**
527
+ * if {@link sessionIdentity} provided, this must also be truthy
528
+ */
529
+ identitySecret?: string,
399
530
  updates$: SubjectWitness<SyncSagaContextIbGib_V1>,
400
531
  metaspace: MetaspaceService
401
532
  localSpace: IbGibSpaceAny,
@@ -444,9 +575,10 @@ export class SyncSagaCoordinator {
444
575
  // #endregion set up peer observable for any domainPayloadsMap
445
576
 
446
577
  // ...create/compose the Request Context itself...
447
- const requestCtx = await createSyncSagaContext({
578
+ const requestCtx = await this.createSyncSagaContext({
448
579
  sagaFrame: currentFrame,
449
- sessionKeystones: sessionIdentity ? [sessionIdentity] : undefined,
580
+ sessionKeystone: sessionIdentity,
581
+ sessionSecret: identitySecret,
450
582
  /**
451
583
  * init frame: empty
452
584
  * ack frame: possible push offers
@@ -455,6 +587,7 @@ export class SyncSagaCoordinator {
455
587
  */
456
588
  payloadIbGibsDomain: nextDomainIbGibs,
457
589
  localSpace,
590
+ metaspace,
458
591
  });
459
592
 
460
593
  // #region Log what we're sending
@@ -582,6 +715,146 @@ export class SyncSagaCoordinator {
582
715
  }
583
716
  }
584
717
 
718
+
719
+ /**
720
+ * Creates new SyncSagaContext stone. Puts/registers in {@link localSpace}
721
+ * immediately after creation.
722
+ *
723
+ * @returns The context ibGib.
724
+ *
725
+ * ## notes
726
+ *
727
+ * the other ibgibs that are related to this context stone should already be
728
+ * put/registered in {@link localSpace}.
729
+ */
730
+ private async createSyncSagaContext({
731
+ sagaFrame,
732
+ sessionKeystone,
733
+ sessionSecret,
734
+ payloadIbGibsDomain,
735
+ metaspace,
736
+ localSpace,
737
+ }: {
738
+ /**
739
+ * The main saga frame (Init, Ack, etc.).
740
+ */
741
+ sagaFrame: SyncIbGib_V1;
742
+ /**
743
+ * Session identity keystone.
744
+ */
745
+ sessionKeystone: KeystoneIbGib_V1 | undefined;
746
+ /**
747
+ * If using session ({@link sessionKeystone} is truthy), this must be
748
+ * provided in order to sign it.
749
+ */
750
+ sessionSecret: string | undefined;
751
+ /**
752
+ * Domain payload ibgibs when the sync saga frame includes actual domain
753
+ * payloads to send, e.g., in a Delta frame.
754
+ */
755
+ payloadIbGibsDomain?: IbGib_V1[];
756
+ /**
757
+ * reference to the current metaspace
758
+ */
759
+ metaspace: MetaspaceService,
760
+ /**
761
+ * we persist the context in the local/sender space (relative to our
762
+ * execution POV) right when we create it.
763
+ */
764
+ localSpace: IbGibSpaceAny;
765
+ }): Promise<SyncSagaContextIbGib_V1> {
766
+ const lc = `[${this.createSyncSagaContext.name}]`;
767
+ try {
768
+ if (logalot) { console.log(`${lc} starting... (I: 6b87bee313e811d1d2fc90e87fbec826)`); }
769
+
770
+ // #region sanity/validation
771
+ if (!sagaFrame.data) { throw new Error(`(UNEXPECTED) sagaFrame.data falsy? (E: 04c49b4cccba6842a8b52e4c6f570726)`); }
772
+ if (!sagaFrame.data.n && sagaFrame.data.n !== 0) { throw new Error(`(UNEXPECTED) sagaFrame.data.n falsy and not 0? (E: 45b508da64a8b28428b11765d684b826)`); }
773
+ if (sessionKeystone && !sessionSecret) { throw new Error(`(UNEXPECTED) sessionKeystone truthy but sessionSecret falsy? (E: 705ecc25038b12df0e94c90c5561e426)`); }
774
+ // #endregion sanity/validation
775
+
776
+ const date = new Date();
777
+ const timestamp = getTimestamp(date);
778
+ const timestampMs = date.getMilliseconds();
779
+
780
+ const data: SyncSagaContextData_V1 = {
781
+ timestamp,
782
+ timestampMs,
783
+ sagaN: sagaFrame.data.n,
784
+ };
785
+
786
+ // Domain Payloads
787
+ const payloadAddrsDomain = payloadIbGibsDomain ?
788
+ payloadIbGibsDomain?.map(x => getIbGibAddr({ ibGib: x })) :
789
+ undefined;
790
+ if (payloadAddrsDomain && payloadAddrsDomain.length > 0) {
791
+ data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] = payloadAddrsDomain;
792
+ }
793
+
794
+ // rel8ns should always have saga frame, sometimes have keystone
795
+ const rel8ns: SyncSagaContextRel8ns_V1 = {
796
+ sagaFrame: [getIbGibAddr({ ibGib: sagaFrame })],
797
+ };
798
+ if (sessionKeystone) {
799
+ const keystoneErrors = await validateKeystoneGraph({
800
+ keystoneIbGib: sessionKeystone,
801
+ space: localSpace
802
+ });
803
+ if (keystoneErrors.length > 0) {
804
+ throw new Error(`invalid sessionKeystone. errors: ${keystoneErrors} (E: 3881b8caf2d803767a331e1141e84826)`);
805
+ }
806
+ // this addr is BEFORE we sign. So each context ibgib itself will
807
+ // point to the frame of the keystone just before that keystone
808
+ // signs with this context as its target.
809
+ rel8ns.sessionKeystone = [getIbGibAddr({ ibGib: sessionKeystone })];
810
+ }
811
+
812
+ // Generate standard ib
813
+ const ib = await getSyncSagaContextIb({ data });
814
+
815
+ const contextIbGib = await Factory_V1.stone<SyncSagaContextData_V1, SyncSagaContextRel8ns_V1>({
816
+ parentPrimitiveIb: SYNC_SAGA_CONTEXT_ATOM,
817
+ ib,
818
+ data,
819
+ rel8ns,
820
+ }) as SyncSagaContextIbGib_V1;
821
+
822
+ // put/register immediately. Note that contextIbGib at this point is
823
+ // pure DTO, i.e., only ib, gib, data, rel8ns props.
824
+ await putInSpace({ ibGib: contextIbGib, space: localSpace, });
825
+ await registerNewIbGib({
826
+ ibGib: contextIbGib,
827
+ space: localSpace,
828
+ fnBroadcast: undefined,
829
+ });
830
+
831
+ // Attach actual ibgibs for transport (not pure DTO now)
832
+ contextIbGib.sagaFrame = sagaFrame;
833
+ if (payloadIbGibsDomain && payloadIbGibsDomain.length > 0) {
834
+ contextIbGib.payloadIbGibsDomain = payloadIbGibsDomain;
835
+ }
836
+
837
+ if (sessionKeystone) {
838
+ if (!sessionSecret) { throw new Error(`(UNEXPECTED) sessionKeystone truthy but sessionSecret falsy? we should have thrown before now (E: a2b0517a37b559543968b888f2067826)`); }
839
+ const contextAddr = getIbGibAddr({ ibGib: contextIbGib });
840
+ contextIbGib.signedSessionKeystone = await this.keystoneSvc.sign({
841
+ latestKeystone: sessionKeystone,
842
+ claim: { target: contextAddr, }, // verb?
843
+ space: localSpace,
844
+ masterSecret: sessionSecret,
845
+ metaspace,
846
+ });
847
+ }
848
+
849
+ return contextIbGib;
850
+ } catch (error) {
851
+ console.error(`${lc} ${extractErrorMsg(error)}`);
852
+ throw error;
853
+ } finally {
854
+ if (logalot) { console.log(`${lc} complete.`); }
855
+ }
856
+ }
857
+
585
858
  /**
586
859
  * Helper to get Knowledge Map for specific domain ibGibs or TJPs.
587
860
  * Useful for testing and external validation.
@@ -755,7 +1028,7 @@ export class SyncSagaCoordinator {
755
1028
  const sagaFrame = await this.evolveSyncSagaIbGib({
756
1029
  msgStones: [initStone],
757
1030
  conflictStrategy,
758
- sessionIdentity: sessionIdentity,
1031
+ sessionIdentity,
759
1032
  metaspace,
760
1033
  localSpace,
761
1034
  });
@@ -873,7 +1146,6 @@ export class SyncSagaCoordinator {
873
1146
  mySpace,
874
1147
  myTempSpace,
875
1148
  identity,
876
- identitySecret,
877
1149
  metaspace,
878
1150
  }: {
879
1151
  sagaContext: SyncSagaContextIbGib_V1,
@@ -893,7 +1165,6 @@ export class SyncSagaCoordinator {
893
1165
  */
894
1166
  myTempSpace: IbGibSpaceAny,
895
1167
  identity?: KeystoneIbGib_V1,
896
- identitySecret?: string,
897
1168
  metaspace: MetaspaceService,
898
1169
  }): Promise<HandleSagaResponseContextResult> {
899
1170
  const lc = `${this.lc}[${this.handleResponseSagaContext.name}]`;
@@ -916,7 +1187,7 @@ export class SyncSagaCoordinator {
916
1187
  sagaIbGib,
917
1188
  messageData: messageData as SyncSagaMessageInitData_V1,
918
1189
  metaspace, mySpace, myTempSpace,
919
- identity, identitySecret
1190
+ identity,
920
1191
  });
921
1192
  break;
922
1193
 
@@ -987,7 +1258,6 @@ export class SyncSagaCoordinator {
987
1258
  myTempSpace,
988
1259
  metaspace,
989
1260
  identity,
990
- // identitySecret,
991
1261
  }: {
992
1262
  sagaIbGib: SyncIbGib_V1,
993
1263
  messageData: SyncSagaMessageInitData_V1,
@@ -1004,7 +1274,6 @@ export class SyncSagaCoordinator {
1004
1274
  myTempSpace: IbGibSpaceAny,
1005
1275
  metaspace: MetaspaceService,
1006
1276
  identity?: KeystoneIbGib_V1,
1007
- identitySecret?: string,
1008
1277
  }): Promise<NextSagaFrameInfo> {
1009
1278
  const lc = `${this.lc}[${this.handleInitFrame.name}]`;
1010
1279
  try {
@@ -2530,10 +2799,9 @@ export class SyncSagaCoordinator {
2530
2799
  localSpace: IbGibSpaceAny,
2531
2800
  metaspace: MetaspaceService,
2532
2801
  /**
2533
- * does NOT evolve the keystone. this should be done by the caller.
2534
- * (NOT IMPLEMENTED YET ANYWAY)
2802
+ * does NOT evolve the keystone.
2535
2803
  */
2536
- sessionIdentity?: KeystoneIbGib_V1,
2804
+ sessionIdentity: KeystoneIbGib_V1 | undefined,
2537
2805
  }): Promise<SyncIbGib_V1> {
2538
2806
  const lc = `${this.lc}[${this.evolveSyncSagaIbGib.name}]`;
2539
2807
  try {
@@ -2617,6 +2885,11 @@ export class SyncSagaCoordinator {
2617
2885
  const rel8ns: SyncRel8ns_V1 = { [SYNC_MSG_REL8N_NAME]: stoneAddrs, };
2618
2886
  if (identityAddr) { rel8ns.identity = [identityAddr]; }
2619
2887
 
2888
+ // Attach session keystone to saga frame via hard rel8n
2889
+ if (sessionIdentity) {
2890
+ rel8ns.sessionKeystones = [getIbGibAddr({ ibGib: sessionIdentity })];
2891
+ }
2892
+
2620
2893
  const resNew = await createTimeline({
2621
2894
  space: localSpace,
2622
2895
  metaspace,
@@ -55,7 +55,7 @@ export interface SyncSagaMessageIbGib_V1 extends IbGib_V1<SyncSagaMessageData_V1
55
55
  /**
56
56
  * The "Hello" of the sync protocol.
57
57
  *
58
- * Exchanges knowledge maps (what I have) and identity (who I am).
58
+ * Exchanges knowledge maps (what I have).
59
59
  */
60
60
  export interface SyncSagaMessageInitData_V1 extends SyncSagaMessageData_V1 {
61
61
  stage: typeof SyncStage.init;
@@ -68,12 +68,6 @@ export interface SyncSagaMessageInitData_V1 extends SyncSagaMessageData_V1 {
68
68
  */
69
69
  knowledgeMap: { [tjp: string]: IbGibAddr };
70
70
 
71
- /**
72
- * The Keystone Identity of the sender.
73
- * Required for the first handshake or if the receiver doesn't know the sender.
74
- */
75
- identity?: KeystoneIbGib_V1;
76
-
77
71
  /**
78
72
  * What the sender wants to do.
79
73
  * e.g. "push" (I have data for you) or "pull" (Give me data).
@@ -195,6 +195,18 @@ export interface SyncRel8ns_V1 extends IbGibRel8ns_V1 {
195
195
  */
196
196
  identity?: string[];
197
197
 
198
+ /**
199
+ * Session keystones used for signing saga frames.
200
+ *
201
+ * Array contains addresses of keystone evolution chain:
202
+ * - Index 0: Genesis keystone (dual-pool architecture)
203
+ * - Index N: Latest evolved keystone after signing operations
204
+ *
205
+ * Each sync endpoint retrieves the session keystone from this rel8n
206
+ * rather than searching spaces. Keystones are stored in durable spaces.
207
+ */
208
+ sessionKeystones?: IbGibAddr[];
209
+
198
210
  /**
199
211
  * The message stone that contains the information about the particular
200
212
  * stage of the sync process we are in.