@ibgib/core-gib 0.1.27 → 0.1.28

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.
@@ -31,8 +31,9 @@ import {
31
31
  SYNC_CONFLICT_STRATEGY_VALID_VALUES, HandleSagaResponseContextResult,
32
32
  SyncExecutionContext,
33
33
  SYNC_EXECUTION_CONTEXT_VALID_VALUES,
34
+ SyncSagaFrameDependencyGraph,
34
35
  } from "./sync-types.mjs";
35
- import { getExecutionContext, getSyncIb, getTempSpaceName, isPastFrame } from "./sync-helpers.mjs";
36
+ import { getExecutionContext, getFullSyncSagaHistory, getSyncIb, getTempSpaceName, isPastFrame, putInSpace_dnasThenNonDnas, validateFullSyncSagaHistory } from "./sync-helpers.mjs";
36
37
  import { getDeltaDependencyGraph, getDependencyGraph, toFlatGraph } from "../common/other/graph-helper.mjs";
37
38
  import {
38
39
  SyncSagaMessageData_V1, SyncSagaMessageInitData_V1,
@@ -76,10 +77,10 @@ const lcControlDomain = '[ControlDomain]';
76
77
  * to a specific Saga session, not fixed node identities.
77
78
  */
78
79
  export class SyncSagaCoordinator {
79
- protected lc: string = `[${SyncSagaCoordinator.name}]`;
80
+ private lc: string = `[${SyncSagaCoordinator.name}]`;
80
81
 
81
82
  constructor(
82
- protected keystone: KeystoneService_V1
83
+ private keystone: KeystoneService_V1
83
84
  ) {
84
85
 
85
86
  }
@@ -240,7 +241,11 @@ export class SyncSagaCoordinator {
240
241
 
241
242
  if (!contextResult) {
242
243
  if (logalot) { console.log(`${lc} Handler returned null (Saga End). (I: 43da8bb6c846b1fe7766332643be0e26)`); }
243
- return null;
244
+ // does this ever hit now?
245
+ return null; /* <<<< returns early */
246
+ } else if (contextResult.nextFrameInfo?.sagaComplete) {
247
+ // this is the current proper workflow I believe as of 01/22/2026
248
+ return null; /* <<<< returns early */
244
249
  }
245
250
 
246
251
  // #region error conditions throw
@@ -248,8 +253,6 @@ export class SyncSagaCoordinator {
248
253
  throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: 7b41a183cf3cb58a5859c803800cf826)`);
249
254
  } else if (!contextResult.nextFrameInfo) {
250
255
  throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: 5740542f5eb8ccb41dfec188d87c1e26)`);
251
- } else if (contextResult.nextFrameInfo?.responseWasNull) {
252
- throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: ae06748d8c0c5e70c92322c8fb0cb426)`);
253
256
  }
254
257
  // #endregion error conditions throw
255
258
 
@@ -278,7 +281,7 @@ export class SyncSagaCoordinator {
278
281
  }
279
282
  }
280
283
 
281
- protected async getSessionIdentity({
284
+ private async getSessionIdentity({
282
285
  sagaId,
283
286
  metaspace,
284
287
  tempSpace,
@@ -329,7 +332,7 @@ export class SyncSagaCoordinator {
329
332
  * the NEXT request context.
330
333
  * When the Peer responds with data (in the response context), it is resolved and put into `tempSpace`.
331
334
  */
332
- protected async executeSagaLoop({
335
+ private async executeSagaLoop({
333
336
  initFrame,
334
337
  initDomainGraph,
335
338
  peer,
@@ -511,8 +514,8 @@ export class SyncSagaCoordinator {
511
514
  throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: c948e81d513b2a0eb8b8afa878edc626)`);
512
515
  } else if (!contextResult.nextFrameInfo) {
513
516
  throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: c287a82e823e662a77923278e2418826)`);
514
- } else if (contextResult.nextFrameInfo?.responseWasNull) {
515
- throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.responseWasNull? logic flow should not have gotten here. (E: 104a32381db816b7183435e805b3d626)`);
517
+ } else if (contextResult.nextFrameInfo?.sagaComplete) {
518
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo.sagaComplete? logic flow should not have gotten here. (E: 104a32381db816b7183435e805b3d626)`);
516
519
  }
517
520
  // #endregion error conditions throw
518
521
 
@@ -610,7 +613,7 @@ export class SyncSagaCoordinator {
610
613
  }
611
614
  }
612
615
 
613
- protected async analyzeDomainIbGibs({
616
+ private async analyzeDomainIbGibs({
614
617
  domainIbGibs,
615
618
  space,
616
619
  }: {
@@ -656,7 +659,7 @@ export class SyncSagaCoordinator {
656
659
  * Generates the first frame containing the Knowledge Vector of the Local Space.
657
660
  * This is sent to the Receiver to begin Gap Analysis.
658
661
  */
659
- protected async createInitFrame({
662
+ private async createInitFrame({
660
663
  sagaId,
661
664
  sessionIdentity,
662
665
  domainIbGibs,
@@ -739,7 +742,7 @@ export class SyncSagaCoordinator {
739
742
  *
740
743
  * @returns when all {@link expectedAddrs} are done being transmitted.
741
744
  */
742
- protected async pollForDomainPayloads({
745
+ private async pollForDomainPayloads({
743
746
  expectedAddrs,
744
747
  pollIntervalMs,
745
748
  domainPayloadsMap,
@@ -824,7 +827,7 @@ export class SyncSagaCoordinator {
824
827
  *
825
828
  * This is a one-off on the receiver.
826
829
  */
827
- public async handleResponseSagaContext({
830
+ private async handleResponseSagaContext({
828
831
  sagaContext,
829
832
  initDomainGraph,
830
833
  mySpace,
@@ -940,7 +943,7 @@ export class SyncSagaCoordinator {
940
943
  * 3. Identifies what Receiver needs (`deltaRequestAddrInfos`).
941
944
  * 4. Returns an `Ack` frame containing these lists.
942
945
  */
943
- protected async handleInitFrame({
946
+ private async handleInitFrame({
944
947
  sagaIbGib,
945
948
  messageData,
946
949
  mySpace,
@@ -1289,7 +1292,7 @@ export class SyncSagaCoordinator {
1289
1292
  *
1290
1293
  * Returns a `Delta` frame.
1291
1294
  */
1292
- protected async handleAckFrame({
1295
+ private async handleAckFrame({
1293
1296
  sagaContext,
1294
1297
  sagaIbGib,
1295
1298
  initDomainGraph,
@@ -1565,70 +1568,6 @@ export class SyncSagaCoordinator {
1565
1568
  }
1566
1569
  }
1567
1570
 
1568
- private async getPayloadsForRequestedInfos({
1569
- deltaRequestAddrInfos,
1570
- mySpace,
1571
- }: {
1572
- deltaRequestAddrInfos: SyncSagaRequestAddrInfo[];
1573
- mySpace: IbGibSpaceAny;
1574
- }): Promise<IbGib_V1[]> {
1575
- const lc = `${this.lc}[${this.getPayloadsForRequestedInfos.name}]`;
1576
- try {
1577
- if (logalot) { console.log(`${lc} starting... (I: 4fe13d0d80050f20a8b74ba80cee5826)`); }
1578
- /**
1579
- * graph of ibgibs we will send to the receiver. addr-based, so will
1580
- * already be unique (no need to call `unique` on the domains
1581
- * ibgibs)
1582
- */
1583
- const outgoingPayloadIbGibsDomainGraph: FlatIbGibGraph = {};
1584
- for (const { addr, latestAddrAlreadyHave } of deltaRequestAddrInfos) {
1585
- let deltaDepGraph: FlatIbGibGraph;
1586
- if (latestAddrAlreadyHave) {
1587
- // already has some, so only get the delta
1588
- // remember: if we didn't have the other's latest addr, then
1589
- // this would be in the conflicts not requested addrs
1590
- deltaDepGraph = await getDeltaDependencyGraph({
1591
- ibGibAddr: addr,
1592
- latestCommonFrameAddr: latestAddrAlreadyHave,
1593
- space: mySpace,
1594
- live: true,
1595
- });
1596
- } else {
1597
- // doesn't have anything, so get the entire dependency graph
1598
- // INEFFICIENT: we've already gotten all of the domain
1599
- // dependencies in initDomainGraph, but getDependencyGraph
1600
- // only works against a space so we are going to rerun this.
1601
- // an optimization would be to adapt/create a new
1602
- // getDependencyGraph to work against an existing flat ibgib
1603
- // map.
1604
- deltaDepGraph = await getDependencyGraph({
1605
- ibGibAddr: addr,
1606
- space: mySpace,
1607
- live: true,
1608
- });
1609
- }
1610
-
1611
- const depGraphSize = Object.keys(deltaDepGraph).length;
1612
- if (depGraphSize === 0) {
1613
- throw new Error(`(UNEXPECTED) couldn't get requested addrs in mySpace (${mySpace.ib})? How did the receiver know to ask for these addrs if they weren't in our original graph? (E: 281eaebcdf77dd73e8245b2872100826)`);
1614
- } else {
1615
- // we have dependencies!
1616
- Object.values(deltaDepGraph).forEach(x => {
1617
- outgoingPayloadIbGibsDomainGraph[getIbGibAddr({ ibGib: x })] = x;
1618
- });
1619
- }
1620
- }
1621
-
1622
- const result = Object.values(outgoingPayloadIbGibsDomainGraph);
1623
- return result;
1624
- } catch (error) {
1625
- console.error(`${lc} ${extractErrorMsg(error)}`);
1626
- throw error;
1627
- } finally {
1628
- if (logalot) { console.log(`${lc} complete.`); }
1629
- }
1630
- }
1631
-
1632
1571
  /**
1633
1572
  * Handles the `Delta` frame.
1634
1573
  *
@@ -1639,7 +1578,7 @@ export class SyncSagaCoordinator {
1639
1578
  * 2. **Fulfillment**: Checks `requests`. If Peer requested data, gathers it and prepares `outgoingPayload`.
1640
1579
  * 3. **Completion**: If no more requests, transitions to `Commit`.
1641
1580
  */
1642
- protected async handleDeltaFrame({
1581
+ private async handleDeltaFrame({
1643
1582
  sagaContext,
1644
1583
  sagaIbGib,
1645
1584
  srcGraph,
@@ -1701,27 +1640,32 @@ export class SyncSagaCoordinator {
1701
1640
  // V1 timelines carry full history in `past`.
1702
1641
  const pastAddrs = sagaIbGib.rel8ns?.past || [];
1703
1642
  console.log(`${lc} [TEST DEBUG] pastAddrs count: ${pastAddrs.length}`);
1704
- let ackData: SyncSagaMessageAckData_V1 | undefined;
1705
-
1706
- if (pastAddrs.length > 0) {
1707
- // Batch fetch all past frames
1708
- const resPast = await getFromSpace({ addrs: pastAddrs, space: myTempSpace });
1709
- if (resPast.success && resPast.ibGibs) {
1710
- // Iterate backwards (most recent first) to find the latest Ack
1711
- for (let i = resPast.ibGibs.length - 1; i >= 0; i--) {
1712
- const pastFrame = resPast.ibGibs[i];
1713
- const messageStone = await getSyncSagaMessageFromFrame({
1714
- frameIbGib: pastFrame,
1715
- space: myTempSpace
1716
- });
1717
- if (messageStone?.data?.stage === SyncStage.ack) {
1718
- ackData = messageStone.data as SyncSagaMessageAckData_V1;
1719
- console.log(`${lc} [TEST DEBUG] Found Ack Frame. Conflicts: ${ackData.conflicts?.length || 0}`);
1720
- break;
1721
- }
1722
- }
1723
- }
1643
+
1644
+ const sagaHistory = await getFullSyncSagaHistory({
1645
+ sagaIbGib,
1646
+ space: mySpace,
1647
+ });
1648
+ // #region validate/sanity sagaHistory
1649
+ if (sagaHistory.length === 0) { throw new Error(`(UNEXPECTED) sagaHistory.length is 0? We're in handleDeltaFrame. So we should have at least init and ack. (E: 815735c9cf756b275719bf434f180826)`); }
1650
+ if (sagaHistory.length === 1) { throw new Error(`(UNEXPECTED) sagaHistory.length is 1? We're in handleDeltaFrame. So we should have at least init and ack. (E: 0e4ef8e3ed088e83b2cdcd18ecea9826)`); }
1651
+ // #endregion validate/sanity sagaHistory
1652
+
1653
+ const ackGraphs = sagaHistory.filter(x =>
1654
+ x.msgStones.at(0)!.data!.stage === SyncStage.ack
1655
+ );
1656
+ // #region validate/sanity ackGraphs
1657
+ if (ackGraphs.length > 1) {
1658
+ throw new Error(`(UNEXPECTED) more than one ack stage found in sagaHistory? we're expecting exactly one. (E: 7150983bb3a841caf8acd60826ba4f26)`);
1659
+ } else if (ackGraphs.length === 0) {
1660
+ throw new Error(`(UNEXPECTED) couldn't find ack stage in sagaHistory? (E: 7d2da859196b86de28e7c8183af1e826)`);
1661
+ }
1662
+ if (ackGraphs[0].msgStones.length !== 1) {
1663
+ throw new Error(`(UNEXPECTED) ackGraph has more than one msg stone? only one expected right now. (E: f07d388abff8d26f98cbf6e8d3932826)`);
1724
1664
  }
1665
+ // #endregion validate/sanity ackGraphs
1666
+
1667
+ const ackData = ackGraphs[0].msgStones[0].data as SyncSagaMessageAckData_V1;
1668
+ if (!ackData) { throw new Error(`(UNEXPECTED) ackData falsy? (E: f9af88427f66a8db1ba868b810e8b826)`); }
1725
1669
 
1726
1670
  if (ackData && ackData.conflicts) {
1727
1671
  const optimisticConflicts = ackData.conflicts.filter(c => !c.terminal);
@@ -1785,20 +1729,8 @@ export class SyncSagaCoordinator {
1785
1729
  stage: SyncStage.delta,
1786
1730
  payloadAddrs: outgoingPayload.map(p => getIbGibAddr({ ibGib: p })),
1787
1731
  requests: hasMyRequests ? myRequests : undefined,
1788
- proposeCommit: !hasMyRequests // If we are sending data but have no requests, we VALIDATE PROPOSAL?
1789
- // Wait. If we send data, we are NOT committing yet.
1790
- // We are sending data. The OTHER side must ingest it.
1791
- // So proposeCommit = true?
1792
- // "Here is the data. I'm done. If you are good, let's commit."
1793
- // Yes.
1794
1732
  };
1795
1733
 
1796
- // BUT if `peerProposesCommit` was true, and we are sending data, we are effectively rejecting/delaying it.
1797
- // We just send the Delta. Peer receives it, ingests, sees ProposeCommit=True (from us), and then Commits.
1798
-
1799
- // So yes, proposeCommit = true.
1800
- responseDeltaData.proposeCommit = true;
1801
-
1802
1734
  const deltaStone = await this.createSyncMsgStone({
1803
1735
  data: responseDeltaData,
1804
1736
  localSpace: mySpace,
@@ -1813,42 +1745,45 @@ export class SyncSagaCoordinator {
1813
1745
  metaspace,
1814
1746
  });
1815
1747
 
1816
- // Build control payloads: frame + its dependencies (msg stone, identity)
1817
- const payloadIbGibsControl: IbGib_V1[] = [deltaFrame, deltaStone];
1818
- if (identity) { payloadIbGibsControl.push(identity); }
1819
-
1820
- // return { frame: deltaFrame, payloadIbGibsControl, payloadIbGibsDomain: outgoingPayload };
1821
1748
  return { frame: deltaFrame, payloadIbGibsDomain: outgoingPayload };
1822
- // throw new Error(`not implemented (E: 2b38a8afb6d84efcee5ab51673387826)`);
1823
-
1824
1749
  } else {
1825
1750
  // We have nothing to send.
1826
1751
 
1827
1752
  if (peerProposesCommit) {
1828
1753
  // Peer is done. We are done. -> Commit.
1829
- const commitData: SyncSagaMessageCommitData_V1 = {
1830
- sagaId: deltaData.sagaId,
1831
- stage: SyncStage.commit,
1832
- success: true,
1833
- };
1834
-
1835
- const commitStone = await this.createSyncMsgStone({
1836
- data: commitData,
1837
- localSpace: mySpace,
1838
- metaspace
1754
+ // one last validate entire history?
1755
+ const history = await getFullSyncSagaHistory({
1756
+ sagaIbGib,
1757
+ space: mySpace,
1839
1758
  });
1759
+ const validationErrors = await validateFullSyncSagaHistory({
1760
+ history,
1761
+ });
1762
+ if (validationErrors.length > 0) {
1763
+ const errorCommitFrame = await this.createCommitFrame({
1764
+ sagaIbGib,
1765
+ errors: validationErrors,
1766
+ metaspace, mySpace,
1767
+ identity,
1768
+ });
1769
+ return { frame: errorCommitFrame, }; /* <<<< returns early */
1770
+ }
1840
1771
 
1841
- const commitFrame = await this.evolveSyncSagaIbGib({
1842
- prevSagaIbGib: sagaIbGib,
1843
- msgStones: [commitStone],
1844
- sessionIdentity: identity,
1772
+ await this.executeLocalCommit({
1773
+ sagaHistory: history,
1774
+ deltaFrame: sagaIbGib,
1775
+ localTempSpace: myTempSpace,
1845
1776
  localSpace: mySpace,
1846
- metaspace
1847
- });
1777
+ metaspace,
1778
+ })
1848
1779
 
1849
- // Build control payloads for commit
1850
- const commitCtrlPayloads: IbGib_V1[] = [commitFrame, commitStone];
1851
- if (identity) { commitCtrlPayloads.push(identity); }
1780
+ const commitFrame = await this.createCommitFrame({
1781
+ sagaIbGib,
1782
+ errors: undefined,
1783
+ metaspace,
1784
+ mySpace,
1785
+ identity,
1786
+ });
1852
1787
 
1853
1788
  return { frame: commitFrame, };
1854
1789
 
@@ -1924,9 +1859,190 @@ export class SyncSagaCoordinator {
1924
1859
  }
1925
1860
 
1926
1861
  }
1862
+ /**
1863
+ * should throw if fails
1864
+ */
1865
+ private async executeLocalCommit({
1866
+ deltaFrame,
1867
+ sagaHistory,
1868
+ metaspace, localSpace, localTempSpace,
1869
+ identity,
1870
+ }: {
1871
+ deltaFrame: SyncIbGib_V1;
1872
+ sagaHistory: SyncSagaFrameDependencyGraph[];
1873
+ metaspace: MetaspaceService;
1874
+ localSpace: IbGibSpaceAny;
1875
+ localTempSpace: IbGibSpaceAny;
1876
+ identity?: KeystoneIbGib_V1;
1877
+ }): Promise<void> {
1878
+ const lc = `${this.lc}[${this.executeLocalCommit.name}]`;
1879
+ try {
1880
+ if (logalot) { console.log(`${lc} starting... (I: 6734980446b86a63c1af6e2e206de826)`); }
1881
+
1882
+ // #region validate/sanity
1883
+ if (!deltaFrame.data) { throw new Error(`(UNEXPECTED) deltaFrame.data falsy? (E: a8be68d48668d93d992d793834823826)`); }
1884
+ // #endregion validate/sanity
1927
1885
 
1886
+ // * move all payload addrs from temp space to local space
1887
+ // * register each and every iteration of each timeline (including
1888
+ // stones, which are like their own sealed timeline of length 1)
1889
+
1890
+ const allPayloadAddrsDomainTransferred: IbGibAddr[] = [];
1891
+ const fnAddPayloadAddr = (addr: IbGibAddr) => {
1892
+ if (!allPayloadAddrsDomainTransferred.includes(addr)) {
1893
+ allPayloadAddrsDomainTransferred.push(addr);
1894
+ }
1895
+ }
1928
1896
 
1929
- protected async handleCommitFrame({
1897
+ sagaHistory.forEach(x => {
1898
+ if (!x.sagaIbGib.data) { throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: 34d7f8cdee14717ce828878d98f89826)`); }
1899
+ x.msgStones.forEach(msgStone => {
1900
+ if (!msgStone.data) { throw new Error(`(UNEXPECTED) msgStone.data falsy? (E: 21406101f91847514cc759c8a7382f26)`); }
1901
+ if (msgStone.data.stage === SyncStage.ack) {
1902
+ const ackData = msgStone.data as SyncSagaMessageAckData_V1;
1903
+ if (ackData.pushOfferInfos && ackData.pushOfferInfos.length > 0) {
1904
+ ackData.pushOfferInfos.forEach(info => {
1905
+ info.addrs.forEach(addr => fnAddPayloadAddr(addr));
1906
+ });
1907
+ }
1908
+ } else if (msgStone.data.stage === SyncStage.delta) {
1909
+ const deltaData = msgStone.data as SyncSagaMessageDeltaData_V1;
1910
+ if (deltaData.payloadAddrsDomain && deltaData.payloadAddrsDomain.length > 0) {
1911
+ deltaData.payloadAddrsDomain.forEach(addr => fnAddPayloadAddr(addr));
1912
+ }
1913
+ }
1914
+ })
1915
+ });
1916
+
1917
+ // at this point, we have a list of ALL payload addrs retrieved.
1918
+ let allPayloadIbGibsDomainTransferred: IbGib_V1[] = [];
1919
+ const resGetAllTransferred =
1920
+ await getFromSpace({ addrs: allPayloadAddrsDomainTransferred, space: localTempSpace });
1921
+ if (resGetAllTransferred.success && resGetAllTransferred.ibGibs && resGetAllTransferred.ibGibs.length === allPayloadAddrsDomainTransferred.length) {
1922
+ allPayloadIbGibsDomainTransferred = resGetAllTransferred.ibGibs.concat();
1923
+ } else {
1924
+ // errored out, gather info
1925
+ if (!resGetAllTransferred.rawResultIbGib) { throw new Error(`(UNEXPECTED) !resGetAllTransferred.rawResultIbGib falsy? (E: dc6cf8729668f4fbe8c024f887d97a26)`); }
1926
+ const { addrsNotFound, addrsErrored, errors } = resGetAllTransferred.rawResultIbGib.data as IbGibSpaceResultData;
1927
+ throw new Error(`(UNEXPECTED) we couldn't get all addrs transferred throughout the saga from the tempspace (${localTempSpace.ib})? addrsNotFound: ${addrsNotFound ?? []}. addrsErrored: ${addrsErrored ?? []}. errors: ${errors ?? []}(E: 222e341e634862b4d88ae7282584d826)`);
1928
+ }
1929
+
1930
+ // now we have all ibgibs transferred, first put them all the local space
1931
+
1932
+ const { payload_Dnas, payload_NonDnas } =
1933
+ await putInSpace_dnasThenNonDnas({
1934
+ ibGibs: allPayloadIbGibsDomainTransferred,
1935
+ space: localSpace
1936
+ });
1937
+
1938
+ const { mapWithTjp_NoDna, mapWithTjp_YesDna, mapWithoutTjps } =
1939
+ splitPerTjpAndOrDna({ ibGibs: payload_NonDnas, filterPrimitives: true });
1940
+
1941
+ // first register all non-tjp stones
1942
+ const nontjps = Object.values(mapWithoutTjps);
1943
+ for (const nontjp of nontjps) {
1944
+ await metaspace.registerNewIbGib({
1945
+ ibGib: nontjp,
1946
+ space: localSpace,
1947
+ });
1948
+ }
1949
+
1950
+ // next register each timeline in order...
1951
+
1952
+ // ...first the ones without dna...
1953
+ const timelinesByTjpAddr_NoDna =
1954
+ getTimelinesGroupedByTjp({ ibGibs: Object.values(mapWithTjp_NoDna) });
1955
+ const tjpAddrs_NoDna = Object.keys(timelinesByTjpAddr_NoDna);
1956
+ for (const tjpAddr_NoDna of tjpAddrs_NoDna) {
1957
+ const timelineIbGibs = timelinesByTjpAddr_NoDna[tjpAddr_NoDna];
1958
+ for (const ibGib of timelineIbGibs) {
1959
+ await metaspace.registerNewIbGib({
1960
+ ibGib,
1961
+ space: localSpace,
1962
+ });
1963
+ }
1964
+ }
1965
+
1966
+ // ...then the ones WITH dna.
1967
+ const timelinesByTjpAddr_YesDna =
1968
+ getTimelinesGroupedByTjp({ ibGibs: Object.values(mapWithTjp_YesDna) });
1969
+ const tjpAddrs_YesDna = Object.keys(timelinesByTjpAddr_YesDna);
1970
+ for (const tjpAddr_YesDna of tjpAddrs_YesDna) {
1971
+ const timelineIbGibs = timelinesByTjpAddr_YesDna[tjpAddr_YesDna];
1972
+ for (const ibGib of timelineIbGibs) {
1973
+ await metaspace.registerNewIbGib({
1974
+ ibGib,
1975
+ space: localSpace,
1976
+ });
1977
+ }
1978
+ }
1979
+
1980
+
1981
+ } catch (error) {
1982
+ console.error(`${lc} ${extractErrorMsg(error)}`);
1983
+ throw error;
1984
+ } finally {
1985
+ if (logalot) { console.log(`${lc} complete.`); }
1986
+ }
1987
+ }
1988
+
1989
+ private async createCommitFrame({
1990
+ sagaIbGib,
1991
+ errors,
1992
+ metaspace,
1993
+ mySpace,
1994
+ identity,
1995
+ }: {
1996
+ sagaIbGib: SyncIbGib_V1,
1997
+ /**
1998
+ * if truthy and non-empty, will create an errored commit frame (data
1999
+ * has `success: false`)
2000
+ */
2001
+ errors?: string[],
2002
+ metaspace: MetaspaceService,
2003
+ mySpace: IbGibSpaceAny,
2004
+ identity: KeystoneIbGib_V1 | undefined,
2005
+ }): Promise<SyncIbGib_V1> {
2006
+ const lc = `[${this.createCommitFrame.name}]`;
2007
+ try {
2008
+ if (logalot) { console.log(`${lc} starting... (I: 48fc57c023f80122135a284855757526)`); }
2009
+ const commitData: SyncSagaMessageCommitData_V1 = errors && errors.length > 0 ?
2010
+ {
2011
+ // errored
2012
+ sagaId: sagaIbGib.data!.uuid,
2013
+ stage: SyncStage.commit,
2014
+ success: false,
2015
+ errors,
2016
+ } :
2017
+ {
2018
+ // not errored
2019
+ sagaId: sagaIbGib.data!.uuid,
2020
+ stage: SyncStage.commit,
2021
+ success: true,
2022
+ };
2023
+
2024
+ const commitStone = await this.createSyncMsgStone({
2025
+ data: commitData,
2026
+ localSpace: mySpace,
2027
+ metaspace
2028
+ });
2029
+ const commitFrame = await this.evolveSyncSagaIbGib({
2030
+ prevSagaIbGib: sagaIbGib,
2031
+ msgStones: [commitStone],
2032
+ sessionIdentity: identity,
2033
+ localSpace: mySpace,
2034
+ metaspace,
2035
+ });
2036
+ return commitFrame;
2037
+ } catch (error) {
2038
+ console.error(`${lc} ${extractErrorMsg(error)}`);
2039
+ throw error;
2040
+ } finally {
2041
+ if (logalot) { console.log(`${lc} complete.`); }
2042
+ }
2043
+ }
2044
+
2045
+ private async handleCommitFrame({
1930
2046
  sagaIbGib,
1931
2047
  mySpace,
1932
2048
  myTempSpace,
@@ -1943,7 +2059,7 @@ export class SyncSagaCoordinator {
1943
2059
  try {
1944
2060
  if (logalot) { console.log(`${lc} starting... (I: e179573bdd881202f8ba3168da1c3826)`); }
1945
2061
 
1946
-
2062
+ let resNextSagaFrameInfo: NextSagaFrameInfo;
1947
2063
 
1948
2064
  // Sender Logic (Finalizing):
1949
2065
  // If we are here, we received a Commit frame from the Peer.
@@ -1951,15 +2067,52 @@ export class SyncSagaCoordinator {
1951
2067
  // We should now:
1952
2068
  // 1. Validate (implicitly done by receiving valid frame)
1953
2069
  // 2. Perform our own cleanup (Temp -> Dest, if applicable)
1954
- // 3. Return null to signal saga completion.
2070
+ // 3. Return saga completion.
1955
2071
 
1956
- // Note: Currently we don't have explicit cleanup logic implemented here yet (TODO).
2072
+ // one last validate entire history?
2073
+ const history = await getFullSyncSagaHistory({
2074
+ sagaIbGib,
2075
+ space: mySpace,
2076
+ });
2077
+ const validationErrors = await validateFullSyncSagaHistory({
2078
+ history,
2079
+ });
2080
+ if (validationErrors.length > 0) {
2081
+ const errorCommitFrame = await this.createCommitFrame({
2082
+ sagaIbGib,
2083
+ metaspace,
2084
+ mySpace,
2085
+ identity,
2086
+ errors: validationErrors,
2087
+ });
2088
+ return { frame: errorCommitFrame, }; /* <<<< returns early */
2089
+ }
2090
+
2091
+ await this.executeLocalCommit({
2092
+ deltaFrame: sagaIbGib,
2093
+ sagaHistory: history,
2094
+ localSpace: mySpace,
2095
+ localTempSpace: myTempSpace,
2096
+ metaspace,
2097
+ });
2098
+
2099
+ // todo: implement explicit cleanup logic here and in peer
2100
+ console.error(`${lc} NAG ERROR (NOT THROWN): implement cleanup logic, including add a cleanup method to the peer (E: 3a9a24befb98a981a88fbdbf52920e26)`);
1957
2101
  if (logalot) { console.log(`${lc} Peer committed. Finalizing saga locally. Saga Complete.`); }
1958
2102
 
1959
- return { responseWasNull: true };
2103
+ // the holy grail!
2104
+ return { sagaComplete: true };
1960
2105
  } catch (error) {
1961
- console.error(`${lc} ${extractErrorMsg(error)}`);
1962
- throw error;
2106
+ const emsg = `${lc} ${extractErrorMsg(error)}`;
2107
+ console.error(emsg);
2108
+ const errorCommitFrame = await this.createCommitFrame({
2109
+ sagaIbGib,
2110
+ errors: [emsg],
2111
+ metaspace,
2112
+ mySpace,
2113
+ identity,
2114
+ });
2115
+ return { frame: errorCommitFrame }
1963
2116
  } finally {
1964
2117
  if (logalot) { console.log(`${lc} complete.`); }
1965
2118
  }
@@ -1968,7 +2121,7 @@ export class SyncSagaCoordinator {
1968
2121
 
1969
2122
  // #endregion Handlers
1970
2123
 
1971
- protected async createSyncMsgStone<TStoneData extends SyncSagaMessageData_V1>({
2124
+ private async createSyncMsgStone<TStoneData extends SyncSagaMessageData_V1>({
1972
2125
  data,
1973
2126
  localSpace,
1974
2127
  metaspace,
@@ -2000,11 +2153,75 @@ export class SyncSagaCoordinator {
2000
2153
  }
2001
2154
  }
2002
2155
 
2156
+ private async getPayloadsForRequestedInfos({
2157
+ deltaRequestAddrInfos,
2158
+ mySpace,
2159
+ }: {
2160
+ deltaRequestAddrInfos: SyncSagaRequestAddrInfo[];
2161
+ mySpace: IbGibSpaceAny;
2162
+ }): Promise<IbGib_V1[]> {
2163
+ const lc = `${this.lc}[${this.getPayloadsForRequestedInfos.name}]`;
2164
+ try {
2165
+ if (logalot) { console.log(`${lc} starting... (I: 4fe13d0d80050f20a8b74ba80cee5826)`); }
2166
+ /**
2167
+ * graph of ibgibs we will send to the receiver. addr-based, so will
2168
+ * already be unique (no need to call `unique` on the domains
2169
+ * ibgibs)
2170
+ */
2171
+ const outgoingPayloadIbGibsDomainGraph: FlatIbGibGraph = {};
2172
+ for (const { addr, latestAddrAlreadyHave } of deltaRequestAddrInfos) {
2173
+ let deltaDepGraph: FlatIbGibGraph;
2174
+ if (latestAddrAlreadyHave) {
2175
+ // already has some, so only get the delta
2176
+ // remember: if we didn't have the other's latest addr, then
2177
+ // this would be in the conflicts not requested addrs
2178
+ deltaDepGraph = await getDeltaDependencyGraph({
2179
+ ibGibAddr: addr,
2180
+ latestCommonFrameAddr: latestAddrAlreadyHave,
2181
+ space: mySpace,
2182
+ live: true,
2183
+ });
2184
+ } else {
2185
+ // doesn't have anything, so get the entire dependency graph
2186
+ // INEFFICIENT: we've already gotten all of the domain
2187
+ // dependencies in initDomainGraph, but getDependencyGraph
2188
+ // only works against a space so we are going to rerun this.
2189
+ // an optimization would be to adapt/create a new
2190
+ // getDependencyGraph to work against an existing flat ibgib
2191
+ // map.
2192
+ deltaDepGraph = await getDependencyGraph({
2193
+ ibGibAddr: addr,
2194
+ space: mySpace,
2195
+ live: true,
2196
+ });
2197
+ }
2198
+
2199
+ const depGraphSize = Object.keys(deltaDepGraph).length;
2200
+ if (depGraphSize === 0) {
2201
+ throw new Error(`(UNEXPECTED) couldn't get requested addrs in mySpace (${mySpace.ib})? How did the receiver know to ask for these addrs if they weren't in our original graph? (E: 281eaebcdf77dd73e8245b2872100826)`);
2202
+ } else {
2203
+ // we have dependencies!
2204
+ Object.values(deltaDepGraph).forEach(x => {
2205
+ outgoingPayloadIbGibsDomainGraph[getIbGibAddr({ ibGib: x })] = x;
2206
+ });
2207
+ }
2208
+ }
2209
+
2210
+ const result = Object.values(outgoingPayloadIbGibsDomainGraph);
2211
+ return result;
2212
+ } catch (error) {
2213
+ console.error(`${lc} ${extractErrorMsg(error)}`);
2214
+ throw error;
2215
+ } finally {
2216
+ if (logalot) { console.log(`${lc} complete.`); }
2217
+ }
2218
+ }
2219
+
2003
2220
 
2004
2221
  /**
2005
2222
  * Evolves the saga timeline with a new frame.
2006
2223
  */
2007
- protected async evolveSyncSagaIbGib({
2224
+ private async evolveSyncSagaIbGib({
2008
2225
  prevSagaIbGib,
2009
2226
  conflictStrategy,
2010
2227
  msgStones,
@@ -2128,7 +2345,7 @@ export class SyncSagaCoordinator {
2128
2345
  }
2129
2346
  }
2130
2347
 
2131
- protected async getStageAndPayloadFromFrame({
2348
+ private async getStageAndPayloadFromFrame({
2132
2349
  sagaFrame,
2133
2350
  space
2134
2351
  }: {
@@ -2164,7 +2381,7 @@ export class SyncSagaCoordinator {
2164
2381
  }
2165
2382
  }
2166
2383
 
2167
- protected sortTimelinesTopologically(timelines: { [tjp: string]: IbGib_V1[] }): string[] {
2384
+ private sortTimelinesTopologically(timelines: { [tjp: string]: IbGib_V1[] }): string[] {
2168
2385
  const lc = `${this.lc}[${this.sortTimelinesTopologically.name}]`;
2169
2386
  const tjps = Object.keys(timelines);
2170
2387
  if (tjps.length === 0) { return []; }