@ibgib/core-gib 0.1.54 → 0.1.57
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/CHANGELOG.md +11 -3
- package/README.md +12 -15
- package/dist/keystone/aggregate-details.respec.d.mts +2 -0
- package/dist/keystone/aggregate-details.respec.d.mts.map +1 -0
- package/dist/keystone/aggregate-details.respec.mjs +118 -0
- package/dist/keystone/aggregate-details.respec.mjs.map +1 -0
- package/dist/keystone/keystone-constants.d.mts +5 -0
- package/dist/keystone/keystone-constants.d.mts.map +1 -1
- package/dist/keystone/keystone-constants.mjs +6 -1
- package/dist/keystone/keystone-constants.mjs.map +1 -1
- package/dist/keystone/keystone-helpers.d.mts +7 -1
- package/dist/keystone/keystone-helpers.d.mts.map +1 -1
- package/dist/keystone/keystone-helpers.mjs +5 -2
- package/dist/keystone/keystone-helpers.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.d.mts +51 -0
- package/dist/keystone/keystone-service-v1.d.mts.map +1 -1
- package/dist/keystone/keystone-service-v1.mjs +176 -9
- package/dist/keystone/keystone-service-v1.mjs.map +1 -1
- package/dist/keystone/keystone-service-v1.respec.mjs +40 -98
- package/dist/keystone/keystone-service-v1.respec.mjs.map +1 -1
- package/dist/keystone/keystone-types.d.mts +5 -0
- package/dist/keystone/keystone-types.d.mts.map +1 -1
- 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 +26 -30
- package/dist/sync/sync-conflict-adv-multitimelines.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-basic-divergence.respec.mjs +7 -8
- package/dist/sync/sync-conflict-basic-divergence.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs +10 -11
- package/dist/sync/sync-conflict-basic-multitimelines.respec.mjs.map +1 -1
- package/dist/sync/sync-conflict-text-merge.respec.mjs +30 -33
- package/dist/sync/sync-conflict-text-merge.respec.mjs.map +1 -1
- package/dist/sync/sync-constants.d.mts +1 -56
- package/dist/sync/sync-constants.d.mts.map +1 -1
- package/dist/sync/sync-constants.mjs +2 -59
- package/dist/sync/sync-constants.mjs.map +1 -1
- package/dist/sync/sync-helpers.d.mts +0 -1
- package/dist/sync/sync-helpers.d.mts.map +1 -1
- package/dist/sync/sync-helpers.mjs +1 -6
- package/dist/sync/sync-helpers.mjs.map +1 -1
- package/dist/sync/sync-innerspace-constants.respec.mjs +5 -9
- package/dist/sync/sync-innerspace-constants.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs +6 -7
- package/dist/sync/sync-innerspace-deep-updates.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs +22 -22
- package/dist/sync/sync-innerspace-dest-ahead-withid.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs +8 -9
- package/dist/sync/sync-innerspace-dest-ahead.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs +6 -7
- package/dist/sync/sync-innerspace-multiple-timelines.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace-partial-update.respec.mjs +7 -8
- package/dist/sync/sync-innerspace-partial-update.respec.mjs.map +1 -1
- package/dist/sync/sync-innerspace.respec.mjs +8 -11
- package/dist/sync/sync-innerspace.respec.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-http-node-adapter.mjs +10 -5
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-http-node-adapter.mjs.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-types.d.mts +3 -1
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.d.mts +4 -2
- 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 +37 -6
- 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-types.d.mts +3 -1
- package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.d.mts +4 -2
- 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 +37 -0
- 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-types.d.mts +7 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.d.mts +10 -2
- 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 +50 -38
- 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 +21 -5
- package/dist/sync/sync-peer/sync-peer-types.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.d.mts +30 -5
- package/dist/sync/sync-peer/sync-peer-v1.d.mts.map +1 -1
- package/dist/sync/sync-peer/sync-peer-v1.mjs +41 -25
- package/dist/sync/sync-peer/sync-peer-v1.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts +2 -11
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.d.mts.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs +7 -51
- package/dist/sync/sync-saga-context/sync-saga-context-helpers.mjs.map +1 -1
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts +1 -47
- package/dist/sync/sync-saga-context/sync-saga-context-types.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.d.mts +6 -58
- package/dist/sync/sync-saga-coordinator.d.mts.map +1 -1
- package/dist/sync/sync-saga-coordinator.mjs +23 -425
- package/dist/sync/sync-saga-coordinator.mjs.map +1 -1
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts +2 -2
- package/dist/sync/sync-saga-message/sync-saga-message-types.d.mts.map +1 -1
- package/dist/sync/sync-types.d.mts +2 -19
- package/dist/sync/sync-types.d.mts.map +1 -1
- package/dist/sync/sync-types.mjs.map +1 -1
- package/dist/test/mock-space.mjs +1 -1
- package/dist/test/mock-space.mjs.map +1 -1
- package/dist/test-helpers.d.mts +0 -6
- package/dist/test-helpers.d.mts.map +1 -1
- package/dist/test-helpers.mjs +2 -25
- package/dist/test-helpers.mjs.map +1 -1
- package/package.json +10 -34
- package/src/keystone/aggregate-details.respec.mts +137 -0
- package/src/keystone/docs/architecture.md +16 -0
- package/src/keystone/keystone-constants.mts +6 -1
- package/src/keystone/keystone-helpers.mts +9 -1
- package/src/keystone/keystone-service-v1.mts +216 -8
- package/src/keystone/keystone-service-v1.respec.mts +39 -103
- package/src/keystone/keystone-types.mts +6 -0
- package/src/sync/README.md +2 -87
- package/src/sync/docs/architecture.md +26 -5
- package/src/sync/docs/security.md +176 -0
- package/src/sync/graft-info/graft-info-helpers.respec.mts +7 -7
- package/src/sync/sync-conflict-adv-multitimelines.respec.mts +25 -29
- package/src/sync/sync-conflict-basic-divergence.respec.mts +6 -7
- package/src/sync/sync-conflict-basic-multitimelines.respec.mts +9 -10
- package/src/sync/sync-conflict-text-merge.respec.mts +29 -32
- package/src/sync/sync-constants.mts +2 -62
- package/src/sync/sync-helpers.mts +1 -8
- package/src/sync/sync-id-testlog.txt +421 -0
- package/src/sync/sync-innerspace-constants.respec.mts +4 -8
- package/src/sync/sync-innerspace-deep-updates.respec.mts +5 -6
- package/src/sync/sync-innerspace-dest-ahead-withid.respec.mts +23 -25
- package/src/sync/sync-innerspace-dest-ahead.respec.mts +7 -8
- package/src/sync/sync-innerspace-multiple-timelines.respec.mts +5 -6
- package/src/sync/sync-innerspace-partial-update.respec.mts +6 -7
- package/src/sync/sync-innerspace.respec.mts +7 -10
- package/src/sync/sync-peer/sync-peer-http-receiver/sync-http-node-adapter.mts +5 -5
- package/src/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-types.mts +5 -1
- package/src/sync/sync-peer/sync-peer-http-receiver/sync-peer-http-receiver-v1.mts +30 -9
- package/src/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-types.mts +3 -1
- package/src/sync/sync-peer/sync-peer-http-sender/sync-peer-http-sender-v1.mts +29 -2
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-types.mts +7 -1
- package/src/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mts +51 -38
- package/src/sync/sync-peer/sync-peer-types.mts +23 -6
- package/src/sync/sync-peer/sync-peer-v1.mts +68 -28
- package/src/sync/sync-saga-context/sync-saga-context-helpers.mts +6 -66
- package/src/sync/sync-saga-context/sync-saga-context-types.mts +1 -48
- package/src/sync/sync-saga-coordinator.mts +12 -552
- package/src/sync/sync-saga-message/sync-saga-message-types.mts +2 -3
- package/src/sync/sync-types.mts +2 -22
- package/src/sync/unused-identity-backup.mts.md +311 -0
- package/src/test/mock-space.mts +1 -1
- package/src/test-helpers.mts +1 -26
- package/test_output.log +0 -0
- package/test_output_utf8.txt +398 -0
- package/.vscode/core-gib-snippets.code-snippets +0 -293
- package/.vscode/launch.json +0 -40
- package/.vscode/settings.json +0 -58
- package/.vscode/tasks.json +0 -37
- package/dist/sync/sync-peer/sync-peer-http.respec.d.mts +0 -2
- package/dist/sync/sync-peer/sync-peer-http.respec.d.mts.map +0 -1
- package/dist/sync/sync-peer/sync-peer-http.respec.mjs +0 -340
- package/dist/sync/sync-peer/sync-peer-http.respec.mjs.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-helper.d.mts +0 -42
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-helper.d.mts.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-helper.mjs +0 -312
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-helper.mjs.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-types.d.mts +0 -84
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-types.d.mts.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-types.mjs +0 -65
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-types.mjs.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-v1.d.mts +0 -73
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-v1.d.mts.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-v1.mjs +0 -667
- package/dist/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-v1.mjs.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_createAndInit.node-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_createAndInit.node-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_createAndInit.node-filesystem-space-v1.respec.mjs +0 -67
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_createAndInit.node-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_persistTransformResult.node-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_persistTransformResult.node-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_persistTransformResult.node-filesystem-space-v1.respec.mjs +0 -67
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_persistTransformResult.node-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_putGetDelete.node-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_putGetDelete.node-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_putGetDelete.node-filesystem-space-v1.respec.mjs +0 -68
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_putGetDelete.node-filesystem-space-v1.respec.mjs.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-filesystem-space-v1.respec.d.mts +0 -2
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-filesystem-space-v1.respec.d.mts.map +0 -1
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-filesystem-space-v1.respec.mjs +0 -69
- package/dist/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-filesystem-space-v1.respec.mjs.map +0 -1
- package/src/sync/sync-peer/sync-peer-http.respec.mts +0 -396
- package/src/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-helper.mts +0 -298
- package/src/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-types.mts +0 -150
- package/src/witness/space/filesystem-space/node-filesystem-space/node-filesystem-space-v1.mts +0 -666
- package/src/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_createAndInit.node-filesystem-space-v1.respec.mts +0 -87
- package/src/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_persistTransformResult.node-filesystem-space-v1.respec.mts +0 -88
- package/src/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_putGetDelete.node-filesystem-space-v1.respec.mts +0 -88
- package/src/witness/space/filesystem-space/node-filesystem-space/respec/testSpace_registerNewIbGib_GetLatest.node-filesystem-space-v1.respec.mts +0 -90
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
respecfully, iReckon, ifWe, firstOfAll, firstOfEach, lastOfAll, lastOfEach, respecfullyDear, ifWeMight
|
|
3
3
|
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
4
4
|
const maam = `[${import.meta.url}]`, sir = maam;
|
|
5
|
-
import { clone, hash } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
5
|
+
import { clone, hash, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
6
6
|
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
7
7
|
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
8
8
|
|
|
@@ -17,79 +17,6 @@ import { addToBindingMap } from './keystone-helpers.mjs';
|
|
|
17
17
|
const logalot = GLOBAL_LOG_A_LOT;
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
// /**
|
|
21
|
-
// * not sure where to put this, but we probably will want to reuse this in the
|
|
22
|
-
// * future (assuming it works)
|
|
23
|
-
// * @returns metaspace service reference
|
|
24
|
-
// */
|
|
25
|
-
// async function getNewInitializedInMemoryMetaspaceForTesting({
|
|
26
|
-
// defaultSpaceName,
|
|
27
|
-
// }: {
|
|
28
|
-
// defaultSpaceName: string,
|
|
29
|
-
// }): Promise<MetaspaceService> {
|
|
30
|
-
// const lc = `[${getNewInitializedInMemoryMetaspaceForTesting.name}]`;
|
|
31
|
-
// try {
|
|
32
|
-
// if (logalot) { console.log(`${lc} starting... (I: 766d7596addcb73f4820586469233b25)`); }
|
|
33
|
-
|
|
34
|
-
// let metaspace = new Metaspace_Innerspace(/*cacheSvc*/undefined);
|
|
35
|
-
// if (logalot) { console.log(`${lc} creating metaspace complete. initializing... (I: 61b74d62e8832c9fa853e4b8c4c2d825)`); }
|
|
36
|
-
// getGibInfo()
|
|
37
|
-
|
|
38
|
-
// await metaspace.initialize({
|
|
39
|
-
// spaceName: defaultSpaceName,
|
|
40
|
-
// /**
|
|
41
|
-
// * passing in undefined will use the defaults. probably will need to
|
|
42
|
-
// * adjust this for testing purposes, but let's see what happens with
|
|
43
|
-
// * this first.
|
|
44
|
-
// */
|
|
45
|
-
// metaspaceFactory: {
|
|
46
|
-
// fnDtoToSpace: async () => {
|
|
47
|
-
// if (!currentSpace) { currentSpace = new IbGibTestSpace(); }
|
|
48
|
-
// return currentSpace;
|
|
49
|
-
// },
|
|
50
|
-
// fnZeroSpaceFactory: () => {
|
|
51
|
-
// if (!currentZeroSpace) { currentZeroSpace = new IbGibTestSpace(); }
|
|
52
|
-
// return currentZeroSpace;
|
|
53
|
-
// },
|
|
54
|
-
// fnDefaultLocalSpaceFactory: async () => {
|
|
55
|
-
// if (!currentSpace) { currentSpace = new IbGibTestSpace(); }
|
|
56
|
-
// return currentSpace;
|
|
57
|
-
// },
|
|
58
|
-
|
|
59
|
-
// // export type DtoToSpaceFunction = (spaceDto: IbGib_V1) => Promise<IbGibSpaceAny>;
|
|
60
|
-
// // export type ZeroSpaceFactoryFunction = () => IbGibSpaceAny;
|
|
61
|
-
// // export type LocalSpaceFactoryFunction = (opts: CreateLocalSpaceOptions) => Promise<IbGibSpaceAny | undefined>;
|
|
62
|
-
// },
|
|
63
|
-
// getFnAlert: () => { return async ({ title, msg }) => console.log(title, msg) },
|
|
64
|
-
// getFnPrompt: () => {
|
|
65
|
-
// return async ({ title, msg }) => {
|
|
66
|
-
// // if this is needed, we might set up some way for testing
|
|
67
|
-
// // to prepare either a queue of prompts or some kind of map or getter
|
|
68
|
-
// // and put it on the metaspace itself
|
|
69
|
-
// throw new Error(`not implemented (E: c7ef688a02f8cb74487260f9274ac825)`);
|
|
70
|
-
// // promptForText({ title, msg, confirm: false });
|
|
71
|
-
// }
|
|
72
|
-
// },
|
|
73
|
-
// getFnPromptPassword: () => {
|
|
74
|
-
// return async () => {
|
|
75
|
-
// // similar to getFnPrompt, if we need a _different_
|
|
76
|
-
// // password, we might set up some way for testing to prepare
|
|
77
|
-
// // either a queue of passwords or some kind of map or getter
|
|
78
|
-
// // and put it on the metaspace itself
|
|
79
|
-
// return 'password';
|
|
80
|
-
// // promptForSecret({ confirm: true })
|
|
81
|
-
// }
|
|
82
|
-
// },
|
|
83
|
-
// });
|
|
84
|
-
// return metaspace;
|
|
85
|
-
// } catch (error) {
|
|
86
|
-
// console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
87
|
-
// throw error;
|
|
88
|
-
// } finally {
|
|
89
|
-
// if (logalot) { console.log(`${lc} complete.`); }
|
|
90
|
-
// }
|
|
91
|
-
// }
|
|
92
|
-
|
|
93
20
|
/**
|
|
94
21
|
* A simple in-memory map acting as a Space.
|
|
95
22
|
* Pure Storage. No Indexing logic.
|
|
@@ -115,8 +42,8 @@ class MockIbGibSpace {
|
|
|
115
42
|
const addrs = arg.data.ibGibAddrs || [];
|
|
116
43
|
const ibGibs: IbGib_V1[] = [];
|
|
117
44
|
for (const addr of addrs) {
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
45
|
+
const x = await this.get({ addr });
|
|
46
|
+
if (x) { ibGibs.push(x); }
|
|
120
47
|
}
|
|
121
48
|
return { ibGibs };
|
|
122
49
|
}
|
|
@@ -190,13 +117,14 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
190
117
|
|
|
191
118
|
// Setup generic variables
|
|
192
119
|
const masterSecret = "TestSecret_12345";
|
|
193
|
-
const
|
|
120
|
+
const poolId = "TestPool";
|
|
194
121
|
let config: KeystonePoolConfig_HashV1;
|
|
195
122
|
|
|
196
123
|
firstOfAll(sir, async () => {
|
|
197
124
|
// Use our standard builder to get a valid config object
|
|
125
|
+
const salt = (await getUUID()).substring(0, 16);
|
|
198
126
|
config = createStandardPoolConfig({
|
|
199
|
-
id:
|
|
127
|
+
id: poolId,
|
|
200
128
|
salt,
|
|
201
129
|
}) as KeystonePoolConfig_HashV1;
|
|
202
130
|
});
|
|
@@ -243,7 +171,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
243
171
|
const challengeId = "a3ff7843552870fc28bef2b"; // arbitrary random challengeId
|
|
244
172
|
|
|
245
173
|
// 1. Generate Solution
|
|
246
|
-
const solution = await strategy.generateSolution({ poolSecret, poolId:
|
|
174
|
+
const solution = await strategy.generateSolution({ poolSecret, poolId: config.id, challengeId });
|
|
247
175
|
iReckon(sir, solution.value).asTo('solution value exists').isGonnaBeTruthy();
|
|
248
176
|
iReckon(sir, solution.challengeId).asTo('id matches').willEqual(challengeId);
|
|
249
177
|
|
|
@@ -262,7 +190,7 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
262
190
|
const challengeId = "8c994f3ed598f150e25513"; // arbitrary random challengeId
|
|
263
191
|
|
|
264
192
|
// Generate real pair
|
|
265
|
-
const solution = await strategy.generateSolution({ poolSecret, poolId:
|
|
193
|
+
const solution = await strategy.generateSolution({ poolSecret, poolId: config.id, challengeId });
|
|
266
194
|
const challenge = await strategy.generateChallenge({ solution });
|
|
267
195
|
|
|
268
196
|
// Tamper with solution value
|
|
@@ -278,11 +206,11 @@ await respecfully(sir, 'Suite A: Strategy Vectors (HashRevealV1)', async () => {
|
|
|
278
206
|
|
|
279
207
|
// Generate pair A
|
|
280
208
|
const challengeId_A = "416c38cfd6ee63dbf8d4e5ef36"; // arbitrary random challengeId
|
|
281
|
-
const solutionA = await strategy.generateSolution({ poolSecret, poolId:
|
|
209
|
+
const solutionA = await strategy.generateSolution({ poolSecret, poolId: config.id, challengeId: challengeId_A });
|
|
282
210
|
|
|
283
211
|
// Generate pair B
|
|
284
212
|
const challengeId_B = "c487ef6b7878fae798c3"; // arbitrary random challengeId
|
|
285
|
-
const solutionB = await strategy.generateSolution({ poolSecret, poolId:
|
|
213
|
+
const solutionB = await strategy.generateSolution({ poolSecret, poolId: config.id, challengeId: challengeId_B });
|
|
286
214
|
const challengeB = await strategy.generateChallenge({ solution: solutionB });
|
|
287
215
|
|
|
288
216
|
// Check A against B
|
|
@@ -314,9 +242,10 @@ await respecfully(sir, 'Suite B: Service Lifecycle', async () => {
|
|
|
314
242
|
|
|
315
243
|
await respecfully(sir, 'Genesis', async () => {
|
|
316
244
|
await ifWe(sir, 'creates a valid genesis frame and persists it', async () => {
|
|
245
|
+
const salt = (await getUUID()).substring(0, 16);
|
|
317
246
|
const config = createStandardPoolConfig({
|
|
318
247
|
id: POOL_ID_DEFAULT,
|
|
319
|
-
salt
|
|
248
|
+
salt,
|
|
320
249
|
});
|
|
321
250
|
|
|
322
251
|
genesisKeystone = await service.genesis({
|
|
@@ -406,9 +335,10 @@ await respecfully(sir, 'Suite C: Security Vectors', async () => {
|
|
|
406
335
|
mockMetaspace = new MockMetaspaceService(mockSpace);
|
|
407
336
|
|
|
408
337
|
// Setup Alice's Identity
|
|
338
|
+
const salt = (await getUUID()).substring(0, 16);
|
|
409
339
|
const config = createStandardPoolConfig({
|
|
410
340
|
id: POOL_ID_DEFAULT,
|
|
411
|
-
salt
|
|
341
|
+
salt,
|
|
412
342
|
targetBinding: 0,
|
|
413
343
|
size: 10,
|
|
414
344
|
});
|
|
@@ -509,13 +439,15 @@ await respecfully(sir, 'Suite D: Revocation', async () => {
|
|
|
509
439
|
mockMetaspace = new MockMetaspaceService(mockSpace);
|
|
510
440
|
|
|
511
441
|
// Setup Identity WITH a Revocation Pool
|
|
442
|
+
const stdSalt = (await getUUID()).substring(0, 16);
|
|
443
|
+
const revokeSalt = (await getUUID()).substring(0, 16);
|
|
512
444
|
const stdConfig = createStandardPoolConfig({
|
|
513
445
|
id: POOL_ID_DEFAULT,
|
|
514
|
-
salt:
|
|
446
|
+
salt: stdSalt,
|
|
515
447
|
});
|
|
516
448
|
const revokeConfig = createRevocationPoolConfig({
|
|
517
449
|
id: POOL_ID_REVOKE,
|
|
518
|
-
salt:
|
|
450
|
+
salt: revokeSalt,
|
|
519
451
|
}); // Special Config
|
|
520
452
|
|
|
521
453
|
genesisKeystone = await service.genesis({
|
|
@@ -588,7 +520,8 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
588
520
|
|
|
589
521
|
// Helper to generate a "Foreign" pool (e.g. from Bob)
|
|
590
522
|
const createForeignPool = async (id: string, verbs: string[] = []): Promise<KeystoneChallengePool> => {
|
|
591
|
-
const
|
|
523
|
+
const salt = (await getUUID()).substring(0, 16);
|
|
524
|
+
const config = createStandardPoolConfig({ id, salt });
|
|
592
525
|
config.allowedVerbs = verbs;
|
|
593
526
|
|
|
594
527
|
// We use the factory manually here to simulate Bob doing this offline
|
|
@@ -625,7 +558,8 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
625
558
|
mockMetaspace = new MockMetaspaceService(mockSpace);
|
|
626
559
|
|
|
627
560
|
// Alice Genesis: Standard pool (allows all verbs, including 'manage')
|
|
628
|
-
const
|
|
561
|
+
const salt = (await getUUID()).substring(0, 16);
|
|
562
|
+
const config = createStandardPoolConfig({ id: POOL_ID_DEFAULT, salt });
|
|
629
563
|
aliceKeystone = await service.genesis({
|
|
630
564
|
masterSecret: aliceSecret,
|
|
631
565
|
configs: [config],
|
|
@@ -746,7 +680,8 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
746
680
|
|
|
747
681
|
// Helper to simulate Bob creating a pool "offline" to give to Alice
|
|
748
682
|
const createForeignPool = async (id: string, verbs: string[] = []): Promise<KeystoneChallengePool> => {
|
|
749
|
-
const
|
|
683
|
+
const salt = (await getUUID()).substring(0, 16);
|
|
684
|
+
const config = createStandardPoolConfig({ id, salt });
|
|
750
685
|
config.allowedVerbs = verbs;
|
|
751
686
|
|
|
752
687
|
// We use the factory manually here to simulate Bob doing this on his own machine
|
|
@@ -783,9 +718,10 @@ await respecfully(sir, 'Suite E: Structural Evolution (addPools)', async () => {
|
|
|
783
718
|
mockMetaspace = new MockMetaspaceService(mockSpace);
|
|
784
719
|
|
|
785
720
|
// Alice Genesis: Standard pool (allows all verbs, including 'manage')
|
|
721
|
+
const salt = (await getUUID()).substring(0, 16);
|
|
786
722
|
const config = createStandardPoolConfig({
|
|
787
723
|
id: POOL_ID_DEFAULT,
|
|
788
|
-
salt
|
|
724
|
+
salt,
|
|
789
725
|
});
|
|
790
726
|
aliceKeystone = await service.genesis({
|
|
791
727
|
masterSecret: aliceSecret,
|
|
@@ -899,27 +835,27 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
899
835
|
|
|
900
836
|
const service = new KeystoneService_V1();
|
|
901
837
|
const aliceSecret = "Alice_Deep_Inspect";
|
|
902
|
-
const
|
|
838
|
+
const poolId = "granularity_pool";
|
|
903
839
|
|
|
904
840
|
let mockSpace: MockIbGibSpace;
|
|
905
841
|
let mockMetaspace: any;
|
|
906
842
|
let genesisKeystone: KeystoneIbGib_V1;
|
|
907
|
-
|
|
908
843
|
let signedKeystone: KeystoneIbGib_V1;
|
|
909
|
-
|
|
910
|
-
// We use a specific hybrid config to test exact selection logic
|
|
911
|
-
const hybridConfig = createStandardPoolConfig({
|
|
912
|
-
id: salt,
|
|
913
|
-
salt,
|
|
914
|
-
// 2 FIFO + 2 Random = 4 Total per sign
|
|
915
|
-
sequential: 2, random: 2, targetBinding: 0,
|
|
916
|
-
size: 20, // Small enough to track, large enough to be random
|
|
917
|
-
}) as KeystonePoolConfig_HashV1;
|
|
844
|
+
let hybridConfig: KeystonePoolConfig_HashV1;
|
|
918
845
|
|
|
919
846
|
firstOfAll(sir, async () => {
|
|
920
847
|
mockSpace = new MockIbGibSpace();
|
|
921
848
|
mockMetaspace = new MockMetaspaceService(mockSpace);
|
|
922
849
|
|
|
850
|
+
const salt = (await getUUID()).substring(0, 16);
|
|
851
|
+
hybridConfig = createStandardPoolConfig({
|
|
852
|
+
id: poolId,
|
|
853
|
+
salt,
|
|
854
|
+
// 2 FIFO + 2 Random = 4 Total per sign
|
|
855
|
+
sequential: 2, random: 2, targetBinding: 0,
|
|
856
|
+
size: 20, // Small enough to track, large enough to be random
|
|
857
|
+
}) as KeystonePoolConfig_HashV1;
|
|
858
|
+
|
|
923
859
|
genesisKeystone = await service.genesis({
|
|
924
860
|
masterSecret: aliceSecret,
|
|
925
861
|
configs: [hybridConfig],
|
|
@@ -949,7 +885,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
949
885
|
|
|
950
886
|
await ifWe(sir, 'verifies the math manually (White-box Crypto Check)', async () => {
|
|
951
887
|
const proof = signedKeystone.data!.proofs[0];
|
|
952
|
-
const poolSnapshot = genesisKeystone.data!.challengePools.find(p => p.id ===
|
|
888
|
+
const poolSnapshot = genesisKeystone.data!.challengePools.find(p => p.id === poolId)!;
|
|
953
889
|
|
|
954
890
|
// We iterate every solution in the proof and MANUALLY verify the hash relationship
|
|
955
891
|
// bypassing the Service's validation logic to ensure the raw math holds up.
|
|
@@ -978,7 +914,7 @@ await respecfully(sir, 'Suite F: Deep Inspection', async () => {
|
|
|
978
914
|
|
|
979
915
|
await ifWe(sir, 'verifies FIFO logic (Deterministic Selection)', async () => {
|
|
980
916
|
const proof = signedKeystone.data!.proofs[0];
|
|
981
|
-
const poolSnapshot = genesisKeystone.data!.challengePools.find(p => p.id ===
|
|
917
|
+
const poolSnapshot = genesisKeystone.data!.challengePools.find(p => p.id === poolId)!;
|
|
982
918
|
|
|
983
919
|
// The first N keys in the pool should be the FIFO targets.
|
|
984
920
|
// Assumption: Object.keys returns insertion order (Standard in modern JS engines)
|
|
@@ -349,6 +349,12 @@ export interface KeystoneData_V1 extends IbGibData_V1 {
|
|
|
349
349
|
* to the next frame's data during evolution.
|
|
350
350
|
*/
|
|
351
351
|
frameDetails?: any;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Aggregated state of the Keystone identity up to this frame.
|
|
355
|
+
* Acts as a snapshot to avoid walking the entire timeline.
|
|
356
|
+
*/
|
|
357
|
+
checkpointDetails?: any;
|
|
352
358
|
}
|
|
353
359
|
|
|
354
360
|
export interface KeystoneRel8ns_V1 extends IbGibRel8ns_V1 {
|
package/src/sync/README.md
CHANGED
|
@@ -20,7 +20,6 @@ Init -> Ack -> Delta(s) -> Conflict/Commit via "ping pong" exchange.
|
|
|
20
20
|
|
|
21
21
|
1. **Init**: Sender initiates sync.
|
|
22
22
|
* Creates the `SyncSagaIbGib_V1` with an `Init` msg frame containing knowledge about domain ibgibs to sync.
|
|
23
|
-
* Optionally creates session keystone based off of Sender's primary keystone.
|
|
24
23
|
* Wraps the saga ibgib in a `SyncSagaContextIbGib_V1` and passes to the `SyncPeer_V1`.
|
|
25
24
|
2. **Ack**: Receiver gets incoming `SyncSagaContextIbGib_V1` and passes to its `SyncSagaCoordinator`.
|
|
26
25
|
* Handles the `Init` frame, comparing knowledge with Receiver's own status of each timeline/stone in domain ibgibs.
|
|
@@ -34,44 +33,11 @@ Init -> Ack -> Delta(s) -> Conflict/Commit via "ping pong" exchange.
|
|
|
34
33
|
* `SyncPeer_V1.payloadIbGibsDomainReceived$` is an observable for the actual domain payload ibgibs transmitted.
|
|
35
34
|
* So the a node sends domain payloads out in bulk to the `SyncPeer_V1`, receives the return saga ibgib with a manifest, but may have to wait for the observable to complete to actually process the ibgibs.
|
|
36
35
|
* `Delta` frame can include `requests` (e.g., asking for missing conflict history).
|
|
37
|
-
* Once no deltas required, `Delta` frame sets `proposeCommit` flag. If the other has nothing to add, the
|
|
36
|
+
* Once no deltas required, `Delta` frame sets `proposeCommit` flag. If the other has nothing to add, the saga transitions to `Commit`.
|
|
38
37
|
4. **Commit**: When receive `Delta` frame with `proposeCommit`, should trigger the receiver of this to start its commit. If that is successful, it should return a `Commit` frame, which signals the commit was successful on their end. When this `Commit` frame is received, this end should not perform its own commit.
|
|
39
38
|
* For local-only syncs, the `SyncPeer_V1` implementation is responsible for moving verified `ibGibs` from the temporary saga space to the durable/persistent domain space.
|
|
40
39
|
* From each endpoint's POV, the cleanup should remove the temp spaces used, in addition to any other required transaction cleanup (like closing any connections).
|
|
41
40
|
|
|
42
|
-
### Session Identity (`useSessionIdentity`)
|
|
43
|
-
|
|
44
|
-
The sync protocol can optionally generate an ephemeral Keystone Identity for the duration of the saga.
|
|
45
|
-
|
|
46
|
-
**Purpose**: To allow secure, signed exchanges even if the Node doesn't have a long-lived identity, or to isolate the sync session security from master identity.
|
|
47
|
-
|
|
48
|
-
**Current Status**:
|
|
49
|
-
* ✅ Session keystone creation implemented ([getSessionIdentity()](file:./sync-saga-coordinator.mts#L328-L362))
|
|
50
|
-
* ✅ Keystone passed via Init message ([initData.identity](file:./sync-saga-message/sync-saga-message-types.mts#L75))
|
|
51
|
-
* ⚠️ **Signature generation/verification: NOT yet implemented**
|
|
52
|
-
* ⚠️ **Per-frame proof creation: NOT yet implemented**
|
|
53
|
-
|
|
54
|
-
**Usage**:
|
|
55
|
-
```typescript
|
|
56
|
-
const { done } = await coordinator.sync({
|
|
57
|
-
peer,
|
|
58
|
-
domainIbGibs: [timeline],
|
|
59
|
-
localSpace,
|
|
60
|
-
metaspace,
|
|
61
|
-
useSessionIdentity: true, // Default, but currently non-functional
|
|
62
|
-
});
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
> [!CAUTION]
|
|
66
|
-
> **🚧 Session identity is NOT working yet.** Tests explicitly set `useSessionIdentity: false` to avoid failures. See [sync-innerspace-dest-ahead-withid.respec.mts](file:./sync-innerspace-dest-ahead-withid.respec.mts) for the failing test case.
|
|
67
|
-
|
|
68
|
-
**Authentication/Authorization Hooks**:
|
|
69
|
-
|
|
70
|
-
Validation occurs in [SyncPeer_V1.witness()](file:./sync-peer/sync-peer-v1.mts#L129-L219):
|
|
71
|
-
1. `validateContextAndSagaFrame()` - Intrinsic validation
|
|
72
|
-
2. `authenticateContext()` - Verify identity (stub)
|
|
73
|
-
3. `authorizeContext()` - Check permissions (stub)
|
|
74
|
-
|
|
75
41
|
### Conflict Resolution via Grafting
|
|
76
42
|
|
|
77
43
|
> [!IMPORTANT]
|
|
@@ -110,58 +76,6 @@ See [graft-info-helpers.mts](file:./graft-info/graft-info-helpers.mts) for imple
|
|
|
110
76
|
|
|
111
77
|
**Why**: Ensures atomic sync - either all data validates and commits, or none does.
|
|
112
78
|
|
|
113
|
-
|
|
114
|
-
## 🔒 Security Considerations
|
|
115
|
-
|
|
116
|
-
> [!WARNING]
|
|
117
|
-
> Session identity is **currently non-functional**. The mitigations below describe the intended security model.
|
|
118
|
-
|
|
119
|
-
### Attack Vectors & Mitigations
|
|
120
|
-
|
|
121
|
-
| Attack Vector | Description | Mitigation |
|
|
122
|
-
|---------------|-------------|------------|
|
|
123
|
-
| **MITM Replay** | Attacker replays captured saga frames | • Frame signatures include timestamp<br>• Freshness checks reject old frames<br>• Challenge depletion prevents reuse<br>• sagaId deduplication |
|
|
124
|
-
| **Session Fixation** | Attacker pre-generates session keystone | • Session secret derived via KDF (strategy in keystone metadata)<br>• `sessionSecret = KDF(masterSecret, sagaId, strategy)` |
|
|
125
|
-
| **Identity Spoofing** | Impersonation via fake session keystone | • Session keystone includes `derivedFrom` master address<br>• Receiver validates master keystone is known/trusted |
|
|
126
|
-
| **Challenge Grinding** | Brute-force challenge solutions for target binding | • Hash pre-imaging computationally infeasible<br>• Stochastic selection adds randomness |
|
|
127
|
-
| **DoS (Pool Exhaustion)** | Excessive syncs deplete challenge pools | • Top-up replenishment refills challenges<br>• Rate limiting per identity<br>• Pool separation for critical ops |
|
|
128
|
-
| **Saga Hijacking** | Mid-flight frame injection | • All frames signed with session keystone<br>• `past` rel8n creates tamper-evident chain |
|
|
129
|
-
|
|
130
|
-
### Cryptographic Primitives
|
|
131
|
-
|
|
132
|
-
* **Hash Function**: SHA-256 (content addressing, challenge generation)
|
|
133
|
-
* **Proof System**: Hash-reveal protocol (`hash-reveal-v1`)
|
|
134
|
-
* **Binding**: Target address → hex bucket → challenge selection
|
|
135
|
-
* **Entropy**: Stochastic challenge selection (anti-grinding)
|
|
136
|
-
|
|
137
|
-
### Trust Model
|
|
138
|
-
|
|
139
|
-
**Session Keystones**:
|
|
140
|
-
- Ephemeral identity per sync saga
|
|
141
|
-
- Derived from master keystone + `sagaId`
|
|
142
|
-
- Validates via proof-of-work (solving challenges)
|
|
143
|
-
- Post-hoc audit trail: master can prove session authorship
|
|
144
|
-
|
|
145
|
-
**Propagation**:
|
|
146
|
-
- Sync does NOT rely on global PKI or certificate authorities
|
|
147
|
-
- Trust propagates through sync sessions (Alice syncs with Bob → Bob witnesses Alice's keystone)
|
|
148
|
-
- Revocation propagates same way (Alice revokes → syncs revocation to Bob → Bob sees revoked timeline)
|
|
149
|
-
|
|
150
|
-
**Threat Boundaries**:
|
|
151
|
-
- ✅ Protects against: MITM replay, impersonation, frame tampering
|
|
152
|
-
- ⚠️ Does NOT protect against: Compromised endpoint (live memory access during active session)
|
|
153
|
-
- ⚠️ Master secrets: Raw secrets should NOT stored; keystones master secrets to keystones should be derived secrets via key stretching passwords. raw secret files of course can drive these passwords and must be protected
|
|
154
|
-
- ⚠️ Transport security: Use TLS for network layer encryption (ibgib protocol provides authentication/integrity, not confidentiality)
|
|
155
|
-
|
|
156
|
-
### Best Practices
|
|
157
|
-
|
|
158
|
-
1. **Master Secret Protection**: Store master secrets in secure enclaves/keychains
|
|
159
|
-
2. **Session Lifetime**: Limit saga duration, revoke sessions post-completion
|
|
160
|
-
3. **Rate Limiting**: Implement per-identity sync rate limits
|
|
161
|
-
4. **Freshness**: Reject frames older than 60 seconds
|
|
162
|
-
5. **Audit Logs**: Persist saga timelines for post-hoc validation (saga timelines are ibgibs, so integrity is built-in via content addressing)
|
|
163
|
-
6. **TLS**: Always use TLS for network transport (defense-in-depth)
|
|
164
|
-
|
|
165
79
|
## 📚 Documentation
|
|
166
80
|
* **[Architecture](./docs/architecture.md)**: Detailed design of the Saga Protocol, State Machine, and Smart Diff logic.
|
|
167
81
|
* **[Testing & Verification](./docs/testing.md)**: Test strategy, verification matrix, and comprehensive testing guidelines.
|
|
@@ -174,3 +88,4 @@ See [graft-info-helpers.mts](file:./graft-info/graft-info-helpers.mts) for imple
|
|
|
174
88
|
|
|
175
89
|
## Verification
|
|
176
90
|
We rely on rigorous In-Memory Simulation (`InnerSpace`) to verify complex graph scenarios. See **[Verification](./docs/verification.md)** for the full status.
|
|
91
|
+
|
|
@@ -21,7 +21,7 @@ The `SyncSagaCoordinator` drives a Finite State Machine (FSM) via the `handleSag
|
|
|
21
21
|
### 2.1 The Ping-Pong Flow
|
|
22
22
|
|
|
23
23
|
1. **Start (Initiator)**:
|
|
24
|
-
* Initiator (Alice) calls `
|
|
24
|
+
* Initiator (Alice) calls `sync`.
|
|
25
25
|
* Generates `Init` frame containing her `KnowledgeMap` and `Mode` (Push/Pull/Sync).
|
|
26
26
|
* Sends `Init` to Bob.
|
|
27
27
|
|
|
@@ -42,7 +42,7 @@ The `SyncSagaCoordinator` drives a Finite State Machine (FSM) via the `handleSag
|
|
|
42
42
|
|
|
43
43
|
4. **Convergence (`handleDeltaFrame`)**:
|
|
44
44
|
* Bob receives `Delta`.
|
|
45
|
-
* **Verifies**: Checks content-address hashes
|
|
45
|
+
* **Verifies**: Checks content-address hashes.
|
|
46
46
|
* **Merges**: Puts data into his local space.
|
|
47
47
|
* Bob sends `Commit` frame.
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ The `SyncSagaCoordinator` drives a Finite State Machine (FSM) via the `handleSag
|
|
|
55
55
|
### 3.1 SyncSagaMessage
|
|
56
56
|
All frames are `SyncIbGib`s carrying specific data payloads.
|
|
57
57
|
|
|
58
|
-
* `sagaId`: Unique UUID for the
|
|
58
|
+
* `sagaId`: Unique UUID for the duration of this exchange.
|
|
59
59
|
* `stage`: `init` | `ack` | `delta` | `commit` | `conflict`.
|
|
60
60
|
* `knowledgeMap`: `{ [tjpAddr]: [tipAddr, ...ancestors] }`.
|
|
61
61
|
|
|
@@ -65,5 +65,26 @@ The protocol supports two types of data:
|
|
|
65
65
|
* **Stones (Constants)**: Immutable data without a history (e.g., Code lists, Configs). Syncing involves simple existence checks.
|
|
66
66
|
|
|
67
67
|
## 4. Security & Identity
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
|
|
69
|
+
See [Security](./security.md) documentation.
|
|
70
|
+
|
|
71
|
+
> [!WARNING]
|
|
72
|
+
> ## 5. Sync Storage & Persistence Rules (Critical Security)
|
|
73
|
+
>
|
|
74
|
+
> Knowing **when** to put **what** ibgib frames into **which** space is a high-priority security concern. The sync algorithm must balance maintaining a robust audit trail with protecting durable spaces from unauthorized pollution. Always follow these rules:
|
|
75
|
+
>
|
|
76
|
+
> ### Rule 1: Saga/Control Audit Trail (Immediate Persistence)
|
|
77
|
+
> We need an audit trail of the sync saga itself (including context and authentication info).
|
|
78
|
+
> * The initial session keystone (or any valid evolution performed locally) and saga control ibgibs are trusted because *we* just generated them.
|
|
79
|
+
> * Persist these immediately in the **local durable space** (and possibly the local temporary space if needed for transit) so they are available for validation and auditing.
|
|
80
|
+
>
|
|
81
|
+
> ### Rule 2: Domain IbGibs (Deferred Persistence)
|
|
82
|
+
> We do NOT persist *any* domain ibgibs (e.g., user payloads) into the durable space before the `commit` phase of the sync process.
|
|
83
|
+
> * Domain ibgibs must be routed to the **tempSpace** during the `delta` or `ack` phases.
|
|
84
|
+
> * The `commit` phase acts as the transaction boundary. By waiting for the commit, we avoid polluting durable spaces on error, preventing the need for complex rollbacks.
|
|
85
|
+
>
|
|
86
|
+
> ### Rule 3: Validate First, Store Second
|
|
87
|
+
> We never chance storing intrinsically invalid ibgibs.
|
|
88
|
+
> * **First step:** Intrinsically validate ibgibs (hashes, basic structure).
|
|
89
|
+
> * **Second step:** Perform AuthN/AuthZ checks (using the session keystone).
|
|
90
|
+
> * **Final step:** Once verified, saga "control" ibgibs (which only contain "soft" links to domain ibgibs) can be safely persisted to durable spaces.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Ibgib Sync Security
|
|
2
|
+
|
|
3
|
+
We use ibgib's unique Merkle-DAG-based protocol and innovative "keystone" construct (see keystone [README.md](../../keystone/README.md) and other [docs](../../keystone/docs/)) to maximize security while minimizing surface area (code).
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
* **Domain Keystone (I)**: The primary, long-lived identity keystone representing the data owner (e.g., Alice). This dictates high-level access and is registered with a domain provider (e.g., Space-Gib as an initial reference implementation).
|
|
8
|
+
* **Session Keystone (S)**: An ephemeral, short-lived keystone generated locally by the Sender explicitly for a single sync session.
|
|
9
|
+
* **Target Domain (X)**: The specific ibgib timeline or payload that is the subject of the sync.
|
|
10
|
+
* **Sovereign Broker (Receiver)**: The domain provider acts as a passive, high-integrity validator. It does not generate or sign sync frames with its own session identity; it relies entirely on the Sender's cryptographic proofs to authorize actions. While we may treat the provider as "honest" practically, it is ultimately a sovereign entity over its data, and its "honesty" is driven by long-term self-interest.
|
|
11
|
+
|
|
12
|
+
## Development Nuances & Implementation TODOs
|
|
13
|
+
|
|
14
|
+
This section serves as a working scratchpad to capture specific code-level nuances, terminology standardization, and pending tasks before jumping into implementation.
|
|
15
|
+
|
|
16
|
+
### ✅ Agreed: Secret Naming — `senderSecret` & `sessionSecret`
|
|
17
|
+
|
|
18
|
+
* **`senderSecret`**: The master secret corresponding to `senderIdentity`. Drives the KDF to produce the session secret. Replaces the old `nonSessionSecret` / `identitySecret` / `domainSecret` names.
|
|
19
|
+
* **`sessionSecret`**: Deterministically derived via `KDF(senderSecret, sagaId)`. Ephemeral and saga-specific. Used only within `establishSessionIdentity` to create the session keystone genesis.
|
|
20
|
+
* *Rationale*: `sender` pairs naturally with `senderIdentity`; `session` pairs naturally with `sessionIdentity`.
|
|
21
|
+
|
|
22
|
+
### ✅ Agreed: Identity Naming — `senderIdentity` & `sessionIdentity`
|
|
23
|
+
|
|
24
|
+
* **`senderIdentity`**: The long-lived Domain Keystone (I) representing the Sender (Alice). Optional — a sync can run with just a `domainSecret` and no named identity.
|
|
25
|
+
* Using `senderIdentity` (not `domainIdentity` or `primaryIdentity`) because "domain" becomes ambiguous when Bob eventually has his own domain identity in a future symmetric model.
|
|
26
|
+
* **`sessionIdentity`**: The ephemeral keystone (S) generated per-saga. Used consistently as both:
|
|
27
|
+
* A **param/property name** (e.g., `peer.sessionIdentity`, `createSessionIdentity()`).
|
|
28
|
+
* A **rel8n name** on both the sync saga ibgib and the context ibgib (e.g., `syncSagaIbGib.rel8ns.sessionIdentity`, `contextIbGib.rel8ns.sessionIdentity`).
|
|
29
|
+
|
|
30
|
+
### ✅ Agreed: Identity Hard-linking Strategy
|
|
31
|
+
|
|
32
|
+
* **Sync Saga Frame** (`ibgib.data`, soft-link):
|
|
33
|
+
* `data` should record identity details as soft references (addresses in `ibgib.data`, not hard-linked `ibgib.rel8ns`):
|
|
34
|
+
* `senderIdentity` TJP addr.
|
|
35
|
+
* The exact `senderIdentity` frame addr that signed/authorized the session (i.e., the new frame post-`establishSessionIdentity`).
|
|
36
|
+
* `sessionIdentity` TJP addr.
|
|
37
|
+
* No hard-link (`rel8ns`) to `sessionIdentity` from the sync saga ibgib.
|
|
38
|
+
* **Context IbGib** (`ibgib.rel8ns`, hard-link):
|
|
39
|
+
* `rel8ns.sessionIdentity` hard-links to the **previous** session keystone frame (the frame *before* the current signing step). This is the "point forward, sign backward" pattern: the signed (evolved) keystone's claim targets *this context's addr*, so the context can only reference the *prior* frame.
|
|
40
|
+
* The `signedSessionKeystone` property on the context carries the **newly evolved** frame (the current turn's signature).
|
|
41
|
+
* **No transition pool**: The prior symmetric "transition pool" (for the receiver's signing turn) is eliminated. Only the Sender signs.
|
|
42
|
+
* **Sovereign Broker response**: The Receiver includes the *same, unevolved* `sessionIdentity` addr in its response context's `rel8ns.sessionIdentity` — it does not evolve the keystone.
|
|
43
|
+
|
|
44
|
+
### ✅ Agreed: Session Keystone Pool Configuration
|
|
45
|
+
|
|
46
|
+
The session keystone has **two dedicated pools**, each with a matching `poolId` and `verb`:
|
|
47
|
+
|
|
48
|
+
* **`connect` pool** (`poolId: "connect"`, `verb: "connect"`):
|
|
49
|
+
* Used exclusively during `peer.connect()` — the in-band WebSocket challenge/response handshake.
|
|
50
|
+
* The receiver issues challenges from this pool; the sender solves them to prove possession of S.
|
|
51
|
+
* **`sync` pool** (`poolId: "sync"`, `verb: "sync"`):
|
|
52
|
+
* Used to sign each outgoing context frame during the sync ping-pong (Init, Ack, Delta, Commit turns).
|
|
53
|
+
* Pool separation is intentional — the `connect` handshake and per-turn signing are distinct operations with distinct lifetimes.
|
|
54
|
+
* [ ] Add `"connect"` and `"sync"` verbs to `keystone-constants.mts`.
|
|
55
|
+
* [ ] Create standard pool config factories for both in [`keystone-config-builder.mts`](../../keystone/keystone-config-builder.mts).
|
|
56
|
+
|
|
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
|
+
|
|
59
|
+
### ✅ Agreed: `establishSessionIdentity` — Pre-Connect Phase
|
|
60
|
+
|
|
61
|
+
A new `establishSessionIdentity` method on the sync peer base class is the **mandatory first step** before `connect`. It encapsulates:
|
|
62
|
+
|
|
63
|
+
1. **Generate session keystone**: `sessionIdentity` genesis (S^Stjp) is created locally from `KDF(senderSecret, sagaId)`. The genesis includes metadata about `senderIdentity` TJP and the target domain.
|
|
64
|
+
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
|
+
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
|
+
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.
|
|
67
|
+
5. **After this point**: `senderIdentity` IS the new frame. The session keystone takes over authorization for all subsequent sync turns.
|
|
68
|
+
|
|
69
|
+
### ✅ Agreed: Per-Turn Transmission Protocol
|
|
70
|
+
|
|
71
|
+
* **Sender → Receiver (each turn)**: Only the **current evolved frame of `sessionIdentity`** (S) is transmitted. `senderIdentity` is never re-transmitted after `establishSessionIdentity`.
|
|
72
|
+
* **Receiver validation (each turn)**: The Receiver independently loads the latest `senderIdentity` tip from its domain registry, finds the `S^Stjp` addr from the `sync` claim within it, loads the known session keystone graph, and validates the incoming evolved S frame against it.
|
|
73
|
+
* This is possible on every turn because `establishSessionIdentity` guarantees the domain and `I^Itjp` already exist on the Receiver.
|
|
74
|
+
* **Receiver → Sender (each turn)**: The Receiver's response context echoes the same (unevolved) `sessionIdentity` addr — it never produces a new keystone evolution.
|
|
75
|
+
|
|
76
|
+
### 🔲 Pending: `authenticateContext` Placement
|
|
77
|
+
|
|
78
|
+
The receiver-side validation that runs on each incoming context has two steps:
|
|
79
|
+
1. **Transition validity**: Replay the session keystone evolution — verify `data.n` is sequential, challenge solutions are valid, pool not exhausted, etc.
|
|
80
|
+
2. **Target binding**: Verify that the incoming `signedSessionIdentity`'s proof actually targets *this* context's exact address (`proof.claim.target === contextAddr`).
|
|
81
|
+
|
|
82
|
+
**Open**: Where does this code live?
|
|
83
|
+
* **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.
|
|
84
|
+
* **As a pure helper function** (`sync-peer-helpers.mts`): would need the durable space passed as a parameter (plus whatever else the tip-lookup requires).
|
|
85
|
+
|
|
86
|
+
The peer placement is currently preferred since it co-locates the space knowledge with the validation logic. Finalize during implementation.
|
|
87
|
+
|
|
88
|
+
### ✅ Agreed: `KeystoneService_V1` — No Injection Needed
|
|
89
|
+
|
|
90
|
+
`KeystoneService_V1` is stateless. It can be `new`-ed on demand wherever validation or signing is needed. There is no need to pass it as a constructor parameter or inject it into `authenticateContext` or any related helper. Just instantiate inline.
|
|
91
|
+
|
|
92
|
+
### ✅ Agreed: Context Property — `signedSessionIdentity`
|
|
93
|
+
|
|
94
|
+
The context ibgib carries the newly evolved `sessionIdentity` frame in a property named **`signedSessionIdentity`** (replacing the old `signedSessionKeystone`).
|
|
95
|
+
|
|
96
|
+
Context ibgib now has a clean, consistent pair:
|
|
97
|
+
* `rel8ns.sessionIdentity` → addr of the **previous** frame (before signing this turn)
|
|
98
|
+
* `signedSessionIdentity` → the **newly evolved** frame (current turn's cryptographic proof)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## High Level Authorization Flow
|
|
103
|
+
|
|
104
|
+
1. [ ] **`establishSessionIdentity`** *(new pre-connect phase)*:
|
|
105
|
+
[ ] * Sender locally generates `sessionIdentity` (S) and signs `senderIdentity` (I → I1) with a `sync` claim targeting `S^Stjp`.
|
|
106
|
+
[ ] * Both keystones are posted to the domain provider. Receiver validates the evolution against its own known tip of `I^Itjp` and stores them if authorized.
|
|
107
|
+
2. [ ] **`peer.connect()`** *(transport handshake)*:
|
|
108
|
+
[ ] * Sender opens the transport channel (e.g., WebSocket).
|
|
109
|
+
[ ] * Receiver issues challenges from S's **`connect` pool**.
|
|
110
|
+
[ ] * Sender solves and returns proof-of-possession. Session is authorized.
|
|
111
|
+
3. [ ] **Asymmetric Sync (Ping-Pong)**:
|
|
112
|
+
[ ] * Sender signs all outgoing contexts (Init, Ack, Delta, Commit) using S's **`sync` pool**, evolving S each turn.
|
|
113
|
+
[ ] * Only the latest evolved S frame is transmitted per turn — never `senderIdentity`.
|
|
114
|
+
[ ] * Receiver validates by independently loading `I^Itjp`, tracing to `S^Stjp`, and verifying the incoming frame.
|
|
115
|
+
[ ] * Receiver responds with unsigned contexts (same S addr echoed, no new evolution).
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## Attack Vectors & Mitigations
|
|
119
|
+
|
|
120
|
+
| Attack Vector | Description | Mitigation |
|
|
121
|
+
|---------------|-------------|------------|
|
|
122
|
+
| **MITM Replay** | Attacker replays captured saga frames | • Frame signatures include timestamp<br>• Freshness checks reject old frames<br>• Challenge depletion prevents reuse<br>• sagaId deduplication |
|
|
123
|
+
| **Session Fixation** | Attacker pre-generates session keystone | • Session secret derived via KDF (strategy in keystone metadata)<br>• `sessionSecret = KDF(masterSecret, sagaId, strategy)` |
|
|
124
|
+
| **Identity Spoofing** | Impersonation via fake session keystone | • Session keystone includes `derivedFrom` master address<br>• Receiver validates master keystone is known/trusted |
|
|
125
|
+
| **Challenge Grinding** | Brute-force challenge solutions for target binding | • Hash pre-imaging computationally infeasible<br>• Stochastic selection adds randomness |
|
|
126
|
+
| **DoS (Pool Exhaustion)** | Excessive syncs deplete challenge pools | • Top-up replenishment refills challenges<br>• Rate limiting per identity<br>• Pool separation for critical ops |
|
|
127
|
+
| **Saga Hijacking** | Mid-flight frame injection | • All frames signed with session keystone<br>• `past` rel8n creates tamper-evident chain |
|
|
128
|
+
|
|
129
|
+
## Trust Model
|
|
130
|
+
|
|
131
|
+
**Asymmetric Identity (Domain-Based Authorization)**:
|
|
132
|
+
- **Sender Signs**: The Sender is responsible for cryptographically signing all requests using their ephemeral Session Keystone.
|
|
133
|
+
- **Receiver Validates**: The Receiver acts strictly as an "Honest Broker". It validates the Sender's proofs and domain boundaries but does not maintain or sign a symmetric session identity.
|
|
134
|
+
- **Delegated Authority**: Trust is explicitly delegated from a Domain Keystone (I) to a Session Keystone (S) via a `sync` claim evolution.
|
|
135
|
+
|
|
136
|
+
**Session Keystones**:
|
|
137
|
+
- Ephemeral identity per sync saga.
|
|
138
|
+
- Secret derived via `KDF(senderSecret, sagaId)`.
|
|
139
|
+
- Contains two isolated pools: `connect` (for transport handshake) and `sync` (for per-turn frame signing).
|
|
140
|
+
- Validates via proof-of-work (solving challenges from the appropriate pool).
|
|
141
|
+
|
|
142
|
+
**Propagation**:
|
|
143
|
+
- Sync does NOT rely on global PKI or certificate authorities.
|
|
144
|
+
- Trust propagates through sync sessions (Alice syncs with Bob → Bob witnesses Alice's keystone).
|
|
145
|
+
- Revocation propagates same way (Alice revokes → syncs revocation to Bob → Bob sees revoked timeline)
|
|
146
|
+
|
|
147
|
+
**Threat Boundaries**:
|
|
148
|
+
- ✅ Protects against: MITM replay, impersonation, frame tampering
|
|
149
|
+
- ⚠️ Does NOT protect against: Compromised endpoint (live memory access during active session)
|
|
150
|
+
- ⚠️ Master secrets: Raw secrets should NOT stored; keystones master secrets to keystones should be derived secrets via key stretching passwords. raw secret files of course can drive these passwords and must be protected
|
|
151
|
+
- ⚠️ Transport security: Use TLS for network layer encryption (ibgib protocol provides authentication/integrity, not confidentiality)
|
|
152
|
+
|
|
153
|
+
## Best Practices
|
|
154
|
+
|
|
155
|
+
1. **Master Secret Protection**: Store master secrets in secure enclaves/keychains
|
|
156
|
+
2. **Session Lifetime**: Limit saga duration, revoke sessions post-completion
|
|
157
|
+
3. **Rate Limiting**: Implement per-identity sync rate limits
|
|
158
|
+
4. **Freshness**: Reject frames older than 60 seconds
|
|
159
|
+
5. **Audit Logs**: Persist saga timelines for post-hoc validation (saga timelines are ibgibs, so integrity is built-in via content addressing)
|
|
160
|
+
6. **TLS**: Always use TLS for network transport (defense-in-depth)
|
|
161
|
+
|
|
162
|
+
## ❓ Open Questions
|
|
163
|
+
|
|
164
|
+
### Payload Scope Validation: Can the Protocol Police What ibGibs Are Transmitted?
|
|
165
|
+
|
|
166
|
+
**Question**: During a sync session authorized by session substone S (itself authorized by domain keystone I with `claim.target = S^Stjp` and sync subject `X^X10.Xtjp`), should the server validate that all Delta payload ibgibs are genuinely related to X's timeline? Or is it sufficient that they arrive under a valid substone?
|
|
167
|
+
|
|
168
|
+
**Context**: The substone's `frameDetails` records both the parent identity `I^Itjp` and the sync subject `X^X10.Xtjp`. In theory the server could walk the dependency graph of each incoming Delta payload and confirm every ibgib is either:
|
|
169
|
+
- A control ibgib (sync saga, context, substone frames), or
|
|
170
|
+
- Part of the dependency graph rooted at some frame of X's timeline (`X^Xtjp`)
|
|
171
|
+
|
|
172
|
+
**Problem**: This check may not be enforceable at the protocol layer. A client who controls the keystone (has the master secret) could always evolve X's timeline to hard-link any arbitrary ibgib, and that ibgib would then pass the "is in X's dependency graph" check. So the constraint can be trivially bypassed by the legitimate key-holder, making it only useful against *illegitimate* actors — who are already blocked by the keystone proof requirement.
|
|
173
|
+
|
|
174
|
+
**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
|
+
|
|
176
|
+
**Tracked in**: `space-gib.sync-walkthrough.md`
|